Architecture RenderingNG

Chris Harrelson
Chris Harrelson

Vous découvrirez comment les composants de RenderingNG sont configurés et comment le pipeline de rendu les traverse.

En commençant par le niveau le plus élevé, les tâches de rendu sont les suivantes:

  1. Rendez les contenus en pixels à l'écran.
  2. Animez les effets visuels sur les contenus d'un état à un autre.
  3. Défilement en réponse à une entrée.
  4. Acheminez efficacement les entrées vers les bons endroits afin que les scripts de développement et les autres sous-systèmes puissent y répondre.

Les contenus à afficher sont un arbre de cadres pour chaque onglet du navigateur, ainsi que l'interface du navigateur. flux d'événements d'entrée bruts provenant d'écrans tactiles, de souris, de claviers et d'autres périphériques matériels.

Chaque frame comprend les éléments suivants:

  • État du DOM
  • CSS
  • Canevas
  • Ressources externes, telles que des images, des vidéos, des polices et des fichiers SVG

Un frame est un document HTML, ainsi que son URL. Une page Web chargée dans un onglet de navigateur comporte un frame racine, des frames enfants pour chaque iFrame incluse dans le document racine et leurs descendants iFrame récursifs.

Un effet visuel est une opération graphique appliquée à un bitmap, comme le défilement, la transformation, le découpage, le filtre, l'opacité ou le mélange.

Composants de l'architecture

Dans RenderingNG, ces tâches sont réparties de manière logique sur plusieurs étapes et composants de code. Les composants se retrouvent dans divers processus de processeur, threads et sous-composants au sein de ces threads. Chacun joue un rôle important pour assurer la fiabilité, les performances évolutives et l'extensibilité de tous les contenus Web.

Structure du pipeline de rendu

Schéma du pipeline de rendu.
Les flèches indiquent les entrées et les sorties de chaque étage. Les étapes sont notées par couleur pour démontrer le thread ou le processus qu'elles exécutent. Dans certains cas, les étapes peuvent s'exécuter à plusieurs endroits, en fonction des circonstances. C'est pourquoi certaines ont deux couleurs. Les étapes vertes correspondent au thread principal du processus de rendu, les étapes jaunes aux compositeurs du processus de rendu et les étapes orange au processus de visualisation.

Le rendu se déroule dans un pipeline avec un certain nombre d'étapes et d'artefacts créés en cours de route. Chaque étape représente du code qui effectue une tâche bien définie dans le rendu. Les artefacts sont des structures de données, qui sont des entrées ou des sorties des étapes.

Les étapes sont les suivantes:

  1. Animation:modifiez les styles calculés et modifiez les arborescences de propriétés au fil du temps en fonction de chronologies déclaratives.
  2. Style:appliquez le CSS au DOM et créez des styles calculés.
  3. Layout (Mise en page) : déterminez la taille et la position des éléments DOM à l'écran, puis créez l'arborescence de fragments immuables.
  4. Pré-peinture:calculez les arborescences de propriétés et invalidate toutes les listes d'affichage et les tuiles de texture du GPU existantes, le cas échéant.
  5. Défilement:modifiez le décalage de défilement des documents et des éléments DOM à faire défiler en modifiant les arbres de propriétés.
  6. Paint:calcule une liste d'affichage qui décrit comment rasteriser les tuiles de texture du GPU à partir du DOM.
  7. Commit:copiez les arbres de propriétés et la liste d'affichage dans le thread du compositeur.
  8. Coucher:divisez la liste d'affichage en une liste de calques composée pour une rastérisation et une animation indépendantes.
  9. Worklets matricielles, de décodage et de peinture:transformez respectivement les listes d'affichage, les images encodées et le code du Worklet de peinture en tuiles de texture GPU.
  10. Activer:créez un cadre de composition représentant comment dessiner et positionner les tuiles GPU à l'écran, ainsi que les éventuels effets visuels.
  11. Agrégation:combinez les frames du compositeur de tous les frames de compositeur visibles en un seul frame de compositeur global.
  12. Draw (Dessiner) : exécute l'image compositeur agrégée sur le GPU pour créer des pixels à l'écran.

Les étapes du pipeline de rendu peuvent être ignorées si elles ne sont pas nécessaires. Par exemple, les animations d'effets visuels et de défilement peuvent ignorer la mise en page, la pré-peinture et la peinture. C'est pourquoi l'animation et le défilement sont marqués par des points jaunes et verts dans le diagramme. Si la mise en page, la pré-peinture et la peinture peuvent être ignorées pour les effets visuels, elles peuvent être exécutées entièrement sur le thread du compositeur et ignorer le thread principal.

Le rendu de l'interface utilisateur du navigateur n'est pas représenté directement ici, mais peut être considéré comme une version simplifiée de ce même pipeline (et en fait, son implémentation partage une grande partie du code). La vidéo (qui n'est pas non plus représentée directement) est généralement affichée à l'aide d'un code indépendant qui décode les frames en tuiles de texture GPU qui sont ensuite connectées aux frames du compositeur et à l'étape de dessin.

Structure des processus et des threads

Processus du processeur

L'utilisation de plusieurs processus de processeur permet d'assurer l'isolation des performances et de la sécurité entre les sites et l'état du navigateur, ainsi que la stabilité et l'isolation de la sécurité par rapport au matériel du GPU.

Schéma des différentes parties des processus du processeur

  • Le processus de rendu effectue le rendu, l'animation, le défilement et le routage des entrées pour une seule combinaison de site et d'onglet. Il existe plusieurs processus de rendu.
  • Le processus du navigateur effectue le rendu, l'animation et le routage des entrées pour l'interface utilisateur du navigateur (y compris la barre d'adresse, les titres des onglets et les icônes), et achemine toutes les entrées restantes vers le processus de rendu approprié. Il n'y a qu'un seul processus de navigateur.
  • Le processus de visualisation agrège le compositing de plusieurs processus de rendu, ainsi que le processus du navigateur. Il crée des trames et dessine à l'aide du GPU. Il existe un seul processus Viz.

Chaque site se retrouve toujours dans un processus de rendu différent.

Plusieurs onglets ou fenêtres de navigateur du même site suivent généralement des processus d'affichage différents, sauf si les onglets sont liés, par exemple l'ouverture l'autre. En cas de forte pression sur la mémoire sur un ordinateur, Chromium peut placer plusieurs onglets du même site dans le même processus de rendu, même s'ils ne sont pas liés.

Dans un même onglet de navigateur, les cadres de différents sites suivent toujours le même processus d'affichage les uns des autres, mais les images d'un même site suivent toujours le même processus d'affichage. Du point de vue du rendu, l'avantage important des processus de rendu multiples est que les iFrames et les onglets intersites permettent une isolation des performances les uns par rapport aux autres. De plus, les origines peuvent activer une isolation encore plus poussée.

Il existe exactement un processus de visualisation pour l'ensemble de Chromium, car il n'y a généralement qu'un seul GPU et qu'un seul écran pour le dessin.

Séparer la visualisation dans son propre processus est bénéfique pour la stabilité en cas de bugs dans les pilotes ou le matériel du GPU. Il est également utile pour l'isolation de sécurité, ce qui est important pour les API GPU telles que Vulkan et la sécurité en général.

Étant donné que le navigateur peut avoir de nombreux onglets et fenêtres, et que tous ont des pixels d'interface utilisateur du navigateur à dessiner, vous vous demandez peut-être pourquoi il n'y a qu'un seul processus de navigateur. En effet, un seul d'entre eux est actif à la fois. En fait, les onglets de navigateur non visibles sont généralement désactivés et libèrent toute leur mémoire GPU. Cependant, les fonctionnalités de rendu de l'interface utilisateur du navigateur complexes sont de plus en plus implémentées dans les processus de rendu (appelés WebUI). Cela n'est pas dû à l'isolation des performances, mais pour profiter de la facilité d'utilisation du moteur de rendu Web de Chromium.

Sur les appareils Android plus anciens, le processus d'affichage et de navigateur est partagé lorsqu'il est utilisé dans une WebView (cela ne s'applique pas en général à Chromium sur Android, mais uniquement à WebView). Dans WebView, le processus du navigateur est également partagé avec l'application d'intégration, et WebView n'a qu'un seul processus de rendu.

Il existe parfois également un processus utilitaire pour décoder le contenu vidéo protégé. Ce processus n'est pas représenté dans les schémas précédents.

Threads

Les threads permettent d'obtenir une isolation des performances et une réactivité malgré les tâches lentes, la parallélisation de pipeline et le tamponnage multiple.

Schéma du processus de rendu.

  • Le thread principal exécute les scripts, la boucle d'événements de rendu, le cycle de vie du document, les tests de réussite, le traitement des événements de script et l'analyse des formats HTML, CSS et autres.
    • Les assistants de thread principal effectuent des tâches telles que la création de bitmaps d'image et de blobs nécessitant un encodage ou un décodage.
    • Web Workers exécutent le script et une boucle d'événements de rendu pour OffscreenCanvas.
  • Le thread du moteur de composition traite les événements d'entrée, effectue le défilement et les animations du contenu Web, calcule la stratification optimale du contenu Web et coordonne les décodages d'images, les worklets de peinture et les tâches de rastérisation.
    • Les assistants de thread du compositeur coordonnent les tâches de trame Viz et exécutent les tâches de décodage d'image, les Worklets de peinture et les trames de remplacement.
  • Les threads de sortie multimédia, demuxeur ou audio décodent, traitent et synchronisent les flux vidéo et audio. (N'oubliez pas que la vidéo s'exécute en parallèle avec le pipeline de rendu principal.)

La séparation des threads principaux et du compositeur est essentielle pour l'isolation des performances de l'animation et du défilement par rapport au travail du thread principal.

Il n'y a qu'un seul thread principal par processus de rendu, même si plusieurs onglets ou cadres du même site peuvent se retrouver dans le même processus. Toutefois, il existe une isolation des performances par rapport au travail effectué dans différentes API de navigateur. Par exemple, la génération de bitmaps et de blobs d'image dans l'API Canvas s'exécute dans un thread d'assistance du thread principal.

De même, il n'existe qu'un seul thread de composition par processus de rendu. Il n'y a généralement pas de problème s'il n'y en a qu'une, car toutes les opérations très coûteuses sur le thread compositeur sont déléguées à des threads de travail du compositeur ou au processus de visualisation. Ce travail peut être effectué en parallèle avec le routage des entrées, le défilement ou l'animation. Les threads de travail du compositeur coordonnent les tâches exécutées dans le processus Viz, mais l'accélération du GPU partout peut échouer pour des raisons indépendantes de la volonté de Chromium, comme des bugs de pilote. Dans ces situations, le thread de travail effectue la tâche en mode de remplacement sur le processeur.

Le nombre de threads de travail du compositeur dépend des fonctionnalités de l'appareil. Par exemple, les ordinateurs de bureau utilisent généralement plus de threads, car ils disposent de plus de cœurs de processeur et sont moins limités par la batterie que les appareils mobiles. Il s'agit d'un exemple de mise à l'échelle et de réduction de l'échelle.

L'architecture de thread du processus de rendu est une application de trois modèles d'optimisation différents:

  • Threads d'assistance: envoient des sous-tâches de longue durée à des threads supplémentaires pour que le thread parent reste réactif aux autres requêtes simultanées. Les threads d'assistance du thread principal et du compositeur sont de bons exemples de cette technique.
  • Multitamponnage : affiche le contenu précédemment rendu pendant le rendu d'un nouveau contenu, afin de masquer la latence de rendu. Le thread du moteur de rendu utilise cette technique.
  • Parallélisation de pipeline:exécutez le pipeline de rendu à plusieurs endroits simultanément. C'est ainsi que le défilement et l'animation peuvent être rapides. Même si une mise à jour du rendu du thread principal est en cours, le défilement et l'animation peuvent s'exécuter en parallèle.

Processus du navigateur

Schéma du processus du navigateur montrant la relation entre le thread de rendu et de composition, et l'assistant de thread de rendu et de composition.

  • Le thread de rendu et de composition répond aux entrées dans l'interface utilisateur du navigateur, achemine les autres entrées vers le processus de rendu approprié, met en page et peint l'interface utilisateur du navigateur.
  • Les assistants de thread de rendu et de composition exécutent des tâches de décodage d'image et de rastérisation ou de décodage de remplacement.

Le thread de rendu et de composition du processus du navigateur est semblable au code et aux fonctionnalités d'un processus de rendu, à l'exception du fait que le thread principal et le thread du compositeur sont combinés. Un seul thread est nécessaire dans ce cas, car il n'est pas nécessaire d'isoler les performances des longues tâches du thread principal, car il n'y en a pas à la conception.

Processus de visualisation

Le processus de visualisation comprend le thread principal du GPU et le thread du compositeur d'affichage.

  • Les trames Thread principal du GPU affichent des listes et des images vidéo dans des tuiles de texture GPU, et dessine des images du compositeur à l'écran.
  • Le thread du compositeur d'affichage regroupe et optimise la composition de chaque processus de rendu, ainsi que du processus du navigateur, dans un seul cadre de compositeur pour la présentation à l'écran.

Le rastérisation et le dessin se produisent généralement sur le même thread, car ils reposent tous les deux sur les ressources du GPU, et il est difficile d'utiliser de manière fiable le GPU en mode multithread (un accès multithread plus facile au GPU est l'une des motivations du développement de la nouvelle norme Vulkan). Sur Android WebView, un thread de rendu distinct au niveau de l'OS est utilisé pour le dessin en raison de la façon dont les WebViews sont intégrées à une application native. D'autres plates-formes disposeront probablement d'un tel thread à l'avenir.

Le compositeur d'affichage se trouve sur un thread différent, car il doit être réactif en permanence et ne pas bloquer sur une source de ralentissement possible sur le thread principal du GPU. Les appels vers du code autre que Chromium, tels que les pilotes GPU spécifiques au fournisseur, qui peuvent être lents de manière difficile à prévoir, sont une cause de ralentissement du thread principal du GPU.

Structure des composants

Dans chaque thread principal ou de composition du processus de rendu, il existe des composants logiciels logiques qui interagissent entre eux de manière structurée.

Composants du thread principal du processus de rendu

Schéma du moteur de rendu Blink.

Dans le moteur de rendu Blink:

  • Le fragment d'arborescence de frame local représente l'arborescence des frames locaux et le DOM dans les frames.
  • Le composant API DOM et Canvas contient des implémentations de toutes ces API.
  • Le gestionnaire de cycle de vie des documents exécute les étapes du pipeline de rendu jusqu'à l'étape de validation, y compris.
  • Le composant Test et distribution des événements de saisie exécute des tests de contact pour déterminer quel élément DOM est ciblé par un événement, et exécute les algorithmes de distribution des événements de saisie et les comportements par défaut.

Le planificateur et le programmeur de la boucle d'événements de rendu décident de ce qu'il faut exécuter sur la boucle d'événements et quand. Il planifie le rendu à une fréquence correspondant à l'affichage de l'appareil.

Diagramme de l'arborescence des frames.

Les fragments d'arborescence de trames locaux sont un peu complexes. Rappelez-vous qu'une arborescence de frames correspond à la page principale et à ses iFrames enfants, de manière récursive. Une trame est locale par rapport à un processus de rendu si elle est affichée dans ce processus, sinon elle est distante.

Vous pouvez imaginer la coloration des cadres en fonction de leur processus de rendu. Dans l'image précédente, les cercles verts sont tous les cadres d'un processus de rendu ; les cercles orange sont dans un second et le bleu dans un troisième.

Un fragment d'arborescence de trames locales est un composant connecté de la même couleur dans une arborescence de trames. L'image comporte quatre arbres de cadre locaux: deux pour le site A, un pour le site B et un pour le site C. Chaque arborescence de frame locale reçoit son propre composant de rendu Blink. Le moteur de rendu Blink d'une arborescence de frames locale peut ou non se trouver dans le même processus de rendu que les autres arborescences de frames locales. Il est déterminé par la manière dont les processus de rendu sont sélectionnés, comme décrit précédemment.

Structure du thread du compositeur de processus de rendu

Schéma illustrant les composants du compositeur du processus de rendu.

Les composants du compositeur du processus de rendu sont les suivants:

  • Gestionnaire de données qui gère une liste de calques composée, des listes d'affichage et des arborescences de propriétés.
  • Un exécuteur de cycle de vie qui exécute les étapes d'animation, de défilement, de composition, de rastérisation, de décodage et d'activation du pipeline de rendu. (N'oubliez pas que l'animation et le défilement peuvent se produire à la fois dans le thread principal et dans le compositeur.)
  • Un gestionnaire de test des entrées et des hits effectue le traitement des entrées et des tests de positionnement sur la résolution des couches composites afin de déterminer si des gestes de défilement peuvent être exécutés sur le thread compositeur et quel processus de rendu les tests de positionnement doivent cibler.

Exemple d'architecture en pratique

Cet exemple comporte trois onglets:

Onglet 1: foo.com

<html>
  <iframe id=one src="foo.com/other-url"></iframe>
  <iframe  id=two src="bar.com"></iframe>
</html>

Onglet 2: bar.com

<html>
 …
</html>

Onglet 3: baz.com html <html> … </html>

Le processus, le thread et la structure des composants de ces onglets se présentent comme suit:

Schéma du processus des onglets.

Examinons un exemple pour chacune des quatre principales tâches de rendu. Pour rappel:

  1. Affichez les contenus en pixels à l'écran.
  2. Animate les effets visuels sur les contenus d'un état à un autre.
  3. Défilement en réponse à une entrée.
  4. Acheminez efficacement les entrées aux bons endroits afin que les scripts de développement et les autres sous-systèmes puissent répondre.

Pour afficher le DOM modifié pour l'onglet 1:

  1. Un script de développeur modifie le DOM dans le processus de rendu de foo.com.
  2. Le moteur de rendu Blink indique au compositeur qu'un rendu doit être effectué.
  3. Le compositeur indique à Viz qu'un rendu doit être effectué.
  4. Viz signale le début du rendu au compositeur.
  5. Le compositeur transmet le signal de démarrage au moteur de rendu Blink.
  6. Le programme d'exécution de la boucle d'événements du thread principal exécute le cycle de vie du document.
  7. Le thread principal envoie le résultat au thread du compositeur.
  8. L'exécuteur de boucle d'événements du moteur de composition exécute le cycle de vie de la composition.
  9. Toutes les tâches de raster sont envoyées à Viz pour raster (il y en a souvent plusieurs).
  10. La visualisation effectue un tramage du contenu sur le GPU.
  11. Viz confirme la fin de la tâche de rasterisation. Remarque: Souvent, Chromium n'attend pas que la trame se termine. À la place, il utilise ce qu'on appelle un jeton de synchronisation, qui doit être résolu par des tâches matricielles avant l'exécution de l'étape 15.
  12. Une trame de composition est envoyée à Viz.
  13. Viz regroupe les frames du compositeur pour le processus de rendu de foo.com, le processus de rendu de l'iFrame bar.com et l'UI du navigateur.
  14. Visualisation planifie un tirage au sort.
  15. Viz dessine le frame du compositeur agrégé à l'écran.

Pour animer une transition de transformation CSS dans l'onglet 2:

  1. Le thread du compositeur pour le processus de rendu bar.com fait pointer une animation dans sa boucle d'événements de compositeur en modifiant les arborescences de propriétés existantes. Le cycle de vie du compositeur est alors réexécuté. (Des tâches de trame et de décodage peuvent avoir lieu, mais elles ne sont pas décrites ici.)
  2. Une trame de composition est envoyée à Viz.
  3. Viz agrège les frames du compositeur pour le processus de rendu foo.com, le processus de rendu bar.com et l'interface utilisateur du navigateur.
  4. Viz planifie un tirage au sort.
  5. Viz dessine le frame compositeur agrégé à l'écran.

Pour faire défiler la page Web dans le troisième onglet:

  1. Une séquence d'événements input (souris, tactile ou clavier) est transmise au processus du navigateur.
  2. Chaque événement est acheminé vers le thread du compositeur du processus de rendu de baz.com.
  3. Le compositeur détermine si le thread principal doit connaître l'événement.
  4. L'événement est envoyé, si nécessaire, au thread principal.
  5. Le thread principal déclenche des écouteurs d'événements input (pointerdown, touchstar, pointermove, touchmove ou wheel) pour voir si les écouteurs appelleront preventDefault sur l'événement.
  6. Le thread principal indique si preventDefault a été appelé au compositeur.
  7. Sinon, l'événement d'entrée est renvoyé au processus du navigateur.
  8. Le processus du navigateur le convertit en geste de défilement en le combinant à d'autres événements récents.
  9. Le geste de défilement est à nouveau envoyé au thread du compositeur de processus de rendu de baz.com.
  10. Le défilement y est appliqué, et le thread du moteur de rendu pour le processus de rendu bar.com active une animation dans sa boucle d'événements du moteur de rendu. Le décalage de défilement est ensuite modifié dans les arbres de propriétés et le cycle de vie du compositeur est réexécuté. Il indique également au thread principal de déclencher un événement scroll (non représenté ici).
  11. Une trame de composition est envoyée à Viz.
  12. Viz agrège les frames du compositeur pour le processus de rendu foo.com, le processus de rendu bar.com et l'interface utilisateur du navigateur.
  13. Viz planifie un tirage au sort.
  14. Viz dessine le frame du compositeur agrégé à l'écran.

Pour acheminer un événement click vers un lien hypertexte dans l'iFrame 2 de l'onglet 1:

  1. Un événement input (souris, écran tactile ou clavier) est transmis au processus du navigateur. Il effectue un test d'impact approximatif pour déterminer que le processus de rendu de l'iFrame bar.com doit recevoir le clic et l'y envoie.
  2. Le thread du compositeur pour bar.com achemine l'événement click vers le thread principal pour bar.com et planifie une tâche de boucle d'événements de rendu pour le traiter.
  3. Le processeur d'événements d'entrée pour les tests de positionnement du thread principal de bar.com afin de déterminer quel élément DOM de l'iFrame a reçu un clic et déclenche un événement click pour que les scripts les surveillent. Si vous n'entendez pas de preventDefault, l'action permet d'accéder au lien hypertexte.
  4. Lors du chargement de la page de destination de l'hyperlien, le nouvel état est affiché, avec des étapes similaires à celles de l'exemple précédent "Afficher le DOM modifié". (Ces modifications ultérieures ne sont pas représentées ici.)

Plats à emporter

Il faut parfois beaucoup de temps pour mémoriser et intérioriser le fonctionnement de l'affichage.

Le point le plus important à retenir est que le pipeline de rendu, grâce à une modularisation minutieuse et à une attention portée aux détails, a été divisé en plusieurs composants autonomes. Ces composants ont ensuite été répartis sur des processus et des threads parallèles afin de maximiser les performances évolutives et les possibilités d'extensibilité.

Chaque composant joue un rôle essentiel dans les performances et les fonctionnalités des applications Web modernes.

Poursuivez votre lecture sur les structures de données clés, qui sont tout aussi importantes pour RenderingNG que les composants de code.


Illustrations par Una Kravets.