浏览器渲染机制(二)下

简介: 浏览器渲染机制(二)下

Draw


好,现在到了Draw这个步骤,当每个图块都被光栅化之后,合成器线程会为每个图块生成draw quads(在屏幕的指定位置绘制图块的指令,也包含了属性树里面的变换,特效等操作),这些draw quads会被封装在CompositorFrame对象里面,CompositorFrame对象也是Render Process的产物,它会被提交到Gpu Process中,我们平时提到的60fps输出帧率指的帧其实就是CompositorFrame。


Draw指的就是 把光栅化的图块,转换成draw quads的过程。


网络异常,图片无法展示
|


Display Compositor


Display


Draw操作完成之后,就生成了CompositorFrame,它会被输出到Gpu process,它会从多个来源的Render Process接收CompositorFrame。


多个来源:


  • Browser Process也有自己的Compositor来生成Compositor Frame,这些一般是用来绘制Browser UI(导航栏,窗口等)
  • 每次创建tab或者使用iframe,会创建一个独立的Render Process。


网络异常,图片无法展示
|


Display Compositor运行在Viz Compositor thread,Viz会调用OpenGL指令来渲染Compositor Frame里面的draw quads,把像素点输出到屏幕上。


VIz也是双缓冲输出的,它会在后台缓冲区绘制draw quads,然后执行交换命令最终让它们显示在屏幕上。


双缓冲机制:


在渲染的过程中,如果只对一块缓冲区进行读写,这样会导致一方面屏幕要等待去读,而GPU要等待去写,这样要造成性能低下。一个很自然的想法是把读写分开,分为:


  • 前台缓冲区(Front Buffer):屏幕负责从前台缓冲区读取帧数据进行输出显示。
  • 后台缓冲区(Back Buffer):GPU负责向后台缓冲区写入帧数据。


这两个缓冲区并不会直接进行数据拷贝(性能问题),而是在后台缓冲区写入完成,前台缓冲区读出完成,直接进行指针交换,前台变后台,后台变前台,那么什么时候进行交换呢,如果后台缓存区已经准备好,屏幕还没有处理完前台缓冲区,这样就会有问题,显然这个时候需要等屏幕处理完成。屏幕处理完成以后(扫描完屏幕),设备需要重新回到第一行开始新的刷新,这期间有个间隔(Vertical Blank Interval),这个时机就是进行交互的时机。这个操作也被称为垂直同步(VSync)。


垂直同步也会在后续进行介绍。


viz


viz是做什么的?


在 Chromium 中 viz 的核心逻辑运行在 GPU 进程中,负责接收其他进程(渲染进程?)产生的 viz::CompositorFrame(简称 CF),然后把这些 CF 进行合成,并将合成的结果最终渲染在窗口上。


CF是什么?


一个 CF 对象表示一个矩形显示区域中的一帧画面。内部存储了 3 类数据,分别是 CompositorFrameMetadata, TransferableResoruce 和 RenderPass/DrawQuad,如下图所示:


网络异常,图片无法展示
|


  • CompositorFrameMetadata:CF的元数据,比如画面的缩放级别,滚动区域。。。
  • TransferableResoruce :CF引用到的资源。
  • RenderPass/DrawQuad:CF包含的绘制操作。viz::RenderPass 由一系列相关的 viz::DrawQuad 构成。

CF 是 viz 中的核心数据结构,它代表某块区域中UI的一帧画面,使用 DrawQuad 来存储 UI 要显示的内容。它代表了 viz 运行时的数据流。



CF合成


CF 合成指的是viz线程把 CF 中的内容或者多个 CF 合成到一起,形成一个完整的画面。


CF渲染


CF 的渲染主要由 viz::DirectRendererviz::OutputSurface 负责,他们将合成的结果渲染到程序选择的渲染目标上去。


CC


现在我们在统一的梳理一下cc的功能作用以及cc的工作流程。


网络异常,图片无法展示
|


我们先回顾一下,看看上面这个图,Blink 进行了DOM,Style,Layout,comp.assign,prepaint,paint。


我们可以发现,Paint是blink和cc对接的桥梁。


整体的流程其实可以 理解成:


Blink一顿操作 -> paint生成了cc模块的数据源(cc:layer)->commit->(Tiling->)Raster->Active->draw(submit)->Viz(呈像)

就是Blink一顿操作,并在paint阶段生成cc的数据源,cc进行一系列操作并最终在draw阶段将结果(CF)提交给viz。也就是说,Blink负责网页内容绘制,cc负责将绘制的结果合成并提交给viz。


cc的架构设计


cc的设计相对比较简单,我们可以把他理解成一个多线程调度的异步流水线,运行在 Browser 进程中的 cc 负责合成浏览器非网页部分的 UI,运行在 Renderer 进程中的 cc 负责网页的合成。


网络异常,图片无法展示
|


chromium.googlesource.com/chromium/sr… how cc works官网有这样一张图,我觉得可以很好的反应cc的核心逻辑。


cc的多线程体现在在不同的阶段,cc运行在不同线程中,Paint 运行在 Main 线程,CommitActivateSubmit 运行在 Compositor 线程,而 Raster 运行在专门的 Raster 线程。


下面我们开始对cc流水线的各个阶段进行分析。


Paint


Paint阶段会产生cc的数据源(cc:layer树),一个cc:layer会表示一个矩形区域的UI,它有很多子类,用于存储不同类型的UI数据:


  • cc::PictureLayer。用于实现自绘型的UI组件,比如上层的各种 Button,Label 等都可以用它来实现。它允许外部通过实现 cc::ContentLayerClient 接口提供一个 cc::DisplayItemList 对象,它表示一个绘制操作的列表,记录了一系列的绘制操作,比如画线,画矩形,画圆等。通过 cc::PaintCanvas 接口可以方便的创建复杂的自绘 UI。cc::PictureLayer 还是唯需要 Raster 的 cc::Layer。它经过 cc 的流水线之后转换为一个或多个 viz::TileDrawQuad 存储在 viz::CompositorFrame 中。
  • cc::TextureLayer 对应 viz 中的 viz::TextureDrawQuad,所有想要使用自己的逻辑进行 Raster 的 UI 组件都可以使用这种 Layer,比如 Flash 插件,WebGL等。
  • cc::SurfaceLayer 对应 viz 中的 viz::SurfaceDrawQuad,用于嵌入其他的 CompositorFrame。Blink 中的 iframe 和视频播放器可以使用这种 Layer 实现。
  • cc::SolidColorLayer 用于显示纯色的 UI 组件。
  • cc::VideoLayer 以前用于专门显示视频,被 SurfaceLayer 取代。
  • cc::UIResourceLayer/cc::NinePatchLayer 类似 TextureLayer,用于软件渲染。

Blink 通过以上各种 cc::Layer 来描述 UI 并实现和 cc 的对接。由于 cc::Layer 本身可以保存 Child cc::Layer,因此给定一个 Layer 对象,它实际上表示一棵 cc::Layer 树,这个 Layer 树即主线程 Layer 树,因为它运行在主线程中,并且主线程有且只有一棵 cc::Layer 树。


Commit


Commit 阶段的核心作用是将保存在 cc::Layer 中的数据提交到 cc::LayerImpl 中。cc::LayerImplcc::Layer 一一对应,只不过运行在 Compositor 线程中(也称为 Impl 线程)。在 Commit 完成之后会根据需要创建 Tiles 任务,这些任务被 Post 到 Raster 线程中执行。


Tiling+Raster


在 Commit 阶段创建的 Tiles 任务(cc::RasterTaskImpl)在该阶段被执行。Tiling 阶段最重要的作用是将一个 cc::PictureLayerImpl 根据不同的 scale 级别,不同的大小拆分为多个 cc::TileTask 任务。Raster 阶段会执行每一个 TileTask,将 DisplayItemList 中的绘制操作 Playback 到 viz 的资源中。 由于 Raster 比较耗时,属于渲染的性能敏感路径,因此Chromium在这里实现了多种策略以适应不同的情况。这些策略主要在两方面进行优化,一方面是 Raster 结果(也就是资源)存储的位置,一方面是 Raster 中 Playback 的方式。这些方案被封装在了 cc::RasterBufferProvider 的子类中,下面一一进行介绍:


  • cc::GpuRasterBufferProvider 使用 GPU 进行 Raster,Raster 的结果直接存储在 SharedImage 中。(前文以及提到过的硬件加速)
  • cc::OneCopyRasterBufferProvider 使用 Skia 进行 Raster,结果先保存到 GpuMemoryBuffer 中,然后再将 GpuMemoryBuffer 中的数据通过 CopySubTexture 拷贝到资源的 SharedImage 中。GpuMemeoryBuffer 在不同平台有不同的实现,也并不是所有的平台都支持,在 Linux 平台上底层实现为 Native Pixmap(来自X11中的概念),在 Windows 平台上底层实现为 DXGI,在 Android 上底层实现为 AndroidHardwareBuffer,在 Mac 上底层实现为 IOSurface。
  • cc::ZeroCopyRasterBufferProvider 使用 Skia 进行 Raster,结果保存到 GpuMemoryBuffer 中,然后使用 GpuMemoryBuffer 直接创建 SharedImage。
  • cc::BitmapRasterBufferProvider 使用 Skia 进行 Raster,结果保存到共享内存中。


Raster最终会产生一个资源,这个资源被记录在了cc:PictureLayerImpl 中,他们会在Draw阶段被放在CF中。


Activate


在 Impl 端有三个 cc::LayerImpl 树,分别是 Pending,Active,Recycle 树。Commit 阶段提交的目标其实就是 Pending 树,Raster 的结果也被存储在了 Pending 树中。

在 Activate 阶段,Pending 树中的所有 cc::LayerImpl 会被复制到 Active 树中,为了避免频繁的创建 cc::LayerImpl 对象,此时 Pending 树并不会被销毁,而是退化为 Recycle 树。


和主线程 cc::Layer 树不同,cc::LayerImpl 树并不是自己维护树形结构的,而是由 cc::LayerTreeImpl 对象来维护 cc::LayerImpl 树的。三个 Impl 树分别对应三个 cc::LayerTreeImpl 对象。


Draw


Draw 阶段并不执行真正的绘制,而是遍历 Active 树中的 cc::LayerImpl 对象,并调用它的 cc::LayerImpl::AppendQuads 方法创建合适的 viz::DrawQuad 放入 CompositorFrame 的 RenderPass 中。cc::LayerImpl 中的资源会被创建为 viz::TransferabelResource 存入 CompositorFrame 的资源列表中。至此一个 viz::CompositorFrame 对象创建完毕,最后通过 cc::LayerTreeFrameSink 接口将该 CompositorFrame 发送到给 viz 进程(GPU进程)进行渲染。


总结


现在整个渲染流程就基本结束了,前端的代码已经变成了屏幕上的像素点了。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
2月前
|
存储 前端开发 开发者
|
1月前
|
前端开发 JavaScript
宏任务和微任务在浏览器渲染过程中的执行顺序
宏任务和微任务是浏览器事件循环中的两种任务类型。宏任务包括整体代码块、setTimeout等,微任务有Promise.then、MutationObserver等。每个宏任务执行完毕后,会先执行完所有微任务,再进行下一轮渲染或执行下一个宏任务。
|
1月前
|
JavaScript 前端开发 API
浏览器渲染过程中如何处理异步任务
在浏览器渲染过程中,异步任务通过事件循环机制处理。JS执行时,同步任务在主线程上执行,形成一个执行栈。异步任务则被推入任务队列中,待主线程空闲时按顺序调用,确保页面流畅渲染与响应。
|
2月前
|
缓存 自然语言处理 前端开发
浏览器渲染
【10月更文挑战第28天】浏览器渲染涉及将HTML、CSS和JavaScript代码转换为可视网页,主要步骤包括:解析HTML构建DOM树、解析CSS构建CSSOM树、合并DOM与CSSOM生成渲染树、布局确定元素位置和尺寸、绘制元素到屏幕、合成图层形成最终图像。此过程不断优化以提升性能。
|
2月前
|
前端开发 JavaScript 异构计算
简述浏览器的渲染原理
浏览器渲染原理主要包括以下步骤:1)解析HTML文档生成DOM树;2)解析CSS生成CSSOM树;3)结合DOM与CSSOM生成渲染树;4)布局计算(回流)确定元素大小和位置;5)绘制(Paint)将节点转为图形内容;6)合成(Composite)多层图像。整个过程从文档解析到最终输出完整网页,并通过优化技术提升性能。
|
7月前
|
缓存 JavaScript 前端开发
浏览器渲染:理解页面加载的幕后工作
浏览器渲染:理解页面加载的幕后工作
|
6月前
|
监控 前端开发 JavaScript
记录浏览器节能机制导致Websocket断连问题
近期,在使用WebSocket(WS)连接时遇到了频繁断连的问题,这种情况在单个用户上每天发生数百次。尽管利用了socket.io的自动重连机制能够在断连后迅速恢复连接,但这并不保证每一次重连都能成功接收WS消息。因此,我们进行了一些的排查和测试工作。
517 1
记录浏览器节能机制导致Websocket断连问题
|
5月前
|
JavaScript 前端开发 开发者
浏览器事件机制详解
浏览器事件机制详解
59 1
|
5月前
|
存储 JavaScript 前端开发
在?聊聊浏览器事件循环机制
在?聊聊浏览器事件循环机制
50 0
|
6月前
|
移动开发 前端开发 JavaScript
浏览器端图表渲染技术SVG, VML HTML Canvas
浏览器端图表渲染技术SVG, VML HTML Canvas
47 0