Chromium Viz 浅析 - SkiaRenderer & SkiaOutputSurface

简介: **关于 Viz 的系列文章** [Chromium Viz 浅析 - 介绍篇][1] [Chromium Viz 浅析 - 合成器架构篇][2] ---- Chromium 关于光栅化和合成的一些主要性能优化项目包括 OOPD (Out of Process Display Compositor,进程外 Display 合成器), OOPR (Out of Process R

关于 Viz 的系列文章

Chromium Viz 浅析 - 介绍篇
Chromium Viz 浅析 - 合成器架构篇


Chromium 关于光栅化和合成的一些主要性能优化项目包括 OOPD (Out of Process Display Compositor,进程外 Display 合成器), OOPR (Out of Process Rasterization,进程外光栅化) 和 SkiaRenderer。

我们在Chromium Viz 浅析 - 合成器架构篇介绍过 OOPD,它的主要目的是将 Display Compositor 从 Browser 进程迁移到 Viz 进程(也就是原来的 GPU 进程),Browser 则变成了 Viz 的一个 Client,Renderer 跟 Viz 建立链接(CompositorFrameSink)虽然还是要通过 Browser,但是建立链接后提交 CompositorFrame 就是直接提交给 Viz 了。Browser 同样也是提交 CompositorFrame 给 Viz,最后在 Viz 生成最终的 CompositorFrame,通过 Display 交由 Renderer 合成输出。

How cc Works 中文译文一文中,我们简单提及过 OOPR。OOPR 跟目前的 GPU 光栅化机制的主要区别是:

  1. 在当前的 GPU 光栅化机制中,Worker 线程在执行光栅化任务时,会调用 Skia 将 2D 绘图指令转换成 GPU 指令,Skia 发出的 GPU 指令通过 Command Buffer 传送到 Viz 进程的 GPU 线程中执行;
  2. 在 OOPR 中,Worker 线程在执行光栅化任务时,它直接将 2D 绘图指令(DisplayItemList)序列化到 Command Buffer 传送到 GPU 线程,运行在 GPU 线程的部分再调用 Skia 去生成对应的 GPU 指令,并交由 GPU 直接执行;

简而言之,就是将 Skia 光栅化的部分从 Renderer 进程转移到了 Viz 进程。

本文接下来的部分我们会对 SkiaRenderer 和 SkiaOutputSurface 进行分析。

DirectRenderer & OutputSurface

在继续分析 SkiaRenderer 和 SkiaOutputSurface 之前,我们先简单介绍一下它们的基类 DirectRendererOutputSurface

顾名思义,DirectRenderer 是 Display 用来合成输出最终的 CompositorFrame,它遍历 CompositorFrame 中的每一个 RenderPass,遍历每个 RenderPass 中的每个 DrawQuad,为每个 DrawQuad 生成相应的绘制指令。而 OutputSurface 则用来为最终的合成输出提供一个目标 Surface,对 Renderer 来说,它的主要作用是为 Renderer 提供一个绘图上下文。

在 SkiaRenderer 之前,我们在 GPU 合成时,使用的 Renderer 是 GLRenderer,顾名思义,它为每个 DrawQuad 生成的绘制指令是 GL 指令,它需要 OutputSurface 提供一个 ContextProvider 作为绘图上下文,通过 ContextProvider GLRenderer 可以获取一个基于 Command Buffer 的 GL 接口,然后通过该 GL 接口发出 GL 指令。

历史上还曾经有过 DelegatingRenderer,跟 DirectRenderer 对应,但是新的合成器架构应用后,DelegatingRenderer 已经不再需要了,所以现在只有 DirectRenderer 和它的派生类。

SkiaRenderer & SkiaOutputSurface

当我们开启 OOPD 后,Display Compositor 就运行在 Viz 进程的 Viz Compositor 线程,这意味则它跟 GPU 线程是同一个进程,理论上就不再需要使用 Command Buffer,虽然目前的 GLRenderer 还是需要使用一个 InProcessCommandBuffer 来做跨线程的 GL 指令调用。而 SkiaRenderer 则让我们可以完全避免在合成过程中使用 Command Buffer。

跟 GLRenderer 对应,SkiaRenderer 为每个 DrawQuad 生成的绘制指令是一个 Skia 的绘制指令,通过 SkiaOutputSurface 提供的 SkCanvas 输出。SkiaOutputSurface 有两个派生类 SkiaOutputSurfaceImpl 和 SkiaOutputSurfaceImplNonDDL,SkiaOutputSurfaceImplNonDDL 应该只是一个 Android WebView 的特殊合成器架构在重构过程中使用的一个过渡实现,所以我们重点讲解 SkiaOutputSurfaceImpl。

之前我们讲过 Display Compositor 运行在 Viz Compositor 线程,由它来调用 SkiaRenderer,但是 Skia 输出的 GPU 指令必须在 GPU 线程才能执行。所以实际上 SkiaOutputSurfaceImpl 给 SkiaRenderer 提供的 SkCanvas,它的目标 SkSurface 并不是真正用于当前输出的 SkSurface,而是一个 Compatible SkSurface。SkiaOutputSurfaceImpl 使用最终 SkSurface 的特征信息生成 SkDeferredDisplayListRecorder,然后获取 SkDeferredDisplayListRecorder 提供的 SkCanvas,SkiaRenderer 通过该 SkCanvas 输出的绘制指令实际上被存储到 Recorder 内部的 SkDeferredDisplayList 中。

当 SkiaRenderer 绘制完一个 RenderPass 后,SkiaOutputSurfaceImpl 从 SkDeferredDisplayListRecorder 剥离对应的 SkDeferredDisplayList,然后通过 Post GPU Task 的方式传递给在 GPU 线程等待的 SkiaOutputSurfaceImplOnGpu,SkiaOutputSurfaceImplOnGpu 再将这个 SkDeferredDisplayList playback 到真正输出的 SkSurface 上。

Promise SkImage

Promise SkImage 是一个蛮有意思的概念,它的引入主要是因为 SkiaRenderer 运行在 Viz Compositor 线程,并没有 GPU 上下文,所以当它需要绘制一个图片时,此时无法访问该图片对应的 GPU 资源,但是实际上该图片又是一个 GPU 资源的引用。不过因为 SkiaRenderer 是先绘制到 SkDeferredDisplayList,然后 SkDeferredDisplayList 再在 GPU 线程被 playback,所以引入了 Promise SkImage 来解决这个问题。

Promise SkImage 实际上是一个 placeholder,它包含了一些用于定位对应 GPU 资源的 meta 信息和一组回调函数的指针,当 SkDeferredDisplayList 被 playback 过程中,当需要绘制该 Promise SkImage 时,就会调用这些回调函数进行 Fulfill,Fulfill 回调函数会返回一个包含对应的 GPU 资源信息 SkPromiseImageTexture 供 Skia 使用。

所以 Promise SkImage 实际上就是 - "Make a promise, and fulfill in future"。

Next BIG Thing

仅仅是用 SkiaRenderer 替换原来的 GLRenderer,其实并没有马上带来什么性能上的优势。诚然,SkiaRenderer 不需要使用 Command Buffer,省去了 Encode/Decode 的开销,但是 Skia 本身的开销也不小,两相比较,也很难说有多少性能提升。所以 SkiaRenderer 更多是跟 OOPR 一起,为后续的进一步渲染性能优化打下基础。

我们可以看到,当 OOPD,OOPR 和 SkiaRenderer 都开启后:

  1. 光栅化和合成都迁到了 Viz 进程;
  2. 光栅化和合成都使用 Skia 做 2D 绘制,实际上 Chromium 所有的 2D 绘制最终都交由 Skia 来做,由 Skia 生成对应的 GPU 指令;
  3. 光栅化和合成时,Skia 最终输出 GPU 指令都在 GPU 线程,并且使用同一个 Skia GrContext(Skia 内部的 GPU 绘图上下文);

这意味着,当 Skia 对 Vulkan,Metal,DX12 等其它 3D API 的支持完善后,Chromium 就可以根据不同的平台和设备,来决定 Skia 使用哪个 GPU API 来做光栅化和合成。而 Vulkan,Metal,DX12 这些更 Low Level 的 3D API,对比 GL API,可以带来更低的 CPU 开销和更好的性能。

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
目录
相关文章
|
Web App开发 缓存 移动开发
V8 JS AOT化的探索与实践
JS 语言的动态性非常优秀,其弱类型等语言特性也使得一线业务开发者更容易上手,但这也导致 JS 每一次运行前都要重复编译,使得 JS 的执行性能不理想;虽然之前 UC 内核有做过 Code Cache 方案,但支持的场景不够完整,与原生 Native 的技术方案比,尤其是首次启动场景(如各类大促活动等)还是有比较大的差距。为了能尽可能做到与 Native 对标,缩小性能差距,同时让业务开发者无感,我们开发了 JS AOT 功能。本分享将结合目前集团内自有业务形态,以及 JS 在 Web 中的执行过程,介绍JS AOT是如何设计和实现的,以及能给业务带来哪些收益。本篇分享来自阿里巴巴的喻世江在第
2634 0
V8 JS AOT化的探索与实践
|
JavaScript 测试技术 API
|
JavaScript
VUE——三元表达式动态渲染样式
VUE——三元表达式动态渲染样式
146 0
利用宏定义解决问题
利用宏定义解决问题
77 0
利用宏定义解决问题
|
运维 NoSQL 关系型数据库
Serverless 应用引擎产品使用合集之应用,服务,函数的区别有哪些
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
Oracle 关系型数据库 MySQL
Seata常见问题之oracle 数据库 报 just support mysql如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
322 0
|
前端开发 JavaScript UED
前端开发的魔法:CSS动画与JavaScript的完美结合
本文将探讨如何利用CSS动画和JavaScript的结合,为前端页面增添生动的效果。我们将通过实例展示如何使用这两种技术为网页元素创建吸引人的动画效果,并讨论它们的优缺点和适用场景。
237 0
|
缓存 编解码 算法
Skia深入分析10——Skia库的性能与优化潜力
Skia库性能与优化潜力 图形/渲染 算法/架构 作为图形渲染引擎,性能上是非常重要的,按通常Android手机60帧的刷新率,绘制一帧的总时间只有16ms,可谓是毫厘必争。提升性能到最后,就必然跟不同CPU的特性打交道,毕竟一个SIMD下去,好做的提升5、6倍,不那么好做的也达到2、3倍,收益极其可观。 SIMD,在intel上是SSE,在arm上是neon,在
8853 0
|
存储 缓存 算法
理解Android硬件加速原理的小白文
理解Android硬件加速原理的小白文
527 0
理解Android硬件加速原理的小白文
|
JavaScript Java API
微软开源最强Python自动化神器Playwright!不用写一行代码!自动生成代码还竟然如此流畅!妈妈再也不用担心我不会写代码了!
微软开源最强Python自动化神器Playwright!不用写一行代码!自动生成代码还竟然如此流畅!妈妈再也不用担心我不会写代码了!
1893 0
微软开源最强Python自动化神器Playwright!不用写一行代码!自动生成代码还竟然如此流畅!妈妈再也不用担心我不会写代码了!