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 ini 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, yang 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 ini dapat dibagi menjadi empat fase utama:

  1. Prerender: Memproses grafik adegan, menerapkan penyesuaian, dan menyelesaikan tata letak.
  2. Pembuatan perintah: Mengonversi grafik adegan yang diselesaikan menjadi daftar tampilan yang agnostik backend.
  3. Rendering: Menjalankan 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: Prerender

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 pada thread pengurang khusus, terpisah dari loop tampilan utama.

1.1 Dasar DesignCompose

Pipeline HAR dibangun berdasarkan ekosistem DesignCompose.

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

Komponen utama dasar ini adalah:

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

1.2 Loop pengurang

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

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

Berikut adalah garis besar dasar loop pengurang:

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

1.3 Penyerapan tampilan dan inisialisasi repositori

Pipeline dimulai dengan instance DesignComposeDefinition. Ini adalah dokumen desain Figma yang diserialkan oleh DesignCompose ke dalam struktur buffer protokol.

  • Pemuatan awal: Saat startup, desain utama (yang 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 startup dan penggunaan memori, tampilan tambahan (yang bukan bagian dari hierarki node root awal) dimuat secara lambat dari dokumen hanya jika direferensikan secara eksplisit dan diperlukan oleh logika render (misalnya, selama penyesuaian daftar).

1.4 Pass penyesuaian

Hierarki SquooshView dilalui untuk menerapkan status aplikasi dinamis:

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

  • Perluasan daftar: Satu item template di Figma diganti dengan daftar turunan dinamis. ID unik baru dibuat untuk 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 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 dalam hierarki SquooshView.

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

1.7 Dial dan pengukur

Ini adalah langkah khusus untuk UI otomotif.

  • MeterData: Jika node memiliki data pengukur (yang ditentukan 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.
    • Progress bar: Lebar atau tinggi persegi panjang 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 hierarki SquooshView sepenuhnya diselesaikan dan dianimasikan, hierarki tersebut akan dikonversi menjadi urutan perintah gambar 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 ke entri gambar teks.
    • Transformasi dan klip: Dikonversi ke pasangan PushTransform3D dan PopTransform3D atau PushClipRegion dan PopClipRegion untuk mengelola tumpukan status gambar.
    • Masking: Dikonversi ke pasangan PushMaskLayer dan PopMaskLayer untuk membuat dan memadukan lapisan dengan benar.

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

2.1 Handoff ke looper

Setelah DisplayList dibuat, Pengurang akan meng-wrap-nya dalam instance ViewDescriptor dan mengirimkannya melalui channel MPSC Rust (LooperMessage) ke thread looper. Looper bertanggung jawab atas fase rendering dan tampilan, yang mencegah thread Pengurang memblokir pipeline grafis.

Fase 3: Rendering

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

HAR menggunakan Impeller, mesin rendering yang awalnya dibuat untuk Flutter. Impeller dirancang untuk mengatasi masalah gangguan kecepatan frame karena kompilasi shader dengan melakukan prakompilasi kumpulan shader kecil 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 [Introducing Impeller - Flutter's new rendering engine][impeller-video]. Meskipun video ini membahas Flutter, manfaat inti ini secara langsung memberdayakan stack otomotif HAR.

Komponen utama fase rendering adalah:

  • ImpellerRenderer: Mengonversi daftar tampilan dari fase prerender menjadi perintah rendering Impeller.

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

  • TypographyContext: Mengelola pendaftaran font dan pembentukan teks.

impeller-video

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.

  • Platform FBO yang di-wrap: Alih-alih membuat jendelanya sendiri, Impeller merender ke objek framebuffer OpenGL (FBO) 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. Gambar ini diupload ke tekstur GPU dan dikelola oleh struct Resources internal.

  • Font: Font TrueType dan OpenType dimuat dan didaftarkan dengan TypographyContext untuk rendering 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 Pass render

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

  1. Menghapus buffer dan menerapkan transformasi global untuk penskalaan DPI dan rotasi tampilan.

  2. Melakukan iterasi melalui item DisplayListEntry input:

    • Status: save() dan restore() digunakan untuk mendorong dan memunculkan transformasi serta klip region.
    • 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.

Fase 4: Presentasi

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

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)

    • Hal ini dapat diaktifkan jika diperlukan oleh implementasi atau konfigurasi OpenGL ES yang mendasarinya. Dalam kasus seperti itu, buffer 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 rantai swap grafis biasa.

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

4.2 Rantai swap

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

  1. Melakukan blit konten buffer gambar ke buffer render (dengan blit perantara ke buffer penyelesaian, jika diperlukan oleh implementasi).

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

  3. Membuat permintaan atom DRM untuk menukar buffer render ke layar. Permintaan ini berisi FD pagar GPU (disebut pagar masuk) untuk mencegah pengontrol tampilan 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 commit permintaan atom menggunakan flag non-blocking, untuk memungkinkan thread utama terus berjalan saat 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 tampilan AAOS SDV, melewati interaksi dengan pengelola jendela seperti SurfaceFlinger (dalam konfigurasi tertentu), sehingga memungkinkan kontrol hardware tampilan yang eksklusif dan berprioritas tinggi.

4.4 Rendering eksternal

HAR mendukung pendelegasian rendering elemen UI tertentu (yang diidentifikasi berdasarkan 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 kumpulan buffer LocalSurface (Texture plus EGLImage) untuk buffering ganda atau tiga kali lipat.
  • SharedSurfaceExternalImage: Wrapper yang aman untuk thread untuk meneruskan pengendali EGLImage antara layanan eksternal dan perender utama.

4.4.2 Alur kerja

Alur kerja mengikuti urutan ini:

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

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

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

  4. Layanan ini 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. Perender Impeller utama (Fase 3) menerima gambar eksternal. Alih-alih menyalin data piksel, perender ini mengikat EGLImage yang mendasarinya langsung ke tekstur dan menggambarnya sebagai bagian dari adegan utama, sehingga mencapai 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 layar. Platform ini diimplementasikan dalam crate crates/reference/platforms/har-platform-linux.

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

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

  • Mode tanpa layar: Menggunakan crate har-gl-context untuk membuat konteks pbuffer di luar layar 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.