跳至内容

FidelityFX Super Resolution 3.1.4 (FSR3) - 升采样和帧生成

A screenshot showcasing the final output of the effect

目录

引言

AMD FidelityFX Super Resolution 3 (FSR3) 结合了分辨率放大和帧生成功能。

它采用了新的和改进的时间放大技术,以及新的光流实现,用于重投影来自 2 个渲染帧的采样,以生成中间的额外帧。FSR3 还实现了交换链代理,用于调度插值工作负载和处理 DirectX 12 和 Vulkan 的帧步进。

FSR3 flowchart

集成指南

着色语言和 API 要求

DirectX 12

  • CS_6_2
  • CS_6_6† 用于支持 64 宽波前的某些硬件。

Vulkan

  • Vulkan 1.x

虽然本文档基于 DX12,但可以通过 Vulkan FidelityFX API 获得 Vulkan 实现。您可以在 FSR 示例中找到参考集成。

快速入门清单

  • 使用 AMD 预构建和签名的单一 FidelityFX DLL,通过新的 FidelityFX API 进行集成。
  • 建议在实现帧生成之前,先实现仅画面放大功能。
  • 确保高质量的画面放大实现
    • 正确使用抖动模式
    • 正确放置后期处理操作
    • 正确使用响应性掩码
    • 正确使用透明度和合成掩码
    • 正确设置采样器的 mip 偏差
  • 对于帧生成
    • 为帧生成和步进交换链添加两个新上下文
    • 添加帧生成准备调度
    • 添加帧生成配置调用
    • 在需要时使用双缓冲
    • 修改 UI 渲染 - 选择以下 3 种处理 UI 的选项之一
      • 在回调函数中渲染 UI
        • 此函数将在每次呈现帧时被调用一次,并且需要能够异步渲染,同时正在渲染下一帧
        • 渲染两次 UI 会有性能开销,但好处是 UI(包括胶片颗粒等效果)可以以显示频率和低延迟进行更新
      • 将 UI 渲染到单独的纹理,然后将其合成到最终帧之上
        • 这在大多数应用程序中应该仍然非常容易集成
        • 与第三种选项相比,此选项将导致 UI 的渲染频率低于主场景
      • 为帧插值提供一个无 HUD 的纹理,以便自动检测 UI 并将其合成到插值帧上
        • 这可能是大多数应用程序中最容易实现的方案,但可能会在 UI 的半透明部分产生轻微的伪影
    • 帧生成可以同步或异步运行
      • 异步实现可能在某些应用程序和硬件上运行得更快,但可能需要额外的努力

演练

FSR3 使用 FidelityFX API。有关使用介绍,请参阅链接。

通过 FSR3 接口添加画面放大

注意:如果已存在且正常工作的 FSR2 或 FSR 3.0 放大实现,请参考 迁移指南以了解新接口。

包含 ffx_upscale.h 头文件(或 C++ 辅助函数 ffx_upscale.hpp

#include <ffx_api/ffx_upscale.h>

通过填写 ffxCreateContextDescUpsale 结构并提供所需的参数来创建用于放大的 ffxContext。在 pNext 字段中传递 ffxCreateBackendDX12DescffxCreateBackendVKDesc 的实例用于后端创建。

使用 C++ 辅助函数的示例

ffx::Context upscalingContext;
ffx::CreateBackendDX12Desc backendDesc{};
backendDesc.device = GetDevice()->DX12Device();
ffx::CreateContextDescUpscale createUpscaling;
createUpscaling.maxUpscaleSize = {DisplayWidth, DisplayHeight};
createUpscaling.maxRenderSize = {RenderWidth, RenderHeight};
createUpscaling.flags = FFX_UPSCALE_ENABLE_AUTO_EXPOSURE | FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE;
ffx::ReturnCode retCode = ffx::CreateContext(upscalingContext, nullptr, createUpscaling, backendDesc);
根据设置获取分辨率

要从选定的质量模式获取渲染分辨率或放大比例,请调用 ffxQuery 并使用 ffxQueryDescUpscaleGetUpscaleRatioFromQualityModeffxQueryDescUpscaleGetRenderResolutionFromQualityMode。在创建上下文之前调用这些查询是可能的,方法是传递 NULL 作为第一个参数(如果使用 C++ 辅助函数,请改用单个参数调用 ffx::Query)。

在这种情况下,如果使用版本覆盖,请确保也为查询包含相同的版本覆盖,否则查询将使用默认版本。

应用相机抖动

要获取相机抖动相位计数和偏移参数,请使用 ffxQuery。请务必始终传递一个已创建的有效上下文。以下示例展示了正确使用新 API 的方法

ffx::ReturnCode retCode;
int32_t jitterPhaseCount;
ffx::QueryDescUpscaleGetJitterPhaseCount getJitterPhaseDesc{};
getJitterPhaseDesc.displayWidth = resInfo.DisplayWidth;
getJitterPhaseDesc.renderWidth = resInfo.RenderWidth;
getJitterPhaseDesc.pOutPhaseCount = &jitterPhaseCount;
retCode = ffx::Query(m_UpscalingContext, getJitterPhaseDesc);
CauldronAssert(ASSERT_CRITICAL, retCode == ffx::ReturnCode::Ok, L"ffxQuery(FSR_GETJITTERPHASECOUNT) returned %d", retCode);
ffx::QueryDescUpscaleGetJitterOffset getJitterOffsetDesc{};
getJitterOffsetDesc.index = m_JitterIndex;
getJitterOffsetDesc.phaseCount = jitterPhaseCount;
getJitterOffsetDesc.pOutX = &m_JitterX;
getJitterOffsetDesc.pOutY = &m_JitterY;
retCode = ffx::Query(m_UpscalingContext, getJitterOffsetDesc);

有关底层实现以及如何在渲染过程中应用抖动的信息,请参阅 放大器文档的相机抖动相关部分

调度画面放大

要进行调度,请使用类型为 ffxDispatchDescUpscale 的描述调用 ffxDispatch。该结构声明如下

#define FFX_API_DISPATCH_DESC_TYPE_UPSCALE 0x00010001u
struct ffxDispatchDescUpscale
{
ffxDispatchDescHeader header;
void* commandList; ///< Command list to record upscaling rendering commands into.
struct FfxApiResource color; ///< Color buffer for the current frame (at render resolution).
struct FfxApiResource depth; ///< 32bit depth values for the current frame (at render resolution).
struct FfxApiResource motionVectors; ///< 2-dimensional motion vectors (at render resolution if <c><i>FFX_FSR_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS</i></c> is not set).
struct FfxApiResource exposure; ///< Optional resource containing a 1x1 exposure value.
struct FfxApiResource reactive; ///< Optional resource containing alpha value of reactive objects in the scene.
struct FfxApiResource transparencyAndComposition; ///< Optional resource containing alpha value of special objects in the scene.
struct FfxApiResource output; ///< Output color buffer for the current frame (at presentation resolution).
struct FfxApiFloatCoords2D jitterOffset; ///< The subpixel jitter offset applied to the camera.
struct FfxApiFloatCoords2D motionVectorScale; ///< The scale factor to apply to motion vectors.
struct FfxApiDimensions2D renderSize; ///< The resolution that was used for rendering the input resources.
struct FfxApiDimensions2D upscaleSize; ///< The resolution that the upscaler will upscale to (optional, assumed maxUpscaleSize otherwise).
bool enableSharpening; ///< Enable an additional sharpening pass.
float sharpness; ///< The sharpness value between 0 and 1, where 0 is no additional sharpness and 1 is maximum additional sharpness.
float frameTimeDelta; ///< The time elapsed since the last frame (expressed in milliseconds).
float preExposure; ///< The pre exposure value (must be > 0.0f)
bool reset; ///< A boolean value which when set to true, indicates the camera has moved discontinuously.
float cameraNear; ///< The distance to the near plane of the camera.
float cameraFar; ///< The distance to the far plane of the camera.
float cameraFovAngleVertical; ///< The camera angle field of view in the vertical direction (expressed in radians).
float viewSpaceToMetersFactor; ///< The scale factor to convert view space units to meters
uint32_t flags; ///< Zero or a combination of values from FfxApiDispatchFsrUpscaleFlags.
};

有关输入、输出以及在帧中的放置位置的详细信息,请参阅 放大器文档的相关部分。

使用 C++ 辅助函数的 FSR 示例的简略代码

ffx::DispatchDescUpscale dispatchUpscale{};
dispatchUpscale.commandList = pCmdList->GetImpl()->DX12CmdList();
dispatchUpscale.color = SDKWrapper::ffxGetResourceApi(m_pTempTexture->GetResource(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
dispatchUpscale.depth = SDKWrapper::ffxGetResourceApi(m_pDepthTarget->GetResource(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
dispatchUpscale.motionVectors = SDKWrapper::ffxGetResourceApi(m_pMotionVectors->GetResource(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
dispatchUpscale.output = SDKWrapper::ffxGetResourceApi(m_pColorTarget->GetResource(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
dispatchUpscale.reactive = SDKWrapper::ffxGetResourceApi(m_pReactiveMask->GetResource(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
dispatchUpscale.transparencyAndComposition = SDKWrapper::ffxGetResourceApi(m_pCompositionMask->GetResource(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
// Jitter is calculated earlier in the frame using a callback from the camera update
dispatchUpscale.jitterOffset.x = -m_JitterX;
dispatchUpscale.jitterOffset.y = -m_JitterY;
dispatchUpscale.motionVectorScale.x = resInfo.fRenderWidth();
dispatchUpscale.motionVectorScale.y = resInfo.fRenderHeight();
dispatchUpscale.reset = m_ResetUpscale;
dispatchUpscale.enableSharpening = m_RCASSharpen;
dispatchUpscale.sharpness = m_Sharpness;
// Cauldron keeps time in seconds, but FSR expects milliseconds
dispatchUpscale.frameTimeDelta = static_cast<float>(deltaTime * 1000.f);
dispatchUpscale.preExposure = GetScene()->GetSceneExposure();
dispatchUpscale.renderSize.width = resInfo.RenderWidth;
dispatchUpscale.renderSize.height = resInfo.RenderHeight;
dispatchUpscale.upscaleSize.width = resInfo.UpscaleWidth;
dispatchUpscale.upscaleSize.height = resInfo.UpscaleHeight;
// Setup camera params as required
dispatchUpscale.cameraFovAngleVertical = pCamera->GetFovY();
if (s_InvertedDepth)
{
dispatchUpscale.cameraFar = pCamera->GetNearPlane();
dispatchUpscale.cameraNear = FLT_MAX;
}
else
{
dispatchUpscale.cameraFar = pCamera->GetFarPlane();
dispatchUpscale.cameraNear = pCamera->GetNearPlane();
}
ffx::ReturnCode retCode = ffx::Dispatch(m_UpscalingContext, dispatchUpscale);
CauldronAssert(ASSERT_CRITICAL, !!retCode, L"Dispatching FSR upscaling failed: %d", (uint32_t)retCode);

完整代码可以在 fsrapirendermodule.cpp 中找到。

启用 FSR3 的代理帧生成交换链

为了方便集成,FSR3 提供了一个帧生成交换链,它提供了一个与 IDXGISwapChainVkSwapchainKHR 类似的接口。这些类可以替换“普通”交换链,并处理帧生成和 UI 合成工作负载的调度,以及调制帧步进以确保帧以大致均匀的步进显示。它们作为具有自身生命周期的上下文的一部分进行处理,独立于帧生成上下文。

使用帧生成交换链已得到优化,以确保低延迟、最小化画面撕裂,并与可变刷新率显示器良好配合。

由于不允许在全屏模式下替换交换链,帧生成交换链支持具有最小开销的直通模式,以便可以轻松禁用帧生成,而无需重新创建交换链。

DirectX 12 的 FSR 示例片段

#include <ffx_api/dx12/ffx_api_dx12.hpp>
IDXGISwapChain4* dxgiSwapchain = GetSwapChain()->GetImpl()->DX12SwapChain();
dxgiSwapchain->AddRef();
// Unset the swapchain in the engine
cauldron::GetSwapChain()->GetImpl()->SetDXGISwapChain(nullptr);
// For illustration, uses most elaborate call.
// Alternative 1: without hwnd, use ffxCreateContextDescFrameGenerationSwapChainNewDX12
// Alternative 2: replace existing swapchain, use ffxCreateContextDescFrameGenerationSwapChainWrapDX12
ffx::CreateContextDescFrameGenerationSwapChainForHwndDX12 createSwapChainDesc{};
dxgiSwapchain->GetHwnd(&createSwapChainDesc.hwnd);
DXGI_SWAP_CHAIN_DESC1 desc1;
dxgiSwapchain->GetDesc1(&desc1);
createSwapChainDesc.desc = &desc1;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullscreenDesc;
dxgiSwapchain->GetFullscreenDesc(&fullscreenDesc);
createSwapChainDesc.fullscreenDesc = &fullscreenDesc;
dxgiSwapchain->GetParent(IID_PPV_ARGS(&createSwapChainDesc.dxgiFactory));
createSwapChainDesc.gameQueue = GetDevice()->GetImpl()->DX12CmdQueue(cauldron::CommandQueue::Graphics);
dxgiSwapchain->Release();
dxgiSwapchain = nullptr;
createSwapChainDesc.swapchain = &dxgiSwapchain;
ffx::ReturnCode retCode = ffx::CreateContext(m_SwapChainContext, nullptr, createSwapChainDesc);
CauldronAssert(ASSERT_CRITICAL, retCode == ffx::ReturnCode::Ok, L"Couldn't create the ffxapi fg swapchain (dx12): %d", (uint32_t)retCode);
createSwapChainDesc.dxgiFactory->Release();
// Set new swapchain in engine
cauldron::GetSwapChain()->GetImpl()->SetDXGISwapChain(dxgiSwapchain);
// In case the app is handling Alt-Enter manually we need to update the window association after creating a different swapchain
IDXGIFactory7* factory = nullptr;
if (SUCCEEDED(dxgiSwapchain->GetParent(IID_PPV_ARGS(&factory))))
{
factory->MakeWindowAssociation(cauldron::GetFramework()->GetImpl()->GetHWND(), DXGI_MWA_NO_WINDOW_CHANGES);
factory->Release();
}
dxgiSwapchain->Release();
// Call SetHDRMetaData and SetColorSpace1 if needed
cauldron::GetSwapChain()->SetHDRMetadataAndColorspace();

Vulkan 的 FSR 示例片段

cauldron::SwapChain* pSwapchain = GetSwapChain();
VkSwapchainKHR currentSwapchain = pSwapchain->GetImpl()->VKSwapChain();
ffx::CreateContextDescFrameGenerationSwapChainVK createSwapChainDesc{};
createSwapChainDesc.physicalDevice = cauldron::GetDevice()->GetImpl()->VKPhysicalDevice();
createSwapChainDesc.device = cauldron::GetDevice()->GetImpl()->VKDevice();
// Pass swapchain to be replaced. Can also be null to only create new swapchain.
createSwapChainDesc.swapchain = &currentSwapchain;
createSwapChainDesc.createInfo = *cauldron::GetFramework()->GetSwapChain()->GetImpl()->GetCreateInfo();
createSwapChainDesc.allocator = nullptr;
// Set queues
createSwapChainDesc.gameQueue.queue = cauldron::GetDevice()->GetImpl()->VKCmdQueue(cauldron::CommandQueue::Graphics);
createSwapChainDesc.gameQueue.familyIndex = cauldron::GetDevice()->GetImpl()->GetQueueFamilies().familyIndices[cauldron::RequestedQueue::Graphics];
createSwapChainDesc.gameQueue.submitFunc = nullptr; // this queue is only used in vkQueuePresentKHR, hence doesn't need a callback
createSwapChainDesc.asyncComputeQueue.queue = cauldron::GetDevice()->GetImpl()->GetFIAsyncComputeQueue()->queue;
createSwapChainDesc.asyncComputeQueue.familyIndex = cauldron::GetDevice()->GetImpl()->GetQueueFamilies().familyIndices[cauldron::RequestedQueue::FIAsyncCompute];
createSwapChainDesc.asyncComputeQueue.submitFunc = nullptr;
createSwapChainDesc.presentQueue.queue = cauldron::GetDevice()->GetImpl()->GetFIPresentQueue()->queue;
createSwapChainDesc.presentQueue.familyIndex = cauldron::GetDevice()->GetImpl()->GetQueueFamilies().familyIndices[cauldron::RequestedQueue::FIPresent];
createSwapChainDesc.presentQueue.submitFunc = nullptr;
createSwapChainDesc.imageAcquireQueue.queue = cauldron::GetDevice()->GetImpl()->GetFIImageAcquireQueue()->queue;
createSwapChainDesc.imageAcquireQueue.familyIndex = cauldron::GetDevice()->GetImpl()->GetQueueFamilies().familyIndices[cauldron::RequestedQueue::FIImageAcquire];
createSwapChainDesc.imageAcquireQueue.submitFunc = nullptr;
// make sure swapchain is not holding a ref to real swapchain
cauldron::GetFramework()->GetSwapChain()->GetImpl()->SetVKSwapChain(VK_NULL_HANDLE);
auto convertQueueInfo = [](VkQueueInfoFFXAPI queueInfo) {
VkQueueInfoFFX info;
info.queue = queueInfo.queue;
info.familyIndex = queueInfo.familyIndex;
info.submitFunc = queueInfo.submitFunc;
return info;
};
VkFrameInterpolationInfoFFX frameInterpolationInfo = {};
frameInterpolationInfo.device = createSwapChainDesc.device;
frameInterpolationInfo.physicalDevice = createSwapChainDesc.physicalDevice;
frameInterpolationInfo.pAllocator = createSwapChainDesc.allocator;
frameInterpolationInfo.gameQueue = convertQueueInfo(createSwapChainDesc.gameQueue);
frameInterpolationInfo.asyncComputeQueue = convertQueueInfo(createSwapChainDesc.asyncComputeQueue);
frameInterpolationInfo.presentQueue = convertQueueInfo(createSwapChainDesc.presentQueue);
frameInterpolationInfo.imageAcquireQueue = convertQueueInfo(createSwapChainDesc.imageAcquireQueue);
ffx::ReturnCode retCode = ffx::CreateContext(m_SwapChainContext, nullptr, createSwapChainDesc);
// Get replacement function pointers
ffx::QueryDescSwapchainReplacementFunctionsVK replacementFunctions{};
ffx::Query(m_SwapChainContext, replacementFunctions);
cauldron::GetDevice()->GetImpl()->SetSwapchainMethodsAndContext(nullptr, nullptr, replacementFunctions.pOutGetSwapchainImagesKHR, replacementFunctions.pOutAcquireNextImageKHR, replacementFunctions.pOutQueuePresentKHR, replacementFunctions.pOutSetHdrMetadataEXT, replacementFunctions.pOutCreateSwapchainFFXAPI, replacementFunctions.pOutDestroySwapchainFFXAPI, nullptr, replacementFunctions.pOutGetLastPresentCountFFXAPI, m_SwapChainContext, &frameInterpolationInfo);
// Set frameinterpolation swapchain to engine
cauldron::GetFramework()->GetSwapChain()->GetImpl()->SetVKSwapChain(currentSwapchain, true);

在此之后,应用程序应与之前运行相同。帧生成尚未启用。

创建帧生成上下文

与用于放大的上下文创建类似,使用帧生成描述和后端描述调用 ffxCreateContext

使用 C++ 辅助函数的示例

ffx::Context frameGenContext;
ffx::CreateBackendDX12Desc backendDesc{};
backendDesc.device = GetDevice()->DX12Device();
ffx::CreateContextDescFrameGeneration createFg{};
createFg.displaySize = {resInfo.DisplayWidth, resInfo.DisplayHeight};
createFg.maxRenderSize = {resInfo.DisplayWidth, resInfo.DisplayHeight};
createFg.flags = FFX_FRAMEGENERATION_ENABLE_HIGH_DYNAMIC_RANGE;
if (m_EnableAsyncCompute)
createFg.flags |= FFX_FRAMEGENERATION_ENABLE_ASYNC_WORKLOAD_SUPPORT;
createFg.backBufferFormat = SDKWrapper::GetFfxSurfaceFormat(GetFramework()->GetSwapChain()->GetSwapChainFormat());
ffx::ReturnCode retCode = ffx::CreateContext(frameGenContext, nullptr, createFg, backendDesc);

配置帧生成

通过填写 ffxConfigureDescFrameGeneration 结构并提供所需参数,然后调用 ffxConfigure 来配置帧生成。

此函数必须每帧调用一次。帧 ID 必须在连续帧之间递增 1。任何其他差异都将重置帧生成逻辑。

// Update frame generation config
FfxApiResource hudLessResource = SDKWrapper::ffxGetResourceApi(m_pHudLessTexture[m_curUiTextureIndex]->GetResource(), FFX_API_RESOURCE_STATE_COMPUTE_READ);
m_FrameGenerationConfig.frameGenerationEnabled = m_FrameInterpolation;
m_FrameGenerationConfig.flags = 0;
m_FrameGenerationConfig.flags |= m_DrawFrameGenerationDebugTearLines ? FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_TEAR_LINES : 0;
m_FrameGenerationConfig.flags |= m_DrawFrameGenerationDebugResetIndicators ? FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_RESET_INDICATORS : 0;
m_FrameGenerationConfig.flags |= m_DrawFrameGenerationDebugView ? FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_VIEW : 0;
m_FrameGenerationConfig.HUDLessColor = (s_uiRenderMode == 3) ? hudLessResource : FfxApiResource({});
m_FrameGenerationConfig.allowAsyncWorkloads = m_AllowAsyncCompute && m_EnableAsyncCompute;
// assume symmetric letterbox
m_FrameGenerationConfig.generationRect.left = (resInfo.DisplayWidth - resInfo.UpscaleWidth) / 2;
m_FrameGenerationConfig.generationRect.top = (resInfo.DisplayHeight - resInfo.UpscaleHeight) / 2;
m_FrameGenerationConfig.generationRect.width = resInfo.UpscaleWidth;
m_FrameGenerationConfig.generationRect.height = resInfo.UpscaleHeight;
// For sample purposes only. Most applications will use one or the other.
if (m_UseCallback)
{
m_FrameGenerationConfig.frameGenerationCallback = [](ffxDispatchDescFrameGeneration* params, void* pUserCtx) -> ffxReturnCode_t
{
return ffxDispatch(reinterpret_cast<ffxContext*>(pUserCtx), &params->header);
};
m_FrameGenerationConfig.frameGenerationCallbackUserContext = &m_FrameGenContext;
}
else
{
m_FrameGenerationConfig.frameGenerationCallback = nullptr;
m_FrameGenerationConfig.frameGenerationCallbackUserContext = nullptr;
}
m_FrameGenerationConfig.onlyPresentGenerated = m_PresentInterpolatedOnly;
m_FrameGenerationConfig.frameID = m_FrameID;
m_FrameGenerationConfig.swapChain = GetSwapChain()->GetImpl()->DX12SwapChain();
ffx::ReturnCode retCode = ffx::Configure(m_FrameGenContext, m_FrameGenerationConfig);
CauldronAssert(ASSERT_CRITICAL, !!retCode, L"Configuring FSR FG failed: %d", (uint32_t)retCode);

如果使用帧生成回调,交换链将使用适当的参数调用回调。否则,应用程序负责调用帧生成调度并自行设置参数。在这种情况下,帧 ID 必须等于配置中使用的帧 ID。可以从帧生成上下文中查询命令列表和输出纹理,方法是使用 ffxQuery。有关示例,请参阅 示例代码

用户上下文指针只会传递到各自的回调函数。FSR 代码不会尝试解引用它们。

allowAsyncWorkloads 设置为 false 时,将使用主图形队列来执行光流和帧生成工作负载。强烈建议进行性能分析,以确定异步计算使用是否能带来显著的性能提升。不使用异步计算将导致较低的内存开销。

请注意,UI 合成和呈现将始终在异步队列上执行,因此可以对它们进行步进并将其插入到生成下一帧的工作负载中间。

FSR3 non async workflow

allowAsyncWorkloads 设置为 true 时,光流和帧生成工作负载将在异步计算队列上运行,并与主游戏图形队列上的下一帧工作负载重叠。这可以提高 GPU 和工作负载的性能。

FSR3 non async workflow

UI 组成

对于帧插值,用户界面需要特殊处理,否则会产生非常明显的伪影,影响界面的可读性。

为了防止这些伪影,FSR3 支持各种处理 UI 的选项

首选方法是使用 presentCallback。此参数中提供的函数将在每次呈现帧时被调用一次,并允许应用程序调度渲染 UI 所需的 GPU 工作负载。通过使用此函数,应用程序可以减少 UI 输入延迟,并渲染与帧生成不兼容的效果(例如,胶片颗粒)。

UI 合成回调函数将为每一帧(真实或生成的)调用,以允许单独为每一帧渲染 UI,从而 UI 可以以呈现频率渲染以实现平滑的 UI 动画。

ffxReturnCode_t FSR3RenderModule::UiCompositionCallback(ffxCallbackDescFrameGenerationPresent* params, void* userCtx)
{
ID3D12GraphicsCommandList2* pDxCmdList = reinterpret_cast<ID3D12GraphicsCommandList2*>(params->commandList);
ID3D12Resource* pRtResource = reinterpret_cast<ID3D12Resource*>(params->outputSwapChainBuffer.resource);
ID3D12Resource* pBbResource = reinterpret_cast<ID3D12Resource*>(params->currentBackBuffer.resource);
// Use pDxCmdList to copy pBbResource and render UI into the outputSwapChainBuffer.
// The backbuffer is provided as SRV so postprocessing (e.g. adding a blur effect behind the UI) can easily be applied
return FFX_API_RETURN_OK;
}

FSR3 non async workflow

如果禁用帧生成,presentCallback 仍然会在呈现时被调用。FSR3 非异步工作流

处理 UI 的第二种选项是将 UI 渲染到单独的表面,该表面将在呈现前混合到插值和后备缓冲区上。此表面的合成可以由代理交换链自动完成,或在 presentCallback 中手动完成。此方法允许呈现不受帧插值影响的 UI,但 UI 仍以渲染速率渲染。对于 UI 大部分静态的应用程序来说,这可能是一个不错的解决方案,而无需以呈现速率渲染 UI 的额外开销。

FSR3 non async workflow

如果禁用帧生成并提供了 UI 纹理,帧插值交换链仍将执行 UI 合成。FSR3 非异步工作流

在这种情况下,需要通过调用 ffxConfigure 并使用 ffxConfigureDescFrameGenerationSwapChainRegisterUiResourceDX12 结构将表面注册到交换链。

FfxResource uiColor = ffxGetResource(m_pUiTexture[m_curUiTextureIndex]->GetResource(), L"FSR3_UiTexture", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ);
ffx::ConfigureDescFrameGenerationSwapChainRegisterUiResourceDX12 uiConfig{};
uiConfig.uiResource = uiColor;
uiConfig.flags = m_DoublebufferInSwapchain ? FFX_FRAMEGENERATION_UI_COMPOSITION_FLAG_ENABLE_INTERNAL_UI_DOUBLE_BUFFERING : 0;
ffx::Configure(m_SwapChainContext, uiConfig);

处理 UI 的最后一种方法是在 FfxFrameGenerationConfig 中提供一个 HUDLessColor 表面。此表面将在帧插值期间使用,以检测 UI 并避免 UI 元素失真。此方法已添加,以兼容无法采用其他两种 UI 渲染选项的引擎。

FSR3 non async workflow

调度帧生成准备

从 3.1.0 版本开始,帧生成独立于 FSR 画面放大运行。为了替换之前与画面放大器共享的资源,需要一个新的帧生成准备通道。

在调用 ffxConfigure 之后,填写 ffxDispatchDescFrameGenerationPrepare 结构,并使用帧生成上下文和该结构调用 ffxDispatch

对于在 ffxDispatchDescUpscale 中也找到的字段,此处适用相同的输入要求和建议。

将 frameID 设置为与 configure 描述中的相同值。

关闭

在关闭期间,在代理交换链中禁用 UI 处理和帧生成,并销毁上下文。

// disable frame generation before destroying context
// also unset present callback, HUDLessColor and UiTexture to have the swapchain only present the backbuffer
m_FrameGenerationConfig.frameGenerationEnabled = false;
m_FrameGenerationConfig.swapChain = GetSwapChain()->GetImpl()->DX12SwapChain();
m_FrameGenerationConfig.presentCallback = nullptr;
m_FrameGenerationConfig.HUDLessColor = FfxApiResource({});
ffx::Configure(m_FrameGenContext, m_FrameGenerationConfig);
ffx::ConfigureDescFrameGenerationSwapChainRegisterUiResourceDX12 uiConfig{};
uiConfig.uiResource = {};
uiConfig.flags = 0;
ffx::Configure(m_SwapChainContext, uiConfig);
// Destroy the contexts
ffx::DestroyContext(m_UpscalingContext);
ffx::DestroyContext(m_FrameGenContext);

最后,通过释放句柄、使用 ffxDestroyContext 销毁上下文,并重新创建正常的 DX12 交换链来销毁代理交换链。

线程安全

ffx-api 上下文不保证线程安全。在此技术中,FrameGenContextSwapChainContext 并非线程安全。竞态条件症状包括 访问冲突错误崩溃、插值视觉伪影以及在释放交换链的 Dx12CommandPool 析构函数中无限等待。这并不明显,但 FrameInterpolationSwapchainDX12::Present() 实际上会访问 SwapChainContextFrameGenContext(用于调度光流和帧生成)。如果应用程序线程可以同时调用 FrameInterpolationSwapchainDX12::Present()Dispatch(m_FrameGenContext, DispatchDescFrameGenerationPrepare),则会发生竞态条件。如果应用程序线程可以同时调用 FrameInterpolationSwapchainDX12::Present()DestroyContext(SwapChainContext),也会发生竞态条件。应用程序可以在调用访问 FrameGenContextSwapChainContext 的 ffx 函数之前获取互斥锁,以确保任何时候最多只有一个线程可以访问该上下文。

资源生命周期

使用 UiTexture 合成模式时

如果设置了 FFX_FRAMEGENERATION_UI_COMPOSITION_FLAG_ENABLE_INTERNAL_UI_DOUBLE_BUFFERING

UiTexture 将在游戏队列上复制到内部资源。UiTexture 可以在下一帧的 GFX 队列上立即重用。

如果未设置 FFX_FRAMEGENERATION_UI_COMPOSITION_FLAG_ENABLE_INTERNAL_UI_DOUBLE_BUFFERING

应用程序负责确保 UiTexture 在真实帧合成完成之前一直存在。这通常发生在下一帧中间,因此在下一帧期间不应使用 UiTexture。应用程序必须确保 UITexture 的双缓冲。

使用 HUDLess 合成模式时

HUDLess 纹理将在 FrameInterpolation 期间使用。应用程序负责确保它在 FrameInterpolation 完成之前一直存在。如果 FfxFrameGenerationConfig::allowAsyncWorkloads 为 true:Frameinterpolation 在异步计算队列上进行,因此 HUDLess 纹理需要由应用程序进行双缓冲。如果 FfxFrameGenerationConfig::allowAsyncWorkloads 为 false:Frameinterpolation 在游戏 GFX 队列上进行,因此应用程序可以在下一帧安全地修改 HUDLess 纹理。

注册 distortionField 纹理到 FrameInterpolation 时

应用程序负责确保 distortionField 纹理在 FrameInterpolation 完成之前一直存在。如果 FfxFrameGenerationConfig::allowAsyncWorkloads 为 true:Frameinterpolation 在异步计算队列上进行,因此 distortionField 纹理需要由应用程序进行双缓冲。如果 FfxFrameGenerationConfig::allowAsyncWorkloads 为 false:Frameinterpolation 在游戏 GFX 队列上进行,因此应用程序可以在下一帧安全地修改 distortionField 纹理。

调试器

启用调试检查器以验证调度放大时应用程序提供的输入。此功能可以在运行时(例如,PrebuiltSignedDll 文件夹中的发布二进制文件或调试构建)的任何生成配置中启用。建议仅在游戏开发版本中启用此功能。

ffxCreateContextDescFrameGeneration 中传递 FFX_FRAMEGENERATION_ENABLE_DEBUG_CHECKING 标志,默认情况下会将帧生成的文本警告输出到调试器 TTY。ffx::Configure API 允许应用程序设置回调函数,将消息传递给底层应用程序。将 ffxConfigureDescGlobalDebug1 中的 fpMessage 分配给一个合适的函数。

以下是一个当调试检查器发现可能的问题时可能发生的输出示例

FSR_API_DEBUG_WARNING: ffxDispatchDescFrameGenerationPrepareCameraInfo 需要作为链接结构传递。这是 FSR3.1.4 及更高版本获得最佳质量的必需输入。

该技术

FSR3 是一个由四个组件组成的容器效果。有关每个组件的详细信息,请参阅专用文档页面

  1. FfxFsr3Upscaler
  2. FfxOpticalFlow
  3. FfxFrameinterpolation
  4. 帧生成交换链

内存使用情况

数字以最接近的 MB 为单位给出,在 Radeon RX 7900 XTX 上使用 DirectX 12 测量,并且可能发生变化。不包括帧生成交换链的开销。

输出分辨率画质内存使用情况(画面放大器)内存使用情况(帧生成)总内存使用情况
3840x2160原生 AA498.88473971.88
3840x2160质量306268574
3840x2160平衡268.98222.91491.89
3840x2160性能237.26189.88427.14
2560x1440原生 AA227.82221.74449.56
2560x1440质量140.271496.42265.54
2560x1440平衡123.75104.3228.05
2560x1440性能106.9886.21193.19
1920x1080原生 AA130.83129.33260.16
1920x1080质量78.9371.92150.85
1920x1080平衡70.2863.01133.29
1920x1080性能63.7256.72120.44

另请参阅

© . This site is unofficial and not affiliated with AMD.