Le modèle de couche
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 afin de tirer parti des cartes graphiques. On parle alors vaguement d'accélération matérielle. Qu'entend-on par page Web standard (c'est-à-dire autre que Canvas2D ou WebGL) ? Cet article décrit le modèle de base sur lequel repose l'affichage accéléré par le matériel du contenu Web dans Chrome.
Mises en garde importantes
Nous parlons ici de WebKit, et plus précisément de son port Chromium. Cet article décrit en détail l'implémentation de Chrome, pas les fonctionnalités de la plate-forme Web. La plate-forme et les normes Web ne codifient pas ce niveau de détail de l'implémentation. Par conséquent, rien ne garantit qu'aucun élément de cet article s'appliquera à d'autres navigateurs. Toutefois, une connaissance du fonctionnement interne peut être utile pour un débogage avancé et un réglage des performances.
Notez également que l'intégralité de cet article concerne un élément essentiel de l'architecture de rendu de Chrome, qui évolue très rapidement. Cet article s'efforce d'aborder uniquement des sujets peu susceptibles de changer, mais il n'est pas garanti que ces changements s'appliqueront dans six mois.
Il est important de comprendre que Chrome propose depuis un certain temps 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 ce document, toutes les pages suivent la procédure accélérée matérielle sur Windows, ChromeOS et Chrome pour Android. Sur Mac et Linux, seules les pages nécessitant une composition pour une partie de leur contenu empruntent le chemin accéléré (voir ci-dessous pour plus d'informations sur ce qui nécessite une composition). Bientôt, toutes les pages suivront également le chemin accéléré.
Enfin, nous explorons les dessous du moteur de rendu et examinons 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, mais il est également facile de se faire prendre dans le pied: les calques sont des constructions utiles, mais la création d'un grand nombre d'entre elles peut entraîner une surcharge sur l'ensemble de la pile graphique. Considérez-vous comme 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 par une structure que de nombreux développeurs Web connaissent: DOM. Toutefois, lors de l'affichage d'une page, le navigateur comporte 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: RenderLayers, qui gèrent les sous-arborescences du DOM, et GraphicsLayers, qui sont responsables des sous-arborescences des RenderLayers Ce dernier point est particulièrement intéressant pour nous, car GraphicsLayers est ce qui est importé sur le GPU en tant que textures. Je vais simplement dire « calque » à partir de maintenant pour signifier « GraphicsLayer ».
Remarque concernant la terminologie GPU: qu'est-ce qu'une texture ? Considérez-la comme une image bitmap déplacée de la mémoire principale (la RAM) vers la mémoire vidéo (la VRAM sur votre GPU). Une fois qu'il est installé sur le GPU, vous pouvez le mapper à une géométrie de maillage. Dans les jeux vidéo ou les programmes de CAO, cette technique est utilisée pour donner une "apparence" aux modèles 3D squelettiques. Chrome utilise des textures pour transmettre des fragments du contenu des pages Web au 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 faire défiler rapidement le contenu. Nous en reparlerons par la suite.
Étudions quelques exemples pour illustrer le concept de calque.
Lorsque vous étudiez les calques dans Chrome, l'option "Afficher les bordures des calques composites" dans les paramètres (c'est-à-dire la petite icône en forme de roue dentée) des outils de développement, sous l'en-tête "Rendu", est très utile. Elle indique très simplement où se trouvent les calques à l'écran. Allons-le. 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 ce document.
Figure 1: page à une seule couche
<!doctype html>
<html>
<body>
<div>I am a strange root.</div>
</body>
</html>
Cette page ne comporte qu'un seul calque. La grille bleue représente les tuiles, que vous pouvez considérer comme des sous-unités d'un calque que Chrome utilise pour importer simultanément certaines parties d'un calque volumineux sur le GPU. Ils ne sont pas vraiment importants ici.
Figure 2: Un élément dans sa propre couche
<!doctype html>
<html>
<body>
<div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
I am a strange root.
</div>
</body>
</html>
En plaçant une propriété CSS 3D sur la <div>
qui la fait pivoter, nous pouvons voir à quoi cela ressemble lorsqu'un élément dispose de son propre calque: notez la bordure orange, qui définit un calque dans cette vue.
Critères de création du calque
Quel autre élément obtient sa propre couche ? Dans ce cas, l'heuristique de Chrome a évolué au fil du temps et continue de l'être, mais actuellement, l'une des couches de déclencheur suivantes sont à l'origine de la création:
- Propriétés CSS de transformation en 3D ou en 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 (Flash, par exemple)
- Éléments avec une animation CSS pour leur opacité ou utilisant une transformation animée
- Éléments avec filtres CSS accélérés
- L'élément a un descendant avec une couche de composition (en d'autres termes, si l'élément a un élément enfant dans sa propre couche).
- L'élément possède un élément frère avec un z-index inférieur qui comporte une couche de composition (en d'autres termes, il est rendu au-dessus d'une couche composite)
Implications pratiques: animation
Nous pouvons également déplacer les calques, ce qui les rend très utiles pour les animations.
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 du contenu Web statique. Dans le cas de base, Chrome affiche 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'est pas nécessaire de le repeindre. C'est une bonne chose: repeindre prend du temps qui peut être consacré à d'autres tâches, comme exécuter JavaScript, et si la peinture est longue, cela entraîne des problèmes ou des retards dans les animations.
Vous pouvez par exemple voir cette vue de la chronologie des outils de développement: aucune opération "Paint" n'est effectuée lors de la rotation de cette couche dans les deux sens.
Non valide ! Remise en peinture
En revanche, si le contenu du calque change, vous devez le repeindre.
Figure 4: Repeindre des couches
<!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 clic sur l'élément d'entrée, l'élément rotatif s'agrandit d'1 px. Cela entraîne une remise en page et une nouvelle peinture de l'élément entier, qui, dans le cas présent, correspond à 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 disponible dans la section "Rendu" des paramètres des outils de développement. Après l'avoir activé, notez que l'élément animé et le bouton clignotent tous deux en rouge lorsque l'utilisateur clique dessus.
Les événements de peinture s'affichent également dans la chronologie des outils de développement. Les lecteurs acharnés remarqueront peut-être la présence de deux événements de peinture: un pour le calque et un pour le bouton lui-même, qui est repeint lorsqu'il passe de l'état déprimé à un autre.
Notez que Chrome n'a pas toujours besoin de repeindre l'intégralité du calque. Il s'efforce de ne repeindre que la partie du DOM invalidée. Dans ce cas, l'élément DOM que nous avons modifié correspond à la taille de l'ensemble du calque. Mais dans de nombreux autres cas, un calque comporte de nombreux éléments DOM.
Une autre question évidente est de savoir ce qui provoque une invalidation et force une nouvelle peinture. Il est difficile de répondre à cette question de manière exhaustive, car de nombreux cas spéciaux peuvent forcer des invalidations. La cause la plus courante consiste à modifier le DOM en manipulant les styles CSS ou en entraînant une remise en page. Tony Gentilcore a publié un excellent article de blog sur les causes de la remise en page, et Stoyan Stefanov a publié un article qui couvre la peinture plus en détail (mais se termine par la peinture, et non par la composition sophistiquée).
La meilleure façon de savoir si cela peut avoir un impact sur un élément sur lequel vous travaillez est d'utiliser les outils de timeline des outils de développement et d'afficher les rectangles de peinture pour voir si vous repeignez le document alors que vous ne l'auriez pas pu le faire, puis essayez d'identifier l'endroit où vous avez nettoyé le DOM juste avant la remise en page/repeinture. Si la peinture est inévitable, mais semble prendre beaucoup de temps, 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 à l'écran ? D'un point de vue conceptuel:
- 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 former l'image de l'écran final.
Tout cela doit se produire la première fois que Chrome affiche un frame d'une page Web. Mais il peut ensuite utiliser quelques raccourcis pour les images suivantes:
- 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 différentes propriétés de composition (par exemple, à différentes positions, avec différentes opacités, etc.).
- Si une partie d'un calque n'est plus valide, elle est repeinte et importée de nouveau. 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 cadre.
Comme il devrait maintenant être évident, le modèle de composition graphique basé sur des couches a de profondes conséquences sur les performances de rendu. La composition est relativement peu onéreuse lorsqu'il n'y a rien à peindre. Par conséquent, il est préférable d'éviter de repeindre les calques à l'échelle globale lorsqu'il s'agit de déboguer des performances de rendu. Les développeurs expérimentés examineront la liste des déclencheurs de composition ci-dessus et comprendront qu'il est possible de forcer facilement la création de calques. Toutefois, soyez prudent en les créant aveuglément, car ils ne sont pas libres: ils occupent de la mémoire dans la RAM du système et sur le GPU (en particulier sur les appareils mobiles), et leur nombre peut entraîner d'autres frais généraux dans la logique pour identifier ceux qui sont visibles. De nombreux calques peuvent également augmenter le temps consacré à la rastérisation s'ils sont volumineux et se chevauchent beaucoup là où ils ne l'étaient pas auparavant, ce que l'on appelle parfois une "superposition". Alors utilisez vos connaissances à bon escient !
C'est tout pour aujourd'hui. D'autres articles sur les implications pratiques du modèle de calque seront bientôt disponibles.