在GLES中使用Separate Shader Objects
一些介绍
在苹果的Best Practices for Shaders中,介绍到了Separate Shader Objects,用于加快shader编译和链接的速度,减少program和shader object的占用。
对于许多项目来说,vertex shader和fragment shader都是复用的,例如一个vertex shader配上各种fragment shader,实现不同的渲染效果,我们将其称为一个program。在没有实现Separate Shader Objects之前,一个vertex shader要为每个program编译和链接一遍,最后导致生成了大量的shader 和program。
iOS上支持EXT_separate_shader_objects扩展,让我们能够分别编译链接两种shader,然后使用program pipeline来搭配shader。demo里有3个vs , 12个ps 共计15个program,编译链接加上一些初始化数据需要270+ms,使用了Separate Shader Objects之后,仅需编译链接3个vs和12个ps,耗时80+ms。
一些栗子
关于如何使用Separate Shader Objects这里 有篇文章给了详细的例子。OpenGL的WIKI上也有很多细节的说明。你可以仍然使用传统的流程, 创建和编译shader,然后创建和链接一个program,只需要加上一句:
1 |
glProgramParameter(program, GL_PROGRAM_SEPARABLE, GL_TRUE); |
另外一种更简练的用法是:
1 |
GLuint glCreateShaderProgramv(GLenum type, GLsizei count, const char **strings); |
如果没有特殊的需求,比较推荐用第二种方法,但也有些缺陷,例如无法再设置任何的pre-link parameters到program中。在之前的博文中,我提到通过glBindAttribLocation来绑定attribute到VBO,在glLinkProgram之前调用。如果使用了glCreateShaderProgramv,就无法再这样做了。
好消息是,我们可以在shader中解决这个问题。我在这篇文章里找到了答案:
1 2 3 4 5 |
// GLSL code: #extension GL_EXT_separate_shader_objects : enable layout(location = 0) attribute vec4 myPostion; layout(location = 1) attribute vec3 myNormal; void main() { ... } |
对于不支持EXT_separate_shader_objects扩展的设备来说,上面那段shader代码会编译出错。考虑到兼容性,需要把代码改成这样:
1 2 3 4 5 6 7 8 9 10 11 12 |
// GLSL code: #extension GL_EXT_separate_shader_objects : enable #ifdef GL_EXT_separate_shader_objects #define UseLayout(x) layout(location = x) #else #define UseLayout(x) #endif UseLayout(0) attribute vec4 myPostion; UseLayout(1) attribute vec3 myNormal; void main() { ... } |
如果设备不支持这个扩展的话,只会在第2行打个warning,不影响使用。
然后,通过glGenProgramPipelines创建pipeline,将vertex shader和fragment shader绑定到pipeline的各个stage上,在渲染时,调用glBindProgramPipeline来启用某个pipeline。值得一提的是,如果你的程序混用了传统program和pipeline,建议在之前先调用glUseProgram(0),防止生效的是program而不是pipeline。
一些坑
切换到Separate Shader Objects,遇到了一个shader编译错误“implementation limit of 32 varying components exceeded”。也就是上一篇博文里提到的,将texCoord的运算提前到vertex shader中进行,我申请了16个vec2的varying。
在传统program的编译链接中,这样写是没问题的,因为16个vec2没有超过32个components(一个x\y\z分量即一个component)。不清楚为什么shader program就不支持。我试着改成8个vec4,就没有问题了。但是我又不能使用zw分量来访问纹理,在之前的博文里提到了,这样很低效。于是,我只能减少varying的使用了。
另一个问题是,有些材质渲染不出来了。前面的几篇文章里也有提到,由于单独编译链接一个shader,无法在链接时对shader之间的输入输出做匹配。当vertex shader和fragment shader之间出现了mismatch,不会产生错误,但是shader的某些输入会出现undefined的情况。
例如vertex shader中定义:
1 2 |
varying vec4 color; varying vec4 texcoord; |
而fragment shader中仅仅使用了texcoord,就会导致渲染效果不对。
目前只发现了这两个问题, 总的来说还是值得使用的,等以后踩了坑,再来补充: P
学习OpenGL ES3.1的时候无意中发现你的文章,厉害,看日期咱俩应该是同届。认识一下~我的个人网站www.geekfaner.com。