【问题标题】:OpenGL program with Intel HD and NVidia GPU usage使用 Intel HD 和 NVidia GPU 的 OpenGL 程序
【发布时间】:2015-01-10 13:40:27
【问题描述】:

我是 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),但它们的运行速度都很慢。也许英特尔 HD 的运行速度会更快。

那么,为什么这两款 GPU 没有区别呢? 如果使用着色器,程序会更快地运行吗?

【问题讨论】:

  • 简而言之,您受到 CPU 的限制,即函数调用和用户模式→内核模式上下文切换开销,您的进程必须为这些调用中的每一对跳过。

标签: c++ opengl glsl gpu nvidia


【解决方案1】:

可能的瓶颈是遍历顶点,访问数组并提取顶点数据每次渲染 50000 次,然后将数据发送到 GPU 进行渲染。

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

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

【讨论】:

  • “用户内存缓冲区”是什么意思?我在 google 中没有找到任何与 OpenGL 相关的内容。
  • @user3051029 当你将 0 绑定到 GL_ARRAY_BUFFER 然后偏移量在用户内存中时会发生这种情况。
【解决方案2】:

当数组大小大于 50k 时,程序运行很慢。

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

那么,为什么这两种 GPU 没有区别呢?

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

如果使用着色器,程序会更快地运行吗?

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

【讨论】:

    【解决方案3】:

    使用两个具有显着不同性能潜力的 GPU 看到相同的性能肯定表明您的代码受 CPU 限制。但我非常质疑其他答案/cmets 中关于性能瓶颈的一些理论。

    • 一些简单的计算表明内存带宽根本不应该发挥作用。使用 50,000 个三角形,每个三角形有 3 个顶点,每个顶点 24 个字节,您将看到每帧 3,600,000 字节的顶点数据。假设您的目标是 60 帧/秒,这略高于 200 MB/秒。这不到现代 PC 内存带宽的 1%。
    • 现代 GPU 上即时模式最实用的实现是让驱动程序将所有数据收集到缓冲区中,然后在缓冲区填满时一次性提交所有数据。所以不需要进行大量的内核调用,每个顶点的数据肯定不会单独发送到 GPU。

    驱动程序开销很可能是罪魁祸首。对于 50,000 个三角形和每个三角形 3 个 API 调用,这是每帧 150,000 个 API 调用,或者如果您的目标是 60 帧/秒,则为 900 万个 API 调用/秒。好多啊!对于这些调用中的每一个,您将拥有:

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

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

    我怀疑,如果您开始使用float 处理所有坐标(包括您自己的存储,以及将它们传递给 OpenGL),即使使用已弃用的立即模式调用,您也可以获得显着的性能提升。您还可以使用 glVertex*() 调用的版本,它采用单个参数和指向向量的指针,而不是 3 个单独的参数。对于 float 向量,这将是 glVertex3fv()

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

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

    【讨论】:

      猜你喜欢
      • 2019-10-23
      • 2019-02-10
      • 2016-03-06
      • 2018-07-21
      • 1970-01-01
      • 2021-02-03
      • 2013-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多