提升先进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都是为类似的硬件跑腿的呢!

posted @ 2014-02-16 00:24 YangZhao1992 阅读(...) 评论(...) 编辑 收藏