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的源码部分之前,我们需要解决一个疑惑——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,填充FSRData
		for (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 mode
				IsTemporalUpscalingRequested |= (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 Upscale
			InViewFamily.SetPrimarySpatialUpscalerInterface(new FFSRSpatialUpscaler(EFSRMode::UpscalingOnly, ViewData));
            
            // 是否进行副放大Pass
			if (!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,估计只有少数几个派生类需要override
    for (FFSRSubpass* Subpass : FSRSubpasses)
    {
        Subpass->SetData(Data.Get());
    }

    // 如果数据没有初始化
    // ParseEnvironment、CreateResources都是FSRsubPass类的虚函数
    if (!Data->bInitialized)
    {	
        // 解析环境,设置Data对应的成员变量:bUSE_FP16、bFORCE_VSPS、bRCASEnabled等。取决于派生类
        // 遍历所有subPass
        EXECUTE_STEP(ParseEnvironment);
        // 创建Data中的一些资源变量,例如:UpscaleTexture、SharpenedTexture等。取决于派生类
        // 遍历所有subPass
        EXECUTE_STEP(CreateResources);
    }

    // 根据模式,决定不一样的运行方式。
    if (Mode == EFSRMode::UpscalingOnly || Mode == EFSRMode::Combined)
    {
        // 遍历所有subPass
        // 调用Upscale
        EXECUTE_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的适配

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JMXIN422

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值