OpenGL drawcall 边界越界问题

buffer数据时size的大小

在 OpenGL buffer数据一般都是以const void *加size的C语言风格传递数据。例如下面非常经典的
void glBufferData( GLenum target, GLsizeiptr size, const void * data, GLenum usage)
有下面用法

1
2
3
4
5
6
// 数据来源
std::vector<float> dataContainer {···};

// 也就是 dataContainer.size() * sizeof (float)
GLsizeiptr size = dataContainer.size() * sizeof(decltype (dataContainer)::value_type);
const void* data = static_cast<const void*>(dataContainer.data());

可以看到,这里的size是以字节为单位,而且size的类型为 GLsizeiptr

drawcall时count的大小

同样是非常经典的
void glDrawArrays( GLenum mode, GLint first, GLsizei count);

1
2
3
4
5
6
7
8
9
10
11
12
13
std::vector<float> dataContainer {···};
GLsizeiptr size = dataContainer.size() * sizeof(decltype (dataContainer)::value_type);
const void* data = static_cast<const void*>(dataContainer.data());

GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), nullptr);


GLsizei counts = size / (2*sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, counts);

此时count中每一个的大小是由glVertexAttribPointer中的sizetypestride共同决定。如上例中,一个点的大小就是2float的大小。要绘制完buffer中的所有内容需要size / (2*sizeof(GLfloat))

drawcall超出边界时会发生情况

数组越界是一个比较常见的错误。有些语言会直接抛出异常,如C/C++,有些语言则会直接访问到对应的内容,返回不确定的结果。在OpenGL里,drawcall中的越界——如glDrawArrays()中的firstcounts指向的显存段超过当前绑定的vbo则是未定义的行为。(至少我在OpenGL-Refpages中没看到对应的规范)
实际上在具体应用中,同样的越界drawcall在不同显卡和操作系统表现出来的行为也是不一样。应该是由显卡驱动厂商决定越界行为的。

  • macOS OpenGL ES2.0 Intel Iris Plus 645
    在macOS下,drawcall越界了后,越界的部分的内存的默认值通常是0,在显存使用量不高的情况下,通常不会绘制出奇怪的内容。也不会引发OpenGL错误。这和Apple下Clang编译器的行为也一致,变量默认值为0

  • Windows OpenGL ES2.0 Intel UHD 630
    在Windows下drawcall越界同样也不会引发OpenGL错误,但是很容易绘制出奇怪的东西出来。

  • Windows Angle
    Windows下使用Angle转译glES到Dx值得特别提一下。使用Angle时,如果drawcall越界了,则会直接跳过,什么效果都没有。用RenderDoc截取帧分析会找不到越界的drawcall,显示上也没有效果。推测可能时Dx引发错误或者Angel内部记录到会有越界行为发生。