600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Render Hell —— 史上最通俗易懂的GPU入门教程(三)

Render Hell —— 史上最通俗易懂的GPU入门教程(三)

时间:2020-09-21 14:40:37

相关推荐

Render Hell —— 史上最通俗易懂的GPU入门教程(三)

声明:文本非原创,只是翻译,原文链接如下:

https://simonschreibt.de/gat/renderhell-book3/

Render Hell – Book III

本文是 “Render Hell” 系列文章的第三篇。

欢迎来到第三篇!这里我们将检查一些在渲染过程中可能出现的问题。但首先,我们来点小练习:

知道一个问题是有好处的,而真正去感受一个问题则更有助于理解。所以让我们试着感觉自己像个 CPU / GPU 吧。

实验

请创建10000个小文件(例如每个文件1 KB),并将它们从一个硬盘复制到另一个硬盘。你会看到,即使数据总量只有 9.7MB 大小,也需要花很长的时间才能拷贝完成。

现在创建一个 9.7MB 大小的文件并以同样的方式复制它,你会发现进度条跑得飞快!

为什么?它们的数据总量都是一样的啊!

没错,但是对于每个复制操作都有一些额外的事情要做,例如:文件传输的准备工作、分配内存、读写磁头在硬盘中来回移动,等等,这些都是是每笔写操作的额外开销。正如你所感受到的痛苦,如果你复制很多小文件,那么这将是一笔庞大的开销。渲染许多网格(也就是执行许多命令)要比这复杂得多,但其实都类似。

现在让我们来看看在渲染过程中可能会遇到的最坏的情况。

最糟糕的情况

渲染过程中有太多的小网格这本身就不是什么好事,如果他们还使用不同的材质参数,那情况会变得更加糟糕。但是:为什么呢?

1. 大量的 Draw Call

通常 GPU 渲染这些小网格要比 CPU 发送命令快得多。我们的应用程序代码并不是把命令直接发送给 GPU 的,而是要经过图形 API、操作系统层和驱动程序才能给到 GPU 的,这些加起来就是 CPU 的开销。

“减少 Draw Call 的主要原因,是因为图形硬件变换和渲染三角形,要比你提交绘图命令快得多。如果每次调用只提交几个三角形,那么你将被彻底束缚在 CPU 上,而 GPU 则大部分时间都处于空闲状态,CPU 将无法快速地为 GPU 供给资源。“[f05]

此外,每次 Draw Call 都会产生多种开销(如上所述):

“每次 API 调用都会有驱动程序的开销,减少此开销的最佳方法就是尽可能少地调用API。”[a02]

如今控制台 API 或新的 API(DirectX12、Vulkan、Metal)所占的开销已经越来越小了。与提交小任务相比,提交大任务仍然是最好的选择,但是已经没有像过去那么糟糕了。

更多详细内容,请查阅:NVIDIA OpenGL Extension API 的性能优势

2. 大量的命令

这类开销的一个典型例子就是 CPU 与 GPU 之间的通信。你还记得 CPU 填充命令缓冲区,GPU 从中读取数据吗?是的,它们必须就缓冲区内容的变化而进行通信,这也会带来一定的开销(它们通过更新读/写指针来实现这一点 —— 点击此处相关信息)!

因此,驱动程序将多条命令批处理到一个被称之为推送缓冲区(push buffer)的 buffer 中,它并不会挨个挨个地提交命令,而是先把这个缓冲区填满,然后再将一整块命令交给 GPU。

当 CPU 正在填充新的命令缓冲区时,GPU 最好能做些什么事情(例如处理最后一块命令)。为了避免 GPU 必须等到下一个新的命令块准备就绪才开始工作,驱动程序会在命令块的大小上做文章,有时它们会塞进去超过一整帧的命令,直到真正开始提交任务。

您可以在显卡驱动程序的控制面板中找到填充该 buffer 的相关设置(“最大预渲染帧数”)。提交大量帧的缺点是,我们基本上是在“过去”的时间里渲染“未来”的帧,我们的 CPU 当前请求的帧已经有了最新的播放器数据源,但是我们的 GPU 渲染的却是一些过去的帧。这种增加的延迟可能对某些内容(虚拟现实VR …)不利。

现代图形 API 或控制台 API 还允许你同时填充多个命令缓冲区,驱动程序则将它们一个接一个地提交给 GPU 硬件(串行提交队列)。

DirectX 12 与 DirectX 11 的命令缓冲区的主要区别在于,采用并行构建命令缓冲区的这种方式,可以让它们更快的被驱动程序所提交。在 DirectX 11 中,驱动程序仍然需要对串行提交中的内容进行更多的跟踪,这降低了并行构建的性能。

到目前为止,我们只讨论了那些具有相同材质参数(渲染状态)的网格。如果我们想使用不同的材质来渲染网格,会发生什么样的情况呢?

3. 大量的网格和材质

刷新 Pipeline。

“当我们切换 Render State 时,有时需要执行全部或部分的 Pipeline 刷新(flush)动作。因此,修改着色程序或材质参数可能需要付出昂贵的代价[…]“[b01 第711/712页]

你以为没这么糟糕?那好,如果你使用不同的材质来渲染不同的网格,你就有可能会在 CPU 和 GPU 上引入额外的启动时间。你给第一个网格设置 Render State,然后命令 GPU 去渲染它,接着给下一个网格设置新的 Render State,再命令 GPU 渲染下一个网格,以此类推。

我将上图中 “Change State” 命令高亮为红色,是因为a)这些操作的代价是巨大的b)便于阅读

设置 Render State 有时会导致 Pipeline 的部分“刷新(flush)”动作(并不是所有的 Change State 都这样,这取决于要修改的参数和 GPU 上可用的硬件单元)。也就是说,在(使用新的 Render State)渲染新的网格之前,必须先完成当前某些硬件单元(使用当前的 Render State)正在处理的网格,就像上面图片演示的那样。与一次渲染大量顶点相比(例如,将相同渲染状态的多个网格组合在一起进行渲染 —— 我稍后会提到的一种优化方法),多次修改 Render State 却只渲染少量的顶点将是一件多么糟糕的事情,而原因相信大家都已经很清楚了。

如前所述,大多数状态切换的耗时都来自于 CPU 早期的图形 API。由于发起 Draw Call 的时间很短(与网格的复杂度无关,只取决于 API 和操作系统的执行时间),你可以认为渲染2个和200个三角形其实没有多大区别。GPU 的速度非常快,因此它渲染这些网格的时间,实际上要比 CPU 准备这些网格的时间还要快。

当我们讨论将几个小网格合并为一个大网格时,这个“规则”当然会发生变化(我们稍后就讨论这个问题)。

我无法获取到有关当前显卡上“免费”渲染多少个多边形的最新数据。如果你知道这方面数据,或者最近做过一些 benchmark 测试,请告诉我!

4. 网格和多材质

如果一个网格使用不止一种材质进行渲染,又会出现什么情况呢?基本上,你的网格会被大卸八块,然后一块一块地被送进命令缓冲区中。

当然,这种情况下会为每个网格块单独创建一个 Draw Call。

5. 单个图形命令处理器

如今,典型的 GPU 只有一个命令处理器/图形前端。这意味着,即使 CPU 以并行的方式提交命令块,所有与图形相关的命令在分发给 GPU 并行处理单元之前,都将以串行的方式被处理。更多关于 GPU 工作细节,请参阅 Book II 深入详解。

6. 瘦长的三角形(Thin Triangle)

光栅化过程也可能存在与性能相关的细节问题(这取决于硬件),我在 Book II “3.16 光栅化” 中已经对此梳理过了:

目前大多数图形硬件都是以2×2像素四边形为单位进行三角形渲染的(在 NVIDA 硬件上,是以8个这样的四边形为一组的,即一组32个线程)。

如果其中某些片元(fragment)没有覆盖到三角形,那么它们的输出将会被忽略掉。

你可以想象一下,为什么那些又瘦又长的三角形会对硬件很不利?因为这4个线程中只有一个线程可以计算像素。甚至到目前为止,对于非常烧钱的全屏后处理特效技术,我们都不是将它们当作两个三角形来渲染,而是当作一个被放大的巨型三角形来渲染,因此只有没有任何斜线穿过屏幕的区域才可见。

7. 多余的过度渲染(Useless Overdraw)

当我们渲染一个多边形时,如果它使用软件 Alpha 并且它的纹理大部分区域为100%透明,则很可能会浪费掉大量的性能。这种场景可能会发生在使用树枝或树叶作为纹理进行渲染的场景,或者使用一个全屏大小的四边形来做暗角效果(只会使图像的角落变暗)渲染的场景。

在 Book IV “解决方法” 中,将给出一种解决该问题的方法。

8. 移动平台 vs PC平台

许多移动设备能很好地处理颜色混合和抗锯齿,而更多的挑战则在于大量的几何模型。相比之下,桌面/控制台 GPU 则刚好相反。原因就在于移动平台的 GPU 使用片上存储器(on-die/on-chip memory,那些小缓存)作为中间 framebuffer(Xbox360 也有这个功能)。因此,它们可以快速地进行颜色混合,并在相对较低的性能损耗下进行抗锯齿操作。

然而在 FHD 分辨率下,要想把渲染所需的全部内存都装进芯片里,所需费用是极其昂贵的。因此移动端的 GPU 在渲染时并不是一次性地渲染一整帧,而是以一个tile(小块)为单位进行渲染的。一个场景每次只渲染一个 tile,当该 tile 被渲染完成时,就会将其从 tile cache 复制到最终的 FrameBuffer 中。这要比桌面端的 GPU 直接复制一整帧图像到 FrameBuffer 上更加省电。

而这种设计的缺点是 GPU 不得不多次处理同一几何图形,因为该几何图形有可能会与多个 tile 重叠,这也意味着大量的顶点会造成 GPU 性能的下降。

不过,该设计对于 UI 和文本渲染(带纹理的四边形)以及需要大量颜色混合的场景来说是非常有效的,这也正是移动设备的主要用途。

我希望在了解了关于过多的网格和材质会带来哪些坏处的问题之后,能够给你带来一些启发。接下来让我们来看看关于这些问题都有哪些解决方法,因为即使所有这些问题听起来都很糟糕,却仍然有许多漂亮的游戏横空出世,换句话说他们早就以某种方式解决了上述问题。

本篇到此结束。

继续阅读下一篇:解决方法,或返回目录索引

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。