一、源码分析
第一部分:插件启用检查
控制台变量定义
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));
}
}
}
- 检查平台是否为 ES3_1(移动平台)
- 检查主开关是否启用
- 根据子开关设置主放大器和次级锐化器
第二部分:平台支持检查
平台支持验证
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."));
}
- 确保游戏线程没有设置TemporalUpscaler
- 如果游戏线程已设置SpatialUpscaler,则 Fork 到渲染线程
- 调用所有 ViewExtension(包括 MobileFSR)
- 检查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使用率和帧率
- 验证视觉质量:在同一场景中快速切换不同方法,比较图像质量
- 调试渲染问题:当某个上采样方法出现问题时,可以强制使用其他方法来隔离问题
一、生效条件总结
必要条件:
- 平台支持 :
InViewFamily.GetFeatureLevel() == ERHIFeatureLevel::ES3_1 - 主开关启用 :
r.Mobile.FSR.Enabled > 0 - 放大功能启用 :
r.Mobile.FSR.Upsampling.Enabled > 0 - 接口设置成功:MobileFSR ViewExtension 成功设置接口
- 无冲突:没有其他系统已设置空间放大器或时序放大器
执行条件:
- 只要设置了
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"));
}