FidelityFX Super Resolution 3.1.4 (FSR3) - 升采样和帧生成
目录
引言
AMD FidelityFX Super Resolution 3 (FSR3) 结合了分辨率放大和帧生成功能。
它采用了新的和改进的时间放大技术,以及新的光流实现,用于重投影来自 2 个渲染帧的采样,以生成中间的额外帧。FSR3 还实现了交换链代理,用于调度插值工作负载和处理 DirectX 12 和 Vulkan 的帧步进。
集成指南
着色语言和 API 要求
DirectX 12
CS_6_2CS_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 的半透明部分产生轻微的伪影
- 在回调函数中渲染 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 字段中传递 ffxCreateBackendDX12Desc 或 ffxCreateBackendVKDesc 的实例用于后端创建。
使用 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 并使用 ffxQueryDescUpscaleGetUpscaleRatioFromQualityMode 或 ffxQueryDescUpscaleGetRenderResolutionFromQualityMode。在创建上下文之前调用这些查询是可能的,方法是传递 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 0x00010001ustruct 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 updatedispatchUpscale.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 millisecondsdispatchUpscale.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 requireddispatchUpscale.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 提供了一个帧生成交换链,它提供了一个与 IDXGISwapChain 和 VkSwapchainKHR 类似的接口。这些类可以替换“普通”交换链,并处理帧生成和 UI 合成工作负载的调度,以及调制帧步进以确保帧以大致均匀的步进显示。它们作为具有自身生命周期的上下文的一部分进行处理,独立于帧生成上下文。
使用帧生成交换链已得到优化,以确保低延迟、最小化画面撕裂,并与可变刷新率显示器良好配合。
由于不允许在全屏模式下替换交换链,帧生成交换链支持具有最小开销的直通模式,以便可以轻松禁用帧生成,而无需重新创建交换链。
DirectX 12 的 FSR 示例片段
#include <ffx_api/dx12/ffx_api_dx12.hpp>
IDXGISwapChain4* dxgiSwapchain = GetSwapChain()->GetImpl()->DX12SwapChain();dxgiSwapchain->AddRef();// Unset the swapchain in the enginecauldron::GetSwapChain()->GetImpl()->SetDXGISwapChain(nullptr);
// For illustration, uses most elaborate call.// Alternative 1: without hwnd, use ffxCreateContextDescFrameGenerationSwapChainNewDX12// Alternative 2: replace existing swapchain, use ffxCreateContextDescFrameGenerationSwapChainWrapDX12ffx::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 enginecauldron::GetSwapChain()->GetImpl()->SetDXGISwapChain(dxgiSwapchain);
// In case the app is handling Alt-Enter manually we need to update the window association after creating a different swapchainIDXGIFactory7* 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 neededcauldron::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 = ¤tSwapchain;createSwapChainDesc.createInfo = *cauldron::GetFramework()->GetSwapChain()->GetImpl()->GetCreateInfo();createSwapChainDesc.allocator = nullptr;// Set queuescreateSwapChainDesc.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 callbackcreateSwapChainDesc.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 swapchaincauldron::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 pointersffx::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 enginecauldron::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 configFfxApiResource 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 letterboxm_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), ¶ms->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 合成和呈现将始终在异步队列上执行,因此可以对它们进行步进并将其插入到生成下一帧的工作负载中间。
当 allowAsyncWorkloads 设置为 true 时,光流和帧生成工作负载将在异步计算队列上运行,并与主游戏图形队列上的下一帧工作负载重叠。这可以提高 GPU 和工作负载的性能。
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;}如果禁用帧生成,presentCallback 仍然会在呈现时被调用。
处理 UI 的第二种选项是将 UI 渲染到单独的表面,该表面将在呈现前混合到插值和后备缓冲区上。此表面的合成可以由代理交换链自动完成,或在 presentCallback 中手动完成。此方法允许呈现不受帧插值影响的 UI,但 UI 仍以渲染速率渲染。对于 UI 大部分静态的应用程序来说,这可能是一个不错的解决方案,而无需以呈现速率渲染 UI 的额外开销。
如果禁用帧生成并提供了 UI 纹理,帧插值交换链仍将执行 UI 合成。
在这种情况下,需要通过调用 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 渲染选项的引擎。
调度帧生成准备
从 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 backbufferm_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 contextsffx::DestroyContext(m_UpscalingContext);ffx::DestroyContext(m_FrameGenContext);最后,通过释放句柄、使用 ffxDestroyContext 销毁上下文,并重新创建正常的 DX12 交换链来销毁代理交换链。
线程安全
ffx-api 上下文不保证线程安全。在此技术中,FrameGenContext 和 SwapChainContext 并非线程安全。竞态条件症状包括 访问冲突错误崩溃、插值视觉伪影以及在释放交换链的 Dx12CommandPool 析构函数中无限等待。这并不明显,但 FrameInterpolationSwapchainDX12::Present() 实际上会访问 SwapChainContext 和 FrameGenContext(用于调度光流和帧生成)。如果应用程序线程可以同时调用 FrameInterpolationSwapchainDX12::Present() 和 Dispatch(m_FrameGenContext, DispatchDescFrameGenerationPrepare),则会发生竞态条件。如果应用程序线程可以同时调用 FrameInterpolationSwapchainDX12::Present() 和 DestroyContext(SwapChainContext),也会发生竞态条件。应用程序可以在调用访问 FrameGenContext 或 SwapChainContext 的 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 是一个由四个组件组成的容器效果。有关每个组件的详细信息,请参阅专用文档页面
内存使用情况
数字以最接近的 MB 为单位给出,在 Radeon RX 7900 XTX 上使用 DirectX 12 测量,并且可能发生变化。不包括帧生成交换链的开销。
| 输出分辨率 | 画质 | 内存使用情况(画面放大器) | 内存使用情况(帧生成) | 总内存使用情况 |
|---|---|---|---|---|
| 3840x2160 | 原生 AA | 498.88 | 473 | 971.88 |
| 3840x2160 | 质量 | 306 | 268 | 574 |
| 3840x2160 | 平衡 | 268.98 | 222.91 | 491.89 |
| 3840x2160 | 性能 | 237.26 | 189.88 | 427.14 |
| 2560x1440 | 原生 AA | 227.82 | 221.74 | 449.56 |
| 2560x1440 | 质量 | 140.27 | 1496.42 | 265.54 |
| 2560x1440 | 平衡 | 123.75 | 104.3 | 228.05 |
| 2560x1440 | 性能 | 106.98 | 86.21 | 193.19 |
| 1920x1080 | 原生 AA | 130.83 | 129.33 | 260.16 |
| 1920x1080 | 质量 | 78.93 | 71.92 | 150.85 |
| 1920x1080 | 平衡 | 70.28 | 63.01 | 133.29 |
| 1920x1080 | 性能 | 63.72 | 56.72 | 120.44 |