在 Chromium 中 viz
的核心邏輯執行在 GPU 程序中,負責接收其他程序產生的 viz::CompositorFrame(簡稱 CF)
,然後把這些 CF 進行合成,並將合成的結果最終渲染在視窗上。
可以將這個過程拆解成以下幾個步驟來分析:
viz
的初始化;viz
的架構設計;viz
的初始化只是一次性的過程,而且比較枯燥,所以放到最後再介紹。先來看一下 viz::CompositorFrame(簡稱 CF)
的建立,因為它是 viz
中的核心資料結構。
1. CF 的建立
一個 CF 物件表示一個矩形顯示區域中的一幀畫面。內部儲存了 3 類資料,分別是 CompositorFrameMetadata, TransferableResoruce 和 RenderPass/DrawQuad,如下圖所示:
2. CF 的後設資料(Metadata)
viz::CompositorFrameMetadata
記錄了 CF 相關的後設資料,比如畫面的縮放級別,捲動區域,參照到的 Surface 等。需要注意的是這裡面還儲存了一個 ui::LatencyInfo
物件,該物件記錄了從 Chromium 接收到使用者的互動事件到畫面更新每個階段的延時資訊,具體都有哪些時間被記錄了下來,參見 latency_info.h。通過 ui::LatencyInfo
物件可以方便的追蹤互動到渲染各階段的延時資訊。
3. CF 參照到的資源(Resource)
viz::TransferableResource
記錄了該 CF 參照到的資源,所謂的資源可以理解為一張圖片。資源有兩種存在形式,一類是儲存在記憶體中的 Software 資源,一類是儲存在 GPU 中的 Texture(這樣表達並不嚴格,但目前為止大家都可以先這樣理解)。如果沒有開啟 Chromium 的硬體加速渲染,則只能使用 Software 資源。如果開啟了硬體加速,則只能使用硬體加速的資源。
如何建立一個資源呢? 如果是 Software 資源,只需要申請一塊記憶體(如果資源需要誇程序可以使用共用記憶體),然後將資源的畫素資料放進去就可以了。就像下面這樣:
上面的程式碼直接將 SkBitmap
中的畫素資料拷貝到共用記憶體中,然後在 viz::TransferableResource
中參照該共用記憶體的地址。
如果是 Texture 資源,就要複雜一點,因為需要先初始化 GL Context。關於如何初始化 GL Context 放在後面的 viz
初始化中解釋,這裡先忽略初始化過程直接看如何建立 GL 資源。範例程式碼如下:
上面的程式碼將 SkBitmap
中的畫素資料使用 gpu::SharedImageInterface
介面放入 SharedImage
中,然後使用它返回的 Mailbox 建立 viz::TransferableResource
。就像程式碼中註釋的那樣,不一定非要使用畫素資料來建立 SharedImage,也可以使用 GPU-R 和 OOP-R 相關介面來建立 SharedImage。關於 Mailbox,SharedImage 以及 GPU-R 和 OOP-R 的相關內容可以參考Chromium GPU Resource Share (Shared Image)。
哪些內容會以資源的形式存在呢? 結果可能出乎你的意料,網頁中的每一個 Tile 都參照一個資源。cc
會把網頁分成很多的 Tiles,每一個都以 viz::TileDrawQuad
的形式存在,而每一個 viz::TileDrawQuad
只是簡單參照一個資源(關於 DrawQuad 見下文)。此時,這些資源就是 cc
Raster 的產物。
4. CF 包含的繪製操作(RenderPass/DrawQuad)
viz::RenderPass
由一系列相關的 viz::DrawQuad
構成。可以對一個 RenderPass 單獨應用特效,變換,mipmap,快取,截圖(CopyOutputRequest)等。DrawQuad 有很多種型別,分別用於不同的目的:
viz::TextureDrawQuad
內部參照一個資源。viz::TileDrawQuad
表示一個 Tile 塊(Tile的概念見cc
),和 TextureDrawQuad 類似,內部也參照一個資源,DisplayItemList 會被 cc
Raster 為 TileDrawQuad;viz::PictureDrawQuad
內部直接存放 DisplayItemList,但是目前只能用於Android WebView;viz::SolidColorDrawQuad
表示一個顏色塊;viz::RenderPassDrawQuad
內部參照另外一個 RenderPass 的 Id;viz::SurfaceDrawQuad
內部儲存一個 viz::SurfaceId,該 Surface 的內容由其他 CompositorFrameSinkClient
建立,用於 viz 的巢狀,比如 OOPIF,OffscreenCanvas等;
由於 viz::RenderPassDrawQuad
的存在使得 CF 中可以儲存一個 RenderPass 樹,由於 viz::SurfaceDrawQuad
的存在使得viz可以實現UI的誇程序巢狀。
下面是一個典型的 CF 範例(圖片來自這裡):
5. 總結
CF 是 viz
中的核心資料結構,它代表某塊區域中UI的一幀畫面,使用 DrawQuad 來儲存 UI 要顯示的內容。它代表了 viz 執行時的資料流。
6. 參考文獻
https://keyou.github.io/blog/2020/07/29/how-viz-works/