HAR 圖形管道

本頁面詳細說明高可用性渲染器 (HAR) 的完整圖形管道,追蹤資料從 Figma 設計文件到螢幕上顯示的最終像素流程。

總覽

管道會將高階 UI 定義轉換為低階圖形指令,並有效率地在硬體螢幕上呈現。這個管道專為車輛安全關鍵應用程式設計,強調決定性算繪、有效率的狀態管理,以及與平台圖形子系統 (例如直接算繪管理員 (DRM) 和通用緩衝區管理 (GBM)) 的穩固互動。

管道可分為四個主要階段:

  1. 預先算繪:處理場景圖、套用自訂項目,以及解析版面配置。
  2. 指令生成:將已解析的場景圖轉換為與後端無關的顯示清單。
  3. 算繪:使用 Impeller 繪圖引擎執行繪圖指令。
  4. 簡報:管理影格緩衝區,並與顯示硬體同步。

HAR 圖像流程

圖 1. HAR 圖像流程。

階段 1:預先算繪

這個階段會將靜態 Figma 設計和動態應用程式狀態,轉換為可供算繪的完整解析記憶體內 UI 樹狀結構。這個階段會在專屬的縮減器執行緒上執行,與主要顯示迴圈分開。

1.1 設計 Compose 基礎

HAR 管線是根據 DesignCompose 生態系統建構而成。

  • 來源:UI 是在 Figma 中設計,並使用 DesignCompose 外掛程式匯出。
  • 定義:輸出內容是 DesignComposeDefinition 的執行個體,也就是設計 (節點、樣式、變體) 的序列化表示法。
  • 資料繫結:應用程式的 UI 模型會使用程序巨集 (例如 #[Design(node = "#speed")]),將 Rust 結構體欄位明確繫結至 Figma 文件中的特定具名節點。這樣一來,應用程式狀態就能自動驅動視覺元素的屬性。

這項基礎的關鍵元件包括:

  • Reducer:做為中央事件迴圈,處理動作並更新目前狀態。架構會提供 DefaultReducer,但您也可以視需要提供自訂縮減器實作。
  • 呈現器:將目前狀態橋接至 UI 模型。Presenter 特徵是由 harry 架構 Crate 指定,而參考實作 (UIModelPresenter) 則是在 harry-app-core Crate 中提供。
  • UI 模型:根據目前狀態生成自訂項目。UI 模型程式碼是使用 derive_customizations Crate 提供的 DesignDocument 巨集產生。harry-app-core Crate 中的 UIModel 結構體提供這方面的範例。
  • Squoosh:提供 SquooshView 資料結構和變數存放區,用於根據設計算繪 UI。dc_bundle Crate 會從 DesignCompose 程式庫載入序列化設計文件,並轉換為 SquooshView 結構體的樹狀結構,以提升執行階段效能。

1.2 縮減函式迴圈

管道是由動作驅動。架構會指定 Actions 列舉型別,定義架構本身使用的內部動作,但也會納入 CustomAction 變數,讓使用者定義其他應用程式專屬動作 (例如 UpdateVehicleSpeedButtonPress)。

架構也提供 StateAction 特徵,可簡化影響應用程式狀態的動作實作,並視需要產生連帶效果,然後從縮減器傳回應用程式進行處理。harry-app-core Crate 中的 CustomActions 列舉提供這項功能的詳細範例。

以下是縮減器迴圈的基本大綱:

  • 處理動作: Reducer 接收動作並更新目前狀態。這是原始資料,例如目前的速度或啟用的警示燈。這也可能會產生連帶效應 (例如,安全帶燈閃爍時,信號會播放鈴聲)。
  • 簡報: Presenter 會將新狀態對應至 UIModelUIModel 是檢視模型,專門用於保存 UI 格式的資料 (例如將「120」速度格式化為「65 mph」字串)。
  • 生成自訂項目:呼叫 UI 模型的 apply 方法,生成一組 RenderCustomization 例項。這些是修改 Figma 設計的明確指令 (例如「將節點 #speed 的文字設為『65 mph』」)。
  • UpdatePolicy 進行最佳化:每次預先算繪傳遞後,系統會傳回 UpdatePolicy 值,指出何時需要進行下一次算繪更新。如果沒有待處理的狀態變更,也沒有動畫正在執行,UpdatePolicy 會發出信號,表示不需要立即更新。在這種情況下,Reducer 會停止產生新的顯示清單,避免不必要的算繪週期,並節省資源,直到新的動作或事件觸發變更為止。

1.3 查看擷取和存放區初始化作業

管道會從 DesignComposeDefinition 執行個體開始。這是由 DesignCompose 序列化為通訊協定緩衝區結構的 Figma 設計文件。

  • 初始載入:啟動時,系統會將主要設計 (由根節點指定) 從 DesignComposeDefinition 轉換為初始 SquooshView 樹狀結構。這是一次性程序。

  • 存放區: SquooshVariantRepository管理可重複使用的元件變體和最初載入的檢視畫面。

  • 延遲載入:為盡量縮短啟動時間及減少記憶體用量,只有在明確參照其他檢視區塊,且算繪邏輯需要這些檢視區塊時 (例如在清單自訂期間),系統才會從文件延遲載入這些檢視區塊 (不屬於初始根節點樹狀結構的檢視區塊)。

1.4 自訂通行證

系統會遍歷 SquooshView 樹狀結構,套用動態應用程式狀態:

  • 變體交換:根據執行階段邏輯,將元件執行個體換成特定變體 (例如將代表目前駕駛模式的圖示從運動模式變更為節能模式)。

  • 清單擴展:Figma 中的單一範本項目會由動態子項清單取代。系統會為這些子項產生新的專屬 ID,以驗證動畫的穩定身分。

  • 文字和樣式覆寫:文字內容 (例如速度值) 和樣式 (例如不透明度、顏色) 會根據目前狀態更新。

1.5 可變解析度

系統會解析在 Figma 或應用程式本機中定義的設計權杖和變數。

  • 繫結:參照變數 (例如顏色或尺寸) 的 SquooshView 屬性會替換為目前影格的具體值。

1.6 版面配置計算

  • 動態版面配置: DynamicLayout 會計算 SquooshView 樹狀結構中每個節點的最終位置和大小 (邊界)。

  • 文字版面配置: TextHelper 會使用 LayoutHelper 特徵的實作項目來計算文字指標、換行和成形。這有助於在算繪前,驗證文字是否正確地在限制內流動。

1.7 撥號和測量儀

這是車輛專用使用者介面的特殊步驟。

  • MeterData:如果節點有儀表資料 (在 Figma 中定義),系統會根據 meter_value (例如車速) 動態變更節點的幾何形狀。
    • 圓弧:調整掃掠角度。
    • 旋轉:旋轉轉換是根據開始和結束角度計算。
    • 進度列:矩形的寬度或高度會縮放。
    • 進度向量:調整向量路徑的長度。

1.8 動畫

  • 差異:系統會比較目前的 SquooshViewprevious_squoosh_view (來自 PreRenderCache)。

  • 插補:如果屬性已變更,Squoosh 會建立插補器,以便隨著時間平滑轉換值 (例如不透明度或轉換)。

階段 2:生成指令

SquooshView樹狀結構完全解析並產生動畫後,就會轉換為繪圖指令的線性序列。

這個階段的關鍵元件是 DisplayList Crate:

  • generate_dl:這個函式會遞迴遍歷 SquooshView 樹狀結構。

  • 譯文:

    • 形狀和路徑:轉換為 DisplayListEntry,並使用適當的 DisplayListAppearance 變數 (例如 RectPath)
    • 文字:使用 TextHelper 轉換為文字繪圖項目。
    • 轉換和剪輯:轉換為 PushTransform3DPopTransform3DPushClipRegionPopClipRegion 配對,以管理繪圖狀態堆疊。
    • 遮罩:轉換為 PushMaskLayerPopMaskLayer 配對,以正確建立及混合圖層。

最終結果是 Vec<DisplayListEntry> 的執行個體,用於說明要繪製的內容,與繪製方式無關。

2.1 交給循環器

產生 DisplayList 後,Reducer 會將其包裝在 ViewDescriptor 的例項中,並透過 Rust MPSC 管道 (LooperMessage) 將其傳送至 Looper 執行緒。Looper 負責算繪和顯示階段,可防止 Reducer 執行緒封鎖圖形管道。

階段 3:算繪

與平台無關的 DisplayList 會移交給轉譯後端,抽象指令會在此轉換為 GPU 指令。

HAR 使用 Impeller,這是專為 Flutter 建構的轉譯引擎。Impeller 的設計宗旨是解決因著色器編譯而導致影格率故障的問題,方法是在建構時間預先編譯一小組高效著色器。這種做法搭配有效批次處理和經過高度最佳化的後端,可提供以下優勢:

  • 確定性效能:幾乎可消除執行階段著色器編譯故障。
  • 快速啟動:減少初始化作業負擔。
  • 體積小:產生體積較小的二進位檔。

如要深入瞭解 Impeller 的架構,請觀看 [介紹 Impeller - Flutter 的全新算繪引擎][impeller-video]。雖然影片討論的是 Flutter,但這些核心優勢可直接強化 HAR 汽車堆疊。

算繪階段的主要元件包括:

  • ImpellerRenderer:將預先算繪階段的顯示清單轉換為 Impeller 算繪指令。

  • Impeller Rust API:包裝 Impeller 程式庫,供 Rust 使用 (impellerimpeller-rs-bindgen Crate)。

  • TypographyContext:管理字型註冊和文字成形。

impeller-video

3.1 初始化和表面管理

  • 建立情境:轉譯器會使用 OpenGL ES 後端初始化 impeller::Context 的例項,並傳遞回呼,從平台的 GL 情境解析 OpenGL ES 函式指標。

  • 封裝 FBO 表面:Impeller 不會建立自己的視窗,而是會算繪到 Phase 4 提供的現有 OpenGL framebuffer 物件 (FBO)。方法是呼叫 Surface::create_wrapped_fbo

3.2 資源管理

  • 圖片:支援標準格式和 KTX2 壓縮紋理。這些資料會上傳至 GPU 紋理,並由內部 Resources 結構體管理。

  • 字型:系統會載入 TrueType 和 OpenType 字型,並向 TypographyContext 註冊,以用於文字轉譯。

  • 外部圖片:外部紋理的專門處理方式 (例如攝影機動態饋給和外部 3D 算繪器) 涉及將 EGLImage 執行個體或外部 OpenGL 紋理繫結至 Impeller Texture 物件,以進行零複製算繪。

3.3 轉譯通道

render 迴圈會使用 DisplayListBuilder 建構 Impeller DisplayList 例項 (請勿與預先算繪階段產生的 Vec<DisplayListEntry> 混淆):

  1. 清除緩衝區,並套用 DPI 縮放和螢幕旋轉的全域轉換。

  2. 疊代處理輸入的 DisplayListEntry 項目:

    • 狀態: save()restore() 用於推送和彈出轉換,以及剪輯區域。
    • 基本體: RectRoundedRect 是使用標準繪圖作業繪製。
    • 路徑:系統會建構並繪製複雜的向量路徑 (包括動態 Arc 執行個體)。
    • 文字: TextStyledText 是使用 TypographyContext 算繪。
    • 圖片:系統會使用 draw_texture_rect 繪製標準和外部圖片。
  3. 使用 surface.draw_display_list() 將建構的 Impeller 顯示清單提交至介面,產生基礎 GL 指令。

  4. 在基礎結構定義上呼叫 swap_buffers(),觸發第 4 階段。

階段 4:簡報

最後一個階段會處理與顯示硬體的互動,以顯示算繪的影格。HAR 在 Android Automotive OS (AAOS) 軟體定義車輛 (SDV) 上使用強大的直接算繪路徑。

這個階段的關鍵元件是 HarDirectRenderingContext (位於 har-gl-context Crate 中)。

4.1 架構

簡報層採用雙緩衝區方法,並使用螢幕外繪製目標:

  1. 繪圖緩衝區:Impeller 用於算繪場景的螢幕外 FBO。

  2. 解決緩衝區問題 (選用):選用輔助緩衝區,支援多重取樣反鋸齒 (MSAA)

    • 視基礎 OpenGL ES 實作或設定而定,您可以在需要時啟用這項功能。在這種情況下,它會做為中繼目標,在 blitting (位元區塊轉移) 至算繪緩衝區之前,先解析多重取樣的繪圖緩衝區。
  3. 算繪緩衝區:由 GBM 物件支援的一般緩衝區,對應於一般圖形交換鏈中的後緩衝區。

  4. 前端緩衝區:掃描至螢幕的 GBM 緩衝區。

4.2 交易鏈結交換

呼叫 swap_buffers 時,HAR 會執行下列步驟:

  1. 將繪圖緩衝區的內容 Blit 至算繪緩衝區 (如果實作需要,可先 Blit 至解析緩衝區)。

  2. 在 GL 內容中呼叫 glFlush(),並建立 EGL_SYNC_NATIVE_FENCE_ANDROID 的例項,追蹤 GPU 完成情況。

  3. 建構 DRM 原子要求,將算繪緩衝區交換至螢幕。這項要求包含 GPU 柵欄 FD (稱為「in fence」),可防止顯示控制器在 GPU 完成繪製前顯示算繪緩衝區。

  4. 同時向 DRM 要求新的柵欄 (稱為輸出柵欄),以便在前一個緩衝區 (先前影格的前端緩衝區) 不再顯示於畫面上時發出信號。

  5. 使用非封鎖旗標提交不可分割的要求,讓主執行緒繼續運作,同時保持圖形子系統同步。

  6. 將新的輸出柵欄儲存在內容中,以便 HAR 在後續影格的 swap_buffers 程序開始時等待訊號。這可防止 GPU 繪製到仍在顯示的緩衝區。

4.3 直接模式設定

HAR 會使用 DRM 和 Kernel Mode Setting (KMS) 子系統直接與核心互動,設定 AAOS SDV 的螢幕解析度,並略過與 SurfaceFlinger 等視窗管理員的互動 (在特定設定中),以便專屬且優先控管螢幕硬體。

4.4 外部算繪

HAR 支援將特定 UI 元素 (由 Figma 中的標記識別) 的算繪作業委派給外部程序或執行緒。這項功能有助於整合複雜的 3D 場景 (例如來自 Kanzi 或 Unity 等引擎的自我車輛視覺化效果),或是需要專屬 OpenGL 環境的其他內容。

4.4.1 重要元件

  • HarExternalRenderContext:外部服務專用的螢幕外 EGL 環境。
  • SurfacePool:管理一組 LocalSurface (TextureEGLImage) 緩衝區,用於雙重或三重緩衝。
  • SharedSurfaceExternalImage:用於在外部服務和主要轉譯器之間傳遞 EGLImage 控制代碼的執行緒安全包裝函式。

4.4.2 工作流程

工作流程的順序如下:

  1. 外部服務會啟動並向主要 Looper 註冊,指出要算繪的 Figma 標記 (例如 #cluster/3d-car)。

  2. 服務會等待來自 Looper 的 RenderStart 信號,以便將算繪作業與螢幕的 VSYNC 信號對齊。

  3. 在螢幕外,服務會將內容算繪到 SurfacePool 提供的影格緩衝區。

  4. 服務會在內容中呼叫 swap_buffers,這會輪替集區,並將完成的影格做為 SharedSurface 的執行個體提供。

  5. SharedSurface 會包裝在 ExternalImage 中,並透過 Rust MPSC 管道傳送至 Looper。

  6. 主要的 Impeller 算繪器 (第 3 階段) 會接收不明外部圖片。這個方法不會複製像素資料,而是將基礎 EGLImage 直接繫結至紋理,並將其繪製為主要場景的一部分,達到零複製組合。

4.5 開發與測試平台 (har-platform-linux)

為進行開發和測試,HAR 應用程式可鎖定標準 Linux 電腦環境和無螢幕設定。這些平台是在 crates/reference/platforms/har-platform-linux Crate 中實作。

與正式版 AAOS SDV 目標不同,這些平台不會使用 har-gl-contextdirect-rendering 子系統輸出顯示畫面。而是依賴標準 Rust OpenGL Crate:

  • 視窗模式:使用 winit 管理視窗和事件迴圈,並使用 glutin 建立 OpenGL ES 情境,以及與視窗系統整合。

  • 無頭模式:使用 har-gl-context Crate 建立具有預設 EGL 螢幕的螢幕外 pbuffer 情境。這項功能可將內容算繪至螢幕外緩衝區,不必使用可見視窗或直接存取顯示器硬體,主要用於自動化測試或後端處理。