提升先进OpenGL(一):Multi Bind
来源请注明:http://www.cnblogs.com/vertexshader/articles/3550965.html
为了更好的阅读体验,这里提供了PDF版本。
导言
OpenGL是一个状态机,在渲染过程中有许多的Bind操作,尤其是对于纹理这类开销很大的对象,每次对Texture Unit进行绑定操作会消耗非常多的CPU time。对此OpenGL 4.4推出了GL_ARB_multi_bind扩展,用来解决这个在渲染中消耗过多CPU time的问题。
描述
OpenGL是一个状态机,在渲染过程中有许多的Bind操作,尤其是对于纹理这类开销很大的对象,每次对Texture Unit进行绑定操作会消耗非常多的CPU time。对此OpenGL 4.4推出了GL_ARB_multi_bind扩展,用来解决这个在渲染中消耗过多CPU time的问题。首先让我们来看看,在OpenGL 3时代,应用程序如何上传纹理:
// Bind textures to texture unit. for(...) { glActiveTexture(GL_TEXTURE0 + i); glBindTexture(GL_TEXTURE_2D, tex[i]); } // Update the uniform sampler. for(...) { glUniform1i(loc, tex[i]); }
在一帧渲染过程中,应用程序必须重复调用这些API,在不同的渲染批次中频繁地调用这些API,将导致非常多的CPU time消耗在调用图形API上。如果一帧中有多个渲染批次,每个渲染批次绑定许多的问题,渲染的帧率可能会变得很低。对此OpenGL提升的方法,仍然是从API上着手,既然一次次地绑定Texture,我们就一次性绑定就好了!GL_ARB_multi_bind则因此诞生,这个扩展提供了对Buffer Object,Texture Object和Sampler Object绑定的优化:
// Uniform Buffer Object和Transform Feedback object绑定Buffer Object使用的 void BindBuffersBase(enum target, uint first, sizei count, const uint *buffers); void BindBuffersRange(enum target, uint first, sizei count, const uint *buffers, const intptr *offsets, const sizeiptr *sizes); // Texture Unit绑定Texture和Sampler使用的 void BindTextures(uint first, sizei count, const uint *textures); void BindSamplers(uint first, sizei count, const uint *samplers); void BindImageTextures(uint first, sizei count, const uint *textures); // Vertex Array Object绑定Buffer object使用的 void BindVertexBuffers(uint first, sizei count, const uint *buffers, const intptr *offsets, const sizei *strides);
这些函数在功能上等价于以下伪代码:
//BindBuffersBase is equivalent to: for (i = 0; i < count; i++) { if (buffers == NULL) { glBindBufferBase(target, first + i, 0); } else { glBindBufferBase(target, first + i, buffers[i]); } } //BindBuffersRange is equivalent to: for (i = 0; i < count; i++) { if (buffers == NULL) { glBindBufferRange(target, first + i, 0, 0, 0); } else { glBindBufferRange(target, first + i, buffers[i], offsets[i], sizes[i]); } } //BindTextures is equivalent to for (i = 0; i < count; i++) { uint texture; if (textures == NULL) { texture = 0; } else { texture = textures[i]; } ActiveTexture(TEXTURE0 + first + i); if (texture != 0) { enum target = /* target of texture object textures[i]*/; BindTexture(target, textures[i]); } else { for (target in all supported targets) { BindTexture(target, 0); } } }
// BindSamplers is equivalent to: for (i = 0; i < count; i++) { if (samplers == NULL) { glBindSampler(first + i, 0); } else { glBindSampler(first + i, samplers[i]); } }
也就是说,这个扩展通过单独的一个函数调用,将过去多次分开调用的开销平摊起来,达到了减少CPU time的目的。在这里我们对Texture做了简要的测试,测试的硬件则是Nvidia GTX 650(GeForce 332.21 Driver),Intel Core i3-4130 CPU @ 3.40GHz 3.40GHz,DDR3 1600 4G,Windows 7 sp1 x64,总共创建了192个纹理,使用了192个纹理单元,下面的每段代码执行了一万次,使用time.h里的clock来计时(由于Nsight暂时只支持到OpenGL 4.2 core,所以暂时没法使用Tracker)。
// 代码段1 glBindTextures(0, 192, tex); // 代码段2 for(int i = 0; i < 192; i ++) { glUniform1i(loc, tex[i]); } // 代码段3 for(int i = 0; i < 192; i ++) { glActiveTexture(GL_TEXTURE0 + i); glBindTexture(GL_TEXTURE_2D, tex[i]); }
测试的结果如下:
glBindTextures |
glActiveTexture + glBindTexture |
glUniform1i |
|
第一次 |
3 |
28 |
16 |
第二次 |
3 |
24 |
15 |
第三次 |
3 |
27 |
16 |
平均 |
3 |
26 |
15 |
也就是平均情况下,使用GL_ARB_multi_bind扩展所需要的时间是3+15=18,而传统情况下需要消耗26+15=41!通过这个简单的测试,可以看出GL_ARB_multi_bind所提升的效率非常的明显。当然这里还想说一个扩展就是GL_ARB_bindless_texture,在这个扩展中可以获得texture和sampler的handle,作为bindless texture直接上传handle的值,让shader通过handle来访问纹理数据,而取消了Texture Unit的限制——这个扩展的好处更加明显,首先没有了Texture Unit的限制,Shader直接通过虚地址访问纹理,受到的限制更加的少,只需要glUniform1i的开销即可。在OpenGL 4.4发布的时候,GL_ARB_bindless_texture也一同发布了,遗憾的是这个扩展并不是Core Profile,但是GTX 6xx以上的显卡都支持了,让我们期待一下这个扩展能否成为核心。
还有一个值得提到的是,这个扩展的接口和D3D11的接口完全的相似,而OpenGL 4.4的重点就是方便D3D程序移植到OpenGL上来,可以看得出Khonros Group还在创造“DirectGL”,不过谁让Direct3D和OpenGL都是为类似的硬件跑腿的呢!