GLSL着色器性能对比:频繁切换shader还是片元内if分支更高效
GLSL着色器分支合批方案可行性结论
你提的单着色器分支判断方案在你描述的2D绘制场景下完全合理,性能收益远高于分支带来的开销,是这类无法按图元类型合批场景下通用的成熟实现方案。
你当前实现的实际开销比你想的大得多
你现在频繁切着色器+小批量调用glDrawArrays的逻辑,性能瓶颈根本不在GPU计算上,全耗在CPU调度和管线空转上:
- 每次切完着色器提交绘制,驱动层要走一遍完整的管线状态校验、着色器资源绑定检查、命令编码提交,一个只带4个顶点的
glDrawArrays,固定调度开销能占这个draw call总耗时的90%以上 - 你说的最坏场景下,数千个交替绘制的小图元会产生数千个极小的draw call,GPU大部分时间都在等CPU发指令,管线全是气泡,算力根本喂不满,这是2D渲染里最常见的性能坑。
你担心的片元分支开销基本可以忽略
你怕逐像素跑10次if判断太费性能,实际上这点开销和切着色器的开销根本不在一个量级:
- 你的
shaderType是逐顶点传入的属性,同一个图元(不管是4顶点的矩形还是圆形)所有顶点的shaderType值完全一样,光栅化之后,这个图元覆盖的所有片元拿到的shaderType值是统一的 - 现代GPU遇到一个warp(NVIDIA架构)/wavefront(AMD架构)里所有线程的分支条件完全一致的情况,根本不会产生分支发散,只会跑匹配条件的那一段片元逻辑,剩下9个分支的代码压根不会执行
- 就算是链式if判断,逐片元的比较+跳转也就几个GPU时钟周期的事。你要是把
shaderType设成0-10的连续值,换成switch写法,驱动还会自动优化成跳转表,开销还能再降。
实现时注意两个点就行
- 别在片元着色器里对
shaderType做任何计算修改,直接用顶点着色器传下来的varying值,保证同一个图元覆盖的所有片元分支条件完全一致,就不会出分支发散的问题 - 10个2D基础图元的片元逻辑总指令数很低,不会出现shader太肥导致寄存器溢出、指令缓存命中率暴跌的问题,不用瞎担心shader变“重”。
现在行业里所有主流2D渲染后端,不管是UI框架、矢量图形库还是浏览器的Canvas实现,碰到必须保绘制顺序、没法按图元类型合批的场景,全是用的这种uber shader(超级着色器)思路,没人会为了省几个片元判断的开销,去拆成一堆来回切着色器的小draw call。
内容的提问来源于stack exchange,提问作者KiraHoneybee
相关产品推荐
相关产品推荐

