GPU Gems 1 · Chapter 28. Graphics Pipeline Performance

用可证伪的 workload 探测定位 graphics pipeline 瓶颈,再针对 ROP、纹理、fragment、vertex 或 CPU 阶段优化并复测。

学习目标

  • 能画出 CPU、vertex/index fetch、vertex、fragment 和 ROP 阶段,并说明每阶段的主要工作量
  • 能用单变量 workload 或时钟探测区分 frame-buffer、texture、fragment、vertex 和 CPU 瓶颈
  • 能根据已证实的瓶颈选择减少锁、扩大 batch、优化顶点缓存、early-z 或纹理压缩等杠杆
  • 能在同一 workload、目标硬件和画质约束下复测,确认优化收益不是测量噪声或阶段转移

图形性能优化最容易犯的错误,是把“某段代码看起来复杂”当成瓶颈。GPU 是多个专用单元组成的异构流水线;一个阶段的局部加速只有在它确实限制整帧吞吐时才有收益。GPU Gems 1 第 28 章把工作组织成一个可重复的循环:定位、优化、再次定位。

的顶层划分是 CPU 与 GPU;GPU 内部还包括顶点和索引获取、vertex processing、fragment shading 与 raster operations。理解阶段边界,才能把“帧率低”翻译成可测试的假设。

graphics pipeline:最慢阶段决定吞吐CPUAPIsubmit / managefetchvertex + indexvertextransform / T&Lfragmentpixel shaderROPdepth / color若 ROP 最慢,继续优化 vertex 不会提高整帧吞吐先找瓶颈,再把工作量压到目标阶段
GPU 不是一个单一速度旋钮:各功能单元并行工作,但帧吞吐仍由当前最慢的阶段限制。

1. 用瓶颈循环替代猜测

定位的第一步是改变一个阶段的 workload,或改变与该阶段相关的计算能力,例如 core 或 memory clock;如果性能随之变化,就得到该阶段是瓶颈的证据。第二步才是减少该阶段工作,直到性能不再改善或达到目标;第三步重新测量,因为优化后瓶颈可能转移。

bottleneck loop:实验先于优化identifyvary load / clockoptimizereduce that workmeasurecompare baselinerepeatnext slowest性能不变 → 不是当前瓶颈 · 性能变化 → 继续在该阶段优化避免“感觉很贵”的局部优化把时间花在零收益处
章节的核心方法是一个可证伪循环:只改变一个阶段的负载或计算能力,观察性能是否跟随变化。

这个方法还要注意帧内的空间差异:一个低多边形 skybox 可能受 fragment 或 frame-buffer 访问限制,而只覆盖少量像素的 skinned mesh 可能受 CPU 或 vertex processing 限制。单个 primitive 有一个主要瓶颈,但整帧的瓶颈可能随对象、材质和视图变化,因此应支持 object-by-object 或 material-by-material 的对照。

2. 从 ROP 和 frame-buffer bandwidth 开始

处理深度、模板、颜色和混合,工作量主要消耗 frame-buffer bandwidth。一个直接的探测是只改变 color 或 depth buffer 的位深:如果从 32-bit 降到 16-bit 能显著改善性能,就有理由怀疑帧缓冲带宽;关联的 GPU memory clock 变化也应产生一致方向的响应。

baseline: color = 32-bit, depth = 32-bit
probe:    color = 16-bit, depth = 16-bit
hold:     camera, primitives, shaders, resolution, workload
observe:  frame time, memory clock response, depth precision artifacts

不要把精度降低直接当成发布修复。先证明 ROP 是瓶颈,再评估 16-bit depth/color 是否满足场景的 z 范围、透明度、HDR 和后处理需求。

3. 区分 texture bandwidth 和 fragment shading

发生在 texture fetch 触发内存请求时。与其随意改纹理格式,不如对测试场景施加较大的正 mipmap LOD bias,使用粗 mip 级别降低等效纹理尺寸;若性能明显改善,纹理带宽就是重要嫌疑。

controlled probes:每个变量只瞄准一个阶段stagechange workloadclock / evidenceROP / frame buffercolor + depth bitsmemory clocktexture fetchpositive mip LODmemory clockfragment shaderresolution / program lengthcore clockvertex processingvertex work / shader lengthcore clock若顶点和纹理探测都无效,CPU 可能才是主导瓶颈
探测变量必须能真正改变目标阶段的工作:无效指令可能被优化掉,单纯降低画质也可能同时改变多个阶段。

与 frame-buffer bandwidth 都常被统称为 fill rate,但两者不是同一阶段。已经用位深探测排除 ROP 后,再改变分辨率或 fragment program 的有效长度;如果性能随分辨率或真实 shader 工作变化,才能把证据指向 fragment shading。实验指令必须有真实输出依赖,避免被驱动消除。

float4 shade(float2 uv, Texture2D source, SamplerState samplerState) {
  float4 value = source.Sample(samplerState, uv);
  float3 edge = source.Sample(samplerState, uv + float2(0.001, 0.0)).rgb - value.rgb;
  return float4(value.rgb + edge * edgeGain, value.a);
}

定位后,fragment 优化可以考虑 depth-first 和 early-z、把复杂函数放进 lookup texture、把可在线性插值的 per-fragment 工作移到 vertex、降低不必要精度、减少过度 normalization、降低远处 shader LOD,以及不需要时关闭 trilinear filtering。每一项都要以同一场景和画质对照验证。

4. 检查 vertex processing 与 vertex/index transfer

vertex processing 的成本取决于每个 vertex 的工作和处理的 vertex 数量。改变 vertex program 的有效长度或固定功能中的 specular lighting、texture-coordinate generation 等工作,可以测试它是否是瓶颈;无效 no-op 同样不可靠。

顶点和索引获取是 GPU 部分的第一步,数据可能在 system memory,也可能在 local frame-buffer memory。改变 vertex format size 可以探测 transfer 压力;如果数据在 system memory,AGP/PCI Express 速率更相关;如果在本地显存,memory clock 更相关。若这些探测都不影响性能,CPU bound 就成为更合理的假设。

struct VertexCompact {
  float position[3];
  unsigned short normalOcta[2];
  unsigned short uv[2];
};
 
// 以 indexed draw 复用顶点,并在同一 workload 下比较紧凑格式的帧时间。
drawIndexed(mesh.indexBuffer, mesh.vertexBuffer, mesh.indexCount);

vertex 侧的优化包括使用 indexed primitives 提高 post-T&L vertex cache 命中、减少 vertex 数量和 LOD、使用紧凑格式与 16-bit indices、让访问尽量顺序、把每对象或每帧计算移到 CPU,以及选择更便宜的坐标空间。不要为了省 transfer 带宽把本应由 shader 计算的高频属性硬塞成大量顶点数据。

5. 优化 CPU 锁、batch 和 GPU 等待

同步访问 GPU 正在使用的资源,会让 CPU 等待深流水线清空,GPU 也因停顿和重新填充损失周期。典型风险包括读回先前渲染的 surface,或写入 GPU 正在读取的 texture、vertex buffer。发布代码应尽量避免 CPU 与 GPU 同时争用同一资源,并把 readback 安排到明确的同步边界。

batch 是一次 API draw call 提交的 primitive 集合。对相同材质和状态,扩大 batch 或减少 batch 数量可以降低 CPU API 调用成本。 的增长可以来自 texture pages、合适的 triangle strip stitching、shader branching、常量内存中的矩阵查表,以及把材质决定尽量推迟到更靠后的阶段。

stage-specific levers:把收益投向已证实的瓶颈CPUfewer lockslarger batchesvertexindexed cachecompact formatfragmentdepth firstearly-zmemorymipmapscompressionmeasure again:收益必须在同一 workload 和目标硬件上复现
优化动作要和探测到的阶段相配:扩大 batch 不是修复纹理带宽,减少浮点格式也不应盲目牺牲必要精度。

这些动作不是无条件免费:texture pages 要评估 mipmapping 和 antialiasing,shader branching 会增加 shader cycles,常量查表需要额外索引和一致布局。优化目标是减少总 frame time,不是让某个计数器单独变漂亮。

6. 带宽和画质的共同边界

纹理带宽受尺寸、格式、缓存和 mipmap 共同影响。只要目标表面会 minify,就应使用 mipmapping 来改善 alias 与 cache locality;如果纹理只是 decal 或 detail,可考虑 DXT1、DXT3 或 DXT5 等压缩。64-bit 或 128-bit floating-point texture 只有在必要时才值得付出带宽成本。

frame-buffer 优化可从 depth-first、减少 alpha blending、关闭不必要的 depth writes、避免多余 color clear、粗略 front-to-back 和针对 skybox 的策略开始。floating-point frame buffer、multiple render target 和 32-bit depth 都有额外带宽成本,但不能在 HDR、精度或透明度要求未验证时盲目降成 16-bit。

Graphics Pipeline Performance Lab

先预测:哪个变量能证明当前阶段是瓶颈?

可交互
controlled probe previewfragment shadingbaseline load 0.64 · probe load 0.64throughput 0.70 · response 0.00probe: 改变 resolution 或 shader length

先选阶段,再切换探测负载;若性能响应显著,才有理由把优化预算投入该阶段。

实验只表达“改变一个探测变量后,性能响应是否跟随”的因果方向,不是假装采集真实 GPU 计时。发布报告应保存硬件、驱动、分辨率、场景、shader 版本、workload、基线帧时间、探测帧时间、画质差异和重复次数。

小结:定位—优化—复测

  • graphics pipeline 由多个并行专用阶段组成,整帧吞吐受当前 bottleneck 限制。
  • ROP 用 buffer 位深和 memory clock 探测;texture bandwidth 用正 LOD bias 探测;fragment 用分辨率或真实 shader 工作探测。
  • vertex processing 与 transfer 可通过 vertex program、format size、indexed cache 和数据位置对照;探测无响应时再考虑 CPU bound。
  • CPU 侧应避免锁住 GPU 正在使用的资源,并通过 batch size、texture pages 和延迟决策摊薄提交成本。
  • 每次优化后都要在同一 workload 和画质约束下复测,并检查瓶颈是否转移,而不是只看局部指标。

练习

问题 1|实验设计 要判断一个场景是否受 frame-buffer bandwidth 限制,哪一个单变量探测最合适?哪些变量必须保持不变?

问题 2|归因题 提高正 mipmap LOD bias 后性能上升,而改变 fragment shader 长度没有变化。哪个阶段更可疑?为什么不能直接说“fragment 不贵”?

问题 3|优化题 发现 CPU 提交是瓶颈,同时场景有许多共享材质但矩阵不同的小物体。给出至少三项优化,并列出需要复测的副作用。

名词解释

本章出现的专业名词,用大白话再讲一遍。

graphics pipeline
由 CPU 提交和 GPU 多个功能阶段组成、吞吐受最慢阶段限制的渲染处理链。
bottleneck
限制当前整条渲染流水线吞吐的最慢阶段或资源约束。
raster operations
负责深度、模板、颜色读写及 alpha 测试和混合的管线末端阶段。
texture bandwidth
纹理 fetch 从内存取数据消耗的带宽,受尺寸、格式、mipmap 和缓存影响。
fragment shading
执行 fragment shader、为每个 fragment 计算颜色和深度的阶段。
batch size
一次 draw call 提交的一组共享足够状态的 primitives 数量。

资料与写作方式声明

本章以GPU Gems 系列权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…