Chromium CC渲染層工作流詳解

rmb_999發表於2023-10-30

1. Chromium 的渲染流水線

Blink —> Paint -> Commit -> (Tiling ->) Raster -> Activate -> Draw(Submit) —> Viz

Blink 對接 cc 的繪製介面進行 Paint,Paint 生成 cc 模組的資料來源(cc::Layer),CC 將資料來源進行合成,經過一系列過程最終在 Draw 階段將合成的結果(viz::CompositorFrame)提交到 Viz。 也就是說,Blink 負責網頁內容的繪製,cc 負責將繪製的結果進行合成並提交到 Viz

Blink 負責瀏覽器網頁部分的繪製,//ui/views 模組負責瀏覽器非網頁部分 UI 的繪製,它們的繪製都對接 cc。如果我們擴充套件 //ui/views 的能力,就可以在 cc 之上建立一套新的 UI 框架,比如我們把這套新的 UI 框架叫做 Flutter。真正的 Flutter 當然沒有這麼簡單,但其實 Flutter 最初是由 Chromium 的開發人員建立的(資訊來自網路,未求證),所以 Flutter 渲染流水線或多或少的會吸取 Chromium 渲染的經驗。最近 UC 核心團隊開始投入 Flutter 的研究也算他們團隊的一個新方向了。

2. cc 的架構設計

cc 的設計相對簡單,它不直接涉及跨程式操作(它依賴的 SharedImage 以及 Viz 負責誇程式處理),可以理解為一個簡單的多執行緒非同步流水線。執行在 Browser 程式中的 cc 負責合成瀏覽器非網頁部分的 UI,執行在 Renderer 程式中的 cc 負責網頁的合成。

在 How cc Works 中給出的下圖能很好的反應 cc 的核心邏輯:

cc 的多執行緒體現在不同階段執行在不同的執行緒中,Paint 執行在 Main 執行緒,CommitActivateSubmit 執行在 Compositor 執行緒,而 Raster 執行在專門的 Raster 執行緒。

不同的 cc client 有自己的 paint 方式,比如 views 和 blink 都是 cc 的 client,他們有各自不同的 paint 邏輯,但是其他比如 commit,raster 等都是一樣的邏輯,因此如果要學習 cc 可以從 views 入手,不必一開始就深入到 blink 中。

下面是基於這個 demo 的 cc 類圖:

 

圖中藍色表示核心資料結構,用於儲存資料,橙色表示核心類/介面,用於執行核心邏輯。

3. 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,關於 Raster 見下文。它經過 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::UIResourceLayer/cc::NinePatchLayer 類似 TextureLayer,用於軟體渲染。
  • cc::SolidColorLayer 用於顯示純色的 UI 元件。
  • cc::VideoLayer 以前用於專門顯示影片,被 SurfaceLayer 取代。

Blink 和 //ui/views 等上層 UI 繪製引擎透過以上各種 cc::Layer 來描述 UI 並實現和 cc 的對接。由於 cc::Layer 本身可以儲存 Child cc::Layer,因此給定一個 Layer 物件,它實際上表示一棵 cc::Layer 樹,這個 Layer 樹即主執行緒 Layer 樹,因為它執行在主執行緒中,並且主執行緒有且只有一棵 cc::Layer 樹。

下面是執行 Paint 的堆疊,這裡是呼叫 //ui/views 元件的堆疊,如果是 Blink 則 aura 和 views 名稱空間下的堆疊會換成 blink。

 blink 的如下:

4. Commit

Commit 階段的核心作用是將儲存在 cc::Layer 中的資料提交(PushPropertiesTo)到 cc::LayerImpl 中。cc::LayerImpl 和 cc::Layer 一一對應,只不過執行在 Compositor 執行緒中(也稱為 Impl 執行緒)。在 Commit 完成之後會根據需要建立 Tiles 任務,這些任務被 Post 到 Raster 執行緒中執行。

下面是 Commit 部分的堆疊:

 5. 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 中(可以理解為 GLTexture,詳細資訊見 GPU 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 階段被放入 CompositorFrame 中。

除了 cc::PictureLayer 的 Raster 之外,在該階段還會進行圖片的解碼,關於這部分的詳細資訊以後如果有時間再補充。

TODO: 補充 GpuMemoryBuffer 和 圖片解碼相關內容。

下面是抓取到的 cc::RasterTaskImpl 的執行流程:

 6. 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 物件。

下面是 Activate 階段的執行堆疊:

 7. Draw

Draw 階段並不執行真正的繪製,而是遍歷 Active 樹中的 cc::LayerImpl 物件,並呼叫它的 cc::LayerImpl::AppendQuads 方法建立合適的 viz::DrawQuad 放入 CompositorFrame 的 RenderPass 中。cc::LayerImpl 中的資源會被建立為 viz::TransferabelResource 存入 CompositorFrame 的資源列表中。至此一個 viz::CompositorFrame 物件建立完畢,最後透過 cc::LayerTreeFrameSink 介面將該 CompositorFrame 傳送到給 viz 程式(GPU程式)進行渲染。

下面是 Draw 階段的執行堆疊:

 8. Scheduler 排程器

上面已經介紹了 cc 渲染流水線的各個過程,這些過程是由 cc::Scheduler 進行排程的。它控制 cc 在合適的時機執行合適的動作,內部維護了一個渲染的狀態機, 其核心排程邏輯在 cc::Scheduler::ProcessScheduledActions() 中,程式碼如下:

 9. 參考文獻

 

相關文章