Modèle de calque
Introduction
Pour la plupart des développeurs Web, le modèle fondamental d'une page Web est le DOM. Le rendu est le processus souvent obscur qui consiste à transformer cette représentation d'une page en image à l'écran. Ces dernières années, les navigateurs modernes ont modifié le fonctionnement du rendu pour exploiter les cartes graphiques. On parle souvent de "accélération matérielle". Que signifie ce terme lorsqu'on parle d'une page Web normale (c'est-à-dire pas Canvas2D ou WebGL) ? Cet article explique le modèle de base qui sous-tend le rendu accéléré par matériel du contenu Web dans Chrome.
Mises en garde importantes
Nous parlons ici de WebKit, et plus précisément du port Chromium de WebKit. Cet article décrit les détails d'implémentation de Chrome, et non les fonctionnalités de la plate-forme Web. La plate-forme Web et les normes ne codifient pas ce niveau de détail d'implémentation. Il n'est donc pas garanti que tout ce qui est décrit dans cet article s'applique à d'autres navigateurs. Toutefois, la connaissance des éléments internes peut être utile pour le débogage avancé et le réglage des performances.
Notez également que cet article traite d'un élément essentiel de l'architecture de rendu de Chrome qui évolue très rapidement. Cet article ne tente de couvrir que les éléments qui ne sont pas susceptibles de changer, mais nous ne pouvons pas garantir que tout cela sera toujours applicable dans six mois.
Il est important de comprendre que Chrome dispose depuis un certain temps de deux chemins de rendu différents: le chemin accéléré par le matériel et l'ancien chemin logiciel. Au moment de la rédaction de cet article, toutes les pages suivent le chemin d'accélération matérielle sur Windows, ChromeOS et Chrome pour Android. Sur Mac et Linux, seules les pages qui nécessitent une composition pour certains de leurs contenus empruntent le chemin accéléré (voir ci-dessous pour en savoir plus sur les éléments qui nécessitent une composition). Toutefois, toutes les pages emprunteront bientôt ce chemin accéléré.
Enfin, nous allons jeter un coup d'œil sous le capot du moteur de rendu et examiner ses fonctionnalités qui ont un impact important sur les performances. Lorsque vous essayez d'améliorer les performances de votre propre site, il peut être utile de comprendre le modèle de calques. Toutefois, il est également facile de se tirer une balle dans le pied: les calques sont des constructions utiles, mais en créer beaucoup peut entraîner des coûts supplémentaires dans toute la pile graphique. Vous voilà prévenu !
Du DOM à l'écran
Présentation des calques
Une fois qu'une page est chargée et analysée, elle est représentée dans le navigateur sous la forme d'une structure que de nombreux développeurs Web connaissent: le DOM. Toutefois, lors de l'affichage d'une page, le navigateur dispose d'une série de représentations intermédiaires qui ne sont pas directement exposées aux développeurs. La plus importante de ces structures est une couche.
Dans Chrome, il existe en réalité plusieurs types de calques: les calques de rendu, qui sont responsables des sous-arbres du DOM, et les calques graphiques, qui sont responsables des sous-arbres des calques de rendu. C'est ce dernier qui nous intéresse le plus ici, car ce sont les GraphicsLayers qui sont importés sur le GPU en tant que textures. À partir de maintenant, je vais simplement utiliser le terme "couche" pour désigner GraphicsLayer.
Petit aparté sur la terminologie des GPU: qu'est-ce qu'une texture ? Il s'agit d'une image bitmap transférée de la mémoire principale (RAM) vers la mémoire vidéo (VRAM, sur votre GPU). Une fois sur le GPU, vous pouvez le mapper sur une géométrie de maillage. Dans les jeux vidéo ou les programmes de CAO, cette technique permet de donner une "peau" aux modèles squelettiques 3D. Chrome utilise des textures pour transférer des parties du contenu de la page Web sur le GPU. Les textures peuvent être mappées à différentes positions et transformations à moindre coût en les appliquant à un maillage rectangulaire très simple. C'est ainsi que fonctionne le CSS 3D. Il est également idéal pour le défilement rapide, mais nous en reparlerons plus tard.
Prenons quelques exemples pour illustrer le concept de calque.
L'option "Afficher les bordures des calques composites" dans les paramètres (petite icône en forme de roue dentée) des outils pour les développeurs, sous l'en-tête "Rendu", est un outil très utile pour étudier les calques dans Chrome. Il met simplement en évidence l'emplacement des calques à l'écran. Allons l'activer. Ces captures d'écran et exemples sont tous issus de la dernière version de Chrome Canary, Chrome 27 au moment de la rédaction de cet article.
Figure 1: Page à une seule couche
<!doctype html>
<html>
<body>
<div>I am a strange root.</div>
</body>
</html>

Cette page ne comporte qu'une seule couche. La grille bleue représente des tuiles, que vous pouvez considérer comme des sous-unités d'une couche que Chrome utilise pour importer des parties d'une grande couche à la fois sur le GPU. Ils ne sont pas vraiment importants ici.
Figure 2: Un élément sur son propre calque
<!doctype html>
<html>
<body>
<div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
I am a strange root.
</div>
</body>
</html>

En appliquant une propriété CSS 3D à l'<div>
qui la fait pivoter, nous pouvons voir à quoi ressemble un élément lorsqu'il dispose de sa propre couche: notez la bordure orange qui délimite une couche dans cette vue.
Critères de création de calques
Quel autre élément dispose de sa propre couche ? Les heuristiques de Chrome ont évolué au fil du temps et continuent de le faire. Toutefois, pour le moment, les éléments suivants peuvent déclencher la création de calques:
- Propriétés CSS de transformation 3D ou de perspective
- Éléments
<video>
utilisant le décodage vidéo accéléré - Éléments
<canvas>
avec un contexte 3D (WebGL) ou un contexte 2D accéléré - Plug-ins composites (par exemple, Flash)
- Éléments avec animation CSS pour leur opacité ou utilisant une transformation animée
- Éléments avec filtres CSS accélérés
- L'élément possède un descendant qui comporte une couche de composition (en d'autres termes, si l'élément possède un élément enfant qui se trouve dans sa propre couche)
- L'élément possède un frère avec un z-index inférieur qui comporte une couche de composition (en d'autres termes, il est affiché au-dessus d'une couche composite).
Implications pratiques: animation
Nous pouvons également déplacer les calques, ce qui les rend très utiles pour l'animation.
Figure 3: Calques animés
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div>I am a strange root.</div>
</body>
</html>
Comme indiqué précédemment, les calques sont très utiles pour parcourir le contenu Web statique. Dans le cas de base, Chrome peint le contenu d'un calque dans un bitmap logiciel avant de l'importer dans le GPU en tant que texture. Si ce contenu ne change pas à l'avenir, il n'a pas besoin d'être repeint. C'est une bonne chose: la peinture prend du temps qui peut être consacré à d'autres tâches, comme l'exécution de JavaScript. Si la peinture est longue, elle entraîne des à-coups ou des retards dans les animations.
Par exemple, regardez cette vue de la chronologie des outils de développement: aucune opération de peinture n'est effectuée pendant que cette couche tourne.

Non valide. Remise en peinture
Toutefois, si le contenu de la couche change, elle doit être redessinée.
Figure 4: Repainting Layers
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div id="foo">I am a strange root.</div>
<input id="paint" type="button" value="repaint">
<script>
var w = 200;
document.getElementById('paint').onclick = function() {
document.getElementById('foo').style.width = (w++) + 'px';
}
</script>
</body>
</html>
Chaque fois que l'utilisateur clique sur l'élément de saisie, l'élément rotatif s'élargit de 1 pixel. Cela entraîne une nouvelle mise en page et une nouvelle peinture de l'élément entier, qui dans ce cas est une couche entière.
Pour voir ce qui est peint, vous pouvez utiliser l'outil "Afficher les rectangles de peinture" dans les outils de développement, également sous l'en-tête "Rendu" des paramètres des outils de développement. Une fois l'animation activée, vous remarquerez que l'élément animé et le bouton clignotent en rouge lorsque vous cliquez sur le bouton.

Les événements de peinture apparaissent également dans la chronologie des outils de développement. Les lecteurs attentifs remarqueront qu'il existe deux événements de peinture: un pour la couche et un pour le bouton lui-même, qui est repeint lorsqu'il passe de l'état enfoncé à l'état non enfoncé.

Notez que Chrome n'a pas toujours besoin de repeindre l'ensemble de la couche. Il essaie de ne repeindre que la partie du DOM qui a été invalidée. Dans ce cas, l'élément DOM que nous avons modifié correspond à la taille de l'ensemble de la couche. Toutefois, dans de nombreux cas, une couche contient de nombreux éléments DOM.
La question suivante qui se pose est de savoir ce qui provoque une invalidation et force un nouveau rendu. Il est difficile de répondre de manière exhaustive à cette question, car de nombreux cas particuliers peuvent forcer des invalidations. La cause la plus courante est la pollution du DOM en manipulant des styles CSS ou en provoquant une nouvelle mise en page. Tony Gentilcore a publié un excellent article de blog sur les causes de la redisposition, et Stoyan Stefanov un article plus détaillé sur la peinture (mais qui se termine par la peinture, et non par le compositing).
Le meilleur moyen de savoir si cela affecte un élément sur lequel vous travaillez est d'utiliser les outils "Timeline" (Chronologie) et "Show Paint Rects" (Afficher les rectangles de peinture) des outils de développement pour voir si vous effectuez une nouvelle peinture alors que vous ne le souhaitez pas, puis d'essayer d'identifier l'endroit où vous avez sali le DOM juste avant cette nouvelle mise en page/nouvelle peinture. Si la peinture est inévitable, mais qu'elle semble prendre un temps déraisonnable, consultez l'article d'Eberhard Gräther sur le mode de peinture continue dans les outils de développement.
Synthèse: du DOM à l'écran
Comment Chrome transforme-t-il le DOM en image d'écran ? Sur le plan conceptuel, il:
- Il prend le DOM et le divise en couches.
- Peint chacune de ces couches indépendamment dans des bitmaps logiciels
- Les importe dans le GPU en tant que textures
- Combine les différentes couches pour créer l'image finale de l'écran.
Tout cela doit se produire la première fois que Chrome produit un frame d'une page Web. Vous pouvez ensuite utiliser des raccourcis pour les futurs cadres:
- Si certaines propriétés CSS changent, il n'est pas nécessaire de repeindre quoi que ce soit. Chrome peut simplement recomposer les calques existants qui se trouvent déjà sur le GPU en tant que textures, mais avec des propriétés de composition différentes (par exemple, à des positions différentes, avec des opacités différentes, etc.).
- Si une partie d'un calque est invalidée, elle est redessinée et réimportée. Si son contenu reste le même, mais que ses attributs composites changent (par exemple, s'il est traduit ou que son opacité change), Chrome peut le laisser sur le GPU et le recomposer pour créer un nouveau frame.
Comme il devrait maintenant être clair, le modèle de composition basé sur les calques a des implications profondes sur les performances de rendu. Le compositing est relativement peu coûteux lorsque rien ne doit être peint. Par conséquent, éviter de repeindre des calques est un bon objectif global lorsque vous essayez de déboguer les performances de rendu. Les développeurs avisés examineront la liste des déclencheurs de composition ci-dessus et se rendront compte qu'il est possible de forcer facilement la création de calques. Mais attention à ne pas les créer aveuglément, car ils ne sont pas sans frais: ils occupent de la mémoire dans la RAM système et sur le GPU (particulièrement limité sur les appareils mobiles). De plus, un grand nombre de ces éléments peut entraîner d'autres coûts indirects dans la logique qui permet de suivre les éléments visibles. De nombreuses couches peuvent également augmenter le temps de rastérisation si elles sont volumineuses et se chevauchent beaucoup là où elles ne le faisaient pas auparavant, ce qui entraîne ce que l'on appelle parfois "sur-dessin". Utilisez donc vos connaissances à bon escient.
C'est tout pour aujourd'hui. Nous vous proposerons d'autres articles sur les implications pratiques du modèle de couches.