1. 从FDeferredShadingSceneRenderer::Render入手

1️⃣首先根据大佬的博客,我们可以很快定位到后处理发生的地方(整个函数的靠后处):

进入AddPostProcessingPasses,然后根据笔记(1)(参考官方各种文档),可以知道FSR作为UpScale是位于Tone Mapping之后的,所以我们直接略过中间复杂的过程,可以很快找到主放大Pass副放大Pass


2️⃣考虑主放大Pass,在设置完PassInputs之后,核心代码就是下面这一行:

CustomUpscaler的类型是ISpatialUpscaler,通过官方注释,我们可以知道:ISpatialUpscaler自定义空间缩放算法的接口,意在通过ISceneViewExtension::BeginRenderViewFamily()FSceneViewFamily进行设置。

这个暂且不谈,我们通过代码可以知道,CustomUpscaler实际上存储在View.Family中,而视图类中Family的类型是FSceneViewFamily(其实,可以直接参考注释了,但我当时还没发现)。这个ViewFamily.SpatialUpscaler的设置应该是在FSceneRenderer的构造函数中:

FSceneRenderer的构造又是发生在FRendererModule::BeginRenderingViewFamily中:

FSceneViewFamily一开始,我以为是在这个函数的调用处赋值的(毕竟是作为参数传进来),但通过一系列Check(这些检查函数都需要保证其对应成员之前没有值)和注释(终于用到了),我们确定了其填充过程应该是这个:

但是我们跳转到这个函数,发现这个函数只是个虚函数,没有实际过程,而这个答案很明显在FSR的源码中。

3️⃣在跳转到FSR的源码部分之前,我们需要解决一个疑惑——ViewFamilyViewExtensions是在哪里填充的?

根据大佬的博客,我们可以知道有这样一个调用链:


我们看看UGameViewportClient::Draw,然后我们会发现viewFamily的构造,以及下面这样一行代码:

ViewFamily.ViewExtensions = GEngine->ViewExtensions->GatherActiveExtensions(FSceneViewExtensionContext(InViewport));

感觉没有必要继续挖了,有点累了,先这样吧。

4️⃣我们现在跳转到FSR的源码部分。

2. 进入FSR的源码

2.1 FFSRViewExtension的分析

1️⃣我们很快就能发现,FSR代码中存在一个类FFSRViewExtension,它继承了FSceneViewExtensionBase,同时也实现了BeginRenderViewFamily

PS:因为要进行分析,所以就不截图了。此外,为了方便理解,编辑器部分的代码我都删掉了

void FFSRViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily)
{// the object(s) we assign here will get deleted by UE4 when the scene view tears down, so we need to instantiate a new one every frame.// 我们在这里指定的对象将在场景视图关闭时被UE4删除,所以我们需要在每一帧实例化一个新的对象。if (InViewFamily.GetFeatureLevel() >= ERHIFeatureLevel::SM5 && CVarEnableFSR.GetValueOnAnyThread() > 0){TArray<TSharedPtr<FFSRData>> ViewData;bool IsTemporalUpscalingRequested = false;// 遍历Views,填充FSRDatafor (int i = 0; i < InViewFamily.Views.Num(); i++){const FSceneView* InView = InViewFamily.Views[i];if (ensure(InView)){// if any view is using temporal upscaling, use the Combined upscaling mode.// 如果任何视图使用temporal upscaling,则使用Combined upscaling modeIsTemporalUpscalingRequested |= (InView->PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale);// TSharedPtr will clean up this allocation// 填充FSRData,准确说,是引擎提供的通用参数(视图相关的参数)// 关于FSR算法所需要的特定参数(视图无关参数),应该是在其他地方填充好了FFSRData* Data = new FFSRData();Data->PostProcess_GrainIntensity = InView->FinalPostProcessSettings.GrainIntensity;Data->PostProcess_GrainJitter = InView->FinalPostProcessSettings.GrainJitter;Data->PostProcess_SceneFringeIntensity = InView->FinalPostProcessSettings.SceneFringeIntensity;Data->PostProcess_ChromaticAberrationStartOffset = InView->FinalPostProcessSettings.ChromaticAberrationStartOffset;ViewData.Add(TSharedPtr<FFSRData>(Data));}}// 有视图使用temporal upscaling吗if (!IsTemporalUpscalingRequested){// FSR UpscaleInViewFamily.SetPrimarySpatialUpscalerInterface(new FFSRSpatialUpscaler(EFSRMode::UpscalingOnly, ViewData));// 是否进行副放大Passif (!IsEASUTheLastPass()){InViewFamily.SetSecondarySpatialUpscalerInterface(new FFSRSpatialUpscaler(EFSRMode::PostProcessingOnly, ViewData));}}else{// 混合模式InViewFamily.SetSecondarySpatialUpscalerInterface(new FFSRSpatialUpscaler(EFSRMode::Combined, ViewData));}}
}

基本的分析都在上诉代码的注释中,我们首先需要留意的这个部分:

InViewFamily.GetFeatureLevel() >= ERHIFeatureLevel::SM5

别忘了我们的目标——让FSR可以在手机端运行,而手机端应该是Opengl ES 3.x,所以后续我们应该将这个判断修改一下!加一个四级标题备注下:

⭐️修改点1

回到正题,终于找到正主了,继承了ISpatialUpscalerFFSRSpatialUpscaler,忽略混合模式副放大Pass,那么接下来的重点就是,进入分析FFSRSpatialUpscaler

2.2 分析FFSRSpatialUpscaler

1️⃣首先,承接上文,我们要进行分析的是FFSRSpatialUpscaler的构造函数:

所以,实际上,只要不是None,所有的EFSRMode都会进行如上的7subPass

2️⃣然后,我们进入AddPasses(世界线终于收束了):

#define EXECUTE_STEP(step) \for (FFSRSubpass* Subpass : FSRSubpasses) \{ \Subpass->step(GraphBuilder, View, PassInputs); \}FScreenPassTexture FFSRSpatialUpscaler::AddPasses(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FInputs& PassInputs) const
{RDG_GPU_STAT_SCOPE(GraphBuilder, FidelityFXSuperResolutionPass);check(PassInputs.SceneColor.IsValid());// 获取View对应的FSRData(之前填充的)TSharedPtr<FFSRData> Data = GetDataForView(View);// 遍历subPass,让其内部对应指针,指向这个Data// SetData是个虚函数,我随便打开了一个派生类,没有override,估计只有少数几个派生类需要overridefor (FFSRSubpass* Subpass : FSRSubpasses){Subpass->SetData(Data.Get());}// 如果数据没有初始化// ParseEnvironment、CreateResources都是FSRsubPass类的虚函数if (!Data->bInitialized){    // 解析环境,设置Data对应的成员变量:bUSE_FP16、bFORCE_VSPS、bRCASEnabled等。取决于派生类// 遍历所有subPassEXECUTE_STEP(ParseEnvironment);// 创建Data中的一些资源变量,例如:UpscaleTexture、SharpenedTexture等。取决于派生类// 遍历所有subPassEXECUTE_STEP(CreateResources);}// 根据模式,决定不一样的运行方式。if (Mode == EFSRMode::UpscalingOnly || Mode == EFSRMode::Combined){// 遍历所有subPass// 调用UpscaleEXECUTE_STEP(Upscale);}if (Mode == EFSRMode::PostProcessingOnly || Mode == EFSRMode::Combined){EXECUTE_STEP(PostProcess);}// 获取输出结果FScreenPassTexture FinalOutput = Data->FinalOutput;// 返回最终结果,UE的右移?该复习下了C++了return MoveTemp(FinalOutput);
}

主要代码分析见上诉注释,而EXECUTE_STEP(Upscale);也就是循环调用各个subPassUpScale虚函数。

目前先不分析FSR本身算法的流程。让我们看看怎么在移动端开启它。

3. 进入FMobileSceneRenderer::Render

1️⃣首先,我们很快定位到后处理区域:

进入到AddMobilePostProcessingPasses,其基本逻辑和之前分析的PC端延迟渲染差不多,我们直接定位到主放大Pass:(手机端没有次放大Pass的设置)

// Apply ScreenPercentage
if (PassSequence.IsEnabled(EPass::PrimaryUpscale))
{ISpatialUpscaler::FInputs PassInputs;PassSequence.AcceptOverrideIfLastPass(EPass::PrimaryUpscale, PassInputs.OverrideOutput);PassInputs.Stage = EUpscaleStage::PrimaryToOutput;PassInputs.SceneColor = SceneColor;PassInputs.OverrideOutput.LoadAction = View.IsFirstInFamily() ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;if (const ISpatialUpscaler* CustomUpscaler = View.Family->GetPrimarySpatialUpscalerInterface()){RDG_EVENT_SCOPE(GraphBuilder,"ThirdParty PrimaryUpscale %s %dx%d -> %dx%d",CustomUpscaler->GetDebugName(),SceneColor.ViewRect.Width(), SceneColor.ViewRect.Height(),View.UnscaledViewRect.Width(), View.UnscaledViewRect.Height());SceneColor = CustomUpscaler->AddPasses(GraphBuilder, View, PassInputs);if (PassSequence.IsLastPass(EPass::PrimaryUpscale)){check(SceneColor == ViewFamilyOutput);}else{check(SceneColor.ViewRect.Size() == View.UnscaledViewRect.Size());}}else{SceneColor = ISpatialUpscaler::AddDefaultUpscalePass(GraphBuilder, View, PassInputs, EUpscaleMethod::Bilinear, PaniniConfig);}
}

上面的逻辑基本也和之前的PC端一致,所以目前只发现了一个修改点,我去试试改下会发生什么。

4. 修改尝试

1️⃣首先,我们基于第一个修改点,做了如下简单修改


然后,编译,打开编辑器,切换到ES3.1,果然崩溃。报错如下:

检查后,目测应该是EASU subpassComputer shader生成失败,然后修改了ShouldCompilePermutation(让这个着色器可以进行编译),如下:

2️⃣然后,又报错了:

报错点是AddPasses之后的判断:

SceneColorAddPasses的返回值(理论上是后处理结果),而ViewFamilyOutput是在后处理起点处定义的RT,然后用来构造PassSequence

然后呢,我们将目光放到PassInput,会发现

bool AcceptOverrideIfLastPass(EPass Pass, FScreenPassRenderTarget& OutTargetToOverride, const TOptional<int32>& AfterPassCallbackIndex = TOptional<int32>())
{bool bLastAfterPass = AfterPass[(int32)Pass].Num() == 0;if (AfterPassCallbackIndex){bLastAfterPass = AfterPassCallbackIndex.GetValue() == AfterPass[(int32)Pass].Num() - 1;}else{// Display debug information for a Pass unless it is an after pass.AcceptPass(Pass);}// We need to override output only if this is the last pass and the last after pass.// 我们需要覆盖输出,只有当这是最后一次Pass和最后一次after pass时。if (IsLastPass(Pass) && bLastAfterPass){OutTargetToOverride = OverrideOutput;return true;}return false;
}

经过一些查证和本人的理解(可能很多错误):在后处理过程中,每次传递的都是SceneColor,而其类型FScreenPassTexture,这个根据注释就知道:它只作为后处理链中的数据载体,如果要想将最终结果显示在屏幕上,那么最好的方法就是——在最后一个后处理Pass,将Output设置为RT(也就是ViewFamilyOutput)。

总结来说,报错的原因是两个:

  • 放大Pass在移动端不是最后一个Pass
  • 实际放大过程中(FSR流程中),出现了问题

3️⃣第一个原因很简单就可以知道不对:

那么就去看看FSR吧:(FirstLast就不用看了)

  • 修改所有SubPassShouldCompilePermutation

依然没有用,仔细分析下流程,我们实际加入的SubPass只有三个:HDREASURCAS。然后仔细看看,就会发现HDR没有走。所以嫌疑犯就只剩下了两个。这个时候反复使用check中断大法:

check(1==0);

我们知道,目前引擎走的是FSR,而不是Combined,所以只会调用subPassUpScale虚函数,而不会调用subPassPostprocess虚函数,所以犯人只剩下了EASU

继续使用check中断,我们发现走到是Computer Shader分支,仔细检查代码,并没有什么特殊情况:

4️⃣返璞归真,过程是不可能有问题,那只能是Output有问题,我们很快发现:

而使用check,我们知道了bUseIntermediateRTtrue,所以:

Output = FScreenPassRenderTarget(Data->UpscaleTexture.Texture, Data->UpscaleTexture.ViewRect, ERenderTargetLoadAction::ENoAction)

所以,破案了:七个subpass实际只有EASU subpass在发挥作用,但是这里却没有使用PassInputs.OverrideOutput作为输出RT,而是一个临时RT!。自然而然,我们就不可能通过报错的那个check

如果EASU subpass是一个中间Pass,这样做自然没有问题,但是问题在于,这里它是独苗,看看这个bool类型的赋值:

const bool bUseIntermediateRT = (Data->bRCASEnabled || Data->bChromaticAberrationPassEnabled) || !PassInputs.OverrideOutput.IsValid();

意思很简单:只有当后续的RCASChromaticAberration存在时,又或者PassInputs.OverrideOutput不存在时,我们才使用临时RT。而现在,很明显是前者的问题,通过check,我们发现了bChromaticAberrationPassEnabledfalse,而bRCASEnabledtrue。问题来了,我们并不会走RCAS

5️⃣为什么会这样呢?AMD在UE4里面是不希望,或者说目前未考虑支持移动管线,所以它考虑bRCASEnabled是很简单的:

Data->bRCASEnabled = GFSR_RCAS > 0;

这里逻辑实在很奇怪,我感觉自己的逻辑能力也解释不清,直接给出解决方法:在Only FSR的情况下,直接设置bRCASEnabledfalse

  • 首先,在FFSRData中添加成员变量:

  • 然后,在FFSRViewExtensionBeginRenderViewFamily中添加如下代码:

  • 最后,修改bRCASEnabled的赋值:

编译运行:

以上都是年前处理的,年后似乎发现当时的修改实际没有生效?所以后续又改了一下?

哦哦哦!对了,上诉结果是不对的,这么差的效果怎么可能是FSR,这就是UE4自带的放大Pass!为什么?我忘了在插件里面开启FSR这个插件了!所以,看到这里的朋友们,别忘了!去插件设置里面,启用FSR插件

PS:修改的最后一个问题是:似乎没有考虑编辑器模式下的处理,所以在编辑器模式下,整个场景都是黑的。但没关系,游戏模式是正常的。你可以看到FSR的强大之处!这个问题搁置(不影响我使用麻,叉腰),我暂时没时间搞这个了

下一篇:4.26的适配

AMD FSR技术在UE4移动端可用的研究(二)——4.27的适配相关推荐

  1. AMD FSR技术在UE4移动端可用的研究(一)——介绍以及安装

    1. 介绍 AMD FidelityFX Super Resolution (FSR) 采用先进的优化升级技术,能够在无需用户升级显卡的情况下帮助提高部分游戏的帧率,带来高质量.高分辨率的游戏体验. ...

  2. amd显卡测试大风车软件md,知之实验室 篇三:大家好才是真的好!免费显卡升级工具AMD FSR技术研究测试...

    关于"Upscaling"向上取样技术,也就是让显卡性能在画质损失尽可能小的情况下提升的技术,其实已经有很多年了,比如说早年的TAA抗锯齿,多用于移动端的棋盘渲染等等,包括前两年老 ...

  3. 直播技术(从服务端到客户端)二

    播放 在上一篇文章中,我们叙述了直播技术的环境配置(包括服务端nginx,nginx-rtmp-module, ffmpeg, Android编译,iOS编译).从本文开始,我们将叙述播放相关的东西, ...

  4. [前沿技术] AMD FSR 1.0源码分析(一)

    文章目录 FSR技术分析 1. SIGGRAPH课程分析 1.1 源码介绍 1.2 EASU简单介绍 2. EASU源码分析 2.1 FsrEasuCon分析 2.2 FidelityFXSuperR ...

  5. [前沿技术] AMD FSR 1.0源码分析(二)

    FSR技术分析 前文:[前沿技术] AMD FSR 1.0源码分析(一) 2. EASU源码分析 2.3 FsrEasuF分析 1️⃣首先,就参数而言,主要是: void FsrEasuF(out A ...

  6. 【AMD】FSR技术的源码编译过程

    一.问题描述 AMD的FSR技术是一种超分辨率游戏图像增强技术,在Github代码托管网站上以GPUopen的身份提供了开源代码和示例程序.示例程序提供Vulkan版本和DX12版本的可执行文件,仅支 ...

  7. AMD的超分辨率FSR技术的C实现(without lib)

    1.简介 上个月做完一个图像处理的IP核设计,由于涉及到上采样的超分辨率算法,就看了一下AMD 开源的超分算法--FSR(FidelityFX Super Resolution),并打算用C源代码实现 ...

  8. ECM技术学习:解码端帧内模式推导(Decoder-side Intra Mode Derivation )

    解码端帧内模式推导(DIMD)技术是之前在VVC标准化的过程中提出的技术,因为其在解码端引入的复杂度较高,因此没有被VVC采纳.为了探索下一代压缩标准,JVET最近设立了最新的ECM参考平台,将DIM ...

  9. 全面解密QQ红包技术方案:架构、技术实现、移动端优化、创新玩法等

    本文来自腾讯QQ技术团队工程师许灵锋.周海发的技术分享. 一.引言 自 2015 年春节以来,QQ 春节红包经历了企业红包(2015 年).刷一刷红包(2016 年)和 AR 红包(2017 年)几个 ...

最新文章

  1. 团队开发中的 Git 实践
  2. R语言数据包自带数据集之survival包的lung数据集字段解释、数据导入实战
  3. iptables小案例,nat表应用
  4. 【c_prime_plus】第十七章笔记
  5. 【控制】多智体系统一致性基础知识
  6. 2019年末逆向复习系列之Boss直聘Cookie加密字段__zp_stoken__逆向分析
  7. BZOJ2333 [SCOI2011]棘手的操作 【离线 + 线段树】
  8. html地区三级联下拉列表,JS-三级联下拉列表
  9. PCF上的Spring Cloud合同和Spring Cloud Services
  10. 技术人的“匠心”:一件事竟然做了20年…
  11. android listview 增加单选 复选,ListView里面加入CheckBox如何实现单选?
  12. 第 5 章 MybatisPlus ActiveRecord
  13. 爪哇国新游记之二十----将数字转换成中国汉字大写形式
  14. 关于一直卡死的两段代码,望对LDD3有兴趣者戳开这个blog : )
  15. 使用FFMPEG合并视频
  16. arduino与肌电信号(传感器)的碰撞② 2021 7 20
  17. jxls设置隐藏列隐藏行
  18. 【练习/Python】监测汇率脚本
  19. 【安全知识分享】2021年安全生产月主题宣讲课件(附下载)
  20. EasyRecovery15万能数据恢复软件全面详细功能讲解

热门文章

  1. 华硕2020年显卡_2020年开工大吉华硕RX5600XT显卡
  2. python实现全自动安装第三方库,从此跟pip说拜拜!!「建议收藏」
  3. [大话IT]~~~~闲话操作系统
  4. matplotlib在一张图同时画折线图和柱状图
  5. isd1802_ISD的完整形式是什么?
  6. 硅谷的故事:关于硅谷的学术研究
  7. 走火入魔.NET权限组件-用资源权限(设置权限)思想来解来解决权限的存储问题...
  8. NOIP 1999 旅行家的预算
  9. 女人钱好挣也不好挣,关键看你怎么打动她们
  10. MVP+OKHttp+Recyclerview+Springview下拉刷新上拉加载