为什么绘制调用很耗费资源?

54
假设纹理、顶点和着色器数据已经在图形卡上,你不需要向卡片发送太多数据。有一些字节用于标识数据,可能还有一个4x4矩阵和一些其他参数。
那么所有的开销都来自哪里呢?这些操作是否需要与GPU进行某种握手?
为什么发送包含一堆小模型的单个网格,由CPU计算而来,通常比发送顶点ID和变换矩阵更快?(第二个选项看起来应该发送更少的数据,除非模型比4x4矩阵小)
3个回答

76
首先,我假设你所说的“绘制调用”是指命令告诉GPU使用特定状态(着色器、混合状态等)将某个顶点集渲染成三角形。

绘制调用不一定昂贵。在旧版本的Direct3D中,许多调用需要上下文切换,这是昂贵的,但在新版本中并非如此。

减少绘制调用的主要原因是,图形硬件可以比您更快地转换和渲染三角形。如果每个调用都提交较少的三角形,则您将完全受到CPU的限制,而GPU将大部分时间处于空闲状态。CPU无法以足够快的速度向GPU提供数据。

使用两个三角形进行单个绘制调用是便宜的,但是如果每次调用提交的数据过少,则您将没有足够的CPU时间将更多的几何体提交给GPU。

制作绘制调用的实际成本有一些,这需要设置一堆状态(使用哪组顶点、使用哪个着色器等),而状态更改在硬件方面有一个成本(更新一堆寄存器),在驱动程序方面也有一个成本(验证并转换设置状态的调用)。

但是,绘制调用的主要成本仅适用于每个调用提交的数据过少,因为这将导致您受到CPU约束,停止充分利用硬件。

就像Josh所说的那样,绘制调用还可能导致命令缓冲区被刷新,但根据我的经验,这通常发生在调用SwapBuffers时,而不是在提交几何图形时。视频驱动程序通常会尽可能多地缓存(有时会多达几帧!)以从GPU中挤出尽可能多的并行性。

您应该阅读nVidia的演示文稿“Batch Batch Batch!”,虽然它相当老,但正好涵盖了这个话题。

1
似乎nVidia deck的链接已经失效了。请尝试这个链接:http://tinyurl.com/acezt9b。 - Justin R.

15

像Direct3D这样的图形API将其API级别的调用转换为设备无关命令并将它们排队到缓冲区中。刷新该缓冲区以执行实际工作是昂贵的,因为它意味着实际工作现在正在执行,并且因为它可能会在芯片上从用户模式切换到内核模式(然后再次切换回来),这不是很便宜。

在刷新缓冲区之前,GPU能够与CPU并行进行一些准备工作,只要CPU不发出阻塞请求(例如将数据映射回CPU)。但是GPU不会也不能准备所有内容,直到它需要实际绘制。仅仅因为某些顶点或纹理数据在卡上并不意味着它们已经适当地排列好了,而且可能直到设置顶点布局或绑定着色器等操作后才能排列好。真正的工作大部分发生在命令刷新和绘制调用期间。

DirectX SDK有一个关于准确分析D3D性能的部分,虽然与您的问题没有直接关系,但可以提供一些关于什么是昂贵的和什么不是昂贵的(在某些情况下)以及为什么的提示。

更相关的是这篇博客文章(以及后续文章这里这里),它们提供了GPU逻辑、低级操作过程的良好概述。
但是,基本上(为了直接回答你的问题),调用之所以昂贵,并不一定是因为需要传输大量数据,而是因为除了将数据传输到总线上之外,还有大量的工作需要推迟到命令缓冲区刷新时才能完成。

2
简短回答:驱动程序会缓冲一些或全部实际工作,直到您调用draw。这将显示为在绘制调用中花费的相对可预测的时间量,具体取决于更改了多少状态。
这样做有几个原因:
- 避免不必要的工作:如果在绘制之前多次设置相同的状态(不必要地),则可以避免每次发生此情况时进行昂贵的工作。在大型代码库中,例如生产游戏引擎,这实际上变得非常普遍。 - 能够协调内部是相互依赖的状态,而不是立即处理它们并具有不完整的信息
其他答案:
- 驱动程序用于存储渲染命令的缓冲区已满,应用程序实际上正在等待GPU处理一些较早的工作。这通常会显示为随机绘制调用中阻塞的极大时间块。 - 驱动程序允许缓冲的帧数已达到上限,应用程序正在等待GPU处理其中之一。这通常会显示为帧内第一个绘制调用中的大块时间阻塞,或在上一帧结束时出现在Present上。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接