《WebKit 技术内幕》学习之七(4): 渲染基础

news2024/11/16 5:24:35

4 WebKit软件渲染技术

4.1 软件渲染过程

        在很多情况下,也就是没有那些需要硬件加速内容的时候(包括但不限于CSS3 3D变形、CSS3 03D变换、WebGL和视频),WebKit可以使用软件渲染技术来完成页面的绘制工作(除非读者强行打开硬件加速机制),目前用户浏览的很多门户网站、论坛网站、社交网站等所设计的网页,都是采用这项技术来完成页面的渲染。

        要分析软件渲染过程,需要关注两个方面,其一是RenderLayer树,其二是每个RenderLayer所包含的RenderObject子树。首先来看WebKit如何遍历RenderLayer树来绘制各个层。

        对于每个RenderObject对象,需要三个阶段绘制自己,第一阶段是绘制该层中所有块的背景和边框,第二阶段是绘制浮动内容,第三阶段是前景(Foreground),也就是内容部分、轮廓(它是CSS标准属性,绘制于元素周围的一条线,位于边框边缘的外围)等部分。当然,每个阶段还可能会有一些子阶段。值得指出的是,内嵌元素的背景、边框、前景等都是在第三阶段中被绘制的,这是不同之处。

        下图描述了一个RenderLayer层是如何绘制自己和子女的,这一过程是一个递归过程。图中的函数名未来可能会发生变化,所以读者更多关注它们的含义。图中的调用顺序可以作如下理解:这里主要节选了一些重要步骤,事实上这一绘制过程还可能包含其他一些相对较小的步骤。图中有些步骤的操作并不是总是发生。这里是一个大致的过程,下面是详细分析。

                                图 绘制RenderLayer和它的子女的调用过程

  1. 对于当前的RenderLayer对象而言,WebKit首先绘制反射层(Reflectionlayer),这是由CSS定义的。
  2. 然后WebKit开始绘制RenderLayer对象对应的RenderObject节点的背景层(PaintBackground-ForFragments),也就是调用“PaintPhaseBlockBackground”函数,读者记住这里仅是绘制该对象的背景层,而不包括RenderObject的子女。其中“Fragments”的含义是可能绘制的几个区域,因为网页需要更新的区域可能不是连续的,而是多个小块,所以WebKit绘制的时候需要更新这些非连续的区域即可,下面也是一样的道理。
  3. 图中的“paintList”(z坐标为负数的子女层)阶段负责绘制很多Z坐标为负数的子女层。这是一个递归过程。Z坐标为负数的层在当前RenderLayer对象层的后面,所以WebKit先绘制后面的层,然后当前RenderLayer对象层可能覆盖它们。
  4. 图中“PaintForegroundForFragments()”这个步骤比较复杂,包括以下四个子阶段:首先进入“PaintPhaseChildBlockBackground”阶段,WebKit绘制RenderLayer节点对应的RenderObject节点的所有后代节点的背景,如果某个被选中的话,WebKit改为绘制选中区域背景(网页内容选中的时候可能是另外的颜色);其次,进入“PaintPhaseFloat”绘制阶段,WebKit绘制浮动的元素;再次,进入“PaintPhaseForeground”阶段,WebKit绘制RenderObject节点的内容和后代节点的内容(如文字等);最后,进入“PaintPhaseChildOutlines”绘制阶段,WebKit的目的是绘制所有后代节点的轮廓。
  5. 进入“PaintOutlineForFragments”步骤。WebKit在该步骤中绘制RenderLayer对象对应的RenderObject节点的轮廓(PaintPhaseOutline)。
  6. 进入绘制RenderLayer对象的子女步骤。WebKit首先绘制溢出(Overflow)的RenderLayer节点,之后依次绘制Z坐标为正数的RenderLayer节点。
  7. 进入该RenderObject节点的滤镜步骤。这是CSS标准定义在元素之上的最后一步。

        上面是从RenderLayer节点和它所包含的RenderObject子树来解释软件绘图这一过程,那么对于RenderLayer树包含的每个RenderObject而言,它们是如何被处理的呢?

        因为RenderObject类有很多子类,每个子类都不一样,不过很多子类的绘制其实比较简单,所以,为了能比较清楚地说明RenderObject绘制的过程,这里以典型的RenderBlock类为例来说明,因为它是以框模型为基础的类,下图给出了绘制RenderBlock类的过程。

                        图RenderBlock类的绘制过程

        在上图中,“paint”是RenderObject基类的绘图函数,用来绘制该对象的入口函数,在RenderBlock类中,它被重新实现了。一个RenderObject类的“paint”函数在绘制时可能会被多次调用,因为不同的绘制阶段(前面的一张图提到的)都需要调用它来绘制不同的部分,所以读者会发现上图右侧标记了在哪些阶段才会调用该绘制函数(或者是哪些阶段该函数不会被调用),至于这些阶段的顺序则是由RenderLayer对象中的调用过程来控制的。

        图中的“paintContents”函数主要用来遍历和绘制它的子女,在某些情况下,WebKit其实并不需要该函数,例如RenderLayer对象仅需要绘制对应的RenderObject子树的根节点的时候。

        对于其他类型的节点,绘制过程大致是这一过程的一个子集。例如RenderText类没有子女,也不需要绘制框模型的边框等,所以WebKit仅需要绘制自己的内容。

        在上面这一过程中,Webkit所使用的绘图上下文都是2D的,因为没有GPU加速,所以3D的绘图上下文没有办法工作。这意味着,每一层上的RenderObject子树中不能包含使用3D绘图的节点,例如Canvas 3D(WebGL)节点等。同时,每个RenderLayer层上使用的CSS 3D变形等操作也没有办法得到支持。

        最开始的时候,也就是WebKit第一次绘制网页的时候,WebKit绘制的区域等同于可视区域大小。而这在之后,WebKit只是首先计算需要更新的区域,然后绘制同这些区域有交集的RenderObject节点。这也就是说,如果更新区域跟某个RenderLayer节点有交集,WebKit会继续查找RenderLayer树中包含的RenderObject子树中的特定一个或一些节点,而不是绘制整个RenderLayer对应的RenderObject子树。下图描述了在软件渲染过程中WebKit实际更新的区域,也就是之前描述软件渲染过程的生成结果。

                                       图WebKit绘制网页的更新区域

          WebKit软件渲染结果的储存方式,在不同的平台上可能不一样,但是基本上都是CPU内存的一块区域,多数情况下是一个位图(Bitmap)。至于这个位图如何处理,如何跟之前绘制的结果合并,如何显示出来,都跟WebKit的不同移植相关。下面介绍一下Chromium是如何处理的。

4.2 Chromium的多进程软件渲染技术

        在Chromium的设计和实现中,因为设计者引入了多进程模型,所以Chromium需要将渲染结果从Renderer进程传递到Browser进程。

        先来看看Renderer进程。前面介绍了WebKit的Chromium移植的接口类是RenderViewImpl,该类包含一个用于表示一个网页的渲染结果的WebViewImpl类。其实,RenderViewImpl类还有一个作用就是同Browser进程通信,所以它继承自RenderWidget类。RenderWidget类不仅负责调度页面渲染和页面更新到实际的WebViewImpl类等操作,而且它负责同Browser进程的通信。另一个重要的设施是PlatformCanvas类,也就是SkiaCanvas(Skia是一个2D图形库),RenderObject树的实际绘制操作和绘制结果都由该类来完成,它类似于2D绘图上下文和后端存储的结合体。

        再来看看Browser进程。第一个设施就是RenderWidgetHost类,一样的必不可少,它负责同Renderer进程的通信。RenderWidgetHost类的作用是传递Browser进程中网页操作的请求给Renderer进程的RenderWidget类,并接收来自对方的请求。第二个是BackingStore类,顾名思义,它就是一个后端的存储空间,它的大小通常就是网页可视区域的大小,该空间存储的数据就是页面的显示结果。BackingStore类的作用很明显,第一,它保存当前的可视结果,所以Renderer进程的绘制工作不会影响该网页结果的显示;第二,WebKit只需要绘制网页的变动部分,因为其余的部分保存在该后端存储空间,Chromium只需要将网页的变动更新到该后端存储中即可。

        最后来看看这两个进程是如何传递信息和绘制内容的。两个进程传递绘制结果是通过TransportDIB类来完成,该类在Linux系统下其实是一个共享内存的实现。对Renderer进程来说,Skia Canvas把内容绘制到位图中,该位图的后端即是共享的CPU内存。当Browser进程接收到Renderer进程关于绘制完成的通知消息,Browser进程会把共享内存的内容复制到BackingStore对象中,然后释放共享内存。

        Browser进程中的后端存储最后会被绘制在显示窗口中,用户就能够看到网页的结果。下图显示的是软件渲染的架构图,其思想主要来源于Chromium的官方网站,但这里做了一些扩充。

                        图Chromium的多进程软件渲染结构图

        根据上面的组成部分,一个多进程软件渲染过程大致是这样的:RenderWidget类接收到更新请求时,Chromium创建一个共享内存区域。然后Chromium创建Skia的SkCanvas对象,并且RenderWidget会把实际绘制的工作派发给RenderObject树。具体来讲,WebKit负责遍历RenderObject树,每个RenderObject节点根据需要来绘制自己和子女的内容并存储到目标存储空间,也就是SkCanvas对象所对应的共享内存的位图中。最后,RenderWidgetHost类把位图复制到BackingStore对象的相应区域中,并调用“Paint”函数来把结果绘制到窗口中。

        后面我们会介绍在哪些时候请求绘制网页内容,这里先了解两种会触发重新绘制网页中某些区域的请求,如下面所示。

  • 前端请求: 该类型的请求从Browser进程发起的请求,可能是浏览器自身的一些需求,也有可能是X窗口系统(或者其他窗口系统)的请求。一个典型的例子就是用户因操作网页引起的变化。
  • 后端请求: 由于页面自身的逻辑而发起更新部分区域的请求,例如HTML元素或者样式的改变、动画等。一个典型的例子是JavaScript代码每隔50ms便会更新网页样式,这时样式更新会触发部分区域的重绘。

        下面逼仄来解释一下当有绘制或者更新某个区域的请求时,Chromium和WebKit是如何来处理这些请求的。具体过程下图所示,下面是其中主要的步骤。

                                图Chromium的软件渲染过程

  1. Renderer进程的消息循环(Message Loop)调用处理“界面失效”的回调函数,该函数主要调用RenderWidget::DoDeferredUpdate来完成绘制请求。
  2. RenderWidget::DoDeferredUpdate函数首先调用Layout函数来触发检查是否有需要重新计算的布局和更新请求。
  3. RenderWidget类调用TransportDIB类来创建共享内存,内存大小为绘制区域的高×宽×4,同时调用Skia图形库来创建一个SkCanvas对象。SKCanvas对象的绘制目标是一个使用共享内存存储的位图。
  4. 当渲染该页面的全部或者部分时,ScrollView类请求按照从前到后的顺序遍历并绘制所有RenderLayer对象的内容到目标的位图中。Webkit绘制每个RenderLayer对象通过以下步骤来完成:首先Webkit计算重绘的区域是否和RenderLayer对象有重叠,如果有,Webkit要求绘制该层中的所有RenderObject对象。图7-14中省略了该部分的具体内容,详情请参考代码。
  5. 绘制完成后,Renderer进程发送UpdateRect的消息给Browser进程,Renderer进程同时返回以完成渲染的过程。Browser进程接收到消息后首先由BackingStoreManager类来获取或者创建BackingStoreX对象(在Linux平台上),BackingStoreX对象的大小与可视区域相同,包含整个网页的坐标信息,它根据UpdateRect的更新区域的位置信息将共享内存的内容绘制到自己的对应存储区域中。

        最后Browser进程将UpdateRect的回复消息发送到Renderer进程,这是因为Renderer进程知道Browser进程已经使用完该共享内存,可以进行回收利用等操作,这样就完成了整个过程。

        细心的读者其实可以发现,这一过程需要一些内存方面的拷贝,这是因为网页的渲染和网页的显示是在两个不同的进程,而这些拷贝在下一章介绍的硬件加速渲染机制中可以避免。当然,硬件加速渲染机制也引入一些其他方面的问题。

4.3 实践:软件渲染过程

        为了直接理解Chromium的多进程软件渲染过程,本节中笔者使用Chromium项目提供的“about:tracing”工具来分析,该工具可以收集Chromium内部函数调用的时间分布等信息。具体步骤如下。

  1. 使用Chrome浏览器打开标签页输入“chrome://flags”,找到选项“对所有网页执行GPU合成Mac, Windows, Linux”,选择“停用”,这样确保使用了软件渲染机制。
  2. 打开网页http://www.chromium.org/developers/design-documents,并打开一个新的标签页,输入“chrome://tracing”。
  3. 在标签页“chrome://tracing”中单击“record”按钮并切换到第二步打开的网页“Design Document”,重新加载该网页,之后再切换到“chrome://tracing”标签页中,单击“stop tracing”按钮,这样数据收集完毕,读者会发现有很多如下图中下面的图层所示的信息,它们表示的是浏览器的各个进程和线程的信息。

                          图浏览器“chrome://tracing”结果和任务管理器

  1. 单击浏览器地址栏最右侧的“设置”按钮,选择“工具->任务管理器”,读者会发现三个任务(如果读者的浏览器没有安装其他Chrome扩展或者启动插件等),这三个任务分别是网页“Design Documents”(Renderer进程1)、标签页“chrome://tracing”(Renderer进程2)和浏览器(Browser进程)。下图中显示的任务管理器,读者看到三个任务的进程ID同“chrome://tracing”中一一对应。下面首先分析进程2312。
  2. 在进程2312中,选择线程“CrRendererMain”,通过放大数据图,读者可以看到下图所示的信息,这是Chromium的多进程模型绘制网页使用的一些函数和它们消耗的时间,读者可以将这些函数同图7-14中的Renderer进程中的调用过程作对比。

                               图“Design Documents”网页对应的Renderer进程

  1. 在进程3660中,选择线程“CrBrowserMain”,在Renderer进程完成图7-16中的操作之后,通过放大数据图,读者可以看到如图7-17所示的信息,这是Chromium更新共享内存的数据并把数据绘制到BackingStore对象中,最后绘制到窗口。读者同样可以将这些函数同下图中的Browser进程的调用过程作对比。

                        图“Design Documents”网页对应的Renderer进程

        至此,WebKit的基础部分已介绍完毕。通过前面的分析和介绍,读者应该可以对网页的基本知识和基本的渲染过程有了一些了解。同时,结合Chromium的实现,读者应该理解浏览器是如何使用WebKit和扩展浏览器能力的。当然,WebKit的能力远不止这些,在高级篇中会介绍更多有关WebKit的高级技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1403652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【分布式技术】注册中心zookeeper

目录 一、ZooKeeper是什么 二、ZooKeeper的工作机制 三、ZooKeeper特点 四、ZooKeeper数据结构 五、ZooKeeper应用场景 ●统一命名服务 ●统一配置管理 ●统一集群管理 ●服务器动态上下线 ●软负载均衡 六、ZooKeeper的选举机制 七、实操部署ZooKeeper集群 步骤一…

《WebKit 技术内幕》学习之五(1): HTML解释器和DOM 模型

第五章 HTML 解释器和 DOM 模型 1.DOM 模型 1.1 DOM标准 DOM (Document Object Model)的全称是文档对象模型,它可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构。这里的文档可以是 HTML 文档、XML 文档或者 XHTML 文档。D…

MarkDown学习笔记 直观全面详细

前言 为什么我们要学习Markdown呢?因为Markdown简单易学易上手,可以以纯文本格式编写文档,然后转换成有效的HTML文档,并且以导出 HTML 、Word、图像、PDF、Epub 等多种格式的文档,许多网站平台的文章、博客、论文均可…

将输入框的数据输出成json

刚学,做一下记录 先上效果图,来不及解释了,后面再说 源码:链接:https://pan.baidu.com/s/1pV8hDVwpB1kresHag7gIew 提取码:**** 操作: 进入项目目录下,cmd 清除npm缓存&#xff1…

Java并发基础:Executor接口和Executors类的区别

Executor是Java中的一个接口,它定义了一种将任务提交与任务执行机制(包括线程管理、调度等)分离的方式,Executors是一个工具类,它提供了多个静态工厂方法,用于创建不同类型的Executor实例。 代码案例 下面…

国家急救日倡议活动暨120急救大课堂公益培训在京成功举办

2024年1月20日,由国家卫生健康委员会等多个相关部门指导,中国医院协会急救中心(站)分会主办,北京急救中心承办的“国家急救日”倡议活动暨急救科普大课堂公益培训系列活动,在全国范围内启动。 健康中国行动…

AI企业知识库:企业智慧之源,引领未来发展

AI企业知识库作为优秘智能的核心技术(shuziren06),具有许多引人注目的功能和特点。接下来让我们深入探讨一下AI企业知识库如何在实际应用中助力企业。以下是对AI企业知识库的更详细介绍: 1. 强大的知识整合能力 AI企业知识库能够…

智慧校园大数据应用系统介绍(2)

OA办公管理系统 教育OA管理系统,是为配合学校内部办公自动化的实施而研发的一套稳定、安全、灵活、实用的办公自动化管理系统。该系统结构精明实用;功能模块灵活稳定;设计人性化、使用简单化且扩展性强。系统由通知,信息采集,工作计划,公文流转、听课反馈、行政护导、资…

ML Design Pattern——I see

ML Life Cycle MLOps ML Pipelines Fully automated processes ML Design Patterns Reading the book? 链接:https://pan.baidu.com/s/1MgOSHASAOJ0EVhMYmT9QeQ?pwd96uk 提取码:96uk

即时按需原子 CSS 引擎:比 Tailwind JIT 快 5 倍! | 开源日报 No.149

unocss/unocss Stars: 14.6k License: MIT 该项目是一个即时的按需原子 CSS 引擎,受到 Windi CSS、Tailwind CSS 和 Twind 的启发,解决了定制化、速度和体积的问题。 以下是该项目的特点和核心优势: 完全可定制:通过预设提供所…

阿里巴巴开源联邦学习框架FederatedScope

5月5日,阿里巴巴达摩院发布新型联邦学习框架FederatedScope,声称可以在不共享训练数据的情况下开发机器学习算法,从而保护隐私。,其源代码现已在Apache 2.0许可下发布在GitHub上。 介绍 该平台被描述为一个全面的联邦学习框架&a…

Day16 linuxC高级(存储类型 linux命令 shell命令)

文章目录 C补充标识常量存储类型1.auto // 自动型2.static:修饰变量和函数 // 静态型3.extern:外部引用4.register:寄存器类型 LinuxC高级简介:嵌入式系统(将软件嵌入到硬件里面)Linux起源查看操作系统版本内核系统架构系统关机或…

阿里云优惠券领取入口、使用方法和限制条件,2024最新

阿里云优惠代金券领取入口,阿里云服务器优惠代金券、域名代金券,在领券中心可以领取当前最新可用的满减代金券,阿里云百科aliyunbaike.com分享阿里云服务器代金券、领券中心、域名代金券领取、代金券查询及使用方法: 阿里云优惠券…

Python Process创建进程(2种方法)详解

虽然使用 os.fork() 方法可以启动多个进程,但这种方式显然不适合 Windows,而 Python 是跨平台的语言,所以 Python 绝不能仅仅局限于 Windows 系统,因此 Python 也提供了其他方式在 Windows 下创建新进程。 Python 在 multiproces…

程序员必备命令之-网络状态命令netstat ss

本文介绍一下两个常用的Linux命令,netstat以及ss,这两个命令均用来查看主机的网络相关的信息。 Netstat netstat 是Unix系统上用于显示各种网络统计信息的命令,目前大多数的Linux系统默认自带该命令。该命令用于显示系统网络连接、路由表等…

JAVA工程中引用本地jar的3种常用方式,你用过哪种?

文章目录 前言1. 第1种方式2. 第2种方式3. 第3种方式 前言 实际项目过程中咱们经常会碰到需要本地引用jar包到java工程中的场景,本文就介绍一下遇到此场景时如何在IDEA中导入本地jar包到工程中的3种方式,简单却很常用。 1. 第1种方式 IDEA -> File …

chrome浏览器Extension插件开发框架对比和介绍

想开发chrome浏览器插件的话,如果没有一个好的框架支持,那么你只能手写js和html实现了,这样做的话,可能效率没有那么高,所以github上就有一些牛人来定制化了这些操作,将react和vue等开发框架快速集成&#…

AtCoder Beginner Contest 221 H. Count Multiset(容斥 dp 拆分数 差分 数形结合)

题目 给定m,n(m<n<5e3)&#xff0c; 求大小为k的多重集合&#xff0c;满足元素和为n&#xff0c; 且每种数在集合中出现的次数都小于等于m的集合数有多少个 答案对998244353取模 思路来源 官方题解 「解题报告」[ABC221H] Count Multiset - K8He - 洛谷博客 Solu…

测试不拘一格——掌握Pytest插件pytest-random-order

在测试领域&#xff0c;测试用例的执行顺序往往是一个重要的考虑因素。Pytest插件 pytest-random-order 提供了一种有趣且灵活的方式&#xff0c;让你的测试用例能够以随机顺序执行。本文将深入介绍 pytest-random-order 插件的基本用法和实际案例&#xff0c;助你摆脱固定的测…

CSGO搬砖项目还能火多久?

最近放假回到老家&#xff0c;见了不少亲戚朋友&#xff0c;大家不约而同都在感叹今年大环境不好&#xff0c;工作不顺&#xff0c;生意效益不好&#xff0c;公司状况不佳&#xff0c;反问我们生意如何&#xff1f;为了让他们心里好受一点&#xff0c;我也假装附和道:也不咋地&…