内容创建/渲染

印第安纳琼斯™ 中的路径追踪优化:着色器执行重排序与实时状态降低

本文是“ 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 时间。

剖析的场景 

A still from the opening scene of the game, where the player is walking on a trail in the Peru jungle behind two other characters.
图 1。剖析场景,启用 Full Ray Tracing

本文中的所有数据均来自“Indiana Jones and the Great Circle™”在 GeForce RTX 5080 GPU 上的游戏第一幕,图形设置请参见表 1。

设置 价值
输出分辨率 4K UHD ( 3840 x 2160)
DLSS 模式 DLSS 光线重建 、性能模式
图形预设
路径追踪模式 全景光线追踪
光线追踪光源 所有光源
植被动画质量 极致
表 1. 本文中所有数据的图形设置

游戏的这一部分发生在一个拥有大量密集且经过 Alpha 测试的几何图形的丛林中。它是光线追踪最具挑战性的位置之一。将 DLSS-RR 设置为“Performance”模式,并将输出分辨率设置为 4K UHD (3840 x 2160) 时,路径追踪将在 1080p 下完成。

起点:4.08 毫秒 

图 2 显示了 Nsight Graphics 的 GPU Trace Profiler 中的 TraceMain 通道。

A screenshot of the user interface. In the markers row of the tool, the TraceMain pass is highlighted. The tool shows that the application uses multiple queues. In the right-side panel, the tool shows: Duration=4.08ms and Predicated-On Active Threads per Warp Inst Executed is 12.3 threads per warp (38.3%).
图 2. 在 Nsight GPU Trace Profiler 中的 TraceMain pass,在优化之前

根据 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 工作负载

A diagram shows some brief pseudocode for an if-else statement in a shader and a graphic showing eight lines of execution representing threads split into two chunks of four threads during the if-else blocks.
图 3。SIMT warp 执行模型下线程调度的简化可视化

着色器执行重排序 

对于 RayGen 着色器, Shader Execution Reordering (SER) 是提高每个线程束活动线程的平均百分比的有效方法。SER 可用于在调用 traceRay 后,根据命中对象驱动的密钥和用户提供的可选一致性提示,对 RayGen 着色器的线程进行重新排序。

在重新排序调用后,调用 hit shader 或访问 hit-related 数据将更加一致,因为具有相同或相似密钥的线程会分组在一起执行。

为在引擎中实施 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)。

Predicated-On Active Threads per Warp metric has increased from 38% to 70%.
图 4。Trace 添加 SER 的主要通道
  SER 关闭 SER ON
GPU 时间 4.08 毫秒 3.63 毫秒
活动线程/ 线程束 38% 70%
表 2。TraceMain 的 GPU 指标 (SER 打开和关闭)

光线追踪实时状态溢出 

表 3 显示了 GPU 指标,以便您了解两种追踪之间的变化:

  SER 关闭 SER ON
VidL2 吞吐量 58.3% 85.6%
L1TEX 吞吐量 30.6% 43.0%
来自 L1TEX 的 VidL2 总计 9.22 GiB 12.55 GiB
表 3。开启和关闭 SER 时,TraceMain 的更多 GPU 指标

如果 RayGen 着色器在关闭 SER 的情况下具有大量溢出的光线追踪实时状态字节,则向 RayGen 着色器添加 SER 实际上预计会增加 L2 流量。在继续执行 RayGen 着色器时,这些是 ray-tracing 驱动程序在调用 traceRay 并在完成 traceRay 调用后重新加载之前保存到内存中的局部变量。

由于在重新排序线程时,实时状态数据可能必须通过 SER 实现在 GPU 中传输,因此在出现 reorderThread 时,RT 实时状态溢出的成本可能会更高。traceRayreorderThread 调用站点的 RT 实时状态溢出字节数越多,实时状态可能产生的 GPU 开销就越多。

光线追踪实时状态选项卡 

图 5 来自 GPU Trace Profiler,在工具的右下角区域显示了与选定时间范围 (本例中为 TraceMain 通道) 相关的 Shader Profiler 数据摘要部分。

Screenshot from the bottom panel of GPU Trace, showing instruction mix information. The Shader Pipelines tab shows the current percentage of cumulative latency per shader, with the type, name, hash, and filename for each shader.
图 5。GPU Trace 中的 Real-time Shader Profiler 部分

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

A screenshot from the Ray Tracing Live State tab shows the total Live State Bytes per thread reported by the tool.
图 6。放大区域,显示每个线程的 RT live-state 字节数

图 6 显示,对于每个 RT 调用站点 (traceRayreorderThread 或 callable) ,GLSL 声明了在 RT 调用站点之前声明并在调用站点之后重新使用的变量,并且 RT 驱动程序已针对这些变量在调用站点周围执行 live-state spilling (在调用站点之前将变量值写入内存,然后在之后重新加载) 。

在本例中,此 RayGen 着色器的每个线程总共向全局内存溢出了 222 字节。

优化 1:循环移除 

现在,我们来展开有关 callsite #2 的信息,查看与溢出的 RT 实时状态声明对应的 GLSL 行列表:

Screenshot of the Ray Tracing live state tab, with the call sites and calling contexts expanded, showing one line per line of GLSL, for each RT live-state variable declaration. For each row, the number of used Live State Bytes and the number of Live State Values is reported.
图 7。添加 SER 且无其他更改的 RT 实时状态声明

列表中最大的条目是:

HitDesc primaryHitDesc = PrimaryHitDescFromGbuffer( pixelPos32 );

primaryHitDesc 结构体从 RayGen 开头的 GBuffer 初始化,其值用于确定是否追踪以及如何追踪第一束光线。

但是,鉴于此结构体应仅用于设置第一个光线,为何在首次调用 traceRay 后重复使用此结构体的 72 字节?RayGen 的路径循环如下,其中 PATHTRACE 在内部循环中调用 traceRayHitObjectreorderThreadNV

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 字节
表 4。TraceMain 上的 GPU 指标(实时状态优化前后)

图 8 显示了具有 SER ON 的 Before 版本,大小为 222 字节。

Ray Tracing Live State tab showing one line per GLSL variable declaration. For each row, the number of used Live State Bytes and the number of Live State Values is reported.
图 8。RT live state with SER,在实时状态优化之前

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

Ray Tracing Live State tab showing one line per GLSL variable declaration. For each row, the number of used Live State Bytes and the number of Live State Values is reported.
图 9。RT live state with SER,实时状态优化后

在 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%
表 5。GPU metrics for TraceMain,在实时状态优化之前

现在,在使用 Nsight GPU Trace Profiler 优化 RT 实时状态后,我们将在与之前相同的条件下,再次比较该通道上的 SER ON 与 SER OFF,但 GLSL 与实时状态优化的差异除外。

之后 SER 关闭 SER ON
RT 实时状态泄露 68 字节 84 字节
GPU 时间 4.07 毫秒 3.08 毫秒 ( -24%)
活动线程/ 线程束 38% 68%
表 6。实时状态优化后,TraceMain 的 GPU 指标

减少 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 有助于实现相同的效果。
  • 反弹循环的 bounceIdbounceNum 变量为 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。

 

标签