为啥绘图调用很昂贵?

Posted

技术标签:

【中文标题】为啥绘图调用很昂贵?【英文标题】:why are draw calls expensive?为什么绘图调用很昂贵? 【发布时间】:2011-06-18 17:44:33 【问题描述】:

假设纹理、顶点和着色器数据已经在显卡上,您不需要向显卡发送太多数据。有几个字节来识别数据,大概是一个 4x4 矩阵,还有一些其他参数。

那么所有的开销是从哪里来的呢?这些操作是否需要与 gpu 进行某种握手?

为什么发送包含一堆小模型的单个网格,在 CPU 上计算,通常比发送顶点 ID 和变换矩阵更快? (第二个选项看起来应该发送更少的数据,除非模型小于 4x4 矩阵)

【问题讨论】:

【参考方案1】:

首先,我假设“绘图调用”是指告诉 GPU 将一组特定顶点渲染为具有特定状态(着色器、混合状态等)的三角形的命令。

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

减少绘制调用的主要原因是图形硬件转换和渲染三角形的速度比您提交它们的速度要快得多。如果您在每次调用时提交的三角形很少,您将完全受制于CPU 和 GPU 将大部分时间处于空闲状态。 CPU 无法足够快地为 GPU 供电。

使用两个三角形进行一次绘制调用很便宜,但如果每次调用提交的数据太少,您将没有足够的 CPU 时间向 GPU 提交尽可能多的几何图形。

进行绘制调用有一些实际成本,它需要设置一堆状态(使用哪组顶点,使用什么着色器等等),并且状态更改在硬件方面都有成本(更新一堆寄存器)和驱动端(验证和翻译设置状态的调用)。

只有在每次调用提交的数据太少时才会产生绘制调用的主要成本,因为这会导致您受 CPU 限制,并阻止您充分利用硬件。

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

您应该阅读 nVidia 演示文稿 Batch Batch Batch!,它相当古老,但完全涵盖了这个主题。

【讨论】:

看起来与 nVidia 平台的链接已失效。试试这个:tinyurl.com/acezt9b.【参考方案2】:

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

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

DirectX SDK 有一个section on accurately profiling D3D performance,虽然它与您的问题没有直接关系,但可以提供一些关于什么是昂贵的和不昂贵的以及(在某些情况下)原因的提示。

更相关的是this blog post(以及后续帖子here 和here),它们很好地概述了GPU 的逻辑、低级操作过程。

但是,本质上(尝试直接回答您的问题),调用费用昂贵的原因不是有必须大量数据要传输,而是有大量工作主体超越只是通过总线传输数据,这些数据被推迟到命令缓冲区被刷新。

【讨论】:

【参考方案3】:

简短回答:驱动程序会缓冲部分或全部实际工作,直到您调用 draw。这将显示为在绘制调用中花费的相对可预测的时间量,具体取决于状态发生了多少变化。

这样做有几个原因:

避免做不必要的工作:如果您(不必要地)在绘制之前多次设置相同的状态,则可以避免每次发生这种情况时都做昂贵的工作。这实际上在大型代码库(例如生产游戏引擎)中相当普遍。 能够协调内部相互依赖的状态,而不是使用不完整的信息立即处理它们

替代答案:

驱动程序用于存储渲染命令的缓冲区已满,应用程序正在有效地等待 GPU 处理一些较早的工作。这通常会在一帧内的随机绘制调用中显示为非常大的时间块。 已达到允许驱动程序缓冲的帧数,应用正在等待 GPU 处理其中一帧。这通常会在一帧内的第一次绘制调用中或在前一帧结束时的 Present 上显示为大量时间阻塞。

【讨论】:

以上是关于为啥绘图调用很昂贵?的主要内容,如果未能解决你的问题,请参考以下文章

暂停渲染/绘图 CTreeCtrl (MFC)

为啥用 Matplotlib 绘图这么慢?

为啥我的绘图没有写入磁盘?

Matplotlib 为啥我点击后绘图功能会在按钮内绘制?

当我继承 UIView 时,为啥我不能在图像上绘图?

为啥这个简单的 R Shiny 输出不使用 ggplot 绘图?