首页 > 其他分享 >Chromium VIZ架构详解

Chromium VIZ架构详解

时间:2023-11-02 15:22:04浏览次数:35  
标签:渲染 CF 接口 client viz 详解 线程 VIZ Chromium

1. VIZ的三个端

在设计层面上 viz 的架构如下图所示:

在设计上 viz 分了三个端,分别是 client 端, host 端和 service 端。

client 端用于生成要显示的画面(CF)。应用中至少有一个 root client,可以有多个 child client,它们组成了一个 client 树,每个 Client 都有一个 FrameSinkId 和一个 LocalSurfaceId,如果父子 client 之间的 UI 需要嵌入,则子 client 作为 SurfaceDrawQuad 嵌入到父 client 中。在 Chromium 中,每一个浏览器窗口都对应一个 client 树,拥有一个 root client 和零个或多个子 client。比如,网页中的一个 OOPIF 可以是一个子 client,OffscreenCanvas 也是一个子 client。每个 client 可以独立进行刷新,生成独立的 CF。client 端的核心接口为 viz::mojom::CompositorFrameSinkClient, 开发者可以通过继承该类来创建一个 client。

host 端用于将 client 注册到 service,只能运行在特权端(没有沙箱,比如浏览器进程),负责协助 client 建立起和 service 的连接,也负责建立 client 和 client 之间的树形关系。所有的 client 想要生效必须经过 host 进行注册。host 端的核心接口为 viz::mojom::FrameSinkManagerClient,以及其实现类 viz::HostFrameSinkManager

service 端运行 Viz 的内核,进行 UI 合成以及最终的渲染。它接收所有 client 端生成的 CF,然后把这些 CF 进行合成,并最终显示在窗口中。它内部会为每个 client 创建一个 viz::CompositorFrameSink(Impl/Support),然后通过 viz::Display 将这些 CF 进行合成,最后通过 viz::DirectRenderer 将这些 CF 渲染到 viz::OutputSurface 中,viz::OutputSurface 包装了各种渲染目标,比如窗口,内存等。

在 Chromium 的具体实现中 viz 的架构可以用下图表示:

 绿色部分为 client 的实现,负责提交 CF 到 GPU, 橙色部分 ui::Compositor 为 host 的实现,负责将所有的 client 注册到 service,红色部分为 service 端的实现,负责接收、合成,并最终渲染 CF。

2. VIZ 的线程架构

 

viz 是多线程架构,其中最重要的有两个线程,一个是 VizCompositorThread,也称为(viz的) Compositor 线程(注意和 cc 中的 Compositor 线程区分,它们没有关系)。另一个是 CrGpuMain (多进程架构下)或者 Chrome_InProcGpuThread(单进程架构下)线程,也称为 GPU 线程(只有在开启了硬件加速渲染时才存在)。帧率的调度,CF 的合成,DrawQuad 的绘制都发生在Compositor线程中,viz::Displayviz::DisplaySchedulerviz::SurfaceAggregatorviz::DirectRenderer 也运行在该线程中。而所有最终真实的绘制(比如 GL 的执行,Real SwapBuffer)最终都运行在 CrGpuMain 或 Chrome_InProcGpuThread 线程中。

 

如果要在架构上体现这两个线程,可以这样划分:

虚线之上运行在 Compositor 线程,虚线之下运行在 GPU 线程,OutputSurface 需要对接 DirectRenderer 和最下层的渲染,所以不同部分运行在不同的线程中。

目前 Compositor 线程和 GPU 线程之间的数据传递有两种方式,一种是先将 GL 调用放入进程内的 CommandBuffer,然后在 GPU 线程中取出 GL 命令并执行,GLOutputSurface 使用这种方式。另一种是直接通过 PostTask 将要渲染的操作发送到 GPU 线程,SkiaOutputSurface 使用这种方式(关于 GLOutputSurface 和 SkiaOutputSurface 见下文)。

3. VIZ 的类图

下面是 viz 更详细的类图:

 

 

  • viz::mojom::CompositorFrameSinkClient 作为 client,表示一个画面的来源;
  • viz::CompositorFrameSink(Impl/Support) 用于处理 CF 的地方,一个 client 可以有多个 CompositorFrameSink;
  • viz::RootCompositorFrameSinkImpl 作用和 CompositorFrameSink 类似,只不过专门处理 root client,每个 client 有且只有一个该对象。它还负责为对应的 client 初始化渲染环境,包括 OutputSurface, Display 的创建。
  • viz::FrameSinkManagerImpl 用于管理 CompositorFrameSink, 包括其创建和销毁;
  • viz::SurfaceAggregator 负责 Surface/CF 的合成,比如dirty区域的计算等,不负责绘制;
  • viz::OutputSurface 封装渲染目标,和各平台的渲染目标直接对接;
  • viz::DirectRenderer 封装绘制 DrawQuad 的方式,负责将 DrawQuad 绘制到 OutputSurface 上;
  • viz::Display 一个中控类,将 SurfaceAggregator/DirectRenderer 以及 Overlay 的功能串起来形成流水线;
  • viz::DisplayScheduler 调度 viz::Display 何时应该采取行动;

4. VIZ 的渲染目标

 

viz::DirectRenderer 和 viz::OutputSurface 用于管理渲染目标 。他们对理解 Chromium UI 的呈现方式至关重要。这两个类并不是相互独立的,在 Chromium 中他们有以下组合:

 

    1. viz::GLRenderer + viz::GLOutputSurface
      GLRenderer 已经被标记为 deprecated, 未来会被 SkiaRenderer 取代。它使用基于 CommandBuffer 的 GL Context 来渲染 DrawQuad 到 GLOutputSurface 上,GLOutputSurface 使用窗口句柄创建 Native GL Context。GL 调用发生在 VizCompositorThread 线程中,通过 InProcessCommandBuffer 这些 GL 调用最终在 CrGpuMain 线程中执行。关于 CommandBuffer 相关内容可以参考 Chromium Command Buffer
      GLOutputSurface 有一系列的子类,不同的子类对接不同平台的渲染目标,比如 GLOutputSurfaceAndroid 用于对接Android平台的渲染,GLOutputSurfaceOffscreen 用于支持 GL 的离屏渲染等。

    2. viz::SkiaRenderer + viz::SkiaOutputSurface(Impl) + viz::SkiaOutputDevice
      SkiaRenderer 是未来的发展方向,以后所有其他的渲染方式都会被这种方式取代。因为它具有最大的灵活性,同时支持GL渲染,Vulkan渲染,离屏渲染等。
      SkiaRenderer 将 DrawQuad 绘制到由 SkiaOutputSurfaceImpl 提供的 canvas 上,但是该 canvas 并不会进行真正的绘制动作,而是通过 skia 的 ddl(SkDeferredDisplayListRecorder) 机制把这些绘制操作记录下来,等到所有的 RenderPass 绘制完成,这些被记录下来的绘制操作会被通过 SkiaOutputSurfaceImpl::SubmitPaint 发送到 SkiaOutputSurfaceImplOnGpu 中进行真实的绘制,根据名字可知该类运行在 GPU 线程中。
      SkiaOutputSurface 对渲染目标的控制是通过 SkiaOutputDevice 实现的,后者有很多子类,其中 SkiaOutputDeviceOffscreen 用于实现离屏渲染,SkiaOutputDeviceGL 用于GL渲染。

    3. viz::SoftwareRenderer + viz::SoftwareOutputSurface + viz::SoftwareOutputDevice
      SoftwareRenderer 用于纯软件渲染,当关闭硬件加速的时候使用该种渲染方式。这种方式逻辑相对简单,因此留给读者去探索

5. VIZ 的数据流

viz 中的核心数据为 CF,当用户和网页进行交互时,会触发 UI 的改变,这最终会导致 viz client (比如 cc::LayerTreeHostImpl) 创建一个新的 CF 并使用 viz::mojom::CompositorFrameSink 接口将该 CF 提交到 service,service 中的 viz::CompositorFrameSinkSupport 会为该 CF 所属的 client 创建一个 viz::Surface,然后把 CF 放入该 viz::Surface 中。当 servcie 中的 viz::DisplayScheduler 到达下一个调度周期的时候,会通知 viz::Display 取出当前的 viz::Surface,并交给 viz::SurfaceAggregator 进行合成,合成的结果会被交给 viz::DirectRenderer 进行渲染。viz::DirectRenderer 并不直接渲染,它从 viz::OutputSurface 中取出渲染表面,然后让其子类在该渲染表面上进行绘制。viz::OutputSurface 封装了渲染表面,比如窗口,内存bitmap等。绘制完成之后,viz::Display 会调用 viz::DirectRenderer::SwapBuffer 将该 CF 最终显示出来。

service 绘制 CF 的核心逻辑位于 viz::Display::DrawAndSwap 中,下面是其主要的执行逻辑:

6. VIZ 的分层

从分层架构的角度看,viz 的 API 分了3层,分别为最底层核心实现,中间层mojo接口,最上层viz服务。

最底层是viz的核心实现,主要接口包括 viz::FrameSinkManager(Impl)viz::CompositorFrameSink(Support)viz::Displayviz::OutputSurface。 使用这些接口是使用 viz 最直接的方式,提供最大的灵活性。但这层接口不提供夸进程通信的能力。 Chromium中直接使用这一层的接口的地方不多,具体demo参考 chromium_demo/demo_viz_offscreen.cc at c/80.0.3987 · keyou/chromium_demo

中间mojo层主要将底层API包装到 mojo 接口中,这一层的核心接口包括 viz::HostFrameSinkManager,viz::mojom::FrameSinkManager,viz::mojom::CompositorFrameSink,viz::mojom::CompositorFrameSinkClient,viz::HostDisplayClient。这些接口大多对应底层的 API 接口,将对底层接口的调用转换为对mojo接口的调用,因此这层接口提供夸进程通信的能力。Chromium中几乎所有使用viz的地方都是使用该层接口。viz模块提供的官方demo也是使用该层接口,也可以参考我写的单文件demo,见 chromium_demo/demo_viz_gui.cc at c/80.0.3987 · keyou/chromium_demo

最上层viz服务接口主要将 viz 服务化,提供将viz运行在独立进程的能力。这一层的主要接口包括 viz::GpuHostImplviz::GpuServiceImplviz::VizMainImplviz::Gpu。这些接口需要和中间层mojo接口配合才能起作用,在 Chromium 的多进程架构中使用了该层接口。使用该层接口的demo参考 chromium_demo/demo_viz_gui_gpu.cc at c/80.0.3987 · keyou/chromium_demo

另外,在 viz 中还有一套专门用于 viz 中 GPU 渲染的接口 viz::*ContextProvider。它主要负责为 viz 初始化 GL 环境,使 viz 可以使用 GPU 进行渲染

7. VIZ 的 ID 设计

每一个 client 都至少对应一个 FrameSinkId 和 LocalSurfaceId,在 client 的整个生命周期中所有 FrameSink 的 client_id(见下文)都是固定的,而 LocalSurfaceId 会根据 client 显示画面的 size 或 scale factor 的改变而改变。他们两个共同组成了 SurfaceId,用于在 service 端全局标识一个 Surface 对象。也就是说对于每一个 Surface,都可以获得它是由谁在什么 size 或 scale facotr 下产生的。

FrameSinkId

  • client_id = uint32_t, 每个 client 都会有唯一的一个 ClientId 作为标识符,标识一个 CompositorFrame 是由哪个 client 产生的,也就是标识 CompositorFrame 的来源;
  • sink_id = uint32_t, 在 service 端标识一个 CompositorFrameSink 实例,Manager 会为每个 client 在 service 端创建一个 CompositorFrameSink,专门用于处理该 client 生成的 CompositorFrame,也就是标识 CompositorFrame 的处理端;
  • FrameSinkId = client_id + sink_id,将 CF 的来源和处理者关联起来。

FrameSinkId 可以由 FrameSinkIdAllocator 辅助类生成。

在实际使用中,往往一个进程是一个 client,专门负责一个业务模块 UI 的实现,而一个业务往往由很多的 UI 元素组成,因此可以让每个 FrameSink 负责一部分的 UI,此时就用到了单 client 多 FrameSink 机制,这种机制可以实现 UI 的局部刷新(多 client 也能实现这一点)。在 chrome 中浏览器主程序是一个 client,而每一个插件都是一个独立的 client,网页中除了一些特殊的元素比如 iframe和offscreen canvas位于单独的client中,其他元素全部都位于同一个client中。

LocalSurfaceId

  • parent_sequence_number = uint32_t, 当自己作为父 client,并且 surface 的 size 和 device scale factor 改变的时候改变;
  • child_sequence_number = uint32_t, 当自己作为子 client,并且 surface 的 size 和 device scale factor 改变的时候改变;
  • embed_token = 可以理解为一个随机数, 用于避免 LocalSurafaceId 可猜测,当父 client 和子 client 的父子关系改变的时候改变;
  • LocalSurfaceId = parent_sequence_number + child_sequence_number + embed_token,当 client 的 size 当前 client 产生的画面改变。

LocalSurfaceId 可以由 ParentLocalSurfaceIdAllocator 或者 ChildLocalSurfaceIdAllocator 这两个辅助类生成。前者用于由父 client 负责生成自己的 LocalSurfaceId 的时候,后者用于由子 client 自己负责生成自己的 LocalSurfaceId 的时候。使用哪种方式要看自己的 UI 组件之间的依赖关系的设计。

LocalSurfaceId 在很多时候都包装在 LocalSurfaceIdAllocation 内,该类记录了 LocalSurfaceId 的创建时间。改时间在创建 CF 的时候需要用到。

SurfaceId

SurfaceId = FrameSinkId + LocalSurfaceId

SurfaceId 全局唯一记录一个显示画面,它可以被嵌入其他的 CF 或者 RenderPass 中,从而实现显示界面的嵌入和局部刷新。除了在 Client 端使用 SurfaceDrawQuad 进行 Surface 嵌套之外,大部分应用场景都在 service 端。

8. 参考文献

https://keyou.github.io/blog/2020/07/29/how-viz-works/

 

 

标签:渲染,CF,接口,client,viz,详解,线程,VIZ,Chromium
From: https://www.cnblogs.com/rmb999/p/17805476.html

相关文章

  • 自动化构建工具make/makefile详解
    一、工具介绍1.1为什么需要使用自动化项目构建工具呢在我看来,在平时写较少代码,只有1个源文件的时候,用make和不用make感觉差别不大,但是当项目源文件一多,需要一个个的去输入gcc进行编译,显然是十分麻烦的。所以make这个工具可以极大的提高项目完成后的测试工作。1.2make/makefile介绍......
  • STL之红黑树的模拟实现(万字长文详解)
    STL之红黑树的模拟实现红黑树的概念红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,==红黑树确保没有一条路径会比其他路径长出俩倍==,因而是==接近平衡==的。==意思就是最长路......
  • PHP大文件分割上传详解
    这篇文章主要为大家详细介绍了PHP大文件分割上传,PHP分片上传,具有一定的参考价值,感兴趣的小伙伴们可以参考一下服务端为什么不能直接传大文件?跟php.ini里面的几个配置有关upload_max_filesize=2M //PHP最大能接受的文件大小post_max_size=8M //PHP能收到的最大POST值'me......
  • 高性能渲染——详解Html Canvas的优势与性能
    本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。一、什么是Canvas想必学习前端的同学们对Canvas都不陌生,它是HTML5新增的“画布”元素,可以使用JavaScript来绘制图形。Canvas元素是在HTML5中新增......
  • keystone服务的详解
    一:keystone服务作用:1:提供其余组件的所有认证服务,令牌的管理2:用户管理:验证用户的身份合法流程就是openstack上的用户,进行一个初始化,将信息存储在数据库里面去,然后第一次登录的时候,会去数据库里面去找数据进行校验,然后返回用户一个令牌,以后用户有这个令牌的话,使用某些服务的时候......
  • 【python爬虫】80页md笔记,0基础到scrapy项目高手,第(3)篇,requests网络请求模块详解
    本文主要学习一下关于爬虫的相关前置知识和一些理论性的知识,通过本文我们能够知道什么是爬虫,都有那些分类,爬虫能干什么等,同时还会站在爬虫的角度复习一下http协议。完整版笔记直接地址:请移步这里共8章,37子模块,总计56668字requests模块本阶段本文主要学习requests这......
  • 19、模糊查询操作符详解
    模糊查询:本质是比较运算符运算符语法描述isnullaisnull如果操作符为null,则结果为真isnotnullaisnotnull如果操作符不为null,则结果为真betweenabetweenbandc若a在b和c之间,则结果为真likealikebSQL匹配,如果a匹配到b,则结果为真inain(a1......
  • google test 之 TEST_F详解
    一、基本概念:googletest三种测试用例写法:TEST(test_suite_name,test_name)第一种是最基本写法:#include<gtest/gtest.h>intadd(inta,intb){returna+b;}TEST(testAdd,testArrayAdd){inta[]={1,2,3,4,5};intb[]={5,6,7,8,9};int......
  • hadoop 基础组件详解
           ......
  • Java 线程池详解,图文并茂,还有谁不会?!
    来源:blog.csdn.net/mu_wind/article/details/113806680初识线程池我们知道,线程的创建和销毁都需要映射到操作系统,因此其代价是比较高昂的。出于避免频繁创建、销毁线程以及方便线程管理的需要,线程池应运而生。线程池优势降低资源消耗:线程池通常会维护一些线程(数量为corePool......