FidelityFX Parallel Sort 示例详解:在 DLSSG-to-FSR3 项目中使用 GPU 基数排序验证与集成指南

原创2026-10-03 17:34:551,103 阅读
文章标签:图形学游戏开发

FidelityFX Parallel Sort 示例详解:在 DLSSG-to-FSR3 项目中使用 GPU 基数排序验证与集成指南

本篇技术指南以 FidelityFX SDK 官方 Parallel Sort 示例文档为主线,结合本仓库(dlssg-to-fsr3,即通过替换 NVIDIA DLSS Frame Generation 为 AMD FSR 3 帧生成的加速项目)内携带的 FidelityFX-SDK 源码 与示例实现,完整讲解 Parallel Sort 示例的运行方式、UI 控件作用、GPU 基数排序算法原理,以及如何通过 ffx_parallelsort.h 公开 API 在自己的渲染管线中集成排序能力。读完本文,你将掌握该示例的每个 UI 元素对应哪段代码、如何用 C++ 一次性完成排序 dispatch、以及"8 次迭代 × 5 个 pass"的 radix sort 核心工作流。

示例概览

FidelityFX Parallel Sort 示例用于演示 FidelityFX Parallel Sort 特效:它是一个基于 GPU radix sort(基数排序)算法的技术,可以对一个 32 位 key 缓冲区(以及可选的 payload 缓冲区)进行排序。示例窗口会展示一张验证图,左侧/未勾选状态下显示未排序的随机 key 缓冲区对应的图像,勾选 Render Sorted Keys 后则显示排序完成后 key 缓冲区对应的图像,让开发者直观确认排序结果是否正确。

FidelityFX Parallel Sort 示例运行截图

示例的完整官方文档位于 samples/parallel-sort.md,底层算法详解见 techniques/parallel-sort.md。

运行需求

根据官方示例文档,运行该示例需要满足以下环境条件:

  • 操作系统:Windows
  • 图形 API:DirectX(R) 12 与 Vulkan(R) 双后端支持
  • 着色器语言:HLSL / GLSL,Compute Shader 6.0(CS_6_0)——该要求来自 techniques/parallel-sort.md 的 Shading language requirements 一节

示例通过 FidelityFX Cauldron 框架构建,具体构建方式可参考 SDK 根目录的 BuildSamplesSolutionDX12.bat / BuildSamplesSolutionVK.bat,运行与框架控制说明参见 getting-started/running-samples.md。

示例配置:RenderModules 与资源配置

示例的渲染模块配置文件位于 samples/parallelsort/config/parallelsortconfig.json,内容如下:

{
    "FidelityFX Parallel Sort": {

        "RenderModules": [
            "ParallelSortRenderModule",
            "ToneMappingRenderModule"
        ]
    }
}

配置说明:

在渲染模块初始化时(Init 函数),示例会预生成三种分辨率的未排序 key 数据:key 的初始值是线性访问索引(std::iota),随后用 std::shuffle 配合 std::mt19937 随机打乱,key 数量与分辨率一一对应(源码 parallelsortrendermodule.cpp):

const uint32_t ParallelSortRenderModule::s_NumKeys[ResSize_Count] = { 1920 * 1080, 2560 * 1440, 3840 * 2160 };

即分别对应 1920×1080(约 207 万)、2560×1440(约 368 万)、3840×2160(约 829 万) 个 32 位 key。排序缓冲与 payload 缓冲按最大分辨率(4K)一次性分配(AllowUnorderedAccess 标志),较小的分辨率排序时只使用缓冲区前部区域。

UI 元素与控件说明

官方示例文档用表格总结了四个 UI 控件,以下是完整表格,并补充了它们对应的源码位置:

元素名称 值 描述
Buffer Resolution(缓冲分辨率) 1920x1080, 2560x1440, 3840x2160 切换需要排序的各类 key 缓冲区尺寸
Render Sorted Keys(渲染已排序 key) Checked/Unchecked 切换验证步骤中的输出缓冲区。未勾选渲染未排序缓冲区,勾选则渲染排序后的 key 图像
Sort Payload(排序 payload) Checked/Unchecked 切换是仅排序 key,还是同时排序随附的 payload 缓冲区
Use Indirect Execution(间接执行) Checked/Unchecked 在直接与间接的 compute 执行方式之间切换排序算法

这些控件在 parallelsortrendermodule.cpp 的 Init 中通过 Cauldron 的 UIManager 注册,其内部状态直接影响 FidelityFX context 的创建标志:

// Add resolution sizes combo
uiSection->RegisterUIElement<UICombo>("Buffer resolutions", (int32_t&)m_ParallelSortResolutions, std::move(resolutionSizes));
// Add output visualization selector
uiSection->RegisterUIElement<UICheckBox>("Render Sorted Keys", m_ParallelSortRenderSortedKeys);
// Add sort payload checkbox(切换后重建 context)
uiSection->RegisterUIElement<UICheckBox>("Sort Payload", m_ParallelSortPayload, optionChangeCallback);
// Add indirect execution checkbox(切换后重建 context)
uiSection->RegisterUIElement<UICheckBox>("Use Indirect Execution", m_ParallelSortIndirectExecution, optionChangeCallback);

其中 Sort Payload 与 Use Indirect Execution 两个勾选项通过 optionChangeCallback 触发 ResetParallelSortContext(),即在运行时销毁并重建 FidelityFX Parallel Sort context,以应用新的初始化标志。

控件背后的初始化标志

勾选项与 FidelityFX 初始化标志的对应关系(源码 parallelsortrendermodule.cpp):

m_InitializationParameters.flags = 0;
m_InitializationParameters.flags |= m_ParallelSortIndirectExecution ? FFX_PARALLELSORT_INDIRECT_SORT : 0;
m_InitializationParameters.flags |= m_ParallelSortPayload ? FFX_PARALLELSORT_PAYLOAD_SORT : 0;
// Highest resolution is the max number of keys we'll ever sort
m_InitializationParameters.maxEntries = s_NumKeys[ResSize_Count - 1];

两个标志位的官方定义位于头文件 sdk/include/FidelityFX/host/ffx_parallelsort.h:

FFX_PARALLELSORT_INDIRECT_SORT  = (1 << 0),   ///< 使用间接版本的排序算法
FFX_PARALLELSORT_PAYLOAD_SORT   = (1 << 1),   ///< 同时排序 payload 缓冲区

maxEntries 表示该 context 最多可排序的条目数,示例按最大分辨率(4K 的 829 万)设置,因此任意分辨率切换时无需重建缓冲区。

算法原理:8 次迭代 × 5 个 Pass 的 GPU 基数排序

根据 techniques/parallel-sort.md,FidelityFX Parallel Sort 使用面向 RDNA 架构优化的 GPU radix sort 算法,是大型数据集可用的最快排序算法之一。算法按顺序数据块(blocks of sequential data)进行操作以获得最优读取性能:一个线程组(thread group)根据数据集大小排序 1 个或多个数据块,每个线程组可配置线程数(当前固定为 128)。

要完整排序一个 32 位 key 缓冲区,算法需要执行 8 次迭代——每次迭代处理 4 个 bit,每轮迭代依次执行以下 5 个步骤:

  1. Count(计数):根据已处理的 bit 数,统计本地排序集内各值的出现次数;
  2. ReduceCount(归约计数):将计数进一步归约到线程组大小对齐的粒度,以便跨线程组快速计算偏移;
  3. ScanPrefix(前缀扫描):对归约后的计数求和,得出各线程组的偏移量;
  4. ScanPrefix + Add(前缀扫描并累加):线程内计数求和,并与线程组偏移量相加,得出最终偏移位置;
  5. Scatter(散射):将源值拷贝到新位置(此时按已处理的 n 个 bit 有序)。

FidelityFX Parallel Sort 算法流程

这 5 个步骤与头文件中定义的 pass 枚举一一对应(ffx_parallelsort.h):

FFX_PARALLELSORT_PASS_SETUP_INDIRECT_ARGS = 0,  ///< 设置间接参数(仅当启用 FFX_PARALLEL_SORT_INDIRECT 标志时)
FFX_PARALLELSORT_PASS_SUM,                      ///< 统计数据集中每个值的出现次数(Count)
FFX_PARALLELSORT_PASS_REDUCE,                   ///< 跨线程组进一步归约计数(ReduceCount)
FFX_PARALLELSORT_PASS_SCAN,                     ///< 将计数前缀求和为全局偏移(ScanPrefix)
FFX_PARALLELSORT_PASS_SCAN_ADD,                 ///< 二次前缀加,将全局偏移加到各局部线程组偏移(ScanPrefix+Add)
FFX_PARALLELSORT_PASS_SCATTER,                  ///< 线程组内局部排序并输出到新全局偏移(Scatter)
FFX_PARALLELSORT_PASS_COUNT                     ///< pass 总数

注意 FFX_PARALLELSORT_PASS_SETUP_INDIRECT_ARGS 仅在启用 FFX_PARALLELSORT_INDIRECT_SORT 标志时才会参与执行,这正是 UI 中 Use Indirect Execution 开关所控制的差异。完整的 pass 实现与调度逻辑见组件源码 sdk/src/components/parallelsort/ffx_parallelsort.cpp。

C++ 集成:从创建 context 到完成一次排序

第一步:初始化 FidelityFX 后端接口

示例通过 Cauldron 框架的 SDKWrapper 获取后端接口并分配 scratch 内存(parallelsortrendermodule.cpp):

const size_t scratchBufferSize = SDKWrapper::ffxGetScratchMemorySize(FFX_PARALLELSORT_CONTEXT_COUNT);
void*        scratchBuffer     = calloc(scratchBufferSize, 1u);
FfxErrorCode errorCode = SDKWrapper::ffxGetInterface(&m_InitializationParameters.backendInterface,
                                                     GetDevice(), scratchBuffer, scratchBufferSize,
                                                     FFX_PARALLELSORT_CONTEXT_COUNT);

示例同时用断言锁定了 SDK 版本要求:后端接口必须为 FFX_SDK_MAKE_VERSION(1, 1, 2),Parallel Sort 库必须为 1.3.0 版本(即 FFX_PARALLELSORT_VERSION_MAJOR=1 / MINOR=3 / PATCH=0,见 ffx_parallelsort.h)。

第二步:创建 Parallel Sort context

FfxParallelSortContextDescription 包含三个字段(ffx_parallelsort.h):flags(初始化标志位集合)、maxEntries(最大排序条目数)、backendInterface(后端实现指针集)。context 内部持有所有持久化数据和资源,生命周期应与使用周期一致,销毁前必须确保 GPU 已空闲(官方头文件注释明确建议销毁前 GPU idle)。

第三步:每帧执行排序 dispatch

官方示例文档给出的 C++ 集成代码(静态库方式)如下:

FfxParallelSortDispatchDescription dispatchDesc = {};
dispatchDesc.commandList    = ffxGetCommandList(pCmdList);
dispatchDesc.keyBuffer      = ffxGetResource(m_pKeysToSort->GetResource(), L"ParallelSort_KeyBuffer", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ);
dispatchDesc.payloadBuffer  = ffxGetResource(m_pPayloadToSort->GetResource(), L"ParallelSort_PayloadBuffer", FFX_RESOURCE_STATE_PIXEL_COMPUTE_READ);
dispatchDesc.numKeysToSort  = <Number of keys to sort>;

FfxErrorCode errorCode = ffxParallelSortContextDispatch(&m_ParallelSortContext, &dispatchDesc);
FFX_ASSERT(errorCode == FFX_OK);

各字段含义(ffx_parallelsort.h):

字段 含义
commandList 用于记录排序 compute 命令的 FfxCommandList
keyBuffer 包含待排序 key 的缓冲区资源
payloadBuffer (可选)需要同步排序的 payload 缓冲区(要求设置 FFX_PARALLELSORT_PAYLOAD_SORT 标志)
numKeysToSort 缓冲区中需要排序的 key 数量

在示例每帧执行逻辑中(parallelsortrendermodule.cpp),numKeysToSort 按当前 UI 选择的分辨率取值,并在 dispatch 前把未排序数据通过 CopyBufferRegion 拷入排序缓冲区(先做资源状态 barrier 转换,dispatch 后再用 SetAllResourceViewHeaps 恢复 Cauldron 的 descriptor heap)。

第四步:验证 pass

排序完成后,示例运行一个全屏验证 pass(着色器见 samples/parallelsort/shaders/parallelsort_verify.hlsl):像素着色器 RenderSortValidationPS 以屏幕中心为基准计算查询坐标,从排序/未排序的 StructuredBuffer<uint> 中取出 key 值,再把 key 值反解为 2D 坐标,从预加载的验证纹理(parallelsort_validate1080p.png 等三张,按分辨率切换)采样着色。若排序正确,画面将呈现完整的验证图案;若 key 乱序,图案会呈现破碎效果。该 pass 用到的常量缓冲结构定义于 samples/parallelsort/shaders/parallelsort_common.h(Width/Height 为显示分辨率,SortWidth/SortHeight 为排序数据源分辨率)。

运行时开关行为与 context 重建

UI 中两个选项(Sort Payload、Use Indirect Execution)的切换都会调用 ResetParallelSortContext()(parallelsortrendermodule.cpp),其流程为:

  1. GetDevice()->FlushAllCommandQueues() 排空命令队列,确保 GPU 不再访问 context 资源;
  2. ffxParallelSortContextDestroy 销毁旧 context;
  3. 根据当前 UI 状态重新计算 flags 并 ffxParallelSortContextCreate 重建 context。

此外,窗口尺寸变化(OnResize)也会触发相同的重建流程。context 销毁路径见 DestroyFfxContext(parallelsortrendermodule.cpp),其中同样先 FlushAllCommandQueues 再销毁 context 并释放 scratch 内存。

在 DLSSG-to-FSR3 项目中的关联价值

本仓库(dlssg-to-fsr3)的核心目标是用 AMD FSR 3 帧生成替换 NVIDIA DLSS 帧生成,其实现位于 source/maindll 与 source/wrapper_dt(NGX 接口包装层)。而 FidelityFX SDK 作为整套依赖(dependencies/FidelityFX-SDK)被引入仓库。Parallel Sort 示例本身与帧生成管线无直接耦合,但它是验证 FidelityFX SDK 后端(DX12/VK)正确初始化、compute 资源生命周期管理与 context 重建模式的最佳演练场——这套"创建 context → dispatch → 销毁 context"的模式与 FSR3 帧生成 context 的用法一致,可作为接入该 SDK 其他组件的入门参考。构建 FidelityFX SDK 本体可参见 dependencies/Build-FFX-SDK.bat。

延伸阅读

登录后查看全文
dlssg-to-fsr3