为什么 VBO 顶点大小与 user/system CPU 时间使用成正比?

Why is VBO vertex size proportional to user/system CPU time usage?

我编写了一个简单的基准测试来了解 VBO 的用法。 这在逻辑上非常简单:

  1. 从文件加载 WaveFront 对象(我测试过 Stanford Bunny, Stanford Dragon and Happy Buddha
  2. 创建并初始化 3 个 VBO(一个用于顶点、法线和索引)
  3. 通过调用 一次 次渲染场景(对于每个 实例 ):


    // enable states
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    // bind vertexes
    glBindBuffer(GL_ARRAY_BUFFER, vbos_[0]);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    // normal
    glBindBuffer(GL_ARRAY_BUFFER, vbos_[1]);
    glNormalPointer(GL_FLOAT, 0, 0);
    // indexes
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos_[2]);
    // draw n_i_ triangles using offset of index array
    glDrawElements(GL_TRIANGLES, n_i_, GL_UNSIGNED_INT, 0);
    // deactivate vertex array
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);            
    // bind with 0, so, switch back to normal pointer operation
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

  1. 外层循环如下(sceneVBO告诉有多少个相同的instance要绘制):


    for(const auto& i : sceneVBO) {
        glPushMatrix();
            glColor3fv(i.rgb);
            glTranslatef(i.posX, 0.0f, i.posZ);
            glRotatef(angle*i.r_speed, 0.0f, 1.0f, 0.0f);
            vboTest->draw(); // Executes step 3
        glPopMatrix();
    }

  1. 我已经开始测量 FPS 和总体性能,我观察到 VBO 包含的三角形越多,user 的时间越长system 时间用于渲染循环;请注意我用 getrusage
  2. 测量了 user/system 时间

后面的一些参考数字(w/u/s 是 msecwall/u 的时间ser/s系统).
对于此测试,我渲染了 100 个 instance 完全相同的 VBO(即 sceneVBO 包含 100 个元素,所有这些元素都引用 非常相同 3 VBOs - 顶点、法线和索引)。

因此我的问题是,为什么 VBO 顶点大小与 user/system CPU 时间成正比?

我知道如果 GPU 有更多的三角形要绘制,它会花费更长的时间,但为什么要花费更多 CPU user/system 时间?
我不会重新发送 vertexes/normals 和索引的每一帧 - 所有 应该 都保存在 GPU 内存中(数组缓冲区填充 GL_STATIC_DRAW) - 我期待更长的时间来绘制框架,但相对较少 CPU 使用(用户和系统)。

或者驱动程序 (nVidia 352.63)/GL 在 glXSwapBuffers 上有一个活跃的自旋?

我期待 w所有时间都会增加,但坦率地说 不是 user和 s系统时间...

Ps。当然 V-Sync 被禁用了。

您的代码中存在一些值得怀疑的地方。

您正在使用即时模式,这意味着您的 API 调用依赖于已弃用的行为,您的驱动程序可能未对其进行优化。

// enable states
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
// bind vertexes
glBindBuffer(GL_ARRAY_BUFFER, vbos_[0]);
glVertexPointer(3, GL_FLOAT, 0, 0);
// normal
glBindBuffer(GL_ARRAY_BUFFER, vbos_[1]);
glNormalPointer(GL_FLOAT, 0, 0);
// indexes
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos_[2]);
// draw n_i_ triangles using offset of index array
glDrawElements(GL_TRIANGLES, n_i_, GL_UNSIGNED_INT, 0);
// deactivate vertex array
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);            
// bind with 0, so, switch back to normal pointer operation
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

您强调此代码每个实例只调用一次,但由于 OpenGL 的工作方式,它和 DX9(这是 OpenGL 的即时模式最相似的模式)并没有真正映射到实际显卡的功能特别好——在这些用于正确设置状态的 API 调用中可能会发生许多事情。例如,您对 glVertexPointer 的调用必须设置状态以从正确的内存段读取,并且如果您的缓冲区对象特别大,这可能是一个非常重要的操作,因为必须设置这些指针为 GPU 启动的每个线程 运行 你的着色器。

Or is it that the driver (nVidia 352.63)/GL has an active spin on glXSwapBuffers?

我也不排除这种可能。它确实必须定期查询显卡以查明命令是否已完成执行,因此 Nvidia 可以选择将此功能实现为忙等待。

底线是,如果您担心 OpenGL 中的 CPU 开销,您可能希望研究一些 AZDO techniques (for OpenGL 4.3+), or consider learning DirectX 12 (for Windows 10) or Vulkan(对于任何不是 Windows 10)

按照上面的建议,我进行了以下操作:

  • 将代码移植到 GL 4.5
  • 正确使用VAOs
  • 创建了简单的功能来实现 gluLookAtgluPerspectiveglColor3fvglTranslatefglRotatefglLightfv、.. .
  • 检查上面的每个 warning/error与glDebugMessageCallbackglDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, &unusedIds, GL_TRUE)
  • 确保屏幕上的图片与立即模式
  • 相同

我仍然得到类似的结果:我的 VAO 中的三角形数量越多,CPU 时间越长(usersystem) 我们在 glXSwapBuffers.

里面度过

总而言之,这似乎很出乎意料(至少可以这么说)。
当然,一旦启用 V-Sync,CPU 时间(usersystem) 下降到 ~0.

所以似乎在 glXSwapBuffers 内部我们有一个与要渲染的三角形数量直接相关的活动自旋(要渲染的三角形越多,等待的 CPU 循环将被燃烧一些事件)。