前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RenderingNG中关键数据结构及其角色

RenderingNG中关键数据结构及其角色

作者头像
前端柒八九
发布2022-08-25 14:40:54
2K0
发布2022-08-25 14:40:54
举报
文章被收录于专栏:柒八九技术收纳盒

前言

大家好,我是柒八九。今天这篇文章是Chromium最新「渲染架构」 RenderingNG的译文系列文章的「第二篇」 -- 在RenderingNG渲染过程中关键数据结构和它们所担当的角色。

针对RenderingNG的介绍可以参考之前的文章。

简明扼要

  1. 「帧树Frame Tree」: 由「本地」「远程」节点组成
  2. 每个渲染进程都有「属于自己的」对网页内容进行描述的frame
  3. 一个渲染在不同进程的frame被称为「远程帧」
  4. 「渲染管线」rendering pipeline是以「本地帧树片段」local frame tree fragment的粒度来操作的
  5. 「设备比例因子」device scale factor和「视口大小」viewport size这样的「视觉属性」会影响到渲染输出,并且「必须在本地帧树片段之间同步」
  6. 「不可变的片段树」是渲染管道的「布局阶段」的输出 它表示页面上所有元素的位置和大小 每个「片段」fragment代表一个DOM元素的一部分
  7. 内联片段信息列表中的每个条目都是一个存有(「对象,后代数量」)等特定信息的「元组」Tuple
  8. 「属性树」是解释「视觉和滚动效果」如何应用于DOM元素的数据结构
  9. 每个Web文档都有四个「独立的属性树」「变换」Transform、「剪切」clip、「视觉效果」effect和「滚动」Scroll
  10. 显示list中的显示项包含「低级别」的绘图命令,可以用Skia进行光栅化
  11. 显示项大致对应于CSS绘制顺序规范的「原子步骤」
  12. 绘画块的有序列表,即显示项目组和属性树状态,作为渲染管道「图层化」Layerize步骤的输入数据
  13. 合成器帧是RenderingNG表示如何将栅格化的内容「拼接在一起」,并使用GPU有效地绘制它的数据格式
  14. 视口被划分为「瓦片」Tile>
  15. 「Quad」描述纹理的输入信息,并指出如何对其进行「转换」「应用视觉效果」 「GPU纹理瓦片」是一种特殊的Quad,它只是一类纹理瓦片的别称
  16. 每个GPU纹理瓦片都有一个「quad」

文章概要

  1. 帧树Frame Tree
  2. 不可变的片段树The immutable fragment tree
  3. 属性树Property trees
  4. 显示列表和绘画块Display lists and paint chunke
  5. 合成器帧Compositor frame:表面surface、渲染表面render surface、GPU 纹理瓦片GPU texture tile

前置知识简讲

在渲染流程中出现了大致「五种」比较重要的数据结构。

  1. 帧树Frame Tree: 由「本地」「远程」节点组成,表示对应的文档信息应该被哪个渲染进程中的Blink渲染器所消费
  2. 不可变的片段树Immutable Fragment Tree:代表「布局阶段」的信息产生
  3. 属性树Property Tree:代表了针对文档进行转换transform、剪切clip、视觉效果effect和滚动scroll等操作后的数据格式,并为后续的渲染流程所使用。
  4. 显示列表和绘画块Display lists and paint chunks: 将被传入到「合成线程」中,并被光栅化和分层算法所消费
  5. 合成器帧Compositor frame:将渲染接口和GPU纹理瓦片封装到一起,并使用「GPU」进行绘制

我们通过一个例子,来解释刚才所说的数据结构。大致的文档结构如下:

代码语言:javascript
复制
// 主 frame 为foo.com
<html>
  <div style="overflow: hidden; 
              width: 100px; 
              height: 100px;">
    // 子 frame (foo.com/etc)
    <iframe 
      style="filter: blur(3px);
            transform: rotateZ(1deg);
            width: 100px; 
            height: 300px"
      id="one" 
      src="foo.com/etc"></iframe>
  </div>
  // 子 frame (bar.com)
  <iframe 
      style="top:200px;
             transform: scale(1.1);
             translateX(200px)"
      id="two" 
      src="bar.com"></iframe>
</html>

1. 帧树Frame Tree

Chrome 有时候会选择一个与「父框架」不同的渲染进程来处理跨域框架cross-origin frame。

在上面的提供文档结构中,一共出现了「3个框架结构」

在站点隔离Site Isolation机制的作用下,Chromium将会启用两个渲染进程来渲染该页面结构。

❝每个渲染进程都有「属于自己的」对网页内容进行描述的frame树 ❞

❝一个渲染在不同进程的frame被称为远程帧Remote Frame ❞

「远程帧」在被引用的渲染进程像「占位符」一样,仅仅保存了用于标识该frame「最基础」信息,例如:尺寸信息等。也就是说,远程帧中不包含对应帧在渲染过程中需要任何有用信息。

与之相反,本地帧Local Frame包含了对应frame「所有数据」(DOM树和样式数据)转化为可以渲染和显示的东西所需的所有信息。(这里有点绕口)

❝渲染管线rendering pipeline是以本地帧树片段local frame tree fragment的粒度来操作的 ❞

假如存在如下的文档结构:

代码语言:javascript
复制
// 主 frame 为foo.com
<html>
  // 子 frame (bar.com)
  <iframe src="bar.com">
      // 子 frame (foo.com/etc)
      <iframe src="foo.com/etc"></iframe>
  </iframe>
</html>

foo.com作为主frame, bar.com作为子frame,而foo.com/etc作为bar.com的子frame

尽管,现在也和最上面的示例一样,也存在两个渲染进程,但是此时存在三个 「局部frame树片段」,两个存在于与foo.com所对应的渲染进程中,另外一个位于与bar.com所对应的渲染进程中。

为了将多个「本地帧树」合成一个「合成器帧」, Viz会同时从三个本地帧的「根节点」请求对应的合成器帧,随后将其聚合到一起。

虽然,主帧foo.com和子帧foo.com/other-page位于同一个帧树上,并且同一个「渲染进程」中处理他们的渲染过程,但是,它们位于不同的局部frame树片段local frame tree fragments所以存在「不同」的文档生命周期document lifecycles。由于这个原因,不可能在一次更新中为两者生成一个合成器帧。渲染过程没有足够的信息来将foo.com/etc生成的合成器帧直接合成到foo.com主帧的合成器帧中。例如,在foo.com进程外的bar.com可能通过CSS或者其他方式改变foo.com/ect对应的显隐。

视觉属性更新步骤

❝像设备比例因子device scale factor和视口大小viewport size这样的「视觉属性」会影响到渲染输出,并且「必须在本地帧树片段之间同步」。 ❞

每个本地框架树片段的根部都有一个与之相关的widget对象。视觉属性的更新先到主frame的部件,然后再从上到下传播到其余部件。

当视口大小改变时

这个过程「不是即时」的,所以复制的视觉属性也包括一个同步令牌sync token。Viz合成器使用这个「同步令牌」来等待「所有」本地frame树片段提交一个具有当前同步令牌的合成器帧。这个过程避免了混合具有不同视觉属性的合成器frame。


2. 不可变的片段树The immutable fragment tree

「不可变的片段树」是渲染管道的「布局阶段」的输出 它表示页面上所有元素的位置和大小 ❞

「每个片段fragment代表一个DOM元素的一部分」

通常情况下,每个元素只有一个片段,但如果在渲染管道中绘制Paint阶段被分割Split到不同的页面,则会有更多的片段。

在布局之后,每个片段都变得不可改变Immutable,不再被改变。

还设置了一些额外的限制。

  • 一个孩子节点不能有指向其父辈的指针
  • 数据是单向的(某个节点只能访问其子节点的数据信息,而不能从父级获取)

这些限制使我们能够在随后的布局中「重新使用」一个片段。

大多数「布局」都是典型的增量更新incremental updates,例如,一个网络应用在用户点击某个元素时更新一小部分用户界面。理想情况下,「布局」应该只做与屏幕上「实际改变的内容」相对应的工作。我们可以通过尽可能多地「重复使用」以前的树的部分来实现这一点。

内联Lnline片段信息

「内联内容」使用一个稍微不同的表示方法。我们使用一个扁平化flat的「列表」来表示内联内容。主要的「好处」是,内联内容的扁平化列表表示是快速的,对检查或查询内联数据结构很有用,而且「缓存效率高」

「扁平化的列表」是按照其内联布局子树的深度优先搜索depth-first search的顺序为每个内联格式化上下文lnline formatting context创建的。

❝列表中的每个条目都是一个存有(「对象,后代数量」)等特定信息的元组Tuple。 ❞

例如,考虑这个DOM。

代码语言:javascript
复制
<div style="width: 0;">
  <span style="color: blue; position: relative;">北宸</span> 
  <b>南蓁</b>
  .
</div>

「宽度属性被设置为0,以便在 "北宸 "和 "南蓁"之间进行换行」。从而形成两个「Line Box」

这种情况的内联格式化上下文被表示为一棵树时,它看起来像下面这样。

代码语言:javascript
复制
{
  "Line box": {
    "Box <span>": {
      "Text": "北宸"
    }
  },
  "Line box": {
    "Box <b>": {
      "Text": "南蓁"
    }
  },
  {
    "Text": "."
  }
}

对应的扁平list 如下:「每个条目都是(对象,后代数量)的元组信息」

  • (Line box, 2)
  • (Box <span>, 1)
  • (Text "北宸", 0)
  • (Line box, 3)
  • (Box <b>, 1)
  • (Text "南蓁", 0)
  • (Text ".", 0)

这个数据结构有「很多消费者」:可访问性API和几何API,如getClientRects,和contenteditable。每个消费者都有不同的要求。这些组件通过一个游标cursor访问扁平化数据结构。

游标有MoveToNext, MoveToNextLine, CursorForChildren等API。

3. 属性树Property trees

❝众所周知,「DOM」是一棵由元素(加上文本节点)组成的树,而CSS可以对元素应用各种样式 ❞

属性对应四种类型的效果处理:

  • 布局Layout:作为布局阶段的数据输入
  • 绘制Paint:如何绘制和栅格化当前元素
  • 视觉处理Visual:将变换transforms、过滤filters和剪切clipping等产生的效果应用于DOM 子树
  • 滚动Scrolling:包含子树的轴对齐和圆角剪切和滚动

「属性树」是解释「视觉和滚动效果」如何应用于DOM元素的数据结构 ❞

它们提供了回答问题的方法,例如:一个给定布局尺寸和位置的DOM元素,它应该被放置在相对于屏幕的哪个位置?以及:应该使用什么顺序的GPU操作来应用视觉和滚动效果?

网站中的「视觉效果」「滚动效果」在它们的全貌中是非常复杂的。因此,属性树所做的最重要的事情是「将这种复杂性转化为一个单一的数据结构」,精确地表示它们的结构和意义,同时去除DOM和CSS的其余复杂性。

例如:

  • 将潜在的容易出错的几何图形和其他计算可以集中到一个地方
  • 将建立和更新属性树的繁琐操作隔离到一个渲染管道中
  • 与完整的「DOM状态」相比,将属性树发送到不同的线程和进程中要容易得多,也快得多
  • 更能合理利用缓存机制

RenderingNG将属性树用于很多目的。

  • 将合成与绘制分开,将合成与主线程分开
  • 确定一个「最佳」的合成/绘制策略
  • 避免为「屏幕外」元素和GPU纹理工作
  • 有效而准确地使绘制和光栅失效
  • 测量Core Web Vitals中的布局偏移和最大内容的绘制

❝每个Web文档都有四个「独立的属性树」:变换Transform、剪切clip、视觉效果effect和滚动Scroll ❞

  • 「变换树」表示CSS变换和滚动
  • 「剪切树」表示表示溢出剪切
  • 「视觉效果树」表示所有其他的视觉效果:不透明度opacity、过滤器filters、遮罩masks、混合模式blend modes
  • 「滚动树」表示关于滚动的信息

❝属性树中的「每个节点代表一个DOM元素应用的滚动或视觉效果」

如果它恰好有「多种效果」,那么对于同一个元素,每棵树上可能有「不止一个属性树节点」

每个属性树的拓扑结构topology就像DOM树一样,分散排布。例如,如果有三个DOM元素有溢出剪切overflow clip,那么将有「三个剪切树节点」,剪切树的结构将遵循溢出剪切之间的「包含块关系」

❝每个DOM元素都有一个「属性树状态属性」,它是一个「4元组」transform, clip, effect, scroll),表示该元素的「最近的祖先」如何剪切、变换和效果该元素节点。 ❞

这非常方便,因为有了这些信息,我们就能准确地知道适用于该元素的剪切、变换和效果的「列表」,以及它们的「顺序」。这告诉我们它在屏幕上的位置以及如何绘制它。

示例

代码语言:javascript
复制
// 主 frame 为foo.com
<html>
  <div style="overflow: scroll; 
              width: 100px; 
              height: 100px;">
    // 子 frame (foo.com/etc)
    <iframe 
      style="filter: blur(3px);
            transform: rotateZ(1deg);
            width: 100px; 
            height: 300px"
      id="one" 
      src="foo.com/etc"></iframe>
  </div>
  // 子 frame (bar.com)
  <iframe 
      style="top:200px;
             transform: scale(1.1);
             translateX(200px)"
      id="two" 
      src="bar.com"></iframe>
</html>

这里根据一些属性生成了「四类属性树」

4. 显示列表和绘画块Display lists and paint chunke

❝一个显示项包含低级别的绘图命令,可以用Skia进行光栅化 ❞

显示项通常「很简单」,只有几个绘画命令,比如画一个边框或背景。「绘画操作」在布局树和相关片段上按照CSS顺序进行「迭代」,产生一个显示项列表。

例如:

代码语言:javascript
复制
<div id="green" 
     style="background:green; 
            width:80px;
            height:18px"
     >
    Hello world
</div>
<div id="blue" 
     style="width:100px;
            height:100px; 
            background:blue;
            position:absolute;
            top:0; 
            left:0; 
            z-index:-1;"
  />

这个HTML和CSS将产生以下「显示列表」,其中每项是一个显示项目。(从上到下依次排列)

  1. 绘制视图view背景 :drawRect命令绘制大小为800x600(视图大小),颜色为白色的区块
  2. 绘制#「blue」 背景: drawRect命令在「以视图为参照物」的位置为(0,0)处绘制大小为100x100,颜色为「蓝色」的区块
  3. 绘制#「green」 背景:drawRect命令在「以视图为参照物」的位置为(8,8)处绘制大小为80x18,颜色为「绿色」的区块
  4. 处理#「green」 行内文本:drawTextBlob命令在(8,8)处绘制Hello world文本信息

在上面的例子中,绿色 div 在 「DOM 顺序」中位于蓝色 div 之前,但 「CSS 绘制顺序」要求负 z-index 的蓝色 div 在绿色 div 之前绘制。

❝显示项大致对应于CSS绘制顺序规范的「原子步骤」

「一个DOM元素可能导致多个显示项」,例如#green有一个背景显示项和另一个内联文本显示项。这种粒度对于表现CSS绘画顺序规范的复杂性是很重要的,例如由负边距产生的交错。

代码语言:javascript
复制
<div id="green" 
     style="background:green; 
            width:80px;
            height:18px;">
    Hello world
</div>
<div id="gray" 
     style="width:35px; 
            height:20px;
            background:gray;
            margin-top:-10px;">
</div>

这个HTML和CSS将产生以下「显示列表」,其中每项是一个显示项目。(从上到下依次排列)

  1. 绘制视图view背景 :drawRect命令绘制大小为800x600,颜色为白色的区块
  2. 绘制#「green」 背景:drawRect命令在「以视图为参照物」的位置为(8,8)处绘制大小为80x18,颜色为「绿色」的区块
  3. 绘制#「gray」 背景:drawRect命令在「以视图为参照物」的位置为(8,16)处绘制大小为35x20,颜色为「灰色」的区块
  4. 处理#「green」 行内文本:drawTextBlob命令在(8,8)处绘制Hello world文本信息

「显示项目列表可以被后续更新复用」。如果一个「布局对象」在绘制树的过程中没有改变,它的显示项目就会从「以前的」列表中复制出来。

有一个针对层叠上下文Stacking Context的优化:如果在一个层叠上下文中没有布局对象的变更,那么绘制游标会「直接」跳过该上下文,并且从「之前的」显示列表中复制整个显示序列。

❝当前的属性树状态在绘制过程中被保持,显示项目列表被「划分为」拥有「相同属性树状态」的显示项目块Chunk。 ❞

代码语言:javascript
复制
<div id="scroll" 
     style="background:pink; 
            width:100px;
            height:100px; 
            overflow:scroll;
            position:absolute; 
            top:0; 
            left:0;">
    Hello world
    <div id="orange" 
         style="width:75px; 
                height:200px;
                background:orange; 
                transform:rotateZ(25deg);">
        I'm falling
    </div>
</div>

这个HTML和CSS将产生以下「显示列表」,其中每项是一个显示项目。(从上到下依次排列)

  1. 绘制视图view背景 :drawRect命令绘制大小为800x600,颜色为白色的区块
  2. 绘制#「scrolll」 背景:drawRect命令在「以视图为参照物」的位置为(0,0)处绘制大小为100x100,颜色为「粉色」的区块
  3. 绘制#「scroll」 行内文本:drawTextBlob命令在(0,0)处绘制Hello world文本信息
  4. 处理#「orange」 背景:drawRect命令在「以视图为参照物」的位置为(0,0)处绘制大小为75x200,颜色为「橘色」的区块
  5. 绘制#「orange」 行内文本:drawTextBlob命令在(0,0)处绘制I'm falling文本信息

属性树和绘制块关系如下:

❝绘画块的有序列表,即显示项目组和属性树状态,作为「渲染管道」图层化Layerize步骤的输入数据 ❞

整个「绘制块列表」可以合并成一个合成层并一起栅格化,但这需要在用户每次滚动时进行昂贵的栅格化操作。作为「优化处理」,可以为每个「绘制块」创建一个合成层并「单独」光栅化,以避免所有的重新光栅化,但这将很快耗尽GPU内存。

所以,图层化步骤必须在「GPU内存」「减少事物变化时的成本」之间做出权衡。一个好的方法是「默认合并图块」,也就是「不对具有属性树状态的绘制块进行合并处理」,这些属性树状态可能会在「合成器线程」上发生变化,比如合成器线程的滚动或合成器线程的变换动画。

前面的例子最好能产生两个合成的图层。

  • 一个800x600的合成层(默认图块合并)
    • drawRect命令绘制尺寸为800x600,颜色为白色的图块
    • drawRect命令绘制位于相对于视图(0,0)位置,尺寸为100x100,且颜色为粉色的图块
  • 一个144x244的合成层 (拥有属性树的图块)
    • drawTextBlob命令在(0,0)位置,绘制Hello world文本信息
    • 平移(0,18)
    • 围绕Z轴旋转顺时针旋转25度
    • drawRect命令绘制位于相对于视图(0,0)位置,尺寸为75x200,且颜色为橘色的图块
    • drawTextBlob命令在(0,0)位置,绘制I'm falling文本信息

如果用户滚动#「scroll」,第二个合成层会被移动,但不需要栅格化。

5. 合成器帧Compositor frame:表面surface、渲染表面render surface、GPU 纹理瓦片GPU texture tile

Chromium 最新渲染引擎--RenderingNG最后的示例中,我们得知,浏览器和渲染进程管理内容的「光栅化」,然后将「合成器帧」提交给Viz进程以呈现给屏幕。

❝合成器帧是RenderingNG表示如何将栅格化的内容「拼接」在一起,并使用GPU有效地绘制它的数据格式 ❞

瓦片Tile

理论上,渲染进程或浏览器进程中的合成器compositor可以「将像素栅格化为渲染器视口的单一纹理」,并将该纹理提交给Viz。为了显示它,显示合成器只需将单个纹理中的像素复制到「帧缓冲区」的适当位置(例如,屏幕)。然而,如果该合成器想要「更新哪怕是一个像素」,它就需要对「整个视口」进行重新光栅化处理,并向Viz提交一个新的纹理。

相反,「视口被划分为瓦片Tile」

❝一个「单独」的GPU纹理瓦片为每个瓦片提供了视口部分的光栅化像素 ❞

然后,渲染器可以更新单个瓦片,甚至只是改变现有瓦片在屏幕上的位置。例如,当滚动一个网站时,现有瓦片的位置会向上移动,只是需要为更远的页面内容栅格化一个新瓦片。

上面的图片有四张「瓦片」。当滚动发生时,「第五块」瓦片开始出现。


Quad and surfacesQuad and Surfaces

「GPU纹理瓦片」是一种特殊的Quad,它只是一类纹理瓦片的别称

「Quad」描述纹理的输入信息,并指出如何对其进行「转换」「应用视觉效果」。 ❞

例如,内容瓦片有一个变换,表示它们在瓦片网格中的x、y位置。

这些栅格化的瓦片被包裹在「一个渲染通道」中,它是一个「quad」的列表。「渲染通道不包含任何像素信息」;相反,它有关于在哪里以及如何绘制每个quad所需像素输出的指示。

❝每个GPU纹理瓦片都有一个「quad」

显示合成器只需要在quad列表中进行迭代,用指定的视觉效果绘制每一个quad,以产生渲染通道所需的像素输出。渲染通道的绘制quad合成可以在GPU上有效地完成,因为允许的视觉效果是经过精心挑选的,可以直接映射到GPU的特性上。

除了光栅化瓦片之外,还有其他类型的quad。例如,有一些完全不依赖纹理机制的纯色quad,或者用于「视频」「画布」等纹理绘制quad

「一个合成器帧也有可能嵌入另一个合成器帧」

例如,浏览器合成器会产生一个带有浏览器用户界面的合成器帧,以及一个「空的区域」以便于将渲染合成器的内容嵌入其中。另一个例子是存在「站点隔离」的多个iframe之间。这种嵌入是表面Surface通过完成的。

当一个合成器提交一个合成器帧时,它伴随着一个用于区分合成帧的标识符,即「表面ID」。最新提交的带有特定「表面ID」的合成器帧被Viz储存起来。「另一个」合成器帧随后可以通过「表面quad」来引用它,因此Viz知道要绘制什么。(注意,表面quad只包含表面ID,而不是纹理。)


中间的渲染通道

一些「视觉效果」,如许多滤镜或高级混合模式,需要将两个或更多的quad合并到一个「中间纹理」中。然后,中间纹理被绘制到GPU上的目标缓冲区(或者可能是另一个中间纹理),同时应用视觉效果。为了实现这一点,「一个合成器帧实际上包含一个渲染通道的列表」。并且总是有一个根渲染通道,它是最后绘制的。

每个通道必须在GPU上「按顺序执行」,分为多个 "阶段",而单个阶段可以在「单个大规模并行的GPU计算」中完成。

合成Aggregation

❝多个合成器帧被提交给Viz,它们需要被一起绘制到屏幕上。这是由一个聚合阶段Aggregation完成的,该阶段将它们转换为一个「单一的、聚合的」合成器帧 ❞

聚合将「表面quad」替换成他们指定的合成器帧。

这也是一个优化不必要的中间纹理或屏幕外内容的机会。例如,在很多情况下,一个独立网站的iframe的合成器帧不需要它自己的中间纹理,可以通过绘制quad直接绘制到框架缓冲区。聚合阶段会找出这样的优化,并根据单个渲染合成器无法访问的全局来应用这些优化。

示例

以本文开头的例子做讲解

代码语言:javascript
复制
// 主 frame 为foo.com
<html>
  <div style="overflow: hidden; 
              width: 100px; 
              height: 100px;">
    // 子 frame (foo.com/etc)
    <iframe 
      style="filter: blur(3px);
            transform: rotateZ(1deg);
            width: 100px; 
            height: 300px"
      id="one" 
      src="foo.com/etc"></iframe>
  </div>
  // 子 frame (bar.com)
  <iframe 
      style="top:200px;
             transform: scale(1.1);
             translateX(200px)"
      id="two" 
      src="bar.com"></iframe>
</html>
  • foo.com/index.html surface: ID =0
    • 绘制quad:以3px的模糊度绘制,并夹入渲染通道0
    • 表面绘制quad:ID =2,用比例和平移变换绘制
    • 为#「one」的帧绘制带有x/y位置信息的quad
    • 渲染通道 1:
    • 渲染通道 0 : 绘制到输出
  • 浏览器 UI surface: ID =1
    • 为 浏览器UI绘制quad
    • 渲染通道 0 : 绘制到输出
  • bar.com/index.htmlsurface: ID=2
    • 为#「two」的帧绘制带有x/y位置信息的quad
    • 渲染通道 0 : 绘制到输出

后记

「分享是一种态度」,这篇文章,是一篇译文,算是一个自我学习过程中的一种记录和总结。主要是把自己认为重要的点,都罗列出来。同时,也是为大家节省一下「排雷和踩坑的时间」。当然,可能由于自己认知能力所限,有些点,没能表达很好。

参考资料:

  1. 原文地址 需要🪜
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端柒八九 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 简明扼要
  • 文章概要
  • 前置知识简讲
  • 1. 帧树Frame Tree
    • 视觉属性更新步骤
    • 2. 不可变的片段树The immutable fragment tree
      • 内联Lnline片段信息
      • 3. 属性树Property trees
        • 示例
        • 4. 显示列表和绘画块Display lists and paint chunke
        • 5. 合成器帧Compositor frame:表面surface、渲染表面render surface、GPU 纹理瓦片GPU texture tile
          • 瓦片Tile
            • Quad and surfacesQuad and Surfaces
              • 中间的渲染通道
                • 合成Aggregation
                  • 示例
                  • 后记
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档