为什么渲染线程能够提升渲染效率?

独立渲染线程和图形API线程

独立的渲染线程是指执行准备渲染数据、提交渲染指令过程的线程。这一过程相比起逻辑线程,通常执行时间较长。使用独立的线程可以提高并行度,减少GPU的等待。

此外,为了减轻渲染线程的压力,也会考虑将提交渲染指令这一过程从渲染线程中分离出来,放入单独的图形API线程中。该线程根据渲染线程准备的数据,调用图形API。图形API线程与渲染线程的交互类似于渲染线程和逻辑线程的交互,因此后文将以逻辑线程和渲染线程的交互为例进行介绍。

为什么渲染线程能够提升渲染效率

传统的单线程架构中,我们会在一帧内完成逻辑更新以及渲染绘制。这意味着,当我们在执行逻辑更新时,GPU将进入等待状态。当GPU有机会处于空闲状态时,说明我们没有完全榨干GPU的性能,这带来了资源的浪费。

而将任务并行化后,渲染线程将一直处于活跃状态,此时GPU等待的概率降低,渲染效率得到提升。

另一方面,对于多核CPU硬件,当我们在执行逻辑更新时,有些CPU可能也处于空闲状态,此时并行地执行渲染任务,也能提升CPU的利用率。

概括来说,就是让CPU和GPU时刻都处于高速运转的状态。

逻辑线程和渲染线程

逻辑线程和渲染线程并不是完全独立的,它们存在一定的依赖关系:

(1)渲染线程需要接收来自逻辑线程的指令和数据并执行

(2)逻辑线程有时需要阻塞等待渲染线程的完成

数据访问

在数据访问上,参考线程竞争章节,一般会遵循唯一的访问所有权,并维护拷贝数据或双队列结构。

(1) 双队列结构

其中,双队列结构意味着当逻辑线程往队列A写入的时候,渲染线程读取队列B的内容,等逻辑线程完成了队列A的写入后,交换两个队列。也就是渲染线程读取队列A的内容,而逻辑线程写入队列B。

这样的数据结构可以确保两个线程不会同时访问同一队列,但是,这也意味着两者必须有严格的先后执行顺序。

(2) 拷贝数据

逻辑线程的数据提交到渲染线程时,渲染线程会维护一份独立的数据拷贝。在牺牲一部分空间的情况下,避免数据的竞争。并且该方法对先后执行顺序没有过多限制。

需要注意的情况是指针的拷贝。我们应该尽可能避免指针的浅拷贝,而是直接缓存对应的数据,或者为指针添加引用计数。除非我们能够确保逻辑线程不会直接对指针做修改或销毁的操作,或者确保正确的先后执行顺序。

渲染线程资源访问权

在渲染线程架构里,数据的传输绝大部分都是单向的,也就是只应该从逻辑线程传往渲染线程。当一个数据提交到渲染线程,我们就认为它应该归渲染线程管理,如果想要访问或者修改渲染数据,应该请求渲染线程执行这一操作。

数据的生命周期

在渲染提交过程中,存在两部分数据:

(1)跨帧存储的数据。主要是场景对象数据,包括几何体、灯光等。

(2)每帧的上下文数据。比如当前相机、投影矩阵,渲染状态等。这类数据要么是每帧计算得到的,要么是每次提交指令时重新构造的非缓存状态。

在设计渲染线程的时候,应该合理管理并区分这两种不同生命周期的数据类型。

逻辑线程和渲染线程同步

逻辑线程向渲染线程通过添加指令的方式进行数据和逻辑的交互,命令队列通常由环形队列进行维护。

我们往往用类来封装每个命令,并且将类的结构以字节码(Buffer)的形式进行管道数据传输。

请求分为不需要返回值/同步和需要返回值/同步两种情况。对于前者,通常适用于添加灯光/几何体等简单的请求指令;对于后者,我们可能需要返回值,比如,我们在逻辑层请求渲染线程对当前画面进行拍摄,并能在逻辑线程读取这张快照。

返回值的读取分为同步和异步两种。同步意味着我们将堵塞等待,异步意味着我们将设置一个同步点,当渲染线程完成当前命令后,发起异步回调。

本文摘自:CSDN博主「ZJU_fish1996」的原创文章《[引擎开发] 渲染架构与高级图形编程》,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZJU_fish1996/article/details/112847781

最新文章