取消绑定WebGL缓冲区,值得吗?

时间:2015-02-01 03:08:25

标签: performance buffer webgl

在各种来源中,我看到过关于“解除绑定”的建议。使用后缓冲,即将其设置为null。我很好奇是否确实需要这个。 e.g。

var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

// ... buffer related operations ...

gl.bindBuffer(gl.ARRAY_BUFFER, null); // unbinding

一方面,它可能更适合调试,因为您可能会获得更好的错误消息,但是解除绑定缓冲区是否始终存在显着的性能损失?通常建议尽可能减少WebGL调用。

1 个答案:

答案 0 :(得分:11)

人们经常解开缓冲区和其他对象的原因是为了最小化函数/方法的副作用。它是一种通用的软件开发原则,功能只应执行其广告操作,并且不会产生任何意外的副作用。因此,通常的做法是,如果一个函数绑定对象,它会在返回之前将它们解除绑定。

让我们看一个典型的例子(没有特定的语言语法)。首先,我们定义一个函数来创建一个没有任何已定义内容的纹理:

function GLuint createEmptyTexture(int texWidth, int texHeight) {
    GLuint texId;
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texWidth, texHeight, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, 0);
    return texId;
}

然后,让我们有另一个函数来创建纹理。但是这个用缓冲区中的数据填充纹理(我相信WebGL尚不支持它,但它仍然有助于说明一般原理):

function GLuint createTextureFromBuffer(int texWidth, int texHeight,
                                        GLuint bufferId) {
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, bufferId);
    GLuint texId;
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texWidth, texHeight, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, 0);
    return texId;
}

现在,我可以调用这些函数,一切都按预期工作:

GLuint tex1 = createEmptyTexture(width, height);
GLuint tex2 = createTextureFromBuffer(width, height, bufferId);

但是看看如果我以相反的顺序打电话会发生什么:

GLuint tex1 = createTextureFromBuffer(width, height, bufferId);
GLuint tex2 = createEmptyTexture(width, height);

这次,两个纹理都将填充缓冲区内容,因为像素解包缓冲区在第一个函数返回后仍然绑定,因此在调用第二个函数时。

避免这种情况的一种方法是在绑定它的函数末尾解除像素解包缓冲区的绑定。并且为了确保类似的问题不会发生,因为纹理仍然绑定,它也可以取消绑定:

function GLuint createTextureFromBuffer(int texWidth, int texHeight,
                                        GLuint bufferId) {
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, bufferId);
    GLuint texId;
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texWidth, texHeight, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
    return texId;
}

通过这种实现,使用这两个函数的两个调用序列都将产生相同的结果。

还有其他方法可以解决这个问题。例如:

  1. 每个函数记录其前提条件和副作用,并且调用者负责在调用具有副作用的函数后进行任何必要的状态更改以满足下一个函数的前提条件。
  2. 每个功能都完全负责设置其所有状态。在上面的示例中,这意味着createEmptyTexture()函数必须取消绑定像素解包缓冲区,因为它依赖于没有绑定。
  3. 方法1并不能很好地扩展,并且在较大的系统中维护会很痛苦。方法2也不能令人满意,因为OpenGL具有很多状态,并且必须在每个函数中设置所有相关状态将是冗长且低效的。

    这实际上是一个更大问题的一部分:如何在模块化软件架构中处理OpenGL的基于状态的特性?缓冲区绑定只是您需要处理的状态的一个示例。在您自己编写的小程序中,这通常不是很难处理,但在大型系统中可能存在问题。如果来自不同来源(例如不同供应商)的组件混合在一起会变得更糟。

    我认为没有一种方法适用于所有可能的场景。重要的是你选择一个明确定义的策略,并始终如一地使用它。如何在各种情况下处理这个问题在某种程度上超出了答案的范围。

    虽然解除绑定的缓冲区应该相当便宜,但我并不是不必要的呼叫的粉丝。所以我会尽量避免这些调用,除非你真的觉得你需要它们为你正在编写的软件强制执行明确一致的策略。