具有早期 return 和分支的 GLSL 顶点着色器性能
GLSL vertex shader performance with early return and branching
我有一个顶点着色器
void main (){
vec4 wPos = modelMatrix * vec4( position , 1. );
vWorldPosition = wPos.xyz;
float mask = step(
0.,
dot(
cameraDir,
normalize(normalMatrix * aNormal)
)
);
gl_PointSize = mask * uPointSize;
gl_Position = projectionMatrix * viewMatrix * wPos;
}
我不太确定如何测试着色器的性能,并排除其他因素,如透支。我想象一个大小为 1 的点,在屏幕 space 中排列成网格,没有任何重叠会起作用吗?
否则我对这些调整很好奇:
(删除step
,删除乘法,引入if
else
)
void main (){
if(dot(
cameraDir,
normalize(normalMatrix * aNormal) //remove step
) < 0.) {
gl_Position = vec4(0.,.0,-2.,.1);
gl_PointSize = 0.;
} else {
gl_PointSize = uPointSize; //remove a multiplication
vec4 wPos = modelMatrix * vec4( position , 1. );
vWorldPosition = wPos.xyz;
gl_Position = projectionMatrix * viewMatrix * wPos;
}
}
与这样的东西相比:
void main (){
if(dot(
cameraDir,
normalize(normalMatrix * aNormal)
) < 0.) {
gl_Position = vec4(0.,.0,-2.,.1);
return;
}
gl_PointSize = uPointSize;
vec4 wPos = modelMatrix * vec4( position , 1. );
vWorldPosition = wPos.xyz;
gl_Position = projectionMatrix * viewMatrix * wPos;
}
这些着色器的行为是否会有所不同,why/how?
如果有什么东西可以量化性能差异,我很感兴趣。
- 是否有一些值,例如 MAD 的数量或不同代码显然会产生的其他值?
- 不同代的 GPU 会以不同的方式处理这些差异吗?
- 如果单步版本保证最快,是否有关于如何避免分支的已知模式列表,以及首选哪些操作? (比如用
floor
代替 step
也可以吗?):
.
float condition = clamp(floor(myDot + 1.),0.,1.); //is it slower?
条件分支在 GPU 上的开销很大——通常比乘法开销大得多,因此您修改后的着色器可能更慢。
变量太多,所以答案是 "it depends"。一些 GPU 可以处理分支。有些不能,代码由编译器扩展,因此没有分支,只有乘以 0 的数学和其他不乘以的数学。然后是平铺 GPU 之类的东西,它们试图积极避免透支。我确定还有其他因素。
理论上,您可以 运行 对着色器进行一百万或几百万次迭代,并用
对其计时
gl.readPixels(one pixel);
const start = performance.now();
...draw a bunch..
gl.readPixels(one pixel);
const end = performance.now();
const elapsedTime = end - start;
gl.readPixels
是同步操作,因此它会暂停 GPU 管道。
elapsedTime
本身不是实际时间,因为它包括启动 GPU 和停止它等其他事情,但您似乎可以将一个着色器的 elapsedTime
与另一个着色器进行比较,看看哪个更快。
换句话说,如果 elapsedTime
是 10 秒,这并不意味着您的着色器花费了 10 秒。这意味着启动 gpu、运行 着色器和停止 GPU 需要 10 秒。这些秒中有多少是开始的,有多少是停止的,有多少是你的着色器不可用。但是,如果一个着色器的 elaspedTime
是 10 秒而另一个着色器是 11 秒,那么可以肯定地说一个着色器比另一个快。请注意,您可能想让测试时间足够长,以便得到秒级的差异而不是微秒级的差异。您还需要在多个 GPU 上进行测试,看看速度差异是否始终成立。
请注意,在顶点着色器中调用 return
不会阻止生成顶点。实际上 gl_Position
在那种情况下是未定义的。
我有一个顶点着色器
void main (){
vec4 wPos = modelMatrix * vec4( position , 1. );
vWorldPosition = wPos.xyz;
float mask = step(
0.,
dot(
cameraDir,
normalize(normalMatrix * aNormal)
)
);
gl_PointSize = mask * uPointSize;
gl_Position = projectionMatrix * viewMatrix * wPos;
}
我不太确定如何测试着色器的性能,并排除其他因素,如透支。我想象一个大小为 1 的点,在屏幕 space 中排列成网格,没有任何重叠会起作用吗?
否则我对这些调整很好奇:
(删除step
,删除乘法,引入if
else
)
void main (){
if(dot(
cameraDir,
normalize(normalMatrix * aNormal) //remove step
) < 0.) {
gl_Position = vec4(0.,.0,-2.,.1);
gl_PointSize = 0.;
} else {
gl_PointSize = uPointSize; //remove a multiplication
vec4 wPos = modelMatrix * vec4( position , 1. );
vWorldPosition = wPos.xyz;
gl_Position = projectionMatrix * viewMatrix * wPos;
}
}
与这样的东西相比:
void main (){
if(dot(
cameraDir,
normalize(normalMatrix * aNormal)
) < 0.) {
gl_Position = vec4(0.,.0,-2.,.1);
return;
}
gl_PointSize = uPointSize;
vec4 wPos = modelMatrix * vec4( position , 1. );
vWorldPosition = wPos.xyz;
gl_Position = projectionMatrix * viewMatrix * wPos;
}
这些着色器的行为是否会有所不同,why/how?
如果有什么东西可以量化性能差异,我很感兴趣。
- 是否有一些值,例如 MAD 的数量或不同代码显然会产生的其他值?
- 不同代的 GPU 会以不同的方式处理这些差异吗?
- 如果单步版本保证最快,是否有关于如何避免分支的已知模式列表,以及首选哪些操作? (比如用
floor
代替step
也可以吗?):
.
float condition = clamp(floor(myDot + 1.),0.,1.); //is it slower?
条件分支在 GPU 上的开销很大——通常比乘法开销大得多,因此您修改后的着色器可能更慢。
变量太多,所以答案是 "it depends"。一些 GPU 可以处理分支。有些不能,代码由编译器扩展,因此没有分支,只有乘以 0 的数学和其他不乘以的数学。然后是平铺 GPU 之类的东西,它们试图积极避免透支。我确定还有其他因素。
理论上,您可以 运行 对着色器进行一百万或几百万次迭代,并用
对其计时gl.readPixels(one pixel);
const start = performance.now();
...draw a bunch..
gl.readPixels(one pixel);
const end = performance.now();
const elapsedTime = end - start;
gl.readPixels
是同步操作,因此它会暂停 GPU 管道。
elapsedTime
本身不是实际时间,因为它包括启动 GPU 和停止它等其他事情,但您似乎可以将一个着色器的 elapsedTime
与另一个着色器进行比较,看看哪个更快。
换句话说,如果 elapsedTime
是 10 秒,这并不意味着您的着色器花费了 10 秒。这意味着启动 gpu、运行 着色器和停止 GPU 需要 10 秒。这些秒中有多少是开始的,有多少是停止的,有多少是你的着色器不可用。但是,如果一个着色器的 elaspedTime
是 10 秒而另一个着色器是 11 秒,那么可以肯定地说一个着色器比另一个快。请注意,您可能想让测试时间足够长,以便得到秒级的差异而不是微秒级的差异。您还需要在多个 GPU 上进行测试,看看速度差异是否始终成立。
请注意,在顶点着色器中调用 return
不会阻止生成顶点。实际上 gl_Position
在那种情况下是未定义的。