跳到主要内容
shader-performance · 代码
Graphics Learning Lab Shader Performance / Shader source

看见反常 · 动一处 · 抓证据

每个“优化术语”,都要指回一段真实工作

只读 · 实验运行源码

这页解决什么

按少提交、少顶点、少重绘、少分歧和帧预算阅读。代码直接来自实验视口。

01 · vertex

Instancing:少提交

同网格材质复用一次提交,减少 CPU draw。

attribute float aInstanceId;
uniform float uLodMode;
uniform float uLodBias;
varying float vInstanceId;
varying float vLod;
void main() {
  // SECTION:instancing - instance data changes transforms without multiplying draw submissions
  vInstanceId = aInstanceId;
  vec4 localPosition = vec4(position, 1.0);
  #ifdef USE_INSTANCING
    localPosition = instanceMatrix * localPosition;
  #endif
  float distanceToCamera = length((modelViewMatrix * localPosition).xyz);
02 · vertex

LOD:远处少干活

按距离减少远处顶点工作,不会减少 CPU 提交。

// SECTION:lod - distance uses world depth; screen-size asks how large this
  // object projects through the current camera. They intentionally differ.
  float objectRadius = 0.2;
  #ifdef USE_INSTANCING
    objectRadius *= length(instanceMatrix[0].xyz);
  #endif
  float projectedRadius = projectionMatrix[1][1] * objectRadius / max(distanceToCamera, 0.001);
  float screenSizeMetric = 0.25 / max(projectedRadius, 0.001);
  float lodMetric = uLodMode < 1.5 ? distanceToCamera : screenSizeMetric;
  vLod = uLodMode < 0.5 ? 0.0 : clamp(floor(log2(max(lodMetric, 1.0)) + uLodBias), 0.0, 4.0);
  gl_Position = projectionMatrix * modelViewMatrix * localPosition;
}
03 · fragment

Overdraw:像素重复加班

统计同一像素被多少层片元重复覆盖。

precision highp float;
uniform int uDebugMode;
uniform int uBranchMode;
uniform float uBranchRatio;
uniform float uShaderWork;
uniform float uTextureReads;
uniform float uOverdrawLayers;
uniform float uTargetMs;
uniform float uEstimatedMs;
uniform bool uEarlyZEffective;
varying float vInstanceId;
varying float vLod;

// SECTION:overdraw - transparent layers repeat fragment work for one screen pixel
float overdrawHeat() { return clamp(uOverdrawLayers / 16.0 * (uEarlyZEffective ? 0.55 : 1.0), 0.0, 1.0); }
04 · fragment

分支分歧:同组线程走散

Wave 内是否一致,会改变有效执行率。

// SECTION:branches - divergence makes the same wave serialize different paths
float branchMask() { float coherentMask = step(0.5, fract(vInstanceId * 0.13)); float divergentMask = step(uBranchRatio, fract(vInstanceId * 0.37)); return uBranchMode == 0 ? coherentMask : uBranchMode == 1 ? divergentMask : 1.0; }
05 · fragment

GPU 时间线:回到帧预算

把估算耗时与目标耗时放在同一把尺上。

// SECTION:timeline - show a budget ratio instead of pretending a single knob is performance
vec3 timelineColor() { float ratio = clamp(uEstimatedMs / max(uTargetMs, 0.01), 0.0, 1.8); return mix(vec3(0.17, 0.8, 0.48), vec3(0.94, 0.24, 0.16), clamp(ratio, 0.0, 1.0)); }
06 · fragment

调试输出:让成本现形

直接输出重绘、分支、实例、LOD 与热力图。

// SECTION:debug - expose work, instance identity, LOD and budget evidence
void main() {
  float work = 0.0;
  for (int index = 0; index < 120; index += 1) { if (float(index) >= uShaderWork) break; work += sin(float(index) * 0.17 + vInstanceId * 0.01); }
  float branch = branchMask();
  vec3 color = mix(vec3(0.12, 0.32, 0.5), vec3(0.92, 0.52, 0.2), fract(vInstanceId * 0.071));
  color *= 0.72 + 0.28 * abs(sin(work * 0.02));
  if (uDebugMode == 1) color = mix(vec3(0.12, 0.8, 0.48), vec3(0.94, 0.18, 0.12), overdrawHeat());
  if (uDebugMode == 2) color = vec3(branch, 1.0 - branch, 0.16);
  if (uDebugMode == 3) color = vec3(fract(vInstanceId * 0.071), fract(vInstanceId * 0.17), fract(vInstanceId * 0.31));
  if (uDebugMode == 4) color = vec3(vLod / 4.0, 0.65, 1.0 - vLod / 4.0);
  if (uDebugMode == 5) color = timelineColor();
  if (uDebugMode == 6) color = mix(vec3(0.08, 0.13, 0.2), vec3(0.94, 0.2, 0.12), clamp((uShaderWork + uTextureReads * 8.0) / 120.0, 0.0, 1.0));
  gl_FragColor = vec4(color, 0.82);
  #include <tonemapping_fragment>
  #include <colorspace_fragment>
}