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 du rendu sont les suivantes:

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

Le contenu à afficher se présente sous la forme d'une arborescence de cadres pour chaque onglet du navigateur, ainsi que de 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 trame comprend les éléments suivants:

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

Un cadre est un document HTML et son URL. Une page Web chargée dans un onglet de navigateur comporte un cadre de premier niveau, des cadres enfants pour chaque iFrame inclus dans le document de premier niveau, ainsi que leurs descendants iFrame récursifs.

Un effet visuel est une opération graphique appliquée à un bitmap, comme un défilement, une transformation, un extrait, un filtre, une opacité ou une fusion.

Composants de l'architecture

Dans RenderingNG, ces tâches sont réparties logiquement entre 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 d'eux 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 traitent le thread principal ; les étapes jaunes correspondent aux compositeurs du processus de rendu ; les étapes orange correspondent au processus de visualisation.

Le rendu s'effectue 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 exécute 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:modifie les styles calculés et modifie les arborescences de propriétés au fil du temps en fonction de chronologies déclaratives.
  2. Style:appliquez du code 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é-peindre:calculez les arborescences de propriétés et invalidez les listes d'affichage et les 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 arborescences de propriétés.
  6. Paint:calcule une liste d'affichage décrivant comment matriciel des tuiles de texture GPU à partir du DOM.
  7. Commit:copie les arborescences de propriétés et la liste d'affichage dans le thread du compositeur.
  8. Layerize (Calques) : divisez la liste d'affichage en une liste de calques composites 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 compositeur représentant comment dessiner et positionner les tuiles GPU à l'écran, ainsi que des effets visuels.
  11. Agrégation:combine les cadres compositeurs issus de tous les cadres visibles du compositeur dans un seul cadre 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 indiqués par des points jaunes et verts sur 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'UI 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 directement représentée) effectue généralement le rendu avec un code indépendant qui décode les images en tuiles de texture GPU, qui sont ensuite branché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 une isolation en termes de performances et de sécurité entre les sites et par rapport à l'état du navigateur, ainsi qu'une stabilité et une isolation de sécurité par rapport au matériel GPU.

Diagramme des différentes parties des processus du processeur

  • Le processus de rendu affiche, anime, fait défiler et achemine les entrées pour une seule combinaison de site et d'onglet. Il existe plusieurs processus de rendu.
  • Le processus du navigateur affiche, anime et achemine les entrées de l'UI du navigateur (y compris la barre d'adresse, les titres d'onglet et les icônes), et achemine toutes les entrées restantes vers le processus de rendu approprié. Il existe un seul processus de navigateur.
  • Le processus Viz regroupe la composition 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 processus de visualisation.

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 ordinateur, Chromium peut placer plusieurs onglets d'un même site dans le même processus d'affichage, 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 de plusieurs processus de rendu est que les iFrames et les onglets intersites atteignent une isolation des performances les uns des autres. De plus, les origines peuvent activer une isolation encore plus importante.

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 Viz dans son propre processus est une bonne solution pour assurer la stabilité face aux bugs dans les pilotes ou le matériel 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 comporter de nombreux onglets et fenêtres, et qu'ils peuvent tous dessiner des pixels d'interface utilisateur, vous vous demandez peut-être pourquoi il existe un seul processus de navigateur. En effet, un seul d'entre eux est sélectionné à la fois. En fait, les onglets de navigateur non visibles sont pour la plupart désactivés et suppriment toute leur mémoire GPU. Cependant, des fonctionnalités complexes de rendu d'interface utilisateur de navigateur sont de plus en plus souvent mises en œuvre dans les processus de rendu (connues sous le nom de WebUI). Ce n'est pas pour isoler les 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). Sur WebView, le processus du navigateur est également partagé avec l'application d'intégration, qui ne comporte qu'un seul processus d'affichage.

Parfois, un utilitaire permet également de décoder du contenu vidéo protégé. Ce processus n'est pas représenté dans les schémas précédents.

Fils de discussion

Les threads aident à isoler les performances et à assurer la réactivité malgré les tâches lentes, la parallélisation du pipeline et la mise en mémoire tampon multiple.

Schéma du processus de rendu.

  • Le thread principal exécute les scripts, la boucle d'événement de rendu, le cycle de vie du document, les tests de positionnement, l'envoi des événements de script, et l'analyse du format HTML, CSS et d'autres formats de données.
    • Les assistants de thread principaux effectuent des tâches telles que la création de bitmaps et de blobs d'image qui nécessitent un encodage ou un décodage.
    • Les nœuds de calcul Web exécutent le script et une boucle d'événements de rendu pour OffscreenCanvas.
  • Le thread du compositeur traite les événements d'entrée, effectue le défilement et des animations de contenu Web, calcule la superposition de couches optimale du contenu Web, et coordonne le décodage des images, les peignets et les tâches matricielles.
    • 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 multimédias, de démuxeur ou de sortie 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 principal et compositeur est essentiel pour isoler les performances de l'animation et du défilement des tâches du thread principal.

Il n'y a qu'un seul thread principal par processus de rendu, même si plusieurs onglets ou cadres d'un même site peuvent se retrouver dans le même processus. Toutefois, les performances sont isolées du travail effectué dans les 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 de thread principal.

De même, il n'y a qu'un seul thread compositeur 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 de visualisation, mais l'accélération du GPU dans tous les environnements peut échouer pour des raisons indépendantes de la volonté de Chromium, telles que des bugs au niveau des pilotes. Dans ces situations, le thread de travail effectue le travail en mode de secours sur le processeur.

Le nombre de threads de travail du compositeur dépend des capacités de l'appareil. Par exemple, les ordinateurs de bureau utilisent généralement davantage de threads, car ils ont plus de cœurs de processeur et sont moins limités en batterie que les appareils mobiles. Il s'agit d'un exemple de scaling à la hausse et à la baisse.

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

  • Threads d'assistance: envoyez des tâches secondaires de longue durée à des threads supplémentaires pour que le thread parent réponde à d'autres requêtes simultanées. Les threads principaux de l'assistant de thread et des threads d'aide du compositeur sont de bons exemples de cette technique.
  • Mise en mémoire tampon multiple : affiche le contenu précédemment affiché tout en affichant le nouveau contenu, pour masquer la latence de rendu. Cette technique est utilisée par le fil de discussion du compositeur.
  • Parallélisation du pipeline:exécute 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 de 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'UI du navigateur, achemine les autres entrées vers le processus de rendu approprié, et met en page et peint l'UI du navigateur.
  • Les assistants de rendu et de composition des threads exécutent des tâches de décodage d'image et une trame ou un décodage de remplacement.

Le thread de rendu et de composition du processus de navigateur est semblable au code et aux fonctionnalités d'un processus de rendu, à la différence que le thread principal et le thread compositeur sont combinés en un seul. Un seul thread n'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 inclut le thread principal 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.

Les trames et les dessins s'effectuent généralement sur le même thread, car ils reposent tous deux sur des ressources GPU, et il est difficile d'utiliser de manière fiable le GPU multithread (un accès multithread plus facile au GPU est l'une des motivations pour développer la nouvelle norme Vulkan). Sur Android WebView, il existe un thread de rendu distinct au niveau de l'OS pour le dessin, car les WebViews sont intégrés à 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 à tout moment et ne bloquer aucune source potentielle de ralentissement sur le thread principal du GPU. L'une des causes de ralentissement sur le thread principal GPU est liée aux appels dans du code autre que Chromium, comme les pilotes de GPU propres au fournisseur, qui peuvent être lents de manière difficile à prédire.

Structure des composants

Dans chaque thread principal ou compositeur de processus de rendu, des composants logiciels logiques interagissent les uns avec les autres 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 frames locaux représente l'arborescence des frames locaux et le DOM au sein des frames.
  • Le composant API DOM et Canvas contient des implémentations de toutes ces API.
  • L'exécuteur de cycle de vie des documents exécute les étapes du pipeline de rendu jusqu'à l'étape de commit (incluse).
  • Le composant de test et distribution des événements d'entrée exécute des tests de positionnement pour identifier l'élément DOM ciblé par un événement. Il exécute également les algorithmes de distribution des événements d'entrée et les comportements par défaut.

L'exécuteur et le programmeur de boucle d'événements de rendu décide quels éléments s'exécuter dans la boucle d'événements et à quel moment. Il planifie le rendu à un rythme correspondant à l'écran de l'appareil.

Schéma de l'arborescence des frames.

Les fragments d'arborescence de frames locaux sont un peu compliqués. Rappelez-vous qu'une arborescence de frames correspond à la page principale et à ses iFrames enfants, de manière récursive. Un frame est local dans un processus de rendu si il est rendu dans ce processus. Sinon, il est distant.

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 frames local est un composant connecté de la même couleur dans une arborescence de frames. 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 frames locale dispose de son propre composant de moteur de rendu Blink. Le moteur de rendu Blink d'une arborescence de frames locale peut se trouver dans le même processus de rendu que d'autres arborescences de frames locales. Elle dépend de la façon dont les processus de rendu sont sélectionnés, comme décrit précédemment.

Structure de thread du compositeur du 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 composites, 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 composite, de trame, de décodage et d'activation du pipeline de rendu. N'oubliez pas que les animations et les défilements 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 pour les onglets.

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

  1. Rendez les contenus en pixels à l'écran.
  2. Animez des effets visuels sur le contenu en passant d'un état à un autre.
  3. Défilement en réponse à l'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é de l'onglet 1:

  1. Un script de développement modifie le DOM lors du processus de rendu de foo.com.
  2. Le moteur de rendu Blink indique au compositeur qu'un rendu est nécessaire.
  3. Le compositeur indique à Viz qu'un rendu est nécessaire.
  4. La visualisation indique au compositeur le début du rendu.
  5. Le compositeur transmet le signal de démarrage au moteur de rendu Blink.
  6. L'exécuteur de boucle d'événements de 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 compositeur exécute le cycle de vie de la composition.
  9. Toutes les tâches matricielles sont envoyées à Viz pour la trame (il y en a souvent plusieurs).
  10. Viz les contenus rasters sur le GPU.
  11. La visualisation confirme que la tâche matricielle est terminée. 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 image compositeur est envoyée à Viz.
  13. Viz agrège les images du compositeur pour le processus de rendu de foo.com, le processus de rendu iFrame bar.com et l'interface utilisateur du navigateur.
  14. Visualisation planifie un tirage au sort.
  15. Viz dessine le frame compositeur agrégé à l'écran.

Pour animer une transition de transformation CSS dans le deuxième onglet:

  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 ensuite réexécuté. (Des tâches de trame et de décodage peuvent se produire, mais elles ne sont pas décrites ici.)
  2. Une image compositeur est envoyée à Viz.
  3. Viz regroupe les images du compositeur pour le processus de rendu de foo.com, le processus de rendu bar.com et l'interface utilisateur du navigateur.
  4. Visualisation 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, écran 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 être informé de 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 avec d'autres événements récents.
  9. Le geste de défilement est à nouveau envoyé au thread du compositeur du processus de rendu de baz.com,
  10. Le défilement y est appliqué, et le thread du compositeur pour le processus de rendu bar.com déclenche une animation dans sa boucle d'événements du compositeur. Cette opération modifie ensuite le décalage de défilement dans les arborescences de propriétés et réexécute le cycle de vie du compositeur. Elle indique également au thread principal de déclencher un événement scroll (non représenté ici).
  11. Une image compositeur est envoyée à Viz.
  12. Viz agrège les images du compositeur pour le processus de rendu foo.com, le processus de rendu bar.com et l'interface utilisateur du navigateur.
  13. Visualisation planifie un tirage au sort.
  14. Viz dessine le frame compositeur agrégé à l'écran.

Pour acheminer un événement click sur un lien hypertexte dans l'iFrame #two dans l'onglet 1:

  1. Un événement input (souris, écran tactile ou clavier) est transmis au processus du navigateur. Il effectue un test de positionnement approximatif pour déterminer que le processus de rendu iFrame de bar.com doit recevoir le clic, puis l'envoie.
  2. Le thread 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énement 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 du lien hypertexte, le nouvel état est affiché, avec des étapes semblables à l'exemple précédent "rendu DOM modifié". (Les modifications ultérieures ne sont pas décrites ici.)

Plats à emporter

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

Le plus important à retenir est que, grâce à une modularisation minutieuse et une attention particulière aux détails, le pipeline de rendu a été divisé en plusieurs composants autonomes. Ces composants ont ensuite été répartis en processus et threads parallèles afin de maximiser les opportunités de performances d'évolutivité et 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 d'Una Kravets.