Impeller-Flutter的新渲染引擎

Impeller是什么?它本质上是怎样运行的?

Impeller是Flutter的新的渲染引擎,直到现在Flutter正在用一个叫做Skia的渲染引擎。

问题是Skia不是为了Flutter量身定做的。它有为范围广阔的设备构建的一大堆的渲染特性,这意味着它并不总是为了Flutter的需求优化。

进入到Impeller,Flutter的新渲染器。我们构建Impeller来专门专注在Flutter应用的渲染需要。

主要目标是为了消除你的应用中的卡顿和任何磕磕巴巴。

这样对于你的终端用户来说将总会感觉良好。

对于不是图形工程师的人来说,可能会困惑,渲染器是什么?

渲染器是一个软件,帮助你转变你的UI代码到你实际上在屏幕上看到的像素点。

在Flutter框架中,你的部件树被一个渲染对象的树来处理。

渲染对象包含如何来布局(Layout)和绘制(Paint)部件的指令。

这些指令被给到引擎,并且存储在一个简单命令的有序列表中,称为显示列表。

这是事情变得有趣的地方。

引擎负责使用可用的渲染器之一,Impeller或者是Skia,来绘制这个显示列表到一个表面纹理上(Surface Texture)。或者是一个网格的像素值,可以被显示在屏幕上。

它利用GPU通过设置一系列的事情,称为渲染管线。

可以被使用来渲染显示列表中的一切东西。现在来进入细节。

Tessellation(曲面细分)

在我们能够使用一个渲染管线,我们首先需要得到被显示列表绘制的所有的path。

tessellate(细分)它们到一些列的三角形中。

那么三角形上的每个(vertex)或者是(point)被传递到vertex shader中。

shader只是一小块的代码,在图形设备上运行。

shader是内部的,意味着它们是Flutter引擎代码库的一部分。

但是Flutter开发者可以创作自己的shader,这个晚一些提及。

Vertex Shader接收组成Flutter Logo的顶点,移动它们到我们实际上希望看到的屏幕位置。

现在Flutter Logo在正确的位置了,我们开始迭代所有的三角形。

现在拉近放大一些。对于每个三角形来说,我们弄清楚在它内部具体的像素。这被称为rasterization(光栅化)。接着对于这些方形的每一个,一些检查被运行,来决定我们是否真的需要为它计算出一个颜色值。

对于Impeller来说,这些检查是很重要的。

现在,所有在三角形中的这些像素被传递到一个fragment shader上。

fragment shader是另一个代码片段,但是这次它接收vertex shader的输出,并且计算一个颜色。

正如之前所提到的,Flutter开发者可以重写它通过创建自己的shader,来创建很整洁的特效,使用fragment program API。

无论如何,返回到我们的渲染管线上。

最后,输出颜色被与已经绘制的颜色blended(混合)。

在这个情况下,我们的混合是很简单的,只需要替换颜色。

这就是东西如何在GPU上渲染的。

这些渲染管线是如何在Impeller上实现的,

Impeller有一个层级的架构,每一层使用它下面的一层来完成它的工作。

Flutter部件如何转变到一个显示列表上的。

在这个例子中,显示列表仅仅包含一些操作来为它放置位置到屏幕的中心,除此之外,还有四个DrawPathOps。

Logo中的每一个四边形一个。我们拿到显示列表并且使用Impeller来绘制它。

首先,所有的显示列表操作被发送到一个叫做Aiks的东西那里。

Aiks是Impeller的最上层,主要由Canvas绘制API组成。

随便说下,Aiks是Skia的反过来。

Aiks的任务是接收高阶的命令,例如从显示列表中接收DrawPath和DrawImage,并且把它们转变成为更简单的,自包含的绘制操作称为entities。

下一层是Entities Framework。在我们的例子中,从Flutter logo中的四个DrawPathOps,每一个得到了一个自己的Entity。每个Entity包含每一个单独绘制命令需要的一堆属性,例如一个编码了平移,旋转和缩放的Transformation Matrix(变换矩阵)。

每个Entity也有一个Contents对象分派给它,它包含了绘制Entity所需要的实际的GPU指令。

有SolidColor,Images,Gradients,Text,Clips,一切Fluuter可以绘制的东西。

有时,我们甚至有多个特别的Contents使用不同的拥有不同性能特征的算法来绘制相同的东西。

这样Aiks可以挑选和选择最有效率的渲染算法。

在这样的情况下,Flutter的四个四边形仅仅被纯色(Solid Color)填充。

所以每个实体得到一个SolidColorContents指派到它们。

现在,我们需要详细说明这些指令,以一种GPU可以理解的方式。

因为Flutter APP可以运行在一堆不同的平台上,Impeller需要某种类型的转换层来与GPU交互。

这一层在Entities Framework下,叫做硬件抽象层(Hardware Abstraction Layer)。

这一层是一个薄的抽象,和图形驱动交互,通过各种标准的图形API,例如IOS上的Metal,Android上的Vulkan。

现在我们准备好了到底告诉GPU做什么。

记得那些渲染管线吗?

Vertex Shader  ->  Rasterazation -> Stencil Test -> Fragment Shader -> Blending

这是它们参与进来的地方。

每个Contents使用硬件抽象层来绘制自己通过指令GPU执行渲染管线,包含我们实际想用的shaders。

那些命令通过图形API被执行,接着结果纹理显示到了屏幕上。

但是,还有一个巨大的细节没有提及。那些Shader和渲染管线也需要编译成GPU可以执行的指令。这个过程非常昂贵。

在SKia中,这个编译过程在运行时发生。正好在这一帧上,管线实际上需要被使用来渲染一些事情。这通常导致这一帧远远超过预算(Go Way Over Budget),导致一个明显的卡顿。

我们通常提到这个问题成称为Shader编译卡顿(shader compllation jank)。Impeller很大的改善了这个问题,通过提前执行最昂贵的部分。

这是Impeller的最后一个组件出现。

当Flutter引擎被构建时,所有Impeller的Shader被编译成捆,使用Impeller的离线Shader编译器,称为(Impeller Scene)。

在走过Impeller架构中,你已经看到Flutter对于它如何渲染图形有完全的控制。Flutter不受平台专用的(platform-specific)UI工具包的规定支配,因为Flutter甚至不使用它们。取而代之的是,Flutter引擎直接与图形驱动交互。这允许你构建丰富独特的应用表现得像你在任何的平台上想要的那样。缺少依赖也使得Flutter对于嵌入式的用例,例如汽车仪表板,Iot,数字标牌是一个好的解决方案。

现在你有了一个本质上发生的事情的认识,让我来强调做出一些关键的架构决策,它们使得Impeller对于Flutter应用是一个好的渲染器。一个Impeller提供的巨大的好处是通过编译那些称为Shader 的程序来减少卡顿。为了做这件事情,Impeller并不和Skia一样生成Shaders。取而代之的是,Impeller有一套提前编译的手写的Shaders。

但是,你可以担心初始化一堆预编译的Shaders可能意味着更慢的启动时间,或者是更大的Flutter应用大小。为了避免这个,Impeller使用选择的渲染技术,相比较于Skia动态生成的许多专用的Shader来说,利用一个更小更简单的Shader集合。

另外一个原因为什么Impeller提供了优秀的性能是实现抗锯齿的方式。抗锯齿是消除绘制元素-锯齿边的艺术,所以它们看上去更加的自然。Impeller使用MSAA来执行这个工作,它由Flutter支持的所有的设备上,它执行的成本低,在现代的移动设备上产生出优秀质量的结果。

另一个我们专注于的事情是Impeller如何实现剪裁(Clipping)。当渲染部件时,Flutter频繁的使用剪贴蒙板(clip masks)来剪下形状,所以这个步骤的效率很重要。Impeller特别得采用一个步骤利用硬件的特性来确保剪裁非常的快速。记得之前提到的特殊检查吗?一个称为是模板测试(Stencil Buffer)。现在我们不进入太多的细节中,但是基本上的,当Impeller为剪裁绘制像素时,它告诉GPU检查模板(stencil),过滤掉所有不是填充区域部分的所有的像素。这意味着即使一个裁剪非常的复杂,像这个使用来创建这些万花筒动画,它仍然是一个开销便宜的操作。所以我们可以看到一些非常巨大的性能提升。在左边是Flutter的Impeller之前,每秒7帧渲染。在右边Impeller以每秒60帧渲染。

我们已经谈到了Impeller的一些设计选择,对于大部分的Flutter开发者来说,这意味着你将看到更多可预测的帧数当你使用Flutter时。但是对于那些感兴趣更加高级的渲染用例的人,Impeller也旨在提供一个发射台来解决其它的图形需要。展示了一个3D场景图,直接在Impeller的硬件抽象层之上,称为Impeller Scene。Impeller Scene有自己的Contents在Entity Framework上。它绘制3D对象使用相同种类的渲染管线。

要点:

Buffer Management        Skeleton Animation     Ray Casting

Deferred Rendering       Ray  Tracing     Anti Aliasing       PBR

Signed Distance Fields       Reflection Probes      Post  Processing

Rasterization                    HDR                                  Shadows

现在,开始构建渲染器有很多重大的决定。尽管我们可以构建3D渲染器,有用并且做出聪明的权衡,没有一个渲染器可以完美的解决每一个用例。所以,下一步我们积极地进行实验。所以Flutter社群中的任何人可以构建自己的渲染包,并且创建自定义的渲染管线,并且无缝地和剩余的组件系统集成。

你可能感兴趣的:(跨端渲染,flutter,前端)