我写了一个简单的基准来了解VBO的用法。
就逻辑而言,这非常简单:
从文件加载WaveFront对象(我测试了Stanford Bunny,Stanford Dragon和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
指示要绘制多少个相同实例):
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包含的三角形越多,渲染循环中用户和系统时间花费的时间就越长。请注意,我已经使用getrusage测量了用户/系统时间
以下是一些参考数字(w / u / s是墙/用户/系统的时间,以毫秒为单位)。
对于此测试,我已经渲染了100个实例,它们具有完全相同的VBO(即
sceneVBO
包含100个元素,所有这些元素都引用相同的3个VBO-顶点,法线和索引)。兔子
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顶点大小与用户/系统CPU时间成正比?
我了解到,如果GPU可以绘制更多的三角形,则会花费更长的时间,但是为什么要花费更多的CPU用户/系统时间呢?
我不会重新发送每个帧的顶点/法线或索引-应该将所有帧都保存在GPU内存中(数组缓冲区用
GL_STATIC_DRAW
填充)-我期望绘制帧的时间更长,但是CPU相对较少使用情况(用户和系统)。还是驱动程序(nVidia 352.63)/ GL在
glXSwapBuffers
上具有主动旋转?我原本以为挂墙时间会增加,但是坦率地说,用户和系统时间不会增加。
附言当然,垂直同步被禁用。
最佳答案
您的代码中可能有一些可疑之处。
您正在使用即时模式,这意味着您的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启动运行着色器的每个线程。还是驱动程序(nVidia 352.63)/ GL在
glXSwapBuffers
上具有主动旋转?我也不排除这种可能性。它确实必须定期查询图形卡以查明命令是否已完成执行,因此Nvidia可以选择将此功能实现为繁忙等待。
不过,最重要的是,如果您担心OpenGL中的CPU开销,则不妨考虑一些AZDO techniques(对于OpenGL 4.3+),或者考虑学习DirectX 12(对于Windows 10)或Vulkan(适用于非Windows 10的任何版本)