Unreal Engine MobileFSR插件实现机制分析

一、源码分析

第一部分:插件启用检查

控制台变量定义

Engine/Plugins/Runtime/MobileFSR/Source/MobileFSR/Private/MobileFSRViewExtension.cpp

cpp 复制代码
static TAutoConsoleVariable<int32> CVarEnableFSR(
    TEXT("r.Mobile.FSR.Enabled"),
    1,
    TEXT("Enable Mobile FSR for Primary Upscale"),
    ECVF_RenderThreadSafe);

TAutoConsoleVariable<int32> CVarMobileFSRCASEnabled(
    TEXT("r.Mobile.FSR.RCAS.Enabled"),
    1,
    TEXT("FidelityFX FSR/RCAS : Robust Contrast Adaptive Sharpening Filter"),
    ECVF_RenderThreadSafe);

TAutoConsoleVariable<int32> CVarMobileFSRUpsamplingEnabled(
    TEXT("r.Mobile.FSR.Upsampling.Enabled"),
    1,
    TEXT("FidelityFX FSR/RCAS : Robust Contrast Adaptive Sharpening Filter"),
    ECVF_Default);

定义了三个控制台变量,分别控制主开关、锐化功能和放大功能。

ViewExtension 设置接口

Engine/Plugins/Runtime/MobileFSR/Source/MobileFSR/Private/MobileFSRViewExtension.cpp

cpp 复制代码
void FMobileFSRViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily)
{
    if (InViewFamily.GetFeatureLevel() == ERHIFeatureLevel::ES3_1 && CVarEnableFSR.GetValueOnAnyThread() > 0)
    {
        if (CVarMobileFSRUpsamplingEnabled.GetValueOnAnyThread())
        {
            InViewFamily.SetPrimarySpatialUpscalerInterface(new FMobileFSRUpscaler(true));
        }
        if (CVarMobileFSRCASEnabled.GetValueOnAnyThread())
        {
            InViewFamily.SetSecondarySpatialUpscalerInterface(new FMobileFSRUpscaler(false));
        }
    }
}
  1. 检查平台是否为 ES3_1(移动平台)
  2. 检查主开关是否启用
  3. 根据子开关设置主放大器和次级锐化器

第二部分:平台支持检查

平台支持验证

Engine/Plugins/Runtime/MobileFSR/Source/MobileFSR/Private/MobileFSRUpscaler.cpp

cpp 复制代码
bool FMobileFSRUpscaler::IsSupported(EShaderPlatform Platform)
{
    return IsMobilePlatform(Platform) || IsMetalMobilePlatform(Platform);
}

MobileFSR 只支持移动平台或 Metal 移动平台。

第三部分:FSceneRenderer 接口处理

FSceneRenderer 构造函数中的接口处理流程

Engine/Source/Runtime/Renderer/Private/SceneRendering.cpp

cpp 复制代码
FSceneRenderer::FSceneRenderer(const FSceneViewFamily* InViewFamily, ...)
{
    // 步骤1:检查时序放大器冲突
    check(ViewFamily->GetTemporalUpscalerInterface() == nullptr);
    
    // 步骤2:Fork 游戏线程的空间放大器接口
    if (InViewFamily->PrimarySpatialUpscalerInterface)
    {
        ViewFamily.SetPrimarySpatialUpscalerInterface(InViewFamily->PrimarySpatialUpscalerInterface->Fork_GameThread(ViewFamily));
    }
    
    // 步骤3:调用所有 ViewExtension
    for (int32 ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ++ViewExt)
    {
        ViewFamily->ViewExtensions[ViewExt]->BeginRenderViewFamily(*ViewFamily);
    }
    
    // 步骤4:冲突检查
    checkf(!(ViewFamily->GetTemporalUpscalerInterface() != nullptr && ViewFamily->GetPrimarySpatialUpscalerInterface() != nullptr),
        TEXT("Conflict setting up a third party primary spatial upscaler or temporal upscaler."));
}
  1. 确保游戏线程没有设置TemporalUpscaler
  2. 如果游戏线程已设置SpatialUpscaler,则 Fork 到渲染线程
  3. 调用所有 ViewExtension(包括 MobileFSR)
  4. 检查TemporalUpscaler和PrimarySpatialUpscaler不能同时存在

冲突检查的含义

cpp 复制代码
checkf(!(ViewFamily->GetTemporalUpscalerInterface() != nullptr && ViewFamily->GetPrimarySpatialUpscalerInterface() != nullptr),
    TEXT("Conflict setting up a third party primary spatial upscaler or temporal upscaler."));

这个检查确保时序放大器和空间放大器不能同时启用。如果同时存在,会触发断言失败。这是为了防止渲染逻辑混乱,因为TemporalUpscaler(如 TAAU、TSR)在时域中进行放大,PrimarySpatialUpscaler(如 MobileFSR)在空间域中进行放大,两者不能同时启用。

SetPrimarySpatialUpscalerInterface 的冲突保护

Engine/Source/Runtime/Engine/Public/SceneView.h

cpp 复制代码
FORCEINLINE void SetPrimarySpatialUpscalerInterface(ISpatialUpscaler* InSpatialUpscalerInterface)
{
    check(InSpatialUpscalerInterface);
    checkf(PrimarySpatialUpscalerInterface == nullptr, TEXT("View family already had a primary spatial upscaler assigned."));
    PrimarySpatialUpscalerInterface = InSpatialUpscalerInterface;
}

确保只能有一个PrimarySpatialUpscaler,防止多个PrimarySpatialUpscaler冲突,确保每个ViewFamily只有一个PrimarySpatialUpscaler 。

第四部分:PostProcessing 中的执行逻辑

bShouldPrimaryUpscale 计算逻辑

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp

cpp 复制代码
void AddMobilePostProcessingPasses(FRDGBuilder& GraphBuilder, FScene* Scene, const FViewInfo& View, ...)
{
    // 基础条件:传统空间放大或镜头畸变
    bool bShouldPrimaryUpscale = (View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::SpatialUpscale && View.UnscaledViewRect != View.ViewRect) || View.LensDistortionLUT.IsEnabled();
    
    // 满足任一条件即可启用
    bShouldPrimaryUpscale |= View.Family->GetPrimarySpatialUpscalerInterface() != nullptr;
    
    // 启用 PrimaryUpscale Pass
    PassSequence.SetEnabled(EPass::PrimaryUpscale, bShouldPrimaryUpscale);
}

只要设置了自定义空间放大器(如 MobileFSR),就会启用 PrimaryUpscale Pass,不管是否需要放大。

PrimaryUpscale Pass 执行逻辑

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp

cpp 复制代码
void AddMobilePostProcessingPasses(...)
{
    if (PassSequence.IsEnabled(EPass::PrimaryUpscale))
    {
        ISpatialUpscaler::FInputs PassInputs;
        PassInputs.Stage = EUpscaleStage::PrimaryToOutput;
        PassInputs.SceneColor = SceneColor;
        
        // 检查是否有自定义放大器
        if (const ISpatialUpscaler* CustomUpscaler = View.Family->GetPrimarySpatialUpscalerInterface())
        {
            RDG_EVENT_SCOPE(GraphBuilder, "ThirdParty PrimaryUpscale %s", CustomUpscaler->GetDebugName());
            
            // 执行自定义放大器(MobileFSR)
            SceneColor = CustomUpscaler->AddPasses(GraphBuilder, View, PassInputs);
        }
        else
        {
            // 执行默认放大算法
            SceneColor = ISpatialUpscaler::AddDefaultUpscalePass(GraphBuilder, View, PassInputs, EUpscaleMethod::Bilinear, View.LensDistortionLUT);
        }
    }
}

如果存在自定义放大器,则调用其 AddPasses 方法;否则使用默认的双线性放大。

第五部分:MobileFSR 具体实现

Fork_GameThread 实现

Engine/Plugins/Runtime/MobileFSR/Source/MobileFSR/Private/MobileFSRUpscaler.cpp

cpp 复制代码
class FMobileFSRUpscaler : public ISpatialUpscaler
{
public:
    virtual ISpatialUpscaler* Fork_GameThread(const class FSceneViewFamily& ViewFamily) const override
    {
        return new FMobileFSRUpscaler();
    }
    
    virtual const TCHAR* GetDebugName() const override
    {
        return TEXT("MobileFSR");
    }
    
    virtual FScreenPassTexture AddPasses(
        FRDGBuilder& GraphBuilder,
        const FViewInfo& View,
        const FInputs& PassInputs) const override;
};

Fork_GameThread 创建渲染线程专用的 MobileFSR 实例,确保线程安全。

第六部分:强制测试机制

测试覆盖机制

Engine/Source/Runtime/Renderer/Private/SceneRendering.cpp

cpp 复制代码
static TAutoConsoleVariable<int32> CVarTestPrimaryScreenPercentageMethodOverride(
    TEXT("r.Test.PrimaryScreenPercentageMethodOverride"),
    0,
    TEXT("Override the screen percentage method for all view family.\n")
    TEXT(" 0: view family's screen percentage interface choose; (default)\n")
    TEXT(" 1: old fashion upscaling pass at the very end right before before UI;\n")
    TEXT(" 2: TemporalAA upsample."));

PrepareViewRectsForRendering 有依据 CVarTestPrimaryScreenPercentageMethodOverride 设置的强制测试覆盖机制。

cpp 复制代码
 static TAutoConsoleVariable<int32> CVarTestPrimaryScreenPercentageMethodOverride(
            TEXT("r.Test.PrimaryScreenPercentageMethodOverride"),
            0,
            TEXT("Override the screen percentage method for all view family.\n")
            TEXT(" 0: view family's screen percentage interface choose; (default)\n")
            TEXT(" 1: old fashion upscaling pass at the very end right before before UI;\n")
            TEXT(" 2: TemporalAA upsample.\n")
            TEXT(" 3: Raw output."));
cpp 复制代码
void FSceneRenderer::PrepareViewRectsForRendering(FRHICommandListImmediate& RHICmdList)
{
    // ... 前面的初始化代码 ...
    // Checks that view rects are correctly initialized.
	for (int32 i = 0; i < Views.Num(); i++)
	{
    	#if !UE_BUILD_SHIPPING
		// For testing purpose, override the screen percentage method.
		{
			switch (CVarTestPrimaryScreenPercentageMethodOverride.GetValueOnRenderThread())
			{
			case 1: View.PrimaryScreenPercentageMethod = EPrimaryScreenPercentageMethod::SpatialUpscale; break;
			case 2: View.PrimaryScreenPercentageMethod = EPrimaryScreenPercentageMethod::TemporalUpscale; break;
			case 3: View.PrimaryScreenPercentageMethod = EPrimaryScreenPercentageMethod::RawOutput; break;
			}
		}
		#endif
    }
    // switch 后面的重要逻辑开始
    // 这里是 FSceneRenderer 构造函数的继续执行部分
    // 包括:- View 初始化/渲染状态设置/场景数据准备/渲染器初始化/...
    
    // 步骤:调用所有 ViewExtension
    for (int32 ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ++ViewExt)
    {
        ViewFamily->ViewExtensions[ViewExt]->BeginRenderViewFamily(*ViewFamily);
    }
    
    // 步骤:冲突检查
    checkf(!(ViewFamily->GetTemporalUpscalerInterface() != nullptr && ViewFamily->GetPrimarySpatialUpscalerInterface() != nullptr),
        TEXT("Conflict setting up a third party primary spatial upscaler or temporal upscaler."));
    
    // 更多 FSceneRenderer 初始化逻辑...
}

这里的强制测试机制允许开发者覆盖屏幕百分比方法,用于测试不同的Upsacle。开发者可以通过控制变量 r.Test.PrimaryScreenPercentageMethodOverride 强制切换不同的上采样方法来:

  • 测试性能差异:比较不同方法的GPU使用率和帧率
  • 验证视觉质量:在同一场景中快速切换不同方法,比较图像质量
  • 调试渲染问题:当某个上采样方法出现问题时,可以强制使用其他方法来隔离问题

一、生效条件总结

必要条件

  1. 平台支持InViewFamily.GetFeatureLevel() == ERHIFeatureLevel::ES3_1
  2. 主开关启用r.Mobile.FSR.Enabled > 0
  3. 放大功能启用r.Mobile.FSR.Upsampling.Enabled > 0
  4. 接口设置成功:MobileFSR ViewExtension 成功设置接口
  5. 无冲突:没有其他系统已设置空间放大器或时序放大器

执行条件

  • 只要设置了 PrimarySpatialUpscalerInterface,就会启用 PrimaryUpscale Pass
  • 不检查是否需要放大(UnscaledViewRect != ViewRect)>>> 改进点

如果不生效的检查方法

1. 检查控制台变量

cpp 复制代码
r.Mobile.FSR.Enabled
r.Mobile.FSR.Upsampling.Enabled
// 应该都返回 1

2. 检查接口冲突

cpp 复制代码
// 在 FSceneRenderer 构造函数后添加日志
UE_LOG(LogTemp, Warning, TEXT("TemporalUpscalerInterface = %s"), 
    ViewFamily->GetTemporalUpscalerInterface() ? TEXT("Valid") : TEXT("Null"));
UE_LOG(LogTemp, Warning, TEXT("PrimarySpatialUpscalerInterface = %s"), 
    ViewFamily->GetPrimarySpatialUpscalerInterface() ? TEXT("Valid") : TEXT("Null"));

3. 检查 Pass 启用

cpp 复制代码
// 在 AddMobilePostProcessingPasses 中添加日志
UE_LOG(LogTemp, Warning, TEXT("bShouldPrimaryUpscale = %s"), bShouldPrimaryUpscale ? TEXT("True") : TEXT("False"));
UE_LOG(LogTemp, Warning, TEXT("PrimaryUpscale Pass Enabled = %s"), 
    PassSequence.IsEnabled(EPass::PrimaryUpscale) ? TEXT("True") : TEXT("False"));

4. 强制测试

cpp 复制代码
r.Test.PrimaryScreenPercentageMethodOverride 1
// 强制使用 SpatialUpscale 进行测试

5.检查 ViewExtension 执行顺序

cpp 复制代码
// 在 FSceneRenderer 构造函数中添加调试
for (int32 ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ++ViewExt)
{
    UE_LOG(LogTemp, Warning, TEXT("ViewExtension[%d]: %s"), 
        ViewExt, *ViewFamily->ViewExtensions[ViewExt]->GetClass()->GetName());
    
    ViewFamily->ViewExtensions[ViewExt]->BeginRenderViewFamily(*ViewFamily);
    
    UE_LOG(LogTemp, Warning, TEXT("After ViewExtension[%d]: PrimarySpatialUpscalerInterface = %s"), 
        ViewExt, ViewFamily->GetPrimarySpatialUpscalerInterface() ? TEXT("Valid") : TEXT("Null"));
}
相关推荐
郝学胜-神的一滴19 小时前
图形学中的纹理映射问题:摩尔纹与毛刺的深度解析
c++·程序人生·unity·游戏引擎·图形渲染·unreal engine
BFT白芙堂2 天前
基于 GPU 并行加速的 pRRTC 算法:赋能 Franka 机械臂的高效、稳定运动规划
人工智能·深度学习·算法·机器学习·gpu·具身智能·frankaresearch3
明洞日记3 天前
【VTK手册034】 vtkGeometryFilter 深度解析:高性能几何提取与转换专家
c++·图像处理·算法·ai·vtk·图形渲染
BoBoZz193 天前
BillboardTextActor3D 3D字体随镜头旋转
python·vtk·图形渲染·图形处理
BoBoZz193 天前
Tutorial_Step6 vtkBoxWidget的交互与控制
python·vtk·图形渲染·图形处理
郝学胜-神的一滴3 天前
深入理解Mipmap:原理、实现与应用
c++·程序人生·unity·游戏程序·图形渲染·unreal engine
BoBoZz193 天前
AnatomicalOrientation 3D人体模型及三个人体标准解剖学平面展示
python·vtk·图形渲染·图形处理
InfraTech4 天前
一文了解AI经典GPU架构---Tesla
gpu·cuda
BoBoZz194 天前
TextureCutQuadric 利用3D隐式函数(Quadrics)来生成2D纹理坐标
python·vtk·图形渲染·图形处理