AMD FSR技术在UE4移动端可用的研究(二)——4.27的适配
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
的源码部分之前,我们需要解决一个疑惑——ViewFamily
的ViewExtensions
是在哪里填充的?
根据大佬的博客,我们可以知道有这样一个调用链:
我们看看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
回到正题,终于找到正主了,继承了ISpatialUpscaler
的FFSRSpatialUpscaler
,忽略混合模式和副放大Pass,那么接下来的重点就是,进入分析FFSRSpatialUpscaler
!
2.2 分析FFSRSpatialUpscaler
1️⃣首先,承接上文,我们要进行分析的是FFSRSpatialUpscaler
的构造函数:
所以,实际上,只要不是None
,所有的EFSRMode
都会进行如上的7
个subPass
。
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);
也就是循环调用各个subPass
的UpScale
虚函数。
目前先不分析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 subpass
的Computer shader
生成失败,然后修改了ShouldCompilePermutation
(让这个着色器可以进行编译),如下:
2️⃣然后,又报错了:
报错点是AddPasses
之后的判断:
SceneColor
是AddPasses
的返回值(理论上是后处理结果),而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
吧:(First
和Last
就不用看了)
- 修改所有
SubPass
的ShouldCompilePermutation
:
依然没有用,仔细分析下流程,我们实际加入的SubPass
只有三个:HDR
、EASU
、RCAS
。然后仔细看看,就会发现HDR
没有走。所以嫌疑犯就只剩下了两个。这个时候反复使用check
中断大法:
check(1==0);
我们知道,目前引擎走的是FSR
,而不是Combined
,所以只会调用subPass
的UpScale
虚函数,而不会调用subPass
的Postprocess
虚函数,所以犯人只剩下了EASU
。
继续使用check
中断,我们发现走到是Computer Shader
分支,仔细检查代码,并没有什么特殊情况:
4️⃣返璞归真,过程是不可能有问题,那只能是Output
有问题,我们很快发现:
而使用check
,我们知道了bUseIntermediateRT
是true
,所以:
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();
意思很简单:只有当后续的RCAS
或ChromaticAberration
存在时,又或者PassInputs.OverrideOutput
不存在时,我们才使用临时RT。而现在,很明显是前者的问题,通过check
,我们发现了bChromaticAberrationPassEnabled
是false
,而bRCASEnabled
是true
。问题来了,我们并不会走RCAS
!
5️⃣为什么会这样呢?AMD在UE4里面是不希望,或者说目前未考虑支持移动管线,所以它考虑bRCASEnabled
是很简单的:
Data->bRCASEnabled = GFSR_RCAS > 0;
这里逻辑实在很奇怪,我感觉自己的逻辑能力也解释不清,直接给出解决方法:在Only FSR
的情况下,直接设置bRCASEnabled
是false
。
首先,在
FFSRData
中添加成员变量:
然后,在
FFSRViewExtension
的BeginRenderViewFamily
中添加如下代码:
最后,修改
bRCASEnabled
的赋值:
编译运行:
以上都是年前处理的,年后似乎发现当时的修改实际没有生效?所以后续又改了一下?
哦哦哦!对了,上诉结果是不对的,这么差的效果怎么可能是FSR
,这就是UE4
自带的放大Pass
!为什么?我忘了在插件里面开启FSR
这个插件了!所以,看到这里的朋友们,别忘了!去插件设置里面,启用FSR
插件!
PS:修改的最后一个问题是:似乎没有考虑编辑器模式下的处理,所以在编辑器模式下,整个场景都是黑的。但没关系,游戏模式是正常的。你可以看到
FSR
的强大之处!这个问题搁置(不影响我使用麻,叉腰),我暂时没时间搞这个了
下一篇:4.26的适配。
AMD FSR技术在UE4移动端可用的研究(二)——4.27的适配相关推荐
- AMD FSR技术在UE4移动端可用的研究(一)——介绍以及安装
1. 介绍 AMD FidelityFX Super Resolution (FSR) 采用先进的优化升级技术,能够在无需用户升级显卡的情况下帮助提高部分游戏的帧率,带来高质量.高分辨率的游戏体验. ...
- amd显卡测试大风车软件md,知之实验室 篇三:大家好才是真的好!免费显卡升级工具AMD FSR技术研究测试...
关于"Upscaling"向上取样技术,也就是让显卡性能在画质损失尽可能小的情况下提升的技术,其实已经有很多年了,比如说早年的TAA抗锯齿,多用于移动端的棋盘渲染等等,包括前两年老 ...
- 直播技术(从服务端到客户端)二
播放 在上一篇文章中,我们叙述了直播技术的环境配置(包括服务端nginx,nginx-rtmp-module, ffmpeg, Android编译,iOS编译).从本文开始,我们将叙述播放相关的东西, ...
- [前沿技术] AMD FSR 1.0源码分析(一)
文章目录 FSR技术分析 1. SIGGRAPH课程分析 1.1 源码介绍 1.2 EASU简单介绍 2. EASU源码分析 2.1 FsrEasuCon分析 2.2 FidelityFXSuperR ...
- [前沿技术] AMD FSR 1.0源码分析(二)
FSR技术分析 前文:[前沿技术] AMD FSR 1.0源码分析(一) 2. EASU源码分析 2.3 FsrEasuF分析 1️⃣首先,就参数而言,主要是: void FsrEasuF(out A ...
- 【AMD】FSR技术的源码编译过程
一.问题描述 AMD的FSR技术是一种超分辨率游戏图像增强技术,在Github代码托管网站上以GPUopen的身份提供了开源代码和示例程序.示例程序提供Vulkan版本和DX12版本的可执行文件,仅支 ...
- AMD的超分辨率FSR技术的C实现(without lib)
1.简介 上个月做完一个图像处理的IP核设计,由于涉及到上采样的超分辨率算法,就看了一下AMD 开源的超分算法--FSR(FidelityFX Super Resolution),并打算用C源代码实现 ...
- ECM技术学习:解码端帧内模式推导(Decoder-side Intra Mode Derivation )
解码端帧内模式推导(DIMD)技术是之前在VVC标准化的过程中提出的技术,因为其在解码端引入的复杂度较高,因此没有被VVC采纳.为了探索下一代压缩标准,JVET最近设立了最新的ECM参考平台,将DIM ...
- 全面解密QQ红包技术方案:架构、技术实现、移动端优化、创新玩法等
本文来自腾讯QQ技术团队工程师许灵锋.周海发的技术分享. 一.引言 自 2015 年春节以来,QQ 春节红包经历了企业红包(2015 年).刷一刷红包(2016 年)和 AR 红包(2017 年)几个 ...
最新文章
- 团队开发中的 Git 实践
- R语言数据包自带数据集之survival包的lung数据集字段解释、数据导入实战
- iptables小案例,nat表应用
- 【c_prime_plus】第十七章笔记
- 【控制】多智体系统一致性基础知识
- 2019年末逆向复习系列之Boss直聘Cookie加密字段__zp_stoken__逆向分析
- BZOJ2333 [SCOI2011]棘手的操作 【离线 + 线段树】
- html地区三级联下拉列表,JS-三级联下拉列表
- PCF上的Spring Cloud合同和Spring Cloud Services
- 技术人的“匠心”:一件事竟然做了20年…
- android listview 增加单选 复选,ListView里面加入CheckBox如何实现单选?
- 第 5 章 MybatisPlus ActiveRecord
- 爪哇国新游记之二十----将数字转换成中国汉字大写形式
- 关于一直卡死的两段代码,望对LDD3有兴趣者戳开这个blog : )
- 使用FFMPEG合并视频
- arduino与肌电信号(传感器)的碰撞② 2021 7 20
- jxls设置隐藏列隐藏行
- 【练习/Python】监测汇率脚本
- 【安全知识分享】2021年安全生产月主题宣讲课件(附下载)
- EasyRecovery15万能数据恢复软件全面详细功能讲解
热门文章
- 华硕2020年显卡_2020年开工大吉华硕RX5600XT显卡
- python实现全自动安装第三方库,从此跟pip说拜拜!!「建议收藏」
- [大话IT]~~~~闲话操作系统
- matplotlib在一张图同时画折线图和柱状图
- isd1802_ISD的完整形式是什么?
- 硅谷的故事:关于硅谷的学术研究
- 走火入魔.NET权限组件-用资源权限(设置权限)思想来解来解决权限的存储问题...
- NOIP 1999 旅行家的预算
- 女人钱好挣也不好挣,关键看你怎么打动她们
- MVP+OKHttp+Recyclerview+Springview下拉刷新上拉加载