如何使用多个统一缓冲区对象

How to use multiple Uniform Buffer Objects

在我的 OpenGL ES 3.0 程序中,我需要有两个独立的统一缓冲区对象 (UBO)。只要有一个最终受益人,事情就会按预期进行。该案例的代码如下所示:

GLSL 顶点着色器:

version 300 es

layout (std140) uniform MatrixBlock
{
  mat4 matrix[200];
};

C++头文件成员变量:

GLint  _matrixBlockLocation;
GLuint _matrixBuffer;

static constexpr GLuint _matrixBufferBindingPoint = 0;

glm::mat4 _matrixBufferContent[200];

用于初始化 UBO 的 C++ 代码:

_matrixBlockLocation = glGetUniformBlockIndex(_program, "MatrixBlock");

glGenBuffers(1, &_matrixBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, _matrixBuffer);
glBufferData(GL_UNIFORM_BUFFER, 200 * sizeof(glm::mat4), _matrixBufferContent, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, _matrixBufferBindingPoint, _matrixBuffer);
glUniformBlockBinding(_program, _matrixBlockLocation, _matrixBufferBindingPoint);

为了更新 UBO 的内容,我修改了 _matrixBufferContent 数组,然后调用

glBufferSubData(GL_UNIFORM_BUFFER, 0, 200 * sizeof(glm::mat4), _matrixBufferContent);

这符合我的预期。在顶点着色器中,我可以访问矩阵,生成的图像应该是这样的。


OpenGL ES 3.0 规范定义每个 UBO 的最大可用存储空间为 16K (GL_MAX_UNIFORM_BLOCK_SIZE)。因为我的矩阵数组的大小接近这个限制,所以我想创建第二个 UBO 来存储额外的数据。但是,一旦我添加了第二个 UBO,我就遇到了问题。下面是创建两个 UBO 的代码:

GLSL 顶点着色器:

version 300 es

layout (std140) uniform MatrixBlock
{
  mat4 matrix[200];
};

layout (std140) uniform HighlightingBlock
{
  int highlighting[200];
};

C++头文件成员变量:

GLint  _matrixBlockLocation;
GLint  _highlightingBlockLocation;
GLuint _uniformBuffers[2];

static constexpr GLuint _matrixBufferBindingPoint       = 0;
static constexpr GLuint _highlightingBufferBindingPoint = 1;

glm::mat4 _matrixBufferContent[200];
int32_t   _highlightingBufferContent[200];

用于初始化两个 UBO 的 C++ 代码:

_matrixBlockLocation       = glGetUniformBlockIndex(_program, "MatrixBlock");
_highlightingBlockLocation = glGetUniformBlockIndex(_program, "HighlightingBlock");

glGenBuffers(2, _uniformBuffers);
glBindBuffer(GL_UNIFORM_BUFFER, _uniformBuffers[0]);
glBufferData(GL_UNIFORM_BUFFER, 200 * sizeof(glm::mat4), _matrixBufferContent, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, _uniformBuffers[1]);
glBufferData(GL_UNIFORM_BUFFER, 200 * sizeof(int32_t), _highlightingBufferContent, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, _matrixBufferBindingPoint, _uniformBuffers[0]);
glBindBufferBase(GL_UNIFORM_BUFFER, _highlightingBufferBindingPoint, _uniformBuffers[1]);
glUniformBlockBinding(_program, _matrixBlockLocation, _matrixBufferBindingPoint);
glUniformBlockBinding(_program, _highlightingBlockLocation, _highlightingBufferBindingPoint);

要更新第一个 UBO,我仍然修改 _matrixBufferContent 数组,然后调用

glBindBuffer(GL_UNIFORM_BUFFER, _uniformBuffers[0]);
glBufferSubData(GL_UNIFORM_BUFFER, 0, 200 * sizeof(glm::mat4), _matrixBufferContent);

为了更新第二个 UBO,我修改了 _highlightingBufferContent 数组的内容,然后调用

glBindBuffer(GL_UNIFORM_BUFFER, _uniformBuffers[1]);
glBufferSubData(GL_UNIFORM_BUFFER, 0, 200 * sizeof(int32_t), _highlightingBufferContent);

据我所知,第一个 UBO 仍然按预期工作。但是我在vertex shader中得到的数据并不是我原来放入_highlightingBufferContent中的数据。如果我 运行 此代码作为 WebGL 2.0 代码,我会在 Google Chrome:

中收到以下警告
GL_INVALID_OPERATION: It is undefined behaviour to use a uniform buffer that is too small.

在 Firefox 中,我得到以下信息:

WebGL warning: drawElementsInstanced: Buffer for uniform block is smaller than UNIFORM_BLOCK_DATA_SIZE.

所以,不知何故,第二个 UBO 没有正确映射。但是我看不出哪里出了问题。如何创建两个单独的 UBO 并在同一个顶点着色器中使用它们?


编辑

查询OpenGL期望的GL_UNIFORM_BLOCK_DATA_SIZE后面的值显示它需要比现在大4倍。以下是我查询值的方式:

GLint matrixBlock       = 0;
GLint highlightingBlock = 0;
glGetActiveUniformBlockiv(_program, _matrixBlockLocation, GL_UNIFORM_BLOCK_DATA_SIZE, &matrixBlock);
glGetActiveUniformBlockiv(_program, _highlightingBlockLocation, GL_UNIFORM_BLOCK_DATA_SIZE, &highlightingBlock);

本质上,这意味着缓冲区大小必须是

200 * sizeof(int32_t) * 4

不只是

200 * sizeof(int32_t)

但是,我不清楚为什么会这样。我将 32 位整数放入该数组,我希望它的大小为 4 个字节,但不知何故它们似乎有 16 个字节长。还不确定发生了什么。

正如问题的编辑部分和 Beko 的评论所暗示的那样,存在与 OpenGL 的 std140 布局相关的特定对齐规则。 OpenGL ES 3.0 standard 指定以下内容:

  1. If the member is a scalar consuming N basic machine units, the base alignment is N.
  2. If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N, respectively.
  3. If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N.
  4. If the member is an array of scalars or vectors, the base alignment and array stride are set to match the base alignment of a single array element, according to rules (1), (2), and (3), and rounded up to the base alignment of a vec4. The array may have padding at the end; the base offset of the member following the array is rounded up to the next multiple of the base alignment.

注意强调“四舍五入到 vec4 的基本对齐方式”。这意味着数组中的每个整数不只是简单地占用 4 个字节,而是占用 vec4 的 4 倍大的大小。

因此,数组必须是原始大小的 4 倍。此外,在使用glBufferSubData复制数组内容之前,需要将每个整数填充到相应的大小。如果不这样做,数据就会错位,因此会被 GLSL 着色器误解。