2.2 CPU和GPU之间的通信

渲染流水线的起点是CPU,即应用阶段。应用阶段大致可分为下面3个阶段:

(1)把数据加载到显存中。

(2)设置渲染状态。

(3)调用Draw Call(在本章的最后我们还会继续讨论它)。

2.2.1 把数据加载到显存中

所有渲染所需的数据都需要从硬盘(Hard Disk Drive, HDD)中加载到系统内存(Random Access Memory, RAM)中。然后,网格和纹理等数据又被加载到显卡上的存储空间——显存(Video Random Access Memory, VRAM)中。这是因为,显卡对于显存的访问速度更快,而且大多数显卡对于RAM没有直接的访问权利。图2.3所示给出了这样一个例子。

▲图2.3 渲染所需的数据(两张纹理以及3个网格)从硬盘最终加载到显存中。在渲染时,GPU可以快速访问这些数据

需要注意的是,真实渲染中需要加载到显存中的数据往往比图1.3所示复杂许多。例如,顶点的位置信息、法线方向、顶点颜色、纹理坐标等。

当把数据加载到显存中后,RAM中的数据就可以移除了。但对于一些数据来说,CPU仍然需要访问它们(例如,我们希望CPU可以访问网格数据来进行碰撞检测),那么我们可能就不希望这些数据被移除,因为从硬盘加载到RAM的过程是十分耗时的。

在这之后,开发者还需要通过CPU来设置渲染状态,从而“指导”GPU如何进行渲染工作。

2.2.2 设置渲染状态

什么是渲染状态呢?一个通俗的解释就是,这些状态定义了场景中的网格是怎样被渲染的。例如,使用哪个顶点着色器(Vertex Shader)/片元着色器(Fragment Shader)、光源属性、材质等。如果我们没有更改渲染状态,那么所有的网格都将使用同一种渲染状态。图2.4显示了当使用同一种渲染状态时,渲染3个不同网格的结果。

▲图2.4 在同一状态下渲染3个网格。由于没有更改渲染状态,因此3个网格的外观看起来像是同一种材质的物体

在准备好上述所有工作后,CPU就需要调用一个渲染命令来告诉GPU:“嘿!老兄,我都帮你把数据准备好啦,你可以按照我的设置来开始渲染啦!”而这个渲染命令就是Draw Call。

2.2.3 调用Draw Call

相信接触过渲染优化的读者应该都听说过Draw Call。实际上,Draw Call就是一个命令,它的发起方是CPU,接收方是GPU。这个命令仅仅会指向一个需要被渲染的图元(primitives)列表,而不会再包含任何材质信息——这是因为我们已经在上一个阶段中完成了!图2.5形象化地阐释了这个过程。

▲图2.5 CPU通过调用Draw Call来告诉GPU开始迚行一个渲染过程。一个Draw Call会指向本次调用需要渲染的图元列表

当给定了一个Draw Call时,GPU就会根据渲染状态(例如材质、纹理、着色器等)和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的像素。而这个计算过程,就是我们下一节要讲的GPU流水线。