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

Bre*_*ble 9 performance buffer webgl

在各种来源中,我看到了在使用后对"解除绑定"缓冲区的建议,即将其设置为null.我很好奇是否真的需要这个.例如

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

// ... buffer related operations ...

gl.bindBuffer(gl.ARRAY_BUFFER, null); // unbinding
Run Code Online (Sandbox Code Playgroud)

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

Ret*_*adi 12

人们经常解开缓冲区和其他对象的原因是为了最小化函数/方法的副作用.这是一个通用的软件开发原则,功能应该只执行他们的广告操作,而不会有任何意想不到的副作用.因此,通常的做法是,如果函数绑定对象,它会在返回之前取消绑定它们.

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

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;
}
Run Code Online (Sandbox Code Playgroud)

然后,让我们有另一个函数来创建纹理.但是这个用缓冲区中的数据来填充纹理(我相信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;
}
Run Code Online (Sandbox Code Playgroud)

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

GLuint tex1 = createEmptyTexture(width, height);
GLuint tex2 = createTextureFromBuffer(width, height, bufferId);
Run Code Online (Sandbox Code Playgroud)

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

GLuint tex1 = createTextureFromBuffer(width, height, bufferId);
GLuint tex2 = createEmptyTexture(width, height);
Run Code Online (Sandbox Code Playgroud)

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 每个函数都记录其前提条件和副作用,并且调用者负责在调用具有副作用的函数之后进行任何必要的状态更改以满足下一个函数的前提条件.
  2. 每个功能都完全负责设置它的所有状态.在上面的例子中,这意味着createEmptyTexture()函数必须取消绑定像素解包缓冲区,因为它依赖于没有绑定.

方法1并不能很好地扩展,并且在较大的系统中维护会很痛苦.方法2也不能令人满意,因为OpenGL具有很多状态,并且必须在每个函数中设置所有相关状态将是冗长且低效的.

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

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

虽然解除绑定缓冲区应该相当便宜,但我不喜欢不必要的调用.因此,我会尽量避免这些调用,除非您真的觉得需要它们为您正在编写的软件强制执行明确一致的策略.