本文是“ Indiana Jones™ 系列 中的 Path Tracing Optimizations”的一部分 。
在 2024 年为“ Indiana Jones and the Great Circle™”添加路径追踪模式时,我们使用了 Shader Execution Reordering (SER),一项自 NVIDIA GeForce RTX 40 Series 发布以来,NVIDIA GPU 就一直支持的功能,来提高 GPU 性能。
为优化 SER 在主路径追踪通道 (TraceMain
) 中的使用,我们使用了 NVIDIA Nsight Graphics GPU Trace Profiler。我们发现,其 RayGen 着色器 使用大量光线追踪 (RT) 实时状态字节,这降低了 SER 的效率。通过在 GPU Trace Profiler 中使用“ Ray Tracing Live State ”选项卡,我们确定了一些导致 RT 实时状态字节的 GLSL 变量,并消除了这些变量或减小了其大小。
因此,在 NVIDIA GeForce RTX 5080 GPU 上,SER 现在可以在以下配置场景中的 TraceMain
pass 上节省 24% 的 GPU 时间。
剖析的场景

本文中的所有数据均来自“Indiana Jones and the Great Circle™”在 GeForce RTX 5080 GPU 上的游戏第一幕,图形设置请参见表 1。
设置 | 价值 |
输出分辨率 | 4K UHD ( 3840 x 2160) |
DLSS 模式 | DLSS 光线重建 、性能模式 |
图形预设 | 高 |
路径追踪模式 | 全景光线追踪 |
光线追踪光源 | 所有光源 |
植被动画质量 | 极致 |
游戏的这一部分发生在一个拥有大量密集且经过 Alpha 测试的几何图形的丛林中。它是光线追踪最具挑战性的位置之一。将 DLSS-RR 设置为“Performance”模式,并将输出分辨率设置为 4K UHD (3840 x 2160) 时,路径追踪将在 1080p 下完成。
起点:4.08 毫秒
图 2 显示了 Nsight Graphics 的 GPU Trace Profiler 中的 TraceMain
通道。

根据 GPU Trace 提供的信息,此 cmdTraceRays
工作负载具有以下 GPU 指标:
- GPU 时间: 4.08 毫秒
- 每个 Warp 基于 预测的活动线程数 :38%
Predicated-On Active Threads per Warp 百分比指标是 SM 级别上每个已执行指令的 SIMT 主动线程通道 (32 通道) 的平均值。
以下原因可能会导致每个 warp 的活动线程百分比欠佳:
- 在线程束中具有离散回路数量的动态回路
- 在线程束中具有离散执行路径的动态分支
- 在 RayGen 着色器中,warp 内调用不同的 hit shaders
图 3 显示离散动态分支增加着色器延迟的原因。有关更多信息,请参阅 使用 NVIDIA Nsight Graphics 优化图形应用程序的 GPU 工作负载 。

着色器执行重排序
对于 RayGen 着色器, Shader Execution Reordering (SER) 是提高每个线程束活动线程的平均百分比的有效方法。SER 可用于在调用 traceRay
后,根据命中对象驱动的密钥和用户提供的可选一致性提示,对 RayGen 着色器的线程进行重新排序。
在重新排序调用后,调用 hit shader 或访问 hit-related 数据将更加一致,因为具有相同或相似密钥的线程会分组在一起执行。
- 对于 Vulkan,SER 由 VK_NV_ray_tracing_invocation_reorder 扩展程序公开。
- 对于 DX12,SER 现已在 DXR 1.2 或通过 NvAPI 提供。
为在引擎中实施 SER,我们为支持 SER (GeForce RTX 40 及更高版本) 的 GPU 添加了主路径追踪 RayGen 着色器的着色器置换,并使用以下 GLSL 代码:
#if defined( RPF_RT_ENABLE_SER )
hitObjectNV hitObject;
traceRayHitObject( hitObject, rayFlags, instanceMask , ray, rayPayload, topLevelAccelerationStructure );
reorderThreadNV( hitObject, bounceNum == ( bounceId + 1 ) ? 1 : 0 , 1 );
hitObjectExecuteShaderNV( hitObject, _hitPayloadIndex( rayPayload ) );
#else
traceRayEXT( rayFlags, instanceMask , ray, rayPayload, topLevelAccelerationStructure );
#endif
一致性提示 bounceNum == ( bounceId + 1 ) ? 1 : 0
是原始 Shader Execution Reordering 白皮书中所述理念的实现:“可以在路径追踪器或多次反射反射中找到相关情况。在一致性提示中包含一些关于主循环是否会终止的额外信息通常非常有效。”
使用 SER 时:3.63 毫秒
通过实施 SER,TraceMain
传递成本降低了 11% (4.08 => 3.63 ms),并且每个 Warp 指标的平均 Predicated-On 活动线程数 从 38% 增加到 70% (图 4)。

SER 关闭 | SER ON | |
GPU 时间 | 4.08 毫秒 | 3.63 毫秒 |
活动线程/ 线程束 | 38% | 70% |
光线追踪实时状态溢出
表 3 显示了 GPU 指标,以便您了解两种追踪之间的变化:
SER 关闭 | SER ON | |
VidL2 吞吐量 | 58.3% | 85.6% |
L1TEX 吞吐量 | 30.6% | 43.0% |
来自 L1TEX 的 VidL2 总计 | 9.22 GiB | 12.55 GiB |
如果 RayGen 着色器在关闭 SER 的情况下具有大量溢出的光线追踪实时状态字节,则向 RayGen 着色器添加 SER 实际上预计会增加 L2 流量。在继续执行 RayGen 着色器时,这些是 ray-tracing 驱动程序在调用 traceRay
并在完成 traceRay
调用后重新加载之前保存到内存中的局部变量。
由于在重新排序线程时,实时状态数据可能必须通过 SER 实现在 GPU 中传输,因此在出现 reorderThread
时,RT 实时状态溢出的成本可能会更高。traceRay
或 reorderThread
调用站点的 RT 实时状态溢出字节数越多,实时状态可能产生的 GPU 开销就越多。
光线追踪实时状态选项卡
图 5 来自 GPU Trace Profiler,在工具的右下角区域显示了与选定时间范围 (本例中为 TraceMain
通道) 相关的 Shader Profiler 数据摘要部分。

首先,将水平分隔符从工具的左下角和右下角一直拖动到左侧,然后选择“Ray Tracing Live State”选项卡:

图 6 显示,对于每个 RT 调用站点 (traceRay
、reorderThread
或 callable) ,GLSL 声明了在 RT 调用站点之前声明并在调用站点之后重新使用的变量,并且 RT 驱动程序已针对这些变量在调用站点周围执行 live-state spilling (在调用站点之前将变量值写入内存,然后在之后重新加载) 。
在本例中,此 RayGen 着色器的每个线程总共向全局内存溢出了 222 字节。
优化 1:循环移除
现在,我们来展开有关 callsite #2 的信息,查看与溢出的 RT 实时状态声明对应的 GLSL 行列表:

列表中最大的条目是:
HitDesc primaryHitDesc = PrimaryHitDescFromGbuffer( pixelPos32 );
此 primaryHitDesc
结构体从 RayGen 开头的 GBuffer
初始化,其值用于确定是否追踪以及如何追踪第一束光线。
但是,鉴于此结构体应仅用于设置第一个光线,为何在首次调用 traceRay 后重复使用此结构体的 72 字节?RayGen 的路径循环如下,其中 PATHTRACE
在内部循环中调用 traceRayHitObject
和 reorderThreadNV
:
for( uint pathId = 0; ( pathId < pathNum ) && !primaryHitDesc.isSky; ++pathId ) {
PathOutputDesc pathDesc = PathOutputDescNull();
//...
PATHTRACE( pixelPos, primaryHitDesc, bounceNum, pathDesc );
if( pathDesc.isValid == false ) {
continue;
}
//...
if( pathDesc.isDiffuse ) {
hitDistanceDiff = normHitDist;
radianceDiff += pathDesc.radiance;
numSamplesDiff += 1.0;
} else {
NRD_FrontEnd_SpecHitDistAveraging_Add( hitDistanceSpec, normHitDist );
radianceSpec += pathDesc.radiance;
numSamplesSpec += 1.0;
}
}
radianceDiff /= max( 1.0, numSamplesDiff );
radianceSpec /= max( 1.0, numSamplesSpec );
在发布游戏中,pathNum
始终为 1,因此此循环实际上是一个分支。因此,对于循环的每次新迭代,完整的 primaryHitDesc
结构体都会在 for 循环之前溢出,并在每次循环迭代开始时重新加载。由于此循环在实践中确实是一个分支,因此我们将其重写为:
PathOutputDesc pathDesc = PathOutputDescNull();
if ( !primaryHitDesc.isSky ) {
//...
PATHTRACE( pixelPos, primaryHitDesc, bounceNum, pathDesc, throughput );
//...
if ( pathDesc.isValid ) {
if( pathDesc.isDiffuse ) {
hitDistanceDiff = normHitDist;
radianceDiff += pathDesc.radiance;
} else {
NRD_FrontEnd_SpecHitDistAveraging_Add( hitDistanceSpec, normHitDist );
radianceSpec += pathDesc.radiance;
}
}
}
将循环更改为 isSky
分支可删除溢出的 72 字节 RT 实时状态。
优化 2:使用 FP16 Precision
图 7 的“ Ray Tracing Live State ”选项卡中列出了以下 GLSL 行:
pathDesc.radianceAndAccumHitDist.xyz
这将跟踪每条路径的 accumulated radiance:
pathDesc.radianceAndAccumHitDist.xyz += ( hitDesc.shading.xyz * throughput.xyz );
radianceAndAccumHitDist
变量被声明为 float4
,这在本例中是多余的。通过将 radianceAndAccumHitDist
的精度从 float4
降级为 half4
(f16vec4
),它编译为四个 FP16 值,该四维向量的 RT 实时状态大小将减半,且不会影响图像质量。
RT 实时状态优化前后
启用 SER 后,对于 TraceMain
通道,RT 实时状态优化已将 GPU Trace 中报告的 RT 实时状态从 222 字节减少到 84 字节,并且在 NVIDIA RTX 5080 GPU 上,该通道花费的 GPU 时间减少了 15% (3.63 => 3.08 毫秒)。
开启 SER | 之前 | 之后 |
GPU 时间 | 3.63 毫秒 | 3.08 毫秒 |
活动线程/ 线程束 | 70% | 68% |
RT 实时状态泄露 | 222 字节 | 84 字节 |
图 8 显示了具有 SER ON 的 Before 版本,大小为 222 字节。

图 9 显示了具有 SER ON 的 After 版本,共 84 bytes。

在 RT 实时状态优化后,SER ON 与 SER OFF 的比较
在 RT 实时状态优化之前,启用 SER 可使 GeForce RTX 5080 GPU 上的 TraceMain
通道成本降低 11%。
之前 | SER 关闭 | SER ON |
RT 实时状态泄露 | 180 字节 | 222 字节 |
GPU 时间 | 4.08 毫秒 | 3.63 毫秒 ( -11%) |
活动线程/ 线程束 | 38% | 70% |
现在,在使用 Nsight GPU Trace Profiler 优化 RT 实时状态后,我们将在与之前相同的条件下,再次比较该通道上的 SER ON 与 SER OFF,但 GLSL 与实时状态优化的差异除外。
之后 | SER 关闭 | SER ON |
RT 实时状态泄露 | 68 字节 | 84 字节 |
GPU 时间 | 4.07 毫秒 | 3.08 毫秒 ( -24%) |
活动线程/ 线程束 | 38% | 68% |
减少 GLSL 中 RT 实时状态溢出的字节数使 SER 成为更强大的加速器。在 RT 实时状态归约优化后,启用 SER 可节省 24% 的 GPU 时间。
其他 RT live state 优化
本文比较了在应用 RT 实时状态优化前后,SER 与两个版本的 GLSL 的性能:
- After: 2024 年 12 月发布的 GLSL 发行版本,包含所有优化。
- 之前: 已恢复两项优化的 After 版本 (循环移除,并对
radianceAndAccumHitDist
使用 FP16 精度) 。
实际上,我们在 TraceMain
通道的 GLSL 中实施了额外的 RT 实时状态优化,这是 After 版本的一部分,但由于在本文中生成“Before”状态而未恢复:
- 路径追踪器的 RGB 吞吐量向量为
float3
。将其更改为half3
有助于实现相同的效果。 - 反弹循环的
bounceId
和bounceNum
变量为 32 位整数。制作uint16_t
对他们有所帮助。(uint8_t
也可以实现。) - 反射循环主体中的光线方向为
float3
。将其打包成uint32_t
有助于实现相同的效果。 - 通过消除对
GBuffer
材质数据的依赖关系,将信号解调从 RayGen 的末尾转移到后续传递会有所帮助。
所有这些优化都是在游戏 path-tracing 模式的初始版本发布之前及时实施的。
总结
在每个线程束所占平均活动线程百分比较低的 RayGen 着色器中,添加 SER 可以显著加速,同时尽可能减少代码更改。减少 Nsight Graphics GPU Trace Profiler 中报告的 RT 实时状态溢出字节数可以使 SER 成为更强大的加速器。
在 HLSL 中,可以使用 DXC 和 Shader Model 6.2 或更高版本以及此显式 HLSL 类型:float16_t
将浮点变量声明为 FP16。如需了解更多信息,请参阅 Half The Precision, Twice The Fun: Working With FP16 In HLSL 。
本系列的下一篇文章是“Indiana Jones™ 中的路径追踪优化:Opacity Micro-Maps 和动态 BLAS 的规整”,介绍了如何使用 Opacity MicroMaps (OMMs) 作为一种有效方法,在使用 alpha 测试材质的场景中加速 traceRay 调用 (或 rayQuery 对象) ,以及压缩动态植被的 BLAS 以节省 VRAM。
致谢
在此,我们要感谢 NVIDIA 和 MachineGames 的所有人员,感谢他们于 2024 年 12 月 9 日发布了带有路径追踪功能的“Indiana Jones and the Great Circle™”:
MachineGames:Sergei Kulikov、Andreas Larsson、Marcus Buretorp、Joel de Vahl、Nikita Druzhinin、Patrik Willbo、Markus Ålind、Magnus Auvinen、Jim Kjellin、Truls Bengtsson、Jorge Luna (MPG)。
NVIDIA:Ivan Povarov、Juho Marttila、Jussi Rasanen、Jiho Choi、Oleg Arutiunian、Dmitrii Zhdan、Evgeny Makarov、Johannes Deligiannis、Fedor Gatov、Vladimir Mulabaev、Dajuan Mcdaniel、Dean Bent、Jon Story、Eric Reichley、Magnus Andersson、Pierre Moreau、Rasmus Barringer、Michael Haidl、Martin Stich。