Filament 渲染引擎剖析 之 多线程渲染 2

Filament 渲染一帧流程

Filament 是一款针对多核硬件系统开发的并行渲染引擎,内部通过多线程的合理调度,尤其在android移动平台上针对大小核心架构做了很多优化设计,比如通过设置线程亲和力,大核心执行繁重的渲染任务,每个小核心上执行一个工作线程对场景对象做排序、剔除等工作,这一模型极大的提高了系统的渲染效率。从多线程渲染架构来讲, Filament 多线程的组织方式还是比较先进的,尤其使用了多线程 + forward claster rendering 渲染方案,支持数量庞大的光源。

Filament 渲染引擎剖析 之 多线程渲染 2_第1张图片

 

上图描述了使用Filament 渲染引擎绘制场景时,绘制一帧的大概流程,渲染工作是由渲染器的render方法来完成, 详细说明的代码如下:

 if (renderer->beginFrame(window->getSwapChain())) {
            for (filament::View* offscreenView : mOffscreenViews) {
                renderer->render(offscreenView);
            }
            for (auto const& view : window->mViews) {
                renderer->render(view->getView());
            }
            renderer->endFrame();

 

Filament 命令绘制模型

Filament 为支持高效的多线程并发/并行渲染,场景图并未按传统的层次结构来组织,而是将场景对象的组织扁平化,用一维数组的形式来管理, transform 组件以链表的形式表达父子变换空间关系,这一数据结构切断了父子节点间的空间遍历、依赖关系,为场景图的并行渲染提供了可能,也为支持vulkan , openGL,matel驱动提供了通用的框架, 场景节点的表示如下图:

 

Filament 渲染引擎剖析 之 多线程渲染 2_第2张图片 Filament 场景节点组织形式

 

Filament 对场景的渲染主要分为两个阶段:

  1. 对场景节点进行划分,以“分治”策略创建多个工作Job实例添加到JobSystem 作业队列中,每一次对图形API的调用都会生成相应的渲染命令添加到 commandBuffer 缓冲区中;

  2. 设备线程通过引擎flush接口被唤醒,从命令队列commandBuffer中取出命令逐个执行,这一步是真正调用图形API绘制的过程。

Filament 多线程渲染架构

Filament 多线程架构类似于Java 的Doug Lea在JDK7中引入了的Fork/Join框架, 它是一种基于"分治"思想的计算框架。Filament的工作线程会对自己的任务按照一定的粒度进行拆分,一个大任务拆分成多个子任务之后,子任务放入工作队列中等待执行。任务的划分是以递归深度和节点个数为阀值,当一个线程的工作队列为空时,该线程如果被唤醒,它可以从其它线程的工作队列中steal任务Job执行, 采用了工作窃取算法"work-stealing"来组织渲染任务的分配 。Filament 线程创建、管理、调度由JobSystem类负责。

  • JobSystem 线程管理器的主要工作原理:

系统启动时,如果用户没有配置线程运行环境,作业系统会根据CPU硬件的核心个数创建多个工作者线程,并设置线程的亲和力,这些任务会被设置到CPU小核心上运行,其中系统主线程被添加到作业系统线程池的队尾,成为"协程",系统中有绘制任务Job 时,首先添加到系统主线程的工作队列中,主线程也负责执行渲染工作任务Job,各个工作者线程空闲时会“窃取” 系统主线程工作队列中的任务Job去执行。JobSystem 提供一系列的线程及任务管理接口来管理作业,并对线程调用提供同步操作,比如runAndWait 接口,只有执行完当前所有的任务及其子任务时,线程才会从阻塞状态退出。

  1. JobSystem的每个工作线程都维护着一个双端工作队列(WorkQueue)。

  2. 每个工作线程在运行中添加先的作业任务时,会放入工作队列的队尾,并且工作线程在处理自己的工作队列时,使用的是"LIFO"方式,也就是说每次从队尾取出任务来执行。

  3. 每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务,窃取的任务位于其他线程的工作队列的队首,也就是说工作线程在窃取其他工作线程的任务时,使用的是 FIFO 方式。

  4. 在既没有自己的Job,又没有可以窃取的Job时,作业线程进入休眠。

Filament 渲染引擎剖析 之 多线程渲染 2_第3张图片 work-stealing 

 

  • Filament 的三个主要线程组:

Filament 在运行时,根据线程功能及所处环境,这里抽象成三个主要线程组,分别为 系统主线程, 多个作业线程, 驱动线程。

  1. 系统主线程功能是创建作业任务并将其追加到自己的作业队列,执行作业任务;

  2. 作业线程 ,作业线程可以有零个或多个,如果硬件CPU为单核就不会创建作业线程,作业线程从系统主线程或其它线程中"窃取"渲染作业任务Job并执行。这里有一个很有意思的情况,工作者线程切取任务来执行时并不是直接从系统主线程任务队列中窃取,而是使用随机算法从各个线程任务队列中窃取,但JobSystem 添加任务时总是将任务追加到主线程工作队列里。

  3. 驱动线程,如果系统支持多线程渲染, 在FEngine 初始化时引擎会创建一个驱动线程用来单独执行绘制任务,如果是移动设备,该线程会在大核上运行来执行繁重的渲染命令来绘制实体。 

Filament 渲染引擎剖析 之 多线程渲染 2_第4张图片 Filament 多线程作业方式

 

总之,Filament 执行渲染命令并不是像传统渲染方式那样直接调用硬件图形API,而是以命令对象的方式record, 生成一个命令队列,每个命令对象记录渲染所需硬件资源及GPU端 API 调用过程(也就是绘制函数),当这些命令准备就绪后 , Filament  驱动线程被唤醒,逐个执行渲染命令(也就是调用绘制函数)。 这种设计模式有其自身的好处,使硬件驱动层与应用层的隔离,而且增加了系统底层API可扩展性,缺点是实现复杂。 Filament 使用了自己的内存池,每个类对象(包括命令对象)的内存分配都要考虑字节对齐和精打细算。

你可能感兴趣的:(Filament)