为什么 VBO 顶点大小与 user/system CPU 时间使用成正比?
Why is VBO vertex size proportional to user/system CPU time usage?
我编写了一个简单的基准测试来了解 VBO 的用法。
这在逻辑上非常简单:
- 从文件加载 WaveFront 对象(我测试过 Stanford Bunny, Stanford Dragon and Happy Buddha)
- 创建并初始化 3 个 VBO(一个用于顶点、法线和索引)
- 通过调用 一次 次渲染场景(对于每个 实例 ):
// 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);
- 外层循环如下(
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();
}
- 我已经开始测量 FPS 和总体性能,我观察到 VBO 包含的三角形越多,user 和 的时间越长system 时间用于渲染循环;请注意我用 getrusage
测量了 user/system 时间
后面的一些参考数字(w/u/s 是 msec 中 wall/u 的时间ser/s系统).
对于此测试,我渲染了 100 个 instance 完全相同的 VBO(即 sceneVBO
包含 100 个元素,所有这些元素都引用 非常相同 3 VBOs - 顶点、法线和索引)。
- 兔子
Loaded [bunny.obj] 34835/69666 (Tris/Vertexes)
FPS: 333.3 CPU (ms/frame): 3.0/ 0.8/ 2.0 (w/u/s) Total time (ms): 14999.5/4000.4/10000.2 (w/u/s) Frames: 5000
- 龙
Loaded [dragon.obj] 50000/100000 (Tris/Vertexes)
FPS: 217.4 CPU (ms/frame): 4.6/ 1.4/ 3.0 (w/u/s) Total time (ms): 22999.6/6999.7/15000.9 (w/u/s) Frames: 5000
- 佛系
Loaded [buddha.obj] 543524/1087474 (Tris/Vertexes)
FPS: 27.5 CPU (ms/frame): 36.4/10.4/26.0 (w/u/s) Total time (ms): 181999.9/51999.8/130000.3 (w/u/s) Frames: 5000
因此我的问题是,为什么 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
- 创建了简单的功能来实现
gluLookAt
、gluPerspective
、glColor3fv
、glTranslatef
、glRotatef
、glLightfv
、.. .
- 检查上面的每个单 warning/error与
glDebugMessageCallback
和glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, &unusedIds, GL_TRUE)
- 确保屏幕上的图片与立即模式
相同
我仍然得到类似的结果:我的 VAO 中的三角形数量越多,CPU 时间越长(user 和 system) 我们在 glXSwapBuffers
.
里面度过
总而言之,这似乎很出乎意料(至少可以这么说)。
当然,一旦启用 V-Sync,CPU 时间(user 和 system) 下降到 ~0.
所以似乎在 glXSwapBuffers
内部我们有一个与要渲染的三角形数量直接相关的活动自旋(要渲染的三角形越多,等待的 CPU 循环将被燃烧一些事件)。
我编写了一个简单的基准测试来了解 VBO 的用法。 这在逻辑上非常简单:
- 从文件加载 WaveFront 对象(我测试过 Stanford Bunny, Stanford Dragon and Happy Buddha)
- 创建并初始化 3 个 VBO(一个用于顶点、法线和索引)
- 通过调用 一次 次渲染场景(对于每个 实例 ):
// 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);
- 外层循环如下(
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();
}
- 我已经开始测量 FPS 和总体性能,我观察到 VBO 包含的三角形越多,user 和 的时间越长system 时间用于渲染循环;请注意我用 getrusage 测量了 user/system 时间
后面的一些参考数字(w/u/s 是 msec 中 wall/u 的时间ser/s系统).
对于此测试,我渲染了 100 个 instance 完全相同的 VBO(即 sceneVBO
包含 100 个元素,所有这些元素都引用 非常相同 3 VBOs - 顶点、法线和索引)。
- 兔子
Loaded [bunny.obj] 34835/69666 (Tris/Vertexes) FPS: 333.3 CPU (ms/frame): 3.0/ 0.8/ 2.0 (w/u/s) Total time (ms): 14999.5/4000.4/10000.2 (w/u/s) Frames: 5000
- 龙
Loaded [dragon.obj] 50000/100000 (Tris/Vertexes) FPS: 217.4 CPU (ms/frame): 4.6/ 1.4/ 3.0 (w/u/s) Total time (ms): 22999.6/6999.7/15000.9 (w/u/s) Frames: 5000
- 佛系
Loaded [buddha.obj] 543524/1087474 (Tris/Vertexes) FPS: 27.5 CPU (ms/frame): 36.4/10.4/26.0 (w/u/s) Total time (ms): 181999.9/51999.8/130000.3 (w/u/s) Frames: 5000
因此我的问题是,为什么 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
- 创建了简单的功能来实现
gluLookAt
、gluPerspective
、glColor3fv
、glTranslatef
、glRotatef
、glLightfv
、.. . - 检查上面的每个单 warning/error与
glDebugMessageCallback
和glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, &unusedIds, GL_TRUE)
- 确保屏幕上的图片与立即模式 相同
我仍然得到类似的结果:我的 VAO 中的三角形数量越多,CPU 时间越长(user 和 system) 我们在 glXSwapBuffers
.
总而言之,这似乎很出乎意料(至少可以这么说)。
当然,一旦启用 V-Sync,CPU 时间(user 和 system) 下降到 ~0.
所以似乎在 glXSwapBuffers
内部我们有一个与要渲染的三角形数量直接相关的活动自旋(要渲染的三角形越多,等待的 CPU 循环将被燃烧一些事件)。