Chromium Canvas工作流
blink 中实现了2种 canvas,分别是 blink::HTMLCanvasElement 和 blink::OffscreenCanvas ,前者对应 html/dom 中的 canvas,后者对应 js 中的 OffscrenCanvas。
html canvas 有两种模式,一种是常规模式,这种模式下 canvas 的绘制时机受 viz/cc 的调度,和网页上的其他 dom 绘制的时机一致。另一种是低延迟模式 desynchronized = true,此时 canvas 的绘制会脱离 dom,它会作为一个独立的 viz client 使用 CanvasResourceDispatcher 来自主向 viz 提交要显示的画面(MAC 下还不支持低延迟模式 crbug.com/945835)。
OffscreenCanvas 可以脱离 dom 存在,原理类似 html canvas 的低延迟模式,也是作为一个独立的 viz client 存在,可以自主向 viz 提交要显示的画面。不同的是它可以跑在 worker 线程中,从而避免阻塞 blink 线程(线程名 CrRenderMain,cc 的绘制线程),而 html canvas 的低延迟模式只能跑在 blink 线程。
要在 canvas 上绘制内容,需要先获取绘制 context,最常用的就是 2d context,它在 html canvas 和 OffscreenCanvas 下有不同的实现, 分别为 blink::CanvasRenderingContext2D 和 blink::OffscreenCanvasRenderingContext2D,区别可以理解为后者只支持低延迟渲染模式,而前者不仅支持低延迟渲染模式,同时支持常规 canvas 渲染模式。
除了 2d context,以下这些 context 在两种 canvas 中都可以使用:

1. 网页渲染流程简介
由于 canvas 是网页内容的一部分,很难在不了解网页渲染流程的情况下单独理解 canvas 的渲染,因此这里先介绍下网页渲染的一般流程。
网页的渲染链路非常长,由于这里的重点是 canvas,因此只做简单介绍,不会过多展开,后续会有专门的文章介绍。
下面是网页渲染的全链路流程简图 blink-1000:


下面简单介绍整个流程:
- vsync: 浏览器一帧的渲染从 vsync 信号开始,它会通知 render 进程中的 cc compositor 线程(或者叫 cc impl 线程)开始新的一帧;
- BeginFrame: cc compositor 线程紧接着通知 cc render 线程进行内容的绘制;
- DOM: 此时 blink 开始工作,它会先解析 html 生成 DOM 树;
- Javascript: 此时如果注册有 requestAnimationFrame 回调或者交互事件回调,则会在此时执行(桩点1);
- Styles + Layout: 然后计算每个节点的样式以及对每个节点进行布局排版;
- Paint: 之后开始绘制,不同类别的 DOM 元素采用不同的绘制方法(桩点2),绘制完成之后进行合成,最终产出 cc::Layer 树,然后 blink 通知 cc compositor 线程绘制完成;
- Commit: cc compositor 会从 cc::Layer 树构建自己的 cc::LayerImpl 树;
- Tiles: 然后根据网页视口的范围/页面的缩放比例将 cc::LayerImpl 进行分块(Tiles);CompositorFrame: 回到 cc compositor 线程,他在分发完 raster 任务之后会根据 cc::LayerImpl 树构建 viz::CompositorFrame 对象,该对象表示一帧绘制内容(并不一定是整个网页,参考后面的canvas低延迟模式介绍),它会被提交(submit)到 viz compsoitor 线程中进行合成;
- Raster Tasks: 这些分块会被送往 worker 线程进行 raster;
- Raster: worker 会把raster任务序列化到 commandbuffer, 并通知 CrGpuMain 线程进行真正的 raster 。
- viz Composite: viz compositor 把多个 CF 合成为完整的页面(桩点3),然后提交到 compositor gpu 线程中;
- Display: compositor gpu 调用 GL 进行真正的绘制以及上屏。
我在上面的流程中埋了3个桩点,这三个桩点就是 canvas 渲染涉及到的三个重要节点。下面会把 canvas 的不同流程插入到这些节点中去。
2. Canvas 类图
为了讲清楚 canvas 的实现原理,方便下文的描述,这里先看下 Canvas 相关的类图:

3. 获取用于绘制的 Context
开发者通过 canvas.getContext("XXX") 来获取 context 对象,这个 js api 会通过 blink::HTMLCanvasElement::GetCanvasRenderingContext 方法来获取 context。每种类型的 context 都有对应的 Factory 工厂类,所有这些类都注册在一个静态字典中,创建的时候根据 context 类型找到对应的工厂类,然后使用工厂类就可以直接创建 context 对象了。核心逻辑如下:

js 中的 context 对象对应 C++ 中的 blink::CanvasRenderingContext 对象。不同类型的 js context 分别对应 blink::CanvasRenderingContext 的不同子类,对应关系如下:

4. 向 Canvas 中绘制内容
js 调用 context.drawXXX 方法向 canvas 中绘制内容时,会调用到 C++ blink::CanvasRenderingContext 中对应的方法,对于 2d context, 则对应 blink::CanvasRenderingContext2D。它内部定义了所有 2d context 可以使用的 API,这些 API 分布于三个具有继承关系的类中:

所有的绘制操作都通过 cc::PaintCanvas 记录到 blink::CanvasResourceProvider 中。 cc::PaintCanvas 有个子类 cc::RecordPaintCanvas,专门用来把 2d 绘制操作记录到 cc::DisplayItemList 中,它只记录绘制操作而不会进行真正的绘制。
cc 提供了一个 cc::PaintRecorder 类,专门用来录制绘制操作,相关类图如下:

5. 完成绘制,提交结果
当所有的 js 绘制指令执行完毕之后,html canvas 在 2d context 下不需要显式的提交结果(C++内部会自动 flush),这点和 OffscreenCanvas 以及非 2d context 不同,这些模式都需要显示的提交绘制结果(在某些情况下也可以省略)。
6. 低延迟模式下取出 Canvas 数据
低延迟模式下,canvas 的每次绘制流程开始前都会设置一个标记,表示有新内容绘制了,此时会注册回调监听 blink 线程中当前任务结束的回调,在这个回调中触发 Canvas 内容的 Raster 以及提交。
绘制前注册回调的流程:

注册回调:

Raster 完成之后, CanvasResource 会通过 blink::CanvasResourceDispatcher::DispatchFrame 合成 CompositorFrame 然后提交。
从 CanvasResource 中取出 Raster 的结果,创建 viz::TransferableResource:

创建 CompositorFrame 并提交资源:

7. 总结
Canvas 从开始绘制到上屏经过以下流程:
- canvas 初始化,获取
CanvasRenderingContext; - js 调用绘制 API 进行绘制,绘制的结果被
cc::RecordPaintCanvas录制下来,保存在blink::CanvasResourceProvider中的cc::PaintRecord; - 在普通 Canvas 模式下提交绘制结果:
- blink 进入绘制流程,从
blink::Canvas2DLayerBridge中获取cc::TextureLayer; - 在提交到 cc compositor 线程之前,调用
cc::TextureLayer::Update触发cc::PaintRecord的 Raster,使用 OOP-R 机制将 Raster 任务发送到 CrGpuMain 线程进行 Raster,返回引用 Raster 结果的gpu::Mailbox; - 然后用 Raster 的结果
gpu::Mailbox创建viz::TransferableResource并存入cc::TextureLayer中进行提交; - 将
cc::TextureLayer和网页中的其他元素一起提交到 cc compositor 线程,在那里创建viz::CompositorFrame然后提交到 viz;
- blink 进入绘制流程,从
- 在低延迟 Canvas 模式下提交绘制结果:viz compositor 收到 CompositorFrame 之后等待合适的时机进行上屏;
- 当有绘制的时候注册 blink 线程任务结束回调;
- 当前任务结束之后,触发
blink::CanvasRenderingContext::DidProcessTask; - 然后 flush canvas,将
cc::PaintRecord进行 Raster,使用 OOP-R 机制将 Raster 任务发送到 CrGpuMain 线程进行 Raster,返回引用 Raster 结果的 gpu::Mailbox; - 然后在
blink::CanvasResourceDispatcher::DispatchFrame中创建viz::CompositorFrame包装 Canvas 的内容,并提交到 viz compositor 线程进行合成;
8. 参考文献
https://keyou.github.io/blog/2022/12/01/canvas/
Chromium Canvas工作流的更多相关文章
- canvas绘制工作流之绘制节点
上一篇我们介绍了canvas绘制工作流的大概步骤,接下来会有系列文章细致的介绍怎么用canvas绘制工作流:这篇文章主要介绍用canvas绘制流程节点. 绘制前我们需要先准备一张节点图片,例如::好了 ...
- canvas与工作流的不解之缘
html的标签 <canvas>用于图形的绘制,通过脚本 (通常是JavaScript)来完成,canvas简而言之就是个画布.上一篇文章我们提到工作流的一个重要组成部分:流程建模,也就是 ...
- HTML5 学习总结(四)——canvas绘图、WebGL、SVG
一.Canvas canvas是HTML5中新增一个HTML5标签与操作canvas的javascript API,它可以实现在网页中完成动态的2D与3D图像技术.<canvas> 标记和 ...
- canvas绘图、WebGL、SVG
目录 一.Canvas 1.1.创建canvas元素 1.2.画线 1.3.绘制矩形 1.4.绘制圆弧 1.5.绘制图像 1.6.绘制文字 1.7.随机颜色与简单动画 二.WebGL 2.1.HTML ...
- HTML5 学习笔记(四)——canvas绘图、WebGL、SVG
一.Canvas canvas是HTML5中新增一个HTML5标签与操作canvas的javascript API,它可以实现在网页中完成动态的2D与3D图像技术.<canvas> 标记和 ...
- 浏览器-07 chromium 渲染1
Chromium 软件渲染 软件渲染就是利用CPU,根据一定的算法来计算生成网页的内容; Chromium都是用软件渲染的技术来完成页面的绘制工作(除非强行打开硬件加速绘制); 软件渲染基础和架构 R ...
- JavaScript + SVG实现Web前端WorkFlow工作流DAG有向无环图
一.效果图展示及说明 (图一) (图二) 附注说明: 1. 图例都是DAG有向无环图的展现效果.两张图的区别为第二张图包含了多个分段关系.放置展示图片效果主要是为了说明该例子支持多段关系的展现(当前也 ...
- 工作流引擎Activiti使用总结
http://www.kafeitu.me/activiti/2012/03/22/workflow-activiti-action.html 1.简单介工作流引擎与Activiti 对于工作流引擎的 ...
- Activiti工作流引擎使用
http://www.kafeitu.me/activiti/2012/03/22/workflow-activiti-action.html 1.简单介工作流引擎与Activiti 对于工作流引擎的 ...
- chromium截图实现
声明:本blog是我自己写的,假设要转载,请注明:come from blog of niba! chromium终于显示是在ContentView上.但通过硬件加速.渲染合成的网页之前是在surfa ...
随机推荐
- 音视频开发进阶|第四讲:音频自动增益控制 AGC
在之前的文章中,我们已经接触了两个重要的音频前处理模块 – 回声消除 AEC 和噪声抑制 ANS,它们分别解决了 RTC 场景下的回声.噪声问题,极大提升了用户的体验.至此,音频前处理三剑客中,就只剩 ...
- MariaDB start 报错:mysql-bin.index' not found (Errcode: 2) (Errcode: 13)
问题是修改配置log-bin=/data/mysql/binlog/mysql-bin后出现的. 报错:Errcode: 2 mkdir -p /data/mysql/binlog ## 和正常的DB ...
- 【技术实战】Vue技术实战【三】
需求实战一 效果展示 代码展示 <template> <div style="display: flex;"> <div style="di ...
- 在Java中List, Set, Map是否继承自Collection接口?
List和Set是继承自Collection接口的接口,Set不允许重复的项目,List允许重复项目, Set接口派生的类有TreeSet,HashSet,LinkedHashSet. List接口派 ...
- Burnside 定理
Burnside 定理 问题: 给定一个 \(n\) 个点,\(n\) 条边的环,有 \(m\) 种颜色,给每个顶点染色,问有多少种本质不同的染色方案,答案对 \(10^9+7\) 取模 注意本题的本 ...
- 【RocketMQ】MQ消息发送总结
RocketMQ是通过DefaultMQProducer进行消息发送的,它实现了MQProducer接口,MQProducer接口中定义了消息发送的方法,方法主要分为三大类: send同步进行消息发送 ...
- 《Kali渗透基础》07. 弱点扫描(一)
@ 目录 1:漏洞发现 1.1:Exploit-DB 1.2:searchsploit 1.3:nmap 2:漏洞管理 3:弱点扫描类型 4:漏洞基本概念 4.1:CVSS 4.2:CVE 4.3:O ...
- 安卓APK签名注入大师(APP注入弹窗,注入打开密码,注入过期时间, 注入提示信息,一机一码)
安卓APK签名注入大师可以给安卓APK文件一键注入APP注入弹窗,注入打开密码,注入过期时间, 注入提示信息,一机一码等功能,方便开发人员给自己的APK文件添加消息提示, 密码等功能. 可以保护文件安 ...
- mpi转以太网连接300PLC在气动系统中的应用
mpi转以太网连接300PLC在气动系统中的应用 某企业装备有限公司 摘要 工业通讯迅速发展的今天,MPI转以太网通讯已经发展为成熟,稳定,高效通讯 方式,兴达易控自主研发的MPI转以太网模块MPI- ...
- CAS中ABA问题的解决
转自(here) CAS问题的产生 在运用CAS做Lock-Free操作中有一个经典的ABA问题: 线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A ...