OpenGL程序与英特尔高清和NVidia GPU的使用



我是OpenGL的新手,希望有人向我解释该程序如何使用GPU。

我有一个三角形数组(类包含3个点)。这是绘制它们的代码(我知道这些函数是不复杂的)。

glBegin(GL_LINES);
for(int i=0; i<trsize; ++i){
    glVertex3d((GLdouble)trarr[i].p1().x(), (GLdouble)trarr[i].p1().y(), (GLdouble)trarr[i].p1().z());
    glVertex3d((GLdouble)trarr[i].p2().x(), (GLdouble)trarr[i].p2().y(), (GLdouble)trarr[i].p2().z());
    glVertex3d((GLdouble)trarr[i].p3().x(), (GLdouble)trarr[i].p3().y(), (GLdouble)trarr[i].p3().z());
}
glEnd();

我还使用去复杂函数来旋转、转换等

当数组的大小大于50k时,程序运行速度非常慢。我试着只使用Intel HD或NVidia gtx860M(默认的NVidia程序允许选择GPU),但它们都工作得很慢。也许英特尔高清的工作速度更快。

那么,为什么这两个GPU之间没有区别呢?该程序使用着色器会更快地工作吗?

可能的瓶颈是在顶点上循环,访问阵列,每次渲染提取顶点数据50000次,然后将数据发送到GPU进行渲染。

使用VBO确实会更快,并且在初始化时压缩提取数据并将其发送到GPU的成本。

即使使用用户内存缓冲区也会加快速度,因为您不会调用50k函数,但驱动程序可以对相关数据进行内存复制。

当数组的大小大于50k时,程序运行速度非常慢。

在中间模式下绘制时的主要瓶颈是,所有顶点都必须在每一帧中从程序内存转移到GPU内存。GPU和CPU之间的总线可以传输的数据量有限,因此最好的猜测是,50k个三角形的数量远远超过了总线的传输量。另一个问题是,驱动程序必须在CPU上处理您发送给他的所有命令,这也可能是一个很大的开销。

那么,为什么这两个GPU之间没有区别呢?

Intel HD卡和NVIDIA卡之间(通常)存在巨大的性能差异,但它们之间的总线可能是相同的。

该程序使用着色器会更快地工作吗?

它不会直接受益于着色器的用户,但肯定会受益于将顶点存储在gpu内存上一次(请参见VBO/VAO)。第二个改进是,您可以只使用一个draw调用来呈现整个VBO,这减少了cpu必须处理的指令量。

看到两个GPU的性能相同,但性能潜力却大不相同,这无疑表明您的代码受到CPU限制。但我非常质疑其他答案/评论中关于性能瓶颈的一些理论。

  • 一些简单的计算表明,内存带宽根本不应该发挥作用。对于50000个三角形,每个三角形有3个顶点,每个顶点有24个字节,每帧可以看到3600000字节的顶点数据。假设你的目标是每秒60帧,这是每秒200兆字节多一点。这还不到现代电脑内存带宽的1%
  • 在现代GPU上,即时模式最实用的实现是驱动程序将所有数据收集到缓冲区中,然后在缓冲区填满时一次性提交所有数据。因此,不需要大量的内核调用,而且每个顶点的数据肯定不会单独发送到GPU

司机的开销很可能是罪魁祸首。有50000个三角形,每个三角形有3个API调用,这是每帧150000个API调用,如果目标是每秒60帧,则为每秒900万个API调用。太多了!对于这些电话中的每一个,您都会有:

  • 循环和数组访问在您自己的代码中
  • 实际的函数调用
  • 参数正在传递
  • 驱动程序代码中的状态管理和逻辑
  • 等等

一个重要的方面使情况变得比实际情况更糟:您使用double值作为坐标。与使用float值相比,这使需要传递的数据量增加了一倍。由于OpenGL顶点管道以单精度(*)运行,因此驱动程序必须将所有值转换为float

我怀疑,如果您开始对所有坐标(包括您自己的存储,以及将它们传递给OpenGL)使用float,即使使用不推荐使用的即时模式调用,也可以显著提高性能。您也可以使用glVertex*()调用的版本,该版本接受带有指向向量的指针的单个参数,而不是3个单独的参数。对于float矢量,这将是glVertex3fv()

转移到VBO当然是真正的解决方案。它将按数量级减少API调用的次数,并避免任何数据复制,只要顶点数据不会随时间变化。

(*)OpenGL 4.1增加了对double顶点属性的支持,但它们需要使用特定的API函数,并且只有在单精度浮动确实不够精确时才有意义。

最新更新