Pipeline grafis HAR

Halaman ini menjelaskan pipeline grafis lengkap dari perender ketersediaan tinggi (HAR), yang melacak alur data dari dokumen desain Figma ke piksel akhir yang ditampilkan di layar.

Ringkasan

Pipeline mengonversi definisi UI tingkat tinggi menjadi perintah grafis tingkat rendah dan menampilkannya secara efisien di layar hardware. Pipeline ini dirancang untuk aplikasi penting keselamatan otomotif, dengan menekankan rendering deterministik, pengelolaan status yang efisien, dan interaksi yang kuat dengan subsistem grafis platform, seperti Direct Rendering Manager (DRM) dan Generic Buffer Management (GBM).

Pipeline dapat dibagi menjadi empat fase utama:

  1. Pra-render: Memproses grafik adegan, menerapkan penyesuaian, dan menyelesaikan tata letak.
  2. Pembuatan perintah: Mengonversi grafik adegan yang diselesaikan menjadi daftar tampilan yang agnostik terhadap backend.
  3. Rendering: Mengeksekusi perintah gambar menggunakan mesin grafis Impeller.
  4. Presentasi: Mengelola framebuffer dan menyinkronkan dengan hardware tampilan.

Alur Grafis HAR

Gambar 1. Alur grafis HAR.

Fase 1: Pra-render

Fase ini mengubah desain Figma statis dan status aplikasi dinamis menjadi hierarki UI dalam memori yang sepenuhnya diselesaikan dan siap untuk dirender. Fase ini berjalan di thread reducer khusus, terpisah dari loop tampilan utama.

1.1 Dasar-dasar DesignCompose

Pipeline HAR dibangun di atas ekosistem DesignCompose.

  • Sumber: UI didesain di Figma dan diekspor menggunakan plugin DesignCompose.
  • Definisi: Output adalah instance DesignComposeDefinition, representasi desain yang diserialisasi (node, gaya, varian).
  • Data binding: Model UI aplikasi menggunakan makro prosedural (misalnya, #[Design(node = "#speed")]) untuk secara eksplisit mengikat kolom struct Rust ke node bernama tertentu dalam dokumen Figma. Hal ini memungkinkan status aplikasi secara otomatis mendorong properti elemen visual.

Komponen utama fondasi ini adalah:

  • Reducer: Bertindak sebagai loop peristiwa pusat, memproses tindakan, dan memperbarui status saat ini. Framework ini menyediakan DefaultReducer, tetapi implementasi pengurangan kustom dapat diberikan jika diperlukan.
  • Presenter: Menjembatani status saat ini ke model UI. Trait Presenter ditentukan oleh crate framework harry, dan implementasi referensi (UIModelPresenter) disediakan di crate harry-app-core.
  • Model UI: Menghasilkan penyesuaian berdasarkan status saat ini. Kode model UI dibuat menggunakan makro DesignDocument yang disediakan oleh crate derive_customizations. Struktur UIModel dalam crate harry-app-core memberikan contoh ini.
  • Squoosh: Menyediakan struktur data SquooshView dan repositori varian, yang digunakan untuk merender UI sesuai dengan desain. Dokumen desain yang diserialisasi dimuat oleh crate dc_bundle dari library DesignCompose dan dikonversi menjadi hierarki struct SquooshView untuk performa runtime yang efisien.

1.2 Loop pengurang

Pipeline didorong oleh tindakan. Framework ini menentukan jenis yang di-enum Actions yang menentukan tindakan internal yang digunakan oleh framework itu sendiri, tetapi juga menyertakan varian CustomAction yang memungkinkan pengguna menentukan tindakan tambahan khusus aplikasi (misalnya, UpdateVehicleSpeed atau ButtonPress).

Framework ini juga menyediakan trait StateAction yang menyederhanakan penerapan tindakan yang memengaruhi status aplikasi dan secara opsional menghasilkan efek samping yang kemudian diteruskan kembali ke aplikasi dari pengurangan untuk diproses. Enum CustomActions dalam crate harry-app-core memberikan contoh mendetail tentang hal ini.

Berikut adalah garis besar dasar loop reducer:

  • Pemrosesan tindakan: Reducer menerima tindakan dan memperbarui status saat ini. Ini adalah data mentah seperti kecepatan saat ini atau indikator (lampu peringatan) mana yang aktif. Hal ini juga dapat menimbulkan efek samping (misalnya, sinyal memutar bunyi lonceng saat lampu sabuk pengaman berkedip).
  • Presentasi: Presenter memetakan status baru ke UIModel. UIModel adalah model tampilan, yang menyimpan data yang diformat secara khusus untuk UI (misalnya, memformat kecepatan "120" menjadi string "65 mph").
  • Pembuatan penyesuaian: Metode apply model UI dipanggil untuk membuat sekumpulan instance RenderCustomization. Ini adalah petunjuk eksplisit untuk mengubah desain Figma (misalnya, "Setel teks node #speed ke '65 mph'").
  • UpdatePolicy untuk pengoptimalan: Setelah setiap operasi pra-render, nilai UpdatePolicy akan ditampilkan, yang menunjukkan kapan pembaruan rendering berikutnya diperlukan. Jika tidak ada perubahan status yang tertunda dan tidak ada animasi yang berjalan, UpdatePolicy menandakan bahwa tidak ada update lebih lanjut yang diperlukan saat ini. Dalam kasus tersebut, Reducer berhenti membuat daftar tampilan baru, sehingga mencegah siklus rendering yang tidak perlu dan menghemat resource hingga tindakan atau peristiwa baru memicu perubahan.

1.3 Melihat penyerapan dan inisialisasi repositori

Pipeline dimulai dengan instance DesignComposeDefinition. Ini adalah dokumen desain Figma yang diserialisasi oleh DesignCompose menjadi struktur buffer protokol.

  • Pemuatan awal: Saat startup, desain utama (ditentukan oleh node root-nya) dikonversi dari DesignComposeDefinition menjadi hierarki SquooshView awal. Proses ini hanya dilakukan satu kali.

  • Repositori: SquooshVariantRepository mengelola varian komponen yang dapat digunakan kembali dan tampilan yang dimuat pada awalnya.

  • Pemuatan lambat: Untuk meminimalkan waktu mulai dan penggunaan memori, tampilan tambahan (yang bukan bagian dari hierarki node root awal) dimuat secara lambat dari dokumen hanya saat dirujuk dan diperlukan secara eksplisit oleh logika render (misalnya, selama penyesuaian daftar).

1.4 Kartu penyesuaian

Hierarki SquooshView dilalui untuk menerapkan status aplikasi dinamis:

  • Penggantian varian: Instance komponen diganti dengan varian tertentu (misalnya, mengubah ikon yang merepresentasikan mode mengemudi saat ini dari sport ke eco) berdasarkan logika runtime.

  • Perluasan daftar: Satu item template di Figma digantikan oleh daftar turunan dinamis. ID unik baru dibuat untuk elemen turunan ini guna memverifikasi identitas yang stabil untuk animasi.

  • Penggantian teks dan gaya: Konten teks (misalnya, nilai kecepatan) dan gaya (misalnya, opasitas, warna) diperbarui dari status saat ini.

1.5 Resolusi variabel

Token dan variabel desain yang ditentukan di Figma atau secara lokal di aplikasi akan diselesaikan.

  • Pengikatan: Properti SquooshView yang mereferensikan variabel (seperti warna atau dimensi) diganti dengan nilai konkretnya untuk frame saat ini.

1.6 Penghitungan tata letak

  • Tata letak dinamis: DynamicLayout menghitung posisi dan ukuran akhir (batas) setiap node di hierarki SquooshView.

  • Tata letak teks: TextHelper menggunakan penerapan trait LayoutHelper untuk menghitung metrik, pembungkusan, dan pembentukan teks. Hal ini membantu memverifikasi bahwa teks mengalir dengan benar dalam batasannya sebelum dirender.

1.7 Tombol dan pengukur

Ini adalah langkah khusus untuk UI otomotif.

  • MeterData: Jika node memiliki data meteran (ditetapkan di Figma), geometrinya akan diubah secara dinamis berdasarkan meter_value (misalnya, kecepatan kendaraan).
    • Busur: Sudut sapuan disesuaikan.
    • Rotasi: Transformasi rotasi dihitung berdasarkan sudut awal dan akhir.
    • Status progres: Lebar atau tinggi persegi diskalakan.
    • Vektor progres: Panjang jalur vektor disesuaikan.

1.8 Animasi

  • Perbedaan: SquooshView saat ini dibandingkan dengan previous_squoosh_view dari PreRenderCache.

  • Interpolasi: Jika properti telah berubah, Squoosh akan membuat interpolator untuk mentransisikan nilai (misalnya, opasitas atau transformasi) secara lancar dari waktu ke waktu.

Fase 2: Pembuatan perintah

Setelah pohon SquooshView diselesaikan dan dianimasikan sepenuhnya, pohon tersebut dikonversi menjadi urutan perintah menggambar linear.

Komponen utama fase ini adalah crate DisplayList:

  • generate_dl: Fungsi ini secara rekursif melintasi hierarki SquooshView.

  • Terjemahan:

    • Bentuk dan jalur: Dikonversi ke DisplayListEntry dengan varian DisplayListAppearance yang sesuai (misalnya, Rect atau Path)
    • Teks: Dikonversi dengan TextHelper menjadi entri gambar teks.
    • Transformasi dan klip: Dikonversi menjadi pasangan PushTransform3D dan PopTransform3D atau PushClipRegion dan PopClipRegion untuk mengelola stack status gambar.
    • Masking: Dikonversi menjadi pasangan PushMaskLayer dan PopMaskLayer untuk membuat dan memadukan lapisan dengan benar.

Hasil akhirnya adalah instance Vec<DisplayListEntry> yang menjelaskan apa yang harus digambar, terlepas dari cara menggambarnya.

2.1 Handoff ke looper

Setelah DisplayList dibuat, Reducer akan membungkusnya dalam instance ViewDescriptor dan mengirimkannya melalui saluran MPSC Rust (LooperMessage) ke thread looper. Looper bertanggung jawab atas fase rendering dan tampilan, yang mencegah thread Reducer memblokir pipeline grafis.

Fase 3: Rendering

DisplayList yang agnostik terhadap platform diserahkan ke backend rendering, tempat perintah abstrak diterjemahkan ke dalam instruksi GPU.

HAR menggunakan Impeller, mesin rendering yang awalnya dibuat untuk Flutter. Impeller dirancang untuk memecahkan masalah gangguan kecepatan frame akibat kompilasi shader dengan melakukan pra-kompilasi sekumpulan kecil shader yang efisien pada waktu build. Pendekatan ini, yang dikombinasikan dengan batching yang efektif dan backend yang sangat dioptimalkan, memberikan:

  • Performa deterministik: Hampir menghilangkan gangguan kompilasi shader runtime.
  • Startup cepat: Mengurangi overhead inisialisasi.
  • Jejak kecil: Menghasilkan ukuran biner yang ringkas.

Untuk pengantar menyeluruh tentang arsitektur Impeller, tonton Memperkenalkan Impeller - Mesin rendering baru Flutter. Meskipun video ini membahas Flutter, manfaat inti ini secara langsung mendukung stack otomotif HAR.

Komponen utama fase rendering adalah:

  • ImpellerRenderer: Mengonversi daftar tampilan dari fase pra-render menjadi perintah rendering Impeller.

  • Impeller Rust API: Membungkus library Impeller untuk digunakan di Rust (crate impeller dan impeller-rs-bindgen).

  • TypographyContext: Mengelola pendaftaran font dan pembentukan teks.

3.1 Inisialisasi dan pengelolaan platform

  • Pembuatan konteks: Perender menginisialisasi instance impeller::Context dengan backend OpenGL ES, meneruskan callback untuk menyelesaikan pointer fungsi OpenGL ES dari konteks GL platform.

  • Permukaan FBO yang di-wrap: Alih-alih membuat jendelanya sendiri, Impeller merender ke objek framebuffer (FBO) OpenGL yang ada yang disediakan oleh Fase 4. Hal ini dilakukan dengan memanggil Surface::create_wrapped_fbo.

3.2 Pengelolaan resource

  • Gambar: Mendukung format standar dan tekstur terkompresi KTX2. Data ini diupload ke tekstur GPU dan dikelola oleh struktur Resources internal.

  • Font: Font TrueType dan OpenType dimuat dan didaftarkan dengan TypographyContext untuk merender teks.

  • Gambar eksternal: Penanganan khusus untuk tekstur eksternal (misalnya, feed kamera dan perender 3D eksternal) melibatkan pengikatan instance EGLImage atau tekstur OpenGL eksternal ke objek Texture Impeller untuk rendering tanpa salinan.

3.3 Render pass

Loop render membuat instance DisplayList Impeller (jangan sampai tertukar dengan Vec<DisplayListEntry> yang dihasilkan oleh fase pra-render) menggunakan DisplayListBuilder:

  1. Membersihkan buffer dan menerapkan transformasi global untuk penskalaan DPI dan rotasi layar.

  2. Melakukan iterasi melalui item DisplayListEntry input:

    • Status: save() dan restore() digunakan untuk mendorong dan memunculkan transformasi dan memotong wilayah.
    • Primitif: Rect dan RoundedRect digambar menggunakan operasi paint standar.
    • Jalur: Jalur vektor kompleks (termasuk instance Arc dinamis) dibuat dan digambar.
    • Teks: Text dan StyledText dirender menggunakan TypographyContext.
    • Gambar: Gambar standar dan eksternal digambar menggunakan draw_texture_rect.
  3. Mengirimkan daftar tampilan Impeller yang dibuat ke platform menggunakan surface.draw_display_list(), yang menghasilkan perintah GL yang mendasarinya.

  4. Memanggil swap_buffers() pada konteks yang mendasarinya untuk memicu Fase 4.

Tahap 4: Presentasi

Fase akhir ini menangani interaksi dengan hardware layar untuk menampilkan frame yang dirender. HAR menggunakan jalur rendering langsung yang andal di Software-Defined Vehicle (SDV) Android Automotive OS (AAOS).

Komponen utama fase ini adalah HarDirectRenderingContext (dalam crate har-gl-context).

4.1 Arsitektur

Lapisan presentasi menggunakan pendekatan buffer ganda dengan target gambar di luar layar:

  1. Buffer gambar: FBO di luar layar tempat Impeller merender adegan.

  2. Buffer penyelesaian (opsional): Buffer tambahan opsional untuk mendukung anti-aliasing multisampel (MSAA)

    • Fitur ini dapat diaktifkan jika diperlukan oleh implementasi atau konfigurasi OpenGL ES yang mendasarinya. Dalam kasus tersebut, target ini berfungsi sebagai target perantara untuk menyelesaikan buffer gambar multisampel sebelum blitting (transfer blok bit) ke buffer render.
  3. Buffer render: Buffer generik yang didukung oleh objek GBM, yang sesuai dengan buffer belakang dalam swap chain grafis standar.

  4. Buffer depan: Buffer GBM yang dipindai ke layar.

4.2 Rantai pertukaran

Saat swap_buffers dipanggil, HAR akan mengikuti langkah-langkah berikut:

  1. Menyalin konten buffer gambar ke buffer render (dengan penyalinan perantara ke buffer penyelesaian, jika diperlukan oleh penerapan).

  2. Memanggil glFlush() pada konteks GL, dan membuat instance EGL_SYNC_NATIVE_FENCE_ANDROID untuk melacak penyelesaian GPU.

  3. Membangun permintaan atomik DRM untuk menukar buffer render ke layar. Permintaan ini berisi FD pagar GPU (disebut pagar masuk) untuk mencegah pengontrol layar menampilkan buffer render sebelum GPU selesai menggambar.

  4. Secara bersamaan meminta pagar baru dari DRM (disebut pagar keluar), untuk memberi sinyal saat buffer sebelumnya (buffer depan untuk frame sebelumnya) tidak lagi ditampilkan di layar.

  5. Melakukan permintaan atomik menggunakan tanda non-blocking, untuk memungkinkan thread utama terus berjalan sementara subsistem grafis tetap disinkronkan.

  6. Menyimpan pagar keluar baru dalam konteks sehingga HAR dapat menunggu sinyalnya di awal proses swap_buffers pada frame berikutnya. Hal ini mencegah GPU menggambar ke buffer yang masih ditampilkan.

4.3 Setelan mode langsung

HAR berinteraksi langsung dengan kernel menggunakan subsistem DRM dan Kernel Mode Setting (KMS) untuk mengonfigurasi resolusi layar AAOS SDV, melewati interaksi dengan pengelola jendela seperti SurfaceFlinger (dalam konfigurasi tertentu), sehingga memungkinkan kontrol eksklusif dan berprioritas tinggi terhadap hardware layar.

4.4 Rendering eksternal

HAR mendukung pendelegasian rendering elemen UI tertentu (diidentifikasi oleh tag di Figma) ke proses atau thread eksternal. Hal ini berguna untuk mengintegrasikan adegan 3D yang kompleks (misalnya, visualisasi mobil ego dari mesin seperti Kanzi atau Unity) atau konten lain yang memerlukan konteks OpenGL khusus.

4.4.1 Komponen utama

  • HarExternalRenderContext: Konteks EGL di luar layar khusus untuk layanan eksternal.
  • SurfacePool: Mengelola sekumpulan buffer LocalSurface (Texture plus EGLImage) untuk buffering ganda atau tiga kali lipat.
  • SharedSurfaceExternalImage: Wrapper yang aman untuk thread untuk meneruskan handle EGLImage antara layanan eksternal dan perender utama.

4.4.2 Alur Kerja

Alur kerjanya mengikuti urutan berikut:

  1. Layanan eksternal dimulai dan mendaftarkan dirinya dengan looper utama, mengidentifikasi tag Figma (misalnya, #cluster/3d-car) yang dirender.

  2. Layanan menunggu sinyal RenderStart dari looper untuk menyelaraskan rendering-nya dengan sinyal VSYNC layar.

  3. Di luar layar, layanan merender kontennya ke framebuffer yang disediakan oleh SurfacePool.

  4. Layanan memanggil swap_buffers pada konteksnya, yang memutar kumpulan dan membuat frame yang telah selesai tersedia sebagai instance SharedSurface.

  5. SharedSurface di-wrap dalam ExternalImage dan dikirim melalui channel MPSC Rust ke looper.

  6. Renderer Impeller utama (Fase 3) menerima gambar eksternal. Alih-alih menyalin data piksel, objek ini mengikat EGLImage yang mendasarinya langsung ke tekstur dan menggambarnya sebagai bagian dari adegan utama, sehingga menghasilkan komposisi tanpa salinan.

4.5 Platform pengembangan dan pengujian (har-platform-linux)

Untuk tujuan pengembangan dan pengujian, aplikasi HAR dapat menargetkan lingkungan desktop Linux standar dan penyiapan tanpa antarmuka. Platform ini diterapkan dalam crate crates/reference/platforms/har-platform-linux.

Tidak seperti target SDV AAOS produksi, platform ini tidak menggunakan subsistem direct-rendering dari har-gl-context untuk output tampilan. Sebagai gantinya, mereka mengandalkan crate OpenGL Rust standar:

  • Mode berjendela: Menggunakan winit untuk pengelolaan jendela dan loop peristiwa, serta glutin untuk membuat konteks OpenGL ES dan berintegrasi dengan sistem berjendela.

  • Mode tanpa tampilan: Menggunakan crate har-gl-context untuk membuat konteks pbuffer offscreen dengan tampilan EGL default. Hal ini memungkinkan rendering ke buffer di luar layar tanpa memerlukan jendela yang terlihat atau akses hardware tampilan langsung, yang terutama digunakan untuk pengujian otomatis atau pemrosesan backend.