本頁面詳細說明高可用性渲染器 (HAR) 的完整圖形管道,追蹤資料從 Figma 設計文件到螢幕上顯示的最終像素流程。
總覽
管道會將高階 UI 定義轉換為低階圖形指令,並有效率地在硬體螢幕上呈現。這個管道專為車輛安全關鍵應用程式設計,強調決定性算繪、有效率的狀態管理,以及與平台圖形子系統 (例如直接算繪管理員 (DRM) 和通用緩衝區管理 (GBM)) 的穩固互動。
管道可分為四個主要階段:
- 預先算繪:處理場景圖、套用自訂項目,以及解析版面配置。
- 指令生成:將已解析的場景圖轉換為與後端無關的顯示清單。
- 算繪:使用 Impeller 繪圖引擎執行繪圖指令。
- 簡報:管理影格緩衝區,並與顯示硬體同步。
圖 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-coreCrate 中提供。 - UI 模型:根據目前狀態生成自訂項目。UI 模型程式碼是使用
derive_customizationsCrate 提供的DesignDocument巨集產生。harry-app-coreCrate 中的UIModel結構體提供這方面的範例。 - Squoosh:提供
SquooshView資料結構和變數存放區,用於根據設計算繪 UI。dc_bundleCrate 會從 DesignCompose 程式庫載入序列化設計文件,並轉換為SquooshView結構體的樹狀結構,以提升執行階段效能。
1.2 縮減函式迴圈
管道是由動作驅動。架構會指定 Actions 列舉型別,定義架構本身使用的內部動作,但也會納入 CustomAction 變數,讓使用者定義其他應用程式專屬動作 (例如 UpdateVehicleSpeed 或 ButtonPress)。
架構也提供 StateAction 特徵,可簡化影響應用程式狀態的動作實作,並視需要產生連帶效果,然後從縮減器傳回應用程式進行處理。harry-app-core Crate 中的 CustomActions 列舉提供這項功能的詳細範例。
以下是縮減器迴圈的基本大綱:
- 處理動作:
Reducer接收動作並更新目前狀態。這是原始資料,例如目前的速度或啟用的警示燈。這也可能會產生連帶效應 (例如,安全帶燈閃爍時,信號會播放鈴聲)。 - 簡報:
Presenter會將新狀態對應至UIModel。UIModel是檢視模型,專門用於保存 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 動畫
差異:系統會比較目前的
SquooshView與previous_squoosh_view(來自PreRenderCache)。插補:如果屬性已變更,
Squoosh會建立插補器,以便隨著時間平滑轉換值 (例如不透明度或轉換)。
階段 2:生成指令
SquooshView樹狀結構完全解析並產生動畫後,就會轉換為繪圖指令的線性序列。
這個階段的關鍵元件是 DisplayList Crate:
generate_dl:這個函式會遞迴遍歷SquooshView樹狀結構。譯文:
- 形狀和路徑:轉換為
DisplayListEntry,並使用適當的DisplayListAppearance變數 (例如Rect或Path) - 文字:使用
TextHelper轉換為文字繪圖項目。 - 轉換和剪輯:轉換為
PushTransform3D和PopTransform3D或PushClipRegion和PopClipRegion配對,以管理繪圖狀態堆疊。 - 遮罩:轉換為
PushMaskLayer和PopMaskLayer配對,以正確建立及混合圖層。
- 形狀和路徑:轉換為
最終結果是 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 使用 (
impeller和impeller-rs-bindgenCrate)。TypographyContext:管理字型註冊和文字成形。
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 紋理繫結至 ImpellerTexture物件,以進行零複製算繪。
3.3 轉譯通道
render 迴圈會使用 DisplayListBuilder 建構 Impeller DisplayList 例項 (請勿與預先算繪階段產生的 Vec<DisplayListEntry> 混淆):
清除緩衝區,並套用 DPI 縮放和螢幕旋轉的全域轉換。
疊代處理輸入的
DisplayListEntry項目:- 狀態:
save()和restore()用於推送和彈出轉換,以及剪輯區域。 - 基本體:
Rect和RoundedRect是使用標準繪圖作業繪製。 - 路徑:系統會建構並繪製複雜的向量路徑 (包括動態
Arc執行個體)。 - 文字:
Text和StyledText是使用TypographyContext算繪。 - 圖片:系統會使用
draw_texture_rect繪製標準和外部圖片。
- 狀態:
使用
surface.draw_display_list()將建構的 Impeller 顯示清單提交至介面,產生基礎 GL 指令。在基礎結構定義上呼叫
swap_buffers(),觸發第 4 階段。
階段 4:簡報
最後一個階段會處理與顯示硬體的互動,以顯示算繪的影格。HAR 在 Android Automotive OS (AAOS) 軟體定義車輛 (SDV) 上使用強大的直接算繪路徑。
這個階段的關鍵元件是 HarDirectRenderingContext (位於 har-gl-context Crate 中)。
4.1 架構
簡報層採用雙緩衝區方法,並使用螢幕外繪製目標:
繪圖緩衝區:Impeller 用於算繪場景的螢幕外 FBO。
解決緩衝區問題 (選用):選用輔助緩衝區,支援多重取樣反鋸齒 (MSAA)
- 視基礎 OpenGL ES 實作或設定而定,您可以在需要時啟用這項功能。在這種情況下,它會做為中繼目標,在 blitting (位元區塊轉移) 至算繪緩衝區之前,先解析多重取樣的繪圖緩衝區。
算繪緩衝區:由 GBM 物件支援的一般緩衝區,對應於一般圖形交換鏈中的後緩衝區。
前端緩衝區:掃描至螢幕的 GBM 緩衝區。
4.2 交易鏈結交換
呼叫 swap_buffers 時,HAR 會執行下列步驟:
將繪圖緩衝區的內容 Blit 至算繪緩衝區 (如果實作需要,可先 Blit 至解析緩衝區)。
在 GL 內容中呼叫
glFlush(),並建立EGL_SYNC_NATIVE_FENCE_ANDROID的例項,追蹤 GPU 完成情況。建構 DRM 原子要求,將算繪緩衝區交換至螢幕。這項要求包含 GPU 柵欄 FD (稱為「in fence」),可防止顯示控制器在 GPU 完成繪製前顯示算繪緩衝區。
同時向 DRM 要求新的柵欄 (稱為輸出柵欄),以便在前一個緩衝區 (先前影格的前端緩衝區) 不再顯示於畫面上時發出信號。
使用非封鎖旗標提交不可分割的要求,讓主執行緒繼續運作,同時保持圖形子系統同步。
將新的輸出柵欄儲存在內容中,以便 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(Texture加EGLImage) 緩衝區,用於雙重或三重緩衝。SharedSurfaceExternalImage:用於在外部服務和主要轉譯器之間傳遞EGLImage控制代碼的執行緒安全包裝函式。
4.4.2 工作流程
工作流程的順序如下:
外部服務會啟動並向主要 Looper 註冊,指出要算繪的 Figma 標記 (例如
#cluster/3d-car)。服務會等待來自 Looper 的
RenderStart信號,以便將算繪作業與螢幕的 VSYNC 信號對齊。在螢幕外,服務會將內容算繪到
SurfacePool提供的影格緩衝區。服務會在內容中呼叫
swap_buffers,這會輪替集區,並將完成的影格做為SharedSurface的執行個體提供。SharedSurface會包裝在ExternalImage中,並透過 Rust MPSC 管道傳送至 Looper。主要的 Impeller 算繪器 (第 3 階段) 會接收不明外部圖片。這個方法不會複製像素資料,而是將基礎
EGLImage直接繫結至紋理,並將其繪製為主要場景的一部分,達到零複製組合。
4.5 開發與測試平台 (har-platform-linux)
為進行開發和測試,HAR 應用程式可鎖定標準 Linux 電腦環境和無螢幕設定。這些平台是在 crates/reference/platforms/har-platform-linux Crate 中實作。
與正式版 AAOS SDV 目標不同,這些平台不會使用 har-gl-context 的 direct-rendering 子系統輸出顯示畫面。而是依賴標準 Rust OpenGL Crate:
視窗模式:使用
winit管理視窗和事件迴圈,並使用glutin建立 OpenGL ES 情境,以及與視窗系統整合。無頭模式:使用
har-gl-contextCrate 建立具有預設 EGL 螢幕的螢幕外 pbuffer 情境。這項功能可將內容算繪至螢幕外緩衝區,不必使用可見視窗或直接存取顯示器硬體,主要用於自動化測試或後端處理。