FidelityFX Parallel Sort 示例详解:在 DLSSG-to-FSR3 项目中使用 GPU 基数排序验证与集成指南
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"
]
}
}
配置说明:
ParallelSortRenderModule:核心渲染模块,负责生成未排序数据、执行排序 dispatch 与验证 pass;ToneMappingRenderModule:色调映射模块,负责最终画面输出;- 渲染模块注册表位于 rendermodules/rendermoduleregistry.cpp,
ParallelSortRenderModule的具体实现见 samples/parallelsort/parallelsortrendermodule.cpp。
在渲染模块初始化时(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 个步骤:
- Count(计数):根据已处理的 bit 数,统计本地排序集内各值的出现次数;
- ReduceCount(归约计数):将计数进一步归约到线程组大小对齐的粒度,以便跨线程组快速计算偏移;
- ScanPrefix(前缀扫描):对归约后的计数求和,得出各线程组的偏移量;
- ScanPrefix + Add(前缀扫描并累加):线程内计数求和,并与线程组偏移量相加,得出最终偏移位置;
- 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),其流程为:
GetDevice()->FlushAllCommandQueues()排空命令队列,确保 GPU 不再访问 context 资源;ffxParallelSortContextDestroy销毁旧 context;- 根据当前 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。