1. VIZ的三个端

在规划层面上 viz 的架构如下图所示:

Chromium 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的架构能够用下图表明:

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::Display,viz::DisplaySchedulerviz::SurfaceAggregator,viz::DirectRenderer也运行在该线程中。而一切终究真实的制作(比方 GL 的履行,Real SwapBuffer)终究都运行在CrGpuMainChrome_InProcGpuThread线程中。

如果要在架构上表现这两个线程,能够这样划分:

Chromium VIZ架构详解

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

现在 Compositor 线程和 GPU 线程之间的数据传递有两种方法,一种是先将 GL 调用放入进程内的 CommandBuffer,然后在 GPU 线程中取出 GL 指令并履行,GLOutputSurface 运用这种方法。另一种是直接经过 PostTask 即将烘托的操作发送到 GPU 线程,SkiaOutputSurface 运用这种方法(关于 GLOutputSurface 和 SkiaOutputSurface 见下文)。

3. VIZ的类图

下面是 viz 更具体的类图:

Chromium 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::DirectRendererviz::OutputSurface用于办理烘托方针 。他们对了解 Chromium UI 的呈现方法至关重要。这两个类并不是彼此独立的,在 Chromium 中他们有以下组合:

1.1. viz::GLRenderer viz::GLOutputSurface

    GLRenderer 已经被标记为 deprecated, 未来会被 SkiaRenderer 替代。它运用基于 CommandBufferGL Context 来烘托 DrawQuadGLOutputSurface 上,GLOutputSurface 运用窗口句柄创立 Native GL ContextGL 调用发生在 VizCompositorThread 线程中,经过 InProcessCommandBuffer 这些 GL 调用终究在`CrGpuMain`线程中履行。关于 CommandBuffer 相关内容能够参阅[Chromium Command Buffer](https://keyou.github.io/blog/2020/06/10/commandbuffer/)。  
    GLOutputSurface 有一系列的子类,不同的子类对接不同渠道的烘托方针,比方 GLOutputSurfaceAndroid 用于对接Android渠道的烘托,GLOutputSurfaceOffscreen 用于支撑 GL 的离屏烘托等。
1.  `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烘托。
1.  `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中,下面是其首要的履行逻辑:

Chromium VIZ架构详解

6. VIZ的分层

从分层架构的角度看,viz 的 API 分了3层,分别为最底层中心完结,中间层mojo接口,最上层viz服务。 最底层是viz的中心完结,首要接口包含viz::FrameSinkManager(Impl),viz::CompositorFrameSink(Support),viz::Display,viz::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::GpuHostImpl,viz::GpuServiceImpl,viz::VizMainImpl,viz::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 都至少对应一个FrameSinkIdLocalSurfaceId,在 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. 参阅文献

keyou.github.io/blog/2020/0…

Chromium VIZ架构详解