Cette page décrit le pipeline graphique complet du moteur de rendu à haute disponibilité (HAR, High Availability Renderer), en traçant le flux de données d'un document de conception Figma jusqu'aux pixels finaux affichés à l'écran.
Présentation
Le pipeline convertit les définitions d'UI de haut niveau en commandes graphiques de bas niveau et les présente efficacement sur les écrans matériels. Il est conçu pour les applications automobiles critiques pour la sécurité, en mettant l'accent sur le rendu déterministe, la gestion efficace des états et l'interaction robuste avec les sous-systèmes graphiques de la plate-forme, tels que Direct Rendering Manager (DRM) et Generic Buffer Management (GBM).
Le pipeline peut être divisé en quatre phases principales :
- Prerender : traitement du graphique de scène, application des personnalisations et résolution de la mise en page.
- Génération de commandes : conversion du graphique de scène résolu en une liste d'affichage indépendante du backend.
- Rendu : exécution des commandes de dessin à l'aide du moteur graphique Impeller.
- Présentation : gestion des tampons de trame et synchronisation avec le matériel d'affichage.
Figure 1. Flux graphique HAR.
Phase 1: Prerender
Cette phase transforme la conception Figma statique et l'état dynamique de l'application en une arborescence d'UI entièrement résolue en mémoire, prête pour le rendu. Elle s'exécute sur un thread de réduction dédié, distinct de la boucle d'affichage principale.
1.1 Base de DesignCompose
Le pipeline HAR est basé sur l'écosystème DesignCompose.
- Source : l'UI est conçue dans Figma et exportée à l'aide du plug-in DesignCompose.
- Définition : la sortie est une instance de
DesignComposeDefinition, une représentation sérialisée de la conception (nœuds, styles, variantes). - Liaison de données : le modèle d'UI de l'application utilise des macros procédurales (par exemple,
#[Design(node = "#speed")]) pour lier explicitement les champs de structure Rust à des nœuds nommés spécifiques dans le document Figma. Cela permet à l'état de l'application de contrôler automatiquement les propriétés des éléments visuels.
Les principaux composants de cette base sont les suivants :
- Réducteur : agit comme la boucle d'événements centrale, en traitant les actions et en mettant à jour l'état actuel. Le framework fournit
DefaultReducer, mais une implémentation de réducteur personnalisée peut être fournie si nécessaire. - Présentateur : relie l'état actuel au modèle d'UI. Le
Presentertrait est spécifié par leharrycrate de framework, et une implémentation de référence (UIModelPresenter) est fournie dans leharry-app-corecrate. - Modèle d'UI : génère des personnalisations en fonction de l'état actuel. Le code du modèle d'UI est généré à l'aide de la macro
DesignDocumentfournie par le cratederive_customizations. La structureUIModeldu crateharry-app-coreen fournit un exemple. - Squoosh : fournit la structure de données
SquooshViewet le dépôt de variantes, utilisés pour afficher l'UI en fonction de la conception. Un document de conception sérialisé est chargé par le cratedc_bundleà partir de la bibliothèque DesignCompose et converti en une arborescence de structuresSquooshViewpour des performances d'exécution efficaces.
1.2 Boucle de réduction
Le pipeline est piloté par des actions. Le framework spécifie le type énuméré Actions, qui définit les actions internes utilisées par le framework lui-même, mais inclut également une variante CustomAction qui permet aux utilisateurs de définir des actions supplémentaires spécifiques à l'application (par exemple, UpdateVehicleSpeed ou ButtonPress).
Le framework fournit également le trait StateAction, qui simplifie l'implémentation des actions qui affectent l'état de l'application et génèrent éventuellement des effets secondaires qui sont ensuite renvoyés à l'application à partir du réducteur pour traitement. L'énumération CustomActions du crate harry-app-core en fournit un exemple détaillé.
Voici un aperçu de base de la boucle de réduction :
- Traitement des actions :
Reducerreçoit une action et met à jour l'état actuel. Il s'agit des données brutes telles que la vitesse actuelle ou les témoins (voyants d'avertissement) actifs. Cela peut également générer des effets secondaires (par exemple, un signal sonore lorsque le voyant de la ceinture de sécurité clignote). - Présentation :
Presentermappe le nouvel état dansUIModel.UIModelest un modèle de vue qui contient des données spécifiquement mises en forme pour l'UI (par exemple, la mise en forme de la vitesse "120" en une chaîne "65 mph"). - Génération de personnalisations : la méthode
applydu modèle d'UI est appelée pour générer un ensemble d'instancesRenderCustomization. Il s'agit d'instructions explicites pour modifier la conception Figma (par exemple, "Définir le texte du nœud #speed sur '65 mph'"). UpdatePolicypour l'optimisation : après chaque passe de prerender, une valeurUpdatePolicyest renvoyée, indiquant quand la prochaine mise à jour du rendu est requise. Si aucune modification d'état n'est en attente et qu'aucune animation n'est en cours d'exécution,UpdatePolicysignale qu'aucune autre mise à jour n'est nécessaire immédiatement. Dans ce cas, le réducteur cesse de générer de nouvelles listes d'affichage, ce qui évite les cycles de rendu inutiles et préserve les ressources jusqu'à ce qu'une nouvelle action ou un nouvel événement déclenche une modification.
1.3 Ingestion de vues et initialisation du dépôt
Le pipeline commence par une instance DesignComposeDefinition. Il s'agit du document de conception Figma sérialisé par DesignCompose dans une structure de tampon de protocole.
Chargement initial : au démarrage, la conception principale (spécifiée par son nœud racine) est convertie de
DesignComposeDefinitionen une arborescenceSquooshViewinitiale. Il s'agit d'un processus unique.Dépôt :
SquooshVariantRepositorygère les variantes de composants réutilisables et les vues chargées initialement.Chargement différé : pour minimiser le temps de démarrage et l'utilisation de la mémoire, les vues supplémentaires (celles qui ne font pas partie de l'arborescence du nœud racine initial) ne sont chargées de manière différée à partir du document que lorsqu'elles sont explicitement référencées et nécessaires à la logique de rendu (par exemple, lors d'une personnalisation de liste).
1.4 Passe de personnalisation
L'arborescence SquooshView est parcourue pour appliquer l'état dynamique de l'application :
Échanges de variantes : les instances de composants sont échangées avec des variantes spécifiques (par exemple, en passant d'une icône représentant le mode de conduite actuel de sport à éco) en fonction de la logique d'exécution.
Expansion de la liste : un seul élément de modèle dans Figma est remplacé par une liste dynamique d'enfants. De nouveaux ID uniques sont générés pour ces enfants afin de vérifier une identité stable pour les animations.
Remplacements de texte et de style : le contenu textuel (par exemple, la valeur de la vitesse) et les styles (par exemple, l'opacité, la couleur) sont mis à jour à partir de l'état actuel.
1.5 Résolution variable
Les jetons de conception et les variables définis dans Figma ou localement dans l'application sont résolus.
- Liaison : les propriétés
SquooshViewqui font référence à des variables (comme les couleurs ou les dimensions) sont remplacées par leurs valeurs concrètes pour la trame actuelle.
1.6 Calcul de la mise en page
Mise en page dynamique :
DynamicLayoutcalcule la position et la taille finales (limites) de chaque nœud de l'arborescenceSquooshView.Mise en page du texte :
TextHelperutilise une implémentation du traitLayoutHelperpour calculer les métriques de texte, l'habillage et la mise en forme. Cela permet de vérifier que le texte s'affiche correctement dans ses contraintes avant le rendu.
1.7 Cadrans et jauges
Il s'agit d'une étape spécialisée pour les UI automobiles.
MeterData: si un nœud comporte des données de compteur (définies dans Figma), sa géométrie est modifiée dynamiquement en fonction demeter_value(par exemple, la vitesse du véhicule).- Arcs : l'angle de balayage est ajusté.
- Rotations : la transformation de rotation est calculée en fonction des angles de début et de fin.
- Barres de progression : la largeur ou la hauteur d'un rectangle est mise à l'échelle.
- Vecteurs de progression : la longueur d'un chemin vectoriel est ajustée.
1.8 Animation
Diffing : le
SquooshViewactuel est comparé àprevious_squoosh_viewdePreRenderCache.Interpolation : si des propriétés ont changé,
Squooshcrée des interpolateurs pour effectuer une transition fluide des valeurs (par exemple, l'opacité ou la transformation) au fil du temps.
Phase 2 : Génération de commandes
Une fois l'arborescence SquooshView entièrement résolue et animée, elle est convertie en une séquence linéaire de commandes de dessin.
Le composant clé de cette phase est le crate DisplayList :
generate_dl: cette fonction parcourt de manière récursive l'arborescenceSquooshView.Traduction :
- Formes et chemins : convertis en
DisplayListEntryavec la varianteDisplayListAppearanceappropriée (par exemple,RectouPath) - Texte : converti avec
TextHelperen entrées de dessin de texte. - Transformations et clips : convertis en paires
PushTransform3DetPopTransform3DouPushClipRegionetPopClipRegionpour gérer la pile d'état de dessin. - Masquage : converti en paires
PushMaskLayeretPopMaskLayerpour créer et fusionner correctement les calques.
- Formes et chemins : convertis en
Le résultat final est une instance de Vec<DisplayListEntry> qui décrit ce qu'il faut
dessiner, indépendamment de la manière de le dessiner.
2.1 Transfert au looper
Une fois la DisplayList générée, le réducteur l'encapsule dans une instance de ViewDescriptor et l'envoie via un canal MPSC Rust (LooperMessage) au thread du looper. Le Looper est responsable des phases de rendu et d'affichage, ce qui empêche le thread du réducteur de bloquer le pipeline graphique.
Phase 3 : Rendu
La DisplayList indépendante de la plate-forme est transmise au backend de rendu, où les commandes abstraites sont traduites en instructions GPU.
HAR utilise Impeller, un moteur de rendu initialement conçu pour Flutter. Impeller est conçu pour résoudre le problème des problèmes de fréquence d'images dus à la compilation de shaders en précompilant un petit ensemble efficace de shaders au moment de la compilation. Cette approche, combinée à un traitement par lot efficace et à un backend hautement optimisé, offre les avantages suivants :
- Performances déterministes : élimine pratiquement les problèmes de compilation de nuanceurs au moment de l'exécution.
- Démarrage rapide : réduit la surcharge d'initialisation.
- Faible encombrement : produit une taille binaire compacte.
Pour une présentation complète de l'architecture d'Impeller, regardez [Présentation d'Impeller, le nouveau moteur de rendu de Flutter][impeller-video]. Bien que la vidéo traite de Flutter, ces avantages fondamentaux renforcent directement la pile automobile HAR.
Les principaux composants de la phase de rendu sont les suivants :
ImpellerRenderer: convertit la liste d'affichage de la phase de prerender en commandes de rendu Impeller.API Impeller Rust : encapsule la bibliothèque Impeller pour une utilisation dans Rust (crates
impelleretimpeller-rs-bindgen).TypographyContext: gère l'enregistrement des polices et la mise en forme du texte.
3.1 Initialisation et gestion des surfaces
Création de contexte : le moteur de rendu initialise une instance de
impeller::Contextavec un backend OpenGL ES, en transmettant un rappel pour résoudre les pointeurs de fonction OpenGL ES à partir du contexte GL de la plate-forme.Surface FBO encapsulée : au lieu de créer sa propre fenêtre, Impeller effectue le rendu dans un objet de tampon de trame OpenGL (FBO) existant fourni par la phase 4. Pour ce faire, appelez
Surface::create_wrapped_fbo.
3.2 Gestion des ressources
Images : prend en charge les formats standards et les textures compressées KTX2. Elles sont importées dans des textures GPU et gérées par une structure
Resourcesinterne.Polices : les polices TrueType et OpenType sont chargées et enregistrées auprès de
TypographyContextpour le rendu du texte.Images externes : la gestion spécialisée des textures externes (par exemple, les flux de caméras et les moteurs de rendu 3D externes) implique la liaison d'instances
EGLImageou de textures OpenGL externes à des objetsTextureImpeller pour un rendu sans copie.
3.3 Passe de rendu
La boucle render construit une instance DisplayList Impeller (à ne pas
confondre avec le Vec<DisplayListEntry> généré par la phase de prerender)
à l'aide de DisplayListBuilder :
Efface le tampon et applique des transformations globales pour la mise à l'échelle DPI et la rotation de l'écran.
Parcourt les éléments
DisplayListEntryd'entrée :- État :
save()etrestore()sont utilisés pour envoyer et extraire des transformations et des régions de clip. - Primitifs :
RectetRoundedRectsont dessinés à l'aide d'opérations de peinture standards. - Chemins : les chemins vectoriels complexes (y compris les instances
Arcdynamiques) sont créés et dessinés. - Texte :
TextetStyledTextsont affichés à l'aide deTypographyContext. - Images : les images standards et externes sont dessinées à l'aide de
draw_texture_rect.
- État :
Envoie la liste d'affichage Impeller créée à la surface à l'aide de
surface.draw_display_list(), en générant les commandes GL sous-jacentes.Appelle
swap_buffers()sur le contexte sous-jacent pour déclencher la phase 4.
Phase 4 : Présentation
Cette phase finale gère l'interaction avec le matériel d'affichage pour afficher la trame rendue. HAR utilise un chemin de rendu direct robuste sur le véhicule défini par logiciel (SDV, Software-Defined Vehicle) Android Automotive OS (AAOS).
Le composant clé de cette phase est HarDirectRenderingContext (dans le crate har-gl-context).
4.1 Architecture
La couche de présentation utilise une approche à double tampon avec une cible de dessin hors écran :
Tampon de dessin : FBO hors écran où Impeller affiche la scène.
Tampon de résolution (facultatif) : tampon auxiliaire facultatif pour prendre en charge l'anticrénelage multisampling (MSAA)
- Il peut être activé si nécessaire par l'implémentation ou la configuration OpenGL ES sous-jacente. Dans ce cas, il sert de cible intermédiaire pour résoudre le tampon de dessin multisampling avant le blitting (transfert de bloc de bits) vers le tampon de rendu.
Tampon de rendu : tampon générique soutenu par un objet GBM, qui correspond au tampon arrière d'une chaîne d'échange graphique typique.
Tampon avant : tampon GBM analysé sur l'écran.
4.2 Chaîne d'échange
Lorsque swap_buffers est appelé, HAR suit les étapes suivantes :
Effectue un blitting du contenu du tampon de dessin vers le tampon de rendu (avec un blitting intermédiaire vers le tampon de résolution, si nécessaire pour l'implémentation).
Appelle
glFlush()sur le contexte GL et crée une instance deEGL_SYNC_NATIVE_FENCE_ANDROIDpour suivre l'achèvement du GPU.Crée une requête atomique DRM pour échanger le tampon de rendu avec l'écran. Cette requête contient le FD de la barrière GPU (appelée barrière d'entrée) pour empêcher le contrôleur d'affichage de montrer le tampon de rendu avant que le GPU n'ait terminé le dessin.
Demande simultanément une nouvelle barrière à DRM (appelée barrière de sortie) afin de signaler quand le tampon précédent (le tampon avant de la trame précédente) n'est plus à l'écran.
Valide la requête atomique à l'aide de l'indicateur non bloquant, afin de permettre au thread principal de continuer pendant que les sous-systèmes graphiques restent synchronisés.
Stocke la nouvelle barrière de sortie dans le contexte afin que HAR puisse attendre qu'elle soit signalée au début du processus
swap_bufferssur la trame suivante. Cela empêche le GPU de dessiner dans un tampon qui est toujours affiché.
4.3 Définition du mode direct
HAR interagit directement avec le noyau à l'aide des sous-systèmes DRM et KMS (Kernel Mode Setting) pour configurer la résolution d'affichage AAOS SDV, en contournant les interactions avec les gestionnaires de fenêtres tels que SurfaceFlinger (dans des configurations spécifiques), ce qui permet un contrôle exclusif et prioritaire du matériel d'affichage.
4.4 Rendu externe
HAR permet de déléguer le rendu d'éléments d'UI spécifiques (identifiés par des tags dans Figma) à des processus ou threads externes. Cela est utile pour intégrer des scènes 3D complexes (par exemple, une visualisation de voiture ego à partir de moteurs tels que Kanzi ou Unity) ou d'autres contenus nécessitant un contexte OpenGL dédié.
4.4.1 Composants clés
HarExternalRenderContext: contexte EGL hors écran dédié pour le service externe.SurfacePool: gère un ensemble de tamponsLocalSurface(TextureplusEGLImage) pour la mise en mémoire tampon double ou triple.SharedSurfaceExternalImage: wrapper thread-safe pour transmettre des handlesEGLImageentre le service externe et le moteur de rendu principal.
4.4.2 Workflow
Le workflow suit cette séquence :
Le service externe démarre et s'enregistre auprès du looper principal, en identifiant les tags Figma (par exemple,
#cluster/3d-car) qu'il affiche.Le service attend les signaux
RenderStartdu looper pour aligner son rendu sur le signal VSYNC de l'écran.Hors écran, le service affiche son contenu dans un tampon de trame fourni par
SurfacePool.Le service appelle
swap_bufferssur son contexte, ce qui fait pivoter le pool et met la trame terminée à disposition en tant qu'instance deSharedSurface.SharedSurfaceest encapsulé dansExternalImageet envoyé via un canal MPSC Rust au looper.Le moteur de rendu Impeller principal (phase 3) reçoit l'image externe. Au lieu de copier les données de pixels, il lie directement l'
EGLImagesous-jacent à une texture et le dessine dans le cadre de la scène principale, ce qui permet une composition sans copie.
4.5 Plates-formes de développement et de test (har-platform-linux)
À des fins de développement et de test, les applications HAR peuvent cibler des environnements de bureau Linux standards et des configurations sans interface graphique. Ces plates-formes sont implémentées dans le crate crates/reference/platforms/har-platform-linux.
Contrairement à la cible de production AAOS SDV, ces plates-formes n'utilisent pas le sous-système direct-rendering de har-gl-context pour la sortie d'affichage. Au lieu de cela, elles s'appuient sur des crates Rust OpenGL standards :
Mode fenêtré : utilise
winitpour la gestion des fenêtres et les boucles d'événements, etglutinpour créer des contextes OpenGL ES et s'intégrer au système de fenêtrage.Mode sans interface graphique : utilise le crate
har-gl-contextpour créer un contexte pbuffer hors écran avec l'affichage EGL par défaut. Cela permet d'effectuer le rendu dans un tampon hors écran sans avoir besoin d'une fenêtre visible ni d'un accès direct au matériel d'affichage, principalement utilisé pour les tests automatisés ou le traitement backend.