Beschleunigtes Rendering in Chrome

Das Schichtenmodell

Tom Wiltzius
Tom Wiltzius

Für die meisten Webentwickler ist das DOM das grundlegende Modell einer Webseite. Das Rendering ist der oft undurchsichtige Prozess, bei dem diese Darstellung einer Seite in ein Bild auf dem Bildschirm umgewandelt wird. Moderne Browser haben in den letzten Jahren die Rendering-Funktion geändert, um Grafikkarten zu nutzen. Dies wird oft als „Hardwarebeschleunigung“ bezeichnet. Was bedeutet dieser Begriff im Zusammenhang mit einer normalen Webseite (d. h. nicht Canvas2D oder WebGL)? In diesem Artikel wird das grundlegende Modell erläutert, das dem hardwarebeschleunigten Rendering von Webinhalten in Chrome zugrunde liegt.

Wichtige Hinweise

Wir sprechen hier über WebKit und genauer gesagt über den Chromium-Port von WebKit. In diesem Artikel werden Implementierungsdetails von Chrome beschrieben, nicht Funktionen der Webplattform. Die Webplattform und die Standards codifizieren diese Implementierungsdetails nicht. Daher kann nicht garantiert werden, dass die Informationen in diesem Artikel auf andere Browser zutreffen. Kenntnisse der internen Funktionen können jedoch für erweitertes Debugging und Leistungsoptimierung nützlich sein.

Beachten Sie außerdem, dass in diesem Artikel ein zentraler Bestandteil der Rendering-Architektur von Chrome beschrieben wird, der sich sehr schnell ändert. In diesem Artikel werden nur Dinge behandelt, die sich wahrscheinlich nicht ändern werden. Wir können jedoch nicht garantieren, dass alles in sechs Monaten noch zutrifft.

Es ist wichtig zu wissen, dass Chrome seit einiger Zeit zwei unterschiedliche Renderingpfade hat: den hardwarebeschleunigten Pfad und den älteren Softwarepfad. Zum Zeitpunkt der Erstellung dieses Artikels werden alle Seiten unter Windows, ChromeOS und Chrome für Android über den hardwarebeschleunigten Pfad aufgerufen. Unter Mac und Linux werden nur Seiten, für die einige Inhalte zusammengesetzt werden müssen, über den beschleunigten Pfad verarbeitet. Weitere Informationen dazu, was ein Zusammensetzen erfordert, finden Sie unten. Bald werden aber auch dort alle Seiten über den beschleunigten Pfad verarbeitet.

Zum Schluss sehen wir uns die Funktionen der Rendering-Engine an, die einen großen Einfluss auf die Leistung haben. Wenn Sie die Leistung Ihrer eigenen Website verbessern möchten, kann es hilfreich sein, das Ebenenmodell zu verstehen. Es ist jedoch auch leicht, sich selbst ins Knie zu schießen: Ebenen sind nützliche Konstrukte, aber wenn Sie viele davon erstellen, kann dies zu einem Overhead im gesamten Grafik-Stack führen. Sei gewarnt!

Vom DOM zum Bildschirm

Jetzt neu: Ebenen

Nachdem eine Seite geladen und geparst wurde, wird sie im Browser als Struktur dargestellt, die vielen Webentwicklern vertraut ist: das DOM. Beim Rendern einer Seite verwendet der Browser jedoch eine Reihe von Zwischendarstellungen, die Entwicklern nicht direkt zugänglich sind. Die wichtigste dieser Strukturen ist eine Schicht.

In Chrome gibt es tatsächlich mehrere verschiedene Ebenentypen: Renderebenen, die für Unterbäume des DOM verantwortlich sind, und Grafikebenen, die für Unterbäume von Renderebenen verantwortlich sind. Letzteres ist für uns hier am interessantesten, da GraphicsLayers als Texturen auf die GPU hochgeladen werden. Ab jetzt benutze ich einfach „Ebene“ als Synonym für „GraphicsLayer“.

Kurzer Hinweis zur GPU-Terminologie: Was ist eine Textur? Stellen Sie sich das als ein Bitmap-Bild vor, das vom Hauptspeicher (RAM) in den Videospeicher (VRAM auf der GPU) verschoben wird. Sobald die Daten auf der GPU sind, können Sie sie einer Mesh-Geometrie zuordnen. In Videospielen oder CAD-Programmen wird diese Technik verwendet, um Skelett-3D-Modellen eine „Haut“ zu geben. Chrome verwendet Texturen, um Webseiteninhalte auf die GPU zu übertragen. Texturen können einfach auf verschiedene Positionen und Transformationen zugeordnet werden, indem sie auf ein sehr einfaches rechteckiges Raster angewendet werden. So funktioniert 3D-CSS. Es eignet sich auch hervorragend für schnelles Scrollen. Mehr dazu später.

Sehen wir uns einige Beispiele an, um das Schichtenkonzept zu veranschaulichen.

Ein sehr nützliches Tool zum Analysieren von Ebenen in Chrome ist die Option „Kompositierte Ebenenränder anzeigen“ in den Einstellungen (Zahnradsymbol) in den Dev-Tools unter der Überschrift „Rendering“. Sie zeigt ganz einfach an, wo sich Ebenen auf dem Bildschirm befinden. Schalten wir es ein. Diese Screenshots und Beispiele stammen alle aus der neuesten Version von Chrome Canary, der zum Zeitpunkt der Erstellung dieses Artikels Chrome 27 war.

Abbildung 1: Eine Seite mit einer einzigen Ebene

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
Screenshot der zusammengesetzten Ebene mit Rändern um die Basisebene der Seite
Screenshot der Ränder der zusammengesetzten Ebene um die Basisebene der Seite

Diese Seite hat nur eine Ebene. Das blaue Raster stellt Kacheln dar, die Sie als Untereinheiten einer Ebene betrachten können. Chrome verwendet sie, um Teile einer großen Ebene gleichzeitig auf die GPU hochzuladen. Sie sind hier nicht wirklich wichtig.

Abbildung 2: Ein Element in einer eigenen Ebene

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
Screenshot der Rendering-Ränder der gedrehten Ebene
Screenshot der Renderränder der gedrehten Ebene

Wenn wir dem <div> eine 3D-CSS-Eigenschaft zuweisen, die es dreht, sehen wir, wie es aussieht, wenn ein Element eine eigene Ebene erhält. Beachten Sie den orangefarbenen Rahmen, der in dieser Ansicht eine Ebene umreißt.

Kriterien für die Ebenenerstellung

Was bekommt noch eine eigene Ebene? Die Heuristiken von Chrome haben sich im Laufe der Zeit weiterentwickelt und werden es auch weiterhin tun. Derzeit werden die folgenden Triggerebenen erstellt:

  • CSS-Eigenschaften für 3D- oder Perspektivtransformationen
  • <video>-Elemente mit beschleunigter Videodecodierung
  • <canvas>-Elemente mit einem 3D-Kontext (WebGL) oder einem beschleunigten 2D-Kontext
  • Zusammengesetzte Plug-ins (z.B. Flash)
  • Elemente mit einer CSS-Animation für die Deckkraft oder mit einer animierten Transformation
  • Elemente mit beschleunigten CSS-Filtern
  • Das Element hat ein untergeordnetes Element mit einer Kompositionebene (d. h., das Element hat ein untergeordnetes Element, das sich in einer eigenen Ebene befindet).
  • Das Element hat ein Geschwisterelement mit einem niedrigeren Z-Index, das eine Kompositionsebene hat (d. h. es wird über einer zusammengesetzten Ebene gerendert).

Praktische Auswirkungen: Animation

Außerdem können wir Ebenen verschieben, was sie für Animationen sehr nützlich macht.

Abbildung 3: Animierte Ebenen

<!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>

Wie bereits erwähnt, sind Ebenen sehr nützlich, um statische Webinhalte zu verschieben. Im Normalfall zeichnet Chrome den Inhalt einer Ebene in eine Software-Bitmap, bevor er sie als Textur auf die GPU hochlädt. Wenn sich diese Inhalte in Zukunft nicht ändern, müssen sie nicht neu gemalt werden. Das ist gut: Das Neuzeichnen kostet Zeit, die für andere Dinge wie das Ausführen von JavaScript verwendet werden kann. Wenn das Neuzeichnen lange dauert, kommt es zu Rucklern oder Verzögerungen bei Animationen.

Sehen Sie sich beispielsweise diese Ansicht der Zeitachse in den Dev-Tools an: Es werden keine Malvorgänge ausgeführt, während sich diese Ebene hin und her dreht.

Screenshot der Zeitachse der Entwicklertools während der Animation
Screenshot der Zeitachse der Entwicklertools während der Animation

Ungültig! Neuanstrich

Wenn sich der Inhalt der Ebene jedoch ändert, muss sie neu gerendert werden.

Abbildung 4: Ebenen neu malen

<!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>

Jedes Mal, wenn auf das Eingabeelement geklickt wird, wird das rotierende Element um 1 Pixel breiter. Das führt dazu, dass das gesamte Element neu angeordnet und neu gerendert wird, in diesem Fall eine ganze Ebene.

Mit dem Tool „Paint rects show“ (Malrechtecke anzeigen) in den DevTools können Sie sich ansehen, was gerade gerendert wird. Es befindet sich auch unter der Überschrift „Rendering“ in den Einstellungen der DevTools. Nachdem Sie die Funktion aktiviert haben, sehen Sie, dass das animierte Element und die Schaltfläche rot blinken, wenn auf die Schaltfläche geklickt wird.

Screenshot des Kästchens „Farblich blinkende Rechtecke anzeigen“
Screenshot des Kästchens „Paint rects anzeigen“

Die Paint-Ereignisse werden auch in der Zeitachse der DevTools angezeigt. Aufmerksame Leser werden bemerken, dass es dort zwei Paint-Ereignisse gibt: eines für die Ebene und eines für die Schaltfläche selbst, die neu gemalt wird, wenn sie in den gedrückten Zustand wechselt.

Screenshot der Zeitachse in den Entwicklertools, auf dem eine Ebene neu gemalt wird
Screenshot der Zeitachse in den Entwicklertools, in dem eine Ebene neu gemalt wird

Chrome muss nicht immer die gesamte Ebene neu zeichnen. Stattdessen wird versucht, nur den Teil des DOM neu zu zeichnen, der ungültig geworden ist. In diesem Fall ist das DOM-Element, das wir geändert haben, die Größe der gesamten Ebene. In vielen anderen Fällen gibt es jedoch viele DOM-Elemente in einer Ebene.

Eine naheliegende nächste Frage ist, was eine Ungültigmachung verursacht und ein Neuzeichnen erzwingt. Diese Frage lässt sich nicht abschließend beantworten, da es viele Grenzfälle gibt, die eine Ungültigkeit erzwingen können. Die häufigste Ursache ist das Beschädigen des DOM durch Manipulation von CSS-Stilen oder das Auslösen eines Neulayouts. Tony Gentilcore hat einen tollen Blogpost darüber, was zu einem Neulayout führt, und Stoyan Stefanov hat einen Artikel, in dem das Malen genauer beschrieben wird (aber er endet nur mit dem Malen, nicht mit diesen ausgefeilten Kompositionen).

Am besten finden Sie heraus, ob sich das auf etwas auswirkt, an dem Sie gerade arbeiten, indem Sie die Zeitachse und die Funktion „Paint Rects anzeigen“ in den Entwicklertools verwenden. So können Sie feststellen, ob das DOM neu gemalt wird, obwohl das nicht nötig wäre. Versuchen Sie dann, herauszufinden, wo Sie das DOM direkt vor dem Neulayout/Neumalen verunreinigt haben. Wenn das Zeichnen unvermeidlich ist, aber unverhältnismäßig lange dauert, lesen Sie den Artikel von Eberhard Gräther zum Modus für kontinuierliches Zeichnen in den Dev-Tools.

Zusammenführen: Vom DOM zum Bildschirm

Wie wandelt Chrome das DOM in ein Bildschirmbild um? Konzeptionell:

  1. Das DOM wird in Schichten unterteilt.
  2. Jede dieser Ebenen wird unabhängig in Software-Bitmaps gemalt.
  3. Sie werden als Texturen auf die GPU hochgeladen.
  4. Die verschiedenen Ebenen werden zu einem endgültigen Bildschirmbild zusammengesetzt.

Das muss jedes Mal passieren, wenn Chrome einen Frame einer Webseite erstellt. Für zukünftige Frames können Sie aber einige Tastenkürzel verwenden:

  1. Wenn sich bestimmte CSS-Properties ändern, muss nichts neu gerendert werden. Chrome kann die vorhandenen Ebenen, die sich bereits auf der GPU als Texturen befinden, einfach neu zusammensetzen, aber mit anderen Kompositionen (z. B. an verschiedenen Positionen, mit unterschiedlicher Deckkraft usw.).
  2. Wenn ein Teil einer Ebene ungültig wird, wird er neu gemalt und noch einmal hochgeladen. Wenn der Inhalt gleich bleibt, sich aber die zusammengesetzten Attribute ändern (z.B. durch eine Übersetzung oder eine Änderung der Deckkraft), kann Chrome das Element auf der GPU belassen und neu zusammensetzen, um einen neuen Frame zu erstellen.

Wie jetzt klar sein sollte, hat das ebenebasierte Compositing-Modell weitreichende Auswirkungen auf die Renderingleistung. Das Compositing ist verhältnismäßig günstig, wenn nichts gemalt werden muss. Daher ist es ein gutes Ziel, beim Optimieren der Renderingleistung das Neuzeichnen von Ebenen zu vermeiden. Erfahrene Entwickler sehen sich die Liste der Kompositionstrigger oben an und erkennen, dass es möglich ist, das Erstellen von Ebenen ganz einfach zu erzwingen. Aber Vorsicht: Erstellen Sie sie nicht blind, denn sie sind nicht kostenlos: Sie belegen Speicherplatz im System-RAM und auf der GPU (besonders begrenzt auf Mobilgeräten) und eine große Anzahl kann zu zusätzlichem Overhead in der Logik führen, die festhält, welche sichtbar sind. Viele Ebenen können auch die Zeit für das Rastern verlängern, wenn sie groß sind und sich häufig überschneiden, was zu dem sogenannten „Übermalen“ führt. Nutzen Sie Ihr Wissen also mit Bedacht!

Das ist im Moment erstmal alles. In den nächsten Artikeln geht es um die praktischen Auswirkungen des Schichtmodells.

Zusätzliche Ressourcen