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. Affichez 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. Et un flux d'événements d'entrée bruts provenant d'écrans tactiles, de souris, de claviers et d'autres appareils 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 de premier niveau, des frames enfants pour chaque iFrame incluse dans le document de premier niveau 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 différents processus, threads et sous-composants de processeur au sein de ces threads. Chacun joue un rôle important pour assurer la fiabilité, les performances évolutives et l'évolutivité 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 étape. Les étapes sont indiquées par couleur pour indiquer le thread ou le processus qu'elles exécutent. Dans certains cas, les étapes peuvent s'exécuter à plusieurs endroits, selon les circonstances. C'est pourquoi certaines d'entre elles sont de 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. Mise en page:détermine la taille et la position des éléments DOM à l'écran, et crée l'arborescence de fragments immuable.
  4. Pré-peinture:calculez les arborescences de propriétés et invalidate toutes les listes d'affichage et tuiles de texture 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 arborescences 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 de rastérisation, de décodage et de peinture:convertissez respectivement les listes d'affichage, les images encodées et le code du worklet de peinture en tuiles de texture de 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 moteur de composition de tous les frames de moteur de composition visibles en un seul frame de moteur de composition global.
  12. Dessin:exécutez le frame du compositeur agrégé 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 de 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 effectue le rastérisation et le dessin à l'aide du GPU. Il existe un seul processus Viz.

Les sites différents se terminent toujours par des processus de rendu différents.

Plusieurs onglets ou fenêtres de navigateur du même site sont généralement traités dans différents processus de rendu, sauf si les onglets sont liés, par exemple si l'un ouvre 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 seul onglet de navigateur, les frames de différents sites sont toujours dans des processus d'affichage différents les uns des autres, mais les frames du même site sont toujours dans 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 n'existe qu'un seul processus de visualisation pour l'ensemble de Chromium, car il n'y a généralement qu'un seul GPU et un seul écran à dessiner.

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 du navigateur non visibles sont généralement désactivés et libèrent toute leur mémoire GPU. Toutefois, 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 anciens appareils Android, le rendu et le processus du navigateur sont partagés lorsqu'ils sont utilisés dans une WebView (cela ne s'applique pas à Chromium sur Android en général, 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 diagrammes 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 tamponnement 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 moteur de composition coordonnent les tâches de rasterisation Viz, et exécutent les tâches de décodage d'image, les worklets de peinture et le raster 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 du 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. En général, ce n'est pas un problème qu'il n'y en ait qu'un seul, car toutes les opérations très coûteuses sur le thread du moteur de rendu sont déléguées aux threads de travail du moteur de rendu ou au processus Viz. 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 threads 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. Dans ce cas, un seul thread est nécessaire, car il n'est pas nécessaire d'isoler les performances des tâches de thread principal longues, car il n'y en a pas par conception.

Processus de visualisation

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

  • Le thread principal du GPU effectue le rastérisation des listes d'affichage et des images vidéo dans des tuiles de texture de GPU, et dessine des images de composition à l'écran.
  • Le thread du moteur de composition d'affichage agrège et optimise la composition de chaque processus de rendu, ainsi que le processus du navigateur, dans un seul frame de moteur de composition 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 le GPU de manière fiable en 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 sur le 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. N'oubliez pas qu'une arborescence de cadres est 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 colorer les cadres en fonction de leur processus de rendu. Dans l'image précédente, les cercles verts représentent tous des frames dans un même processus de rendu. Les cercles orange sont dans un deuxième processus, et le cercle 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 frames 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 incluent les éléments 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 saisie et de test de contact effectue le traitement des entrées et les tests de contact à la résolution des calques composites pour déterminer si les gestes de défilement peuvent être exécutés sur le thread du moteur de rendu et quels tests de contact de processus de rendu doivent cibler.

Exemple d'architecture en pratique

Dans cet exemple, trois onglets sont disponibles:

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>

La structure du processus, du thread et du composant pour ces onglets se présente 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. 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.

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: Chromium n'attend souvent pas la fin du raster et utilise à la place un jeton de synchronisation qui doit être résolu par les tâches de rasterisation 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. Viz planifie un tirage au sort.
  15. Viz dessine le frame du compositeur agrégé à l'écran.

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

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

Pour faire défiler la page Web dans l'onglet 3:

  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 de 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 appellent 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, tactile ou clavier) est envoyé 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 du thread principal de bar.com effectue des tests pour déterminer sur quel élément DOM de l'iFrame l'utilisateur a cliqué, puis déclenche un événement click à observer par les scripts. N'entendant pas de preventDefault, il accède 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 peut s'écouler beaucoup de temps avant de retenir et d'assimiler le fonctionnement du rendu.

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 pour assurer les performances et les fonctionnalités des applications Web modernes.

Poursuivez votre lecture pour découvrir les principales structures de données, qui sont tout aussi importantes pour RenderingNG que les composants de code.


Illustrations par Una Kravets.