Chromium Command Buffer原理解析

2023-10-26 18:00:25

Command Buffer 是支撐 Chromium 多程序硬體加速渲染的核心技術之一。它基於 OpenGLES2.0 定義了一套序列化協定,這套協定規定了所有 OpenGLES2.0 命令的序列化格式,使得應用對 OpenGL 的呼叫可以被快取並傳輸到其他的程序中去執行(GPU程序),從而實現多個程序配合的渲染機制。

1. Command Buffer 命令的序列化

在 CommandBuffer 中共有三類命令,一類是直接對接OpenGLES的命令。例如下面的GL命令:

 

 他們的序列化格式定義為:

可以看到序列化的方式並不複雜,每個命令都有個 CommandId 和 header。header 佔用 32 位,高 21 位表示當前命令的長度,低 11 位表示命令的 Id。其他欄位表示命令的引數。

第二類命令是 CommandBuffer 自己需要用到的,這類命令稱為公共命令(common command,Id<256),比如下面這兩個:

這兩個命令一個用於建立Bucket,一個用於向Bucket中放資料(關於Bucket後面會講到)。他們都不是 OpenGL 定義的命令,是 CommandBuffer 為了自己的某種用途而新增的命令。

最後一種命令如下:

 可以看到 CommandBuffer 針對 glShaderSource 命令定義了不同的序列化格式,沒有一種是按照原本的引數來定義的,這主要是因為 glShaderSource 命令可能會傳輸比較大的資料(第3個引數),如果直接把資料通過 IPC 傳輸可能會比較低效,因此方法1將資料存放在了共用記憶體,然後在命令中保留了對共用記憶體的參照,方法2是將資料儲存在 Bucket,然後在命令中參照了 Bucket。這種處理方式主要是針對那些需要傳輸大批次資料的GL命令。

2. Command Buffer 命令的自動生成

Command Buffer 提供了三種 GL Context,分別時 GLES Context,Raster Context,WebGPU Context,它們用於不同的目的。GLES Context 用於常規的繪製,Raster Context 用於 Raster,WebGPU Context 用於 WebGPU。

在 gpu/command_buffer/gles2_cmd_buffer_functions.txt 檔案中定義了 GLES Context 使用到的 GL 命令,包括 150 多個 OpenGLES2.0 命令,以及由 19 個擴充套件提供的 230 多個擴充套件命令,在編譯過程中 gpu/command_buffer/build_gles2_cmd_buffer.py 指令碼會讀取該檔案並生成相應的 *_autogen.* 檔案。

在 gpu/command_buffer/raster_cmd_buffer_functions.txt 檔案中定義了 Raster Context 使用到的 30 多個 GL 命令,它被 gpu/command_buffer/build_raster_cmd_buffer.py 指令碼使用來生成相應的 *_autogen.* 檔案。

用於 WebGPU Context 的命令定義在 gpu/command_buffer/webgpu_cmd_buffer_functions.txt 中,被指令碼 gpu/command_buffer/build_webgpu_cmd_buffer.py 用來生成相關程式碼。

Command Buffer 通過這些自動生成的程式碼包裝了所有的 GL 呼叫,然後將這些呼叫序列化後傳送到 GPU 程序去執行。

3. Command Buffer 的架構設計

前面已經提到,Command Buffer 主要是為了解決多程序的渲染問題,因此它在設計上分兩個端,分別是 client 端和 service 端。下圖反映了 Chromium 中各種程序和 Command Buffer 中兩個端的對應關係:

 

可以看到,Browser和Render程序都是 client 端, GPU 程序是 service 端。client 端負責呼叫 GL 命令來產生繪製操作,但是這些GL命令並不會真正執行而是被序列化為 Command Buffer 命令,然後通過 IPC 傳輸到 GPU 程序,GPU 程序負責反序列化 Command Buffer 命令並最終執行 GL 呼叫。

在 Chromium 的實現中,引入了更多的概念:

  • 每個client端和server端之間都通過 IPC channel (IPC::Channel) 通道進行連線。
  • 每個 IPC channel 可以有多個排程組(scheduling groups),每個排程組稱為一個 stream,每個 stream 有自己的排程優先順序。
  • 每個 stream 可以承載多個 command buffer
  • 每個 command buffer 都對應一個 GL context,在相同stream中的GL Context都屬於同一個share group
  • 每個 command buffer 都包含一系列的 GL 命令。

下圖反映了 context,commandbuffer,stream,channel 之間的關係:

 下面是 Command Buffer 的模組依賴關係:

 content 模組通過呼叫 GL 或者 Skia 來產生 GL 命令, 然後 Command Buffer client 將這些 GL 命令序列化,然後通過 IPC 傳輸到了 Command Buffer service 端,service 將命令反序列化然後呼叫 ui/gl 模組執行真正的 GL 呼叫。

4. Command Buffer 命令的傳輸方式

Command Buffer 定義了三種命令傳輸方式:

  1. 命令和命令涉及到的資料都直接放在 Command Buffer 中傳輸,在多程序模式下 Command Buffer 本身位於共用記憶體中;
  2. 命令放在 Command Buffer 中,資料放在共用記憶體中,在命令中參照該共用記憶體;
  3. 先使用 SetBucketSize 命令在 service 程序中建立一個足夠大的 Bucket,然後將資料的一部分放在共用記憶體中,然後使用 SetBucketData 命令將該共用記憶體中的資料放到 service 程序的 Bucket 中,然後再放一部分資料到共用記憶體,再使用 SetBucketData 命令將資料傳輸到 service 程序中,迴圈這個操作直到將所有的資料都放到 service 程序中,最後呼叫原本的 GL 命令並參照這個 Bucket 的 Id 。Bucket 機制主要用在共用記憶體不足以存放所有要傳輸的資料的時候。由於涉及到多次資料從共用記憶體拷貝到程序空間的操作,因此效能較低。

5. Command Buffer 的具體實現

 6. 總結

Command Buffer 可以用於實現多程序的渲染架構,並且提供全平臺支援。可以通過設定 is_component_build=true 來將 Command Buffer 模組編譯為動態連結庫,從而嵌入到自己的專案中。例如,Skia 專案就提供了對 Command Buffer 的。

7. 參考文獻