Schlüsseldatenstrukturen in RenderingNG

Chris Harrelson
Chris Harrelson
Daniel Cheng
Daniel Cheng
Philip Rogers
Philip Rogers
Koji Ishi
Koji Ishi
Ian Kilpatrick
Ian Kilpatrick
Kyle Charbonneau
Kyle Charbonneau

Sehen wir uns die wichtigsten Datenstrukturen an, die Eingaben und Ausgaben der Rendering-Pipeline sind.

Diese Datenstrukturen sind:

  • Framebäume bestehen aus lokalen und Remote-Knoten, die darstellen, welche Webdokumente sich in welchem Rendering-Prozess und in welchem Blink-Renderer befinden.
  • Der unveränderliche Fragmentbaum ist die Ausgabe (und Eingabe) des Algorithmus für Layouteinschränkungen.
  • Property-Bäume repräsentieren die Transformations-, Clip-, Effekt- und Scrollhierarchien eines Webdokuments. Sie werden in der gesamten Pipeline verwendet.
  • Anzeigelisten und Farbblöcke sind die Eingaben für die Raster- und Ebenenalgorithmen.
  • Compositor-Frames umfassen Oberflächen, Rendering-Oberflächen und GPU-Textur-Kacheln, die mit der GPU zum Zeichnen verwendet werden.

Bevor wir uns diese Datenstrukturen ansehen, bauen wir im folgenden Beispiel auf einem Beispiel aus der Architekturüberprüfung auf. Dieses Beispiel wird im gesamten Dokument verwendet, um zu veranschaulichen, wie die Datenstrukturen darauf angewendet werden.

<!-- Example code -->
<html>
  <div style="overflow: hidden; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
      id="one" src="foo.com/etc"></iframe>
  </div>
  <iframe style="top:200px;
    transform: scale(1.1) translateX(200px)"
    id="two" src="bar.com"></iframe>
</html>

Rahmen für Bäume

Chrome rendert einen plattformübergreifenden Frame manchmal in einem anderen Rendering-Prozess als seinen übergeordneten Frame.

Im Beispielcode gibt es insgesamt drei Frames:

Ein übergeordneter Frame „foo.com“, der zwei iFrames enthält.

Bei der Website-Isolierung verwendet Chromium zwei Rendering-Prozesse, um diese Webseite zu rendern. Jeder Renderingprozess hat eine eigene Darstellung des Frame-Baums für diese Webseite:

Zwei Frame-Bäume, die die beiden Renderingprozesse darstellen.

Ein Frame, der in einem anderen Prozess gerendert wird, wird als Remote-Frame dargestellt. Ein Remote-Frame enthält die Mindestinformationen, die zum Rendern als Platzhalter erforderlich sind, z. B. seine Abmessungen. Der Remote-Frame enthält sonst keine Informationen, die zum Rendern des Inhalts erforderlich sind.

Ein lokaler Frame hingegen ist ein Frame, der durch die Standard-Rendering-Pipeline geht. Der lokale Frame enthält alle Informationen, die erforderlich sind, um die Daten für diesen Frame (z. B. die DOM-Baum- und -Stildaten) in etwas umzuwandeln, das gerendert und angezeigt werden kann.

Die Rendering-Pipeline arbeitet mit der Granularität eines lokalen Frame-Baumfragments. Betrachten wir ein etwas komplizierteres Beispiel mit foo.com als Hauptframe:

<iframe src="bar.com"></iframe>

Und den folgenden bar.com-Unterframe:

<iframe src="foo.com/etc"></iframe>

Es gibt zwar immer noch nur zwei Renderer, aber jetzt drei lokale Frame-Baumfragmente: zwei im Renderingprozess für foo.com und eines im Renderingprozess für bar.com:

Eine Darstellung der beiden Renderings und drei Frame-Baumfragmente.

Um einen Compositor-Frame für die Webseite zu erstellen, fordert Viz gleichzeitig einen Compositor-Frame vom Stamm-Frame jedes der drei lokalen Framebäume an und fasst sie dann zusammen. Weitere Informationen finden Sie im Abschnitt Compositor-Frames.

Der Hauptframe foo.com und der Subframe foo.com/other-page sind Teil desselben Frame-Baums und werden im selben Prozess gerendert. Die beiden Frames haben jedoch weiterhin unabhängige Dokumentlebenszyklen, da sie zu verschiedenen lokalen Frame-Baumfragmenten gehören. Aus diesem Grund ist es unmöglich, einen einzigen zusammengesetzten Frame für beide in einer Aktualisierung zu generieren. Dem Renderingprozess stehen nicht genügend Informationen zur Verfügung, um den für foo.com/other-page generierten Compositor-Frame direkt in den Compositor-Frame für den Hauptframe von foo.com einzufügen. So kann sich der Out-of-Process-bar.com-Übergeordnete-Frame beispielsweise auf die Darstellung des foo.com/other-url-iFrames auswirken, indem er den iFrame mit CSS transformiert oder Teile des iFrames mit anderen Elementen im DOM verdeckt.

Ablauf der Aktualisierung visueller Properties

Visuelle Eigenschaften wie der Geräteskalierungsfaktor und die Größe des Darstellungsbereichs wirken sich auf die gerenderte Ausgabe aus und müssen zwischen den lokalen Frame-Baumfragmenten synchronisiert werden. Der Wurzelknoten jedes lokalen Frame-Baumfragments ist mit einem Widget-Objekt verknüpft. Updates für visuelle Properties werden zuerst an das Widget im Hauptframe übertragen und dann von oben nach unten an die übrigen Widgets.

Beispielsweise, wenn sich die Größe des Darstellungsbereichs ändert:

Diagramm des im vorherigen Text beschriebenen Prozesses

Dieser Vorgang dauert einige Zeit. Daher enthalten die replizierten visuellen Eigenschaften auch ein Synchronisierungstoken. Der Viz-Compositor verwendet dieses Synchronisierungstoken, um darauf zu warten, dass alle lokalen Frame-Tree-Fragmente einen Compositor-Frame mit dem aktuellen Synchronisierungstoken einreichen. Dadurch wird vermieden, dass zusammengesetzte Frames mit unterschiedlichen visuellen Eigenschaften vermischt werden.

Der unveränderliche Fragmentbaum

Der unveränderliche Fragmentbaum ist die Ausgabe der Layoutphase der Rendering-Pipeline. Er stellt die Position und Größe aller Elemente auf der Seite dar (ohne angewendete Transformationen).

Darstellung der Fragmente in jedem Baum, wobei ein Fragment als erforderliches Layout markiert wird.

Jedes Fragment stellt einen Teil eines DOM-Elements dar. Normalerweise gibt es nur ein Fragment pro Element. Es kann aber auch mehr sein, wenn es beim Drucken auf verschiedene Seiten oder bei einem mehrspaltigen Kontext auf verschiedene Spalten aufgeteilt wird.

Nach dem Layout ist jedes Fragment unveränderlich und nie wieder geändert. Außerdem gelten einige zusätzliche Einschränkungen. Wir tun Folgendes nicht:

  • Alle „nach oben“-Referenzen im Baum zulassen. Ein untergeordnetes Element kann keinen Verweis auf sein übergeordnetes Element enthalten.
  • Daten „durch den Baum hinunterblasen“ (ein untergeordnetes Element liest nur Informationen von seinen untergeordneten Elementen, nicht von seinem übergeordneten Element).

Diese Einschränkungen ermöglichen es uns, ein Fragment für ein nachfolgendes Layout wiederzuverwenden. Ohne diese Einschränkungen müssten wir den gesamten Baum häufig neu generieren, was teuer ist.

Die meisten Layouts sind in der Regel inkrementelle Updates, z. B. wenn eine Webanwendung einen kleinen Teil der Benutzeroberfläche aktualisiert, nachdem der Nutzer auf ein Element geklickt hat. Idealerweise sollte das Layout nur proportional zu den Änderungen auf dem Bildschirm passen. Das erreichen wir, indem wir so viele Teile des vorherigen Baums wie möglich wiederverwenden. Das bedeutet, dass wir in der Regel nur den Stamm des Baums neu aufbauen müssen.

In Zukunft könnten wir mit diesem unveränderlichen Design interessante Dinge tun, z. B. den unveränderlichen Fragmentbaum bei Bedarf über Threadgrenzen hinweg übergeben (um nachfolgende Phasen in einem anderen Thread auszuführen), mehrere Bäume für eine flüssige Layoutanimation generieren oder parallele spekulative Layouts ausführen. Außerdem bietet es die Möglichkeit eines Multi-Threading-Layouts.

Inline-Fragment-Elemente

Für Inline-Inhalte (vorwiegend formatierter Text) wird eine etwas andere Darstellung verwendet. Anstelle einer Baumstruktur mit Feldern und Zeigern stellen wir Inline-Inhalte in einer flachen Liste dar, die den Baum darstellt. Der Hauptvorteil besteht darin, dass eine flache Listendarstellung für Inline-Elemente schnell ist, sich zum Prüfen oder Abfragen von Inline-Datenstrukturen eignet und speichereffizient ist. Das ist extrem wichtig für die Leistung des Web-Renderings, da das Rendern von Text sehr komplex ist und bei mangelnder Optimierung leicht zum langsamsten Teil der Pipeline werden kann.

Die flache Liste wird für jeden In-Line-Formatierungskontext in der Reihenfolge einer Tiefensuche des In-Line-Layout-Unterbaums erstellt. Jeder Eintrag in der Liste ist ein Tupel von (Objekt, Anzahl der Nachfolgerelemente). Betrachten Sie beispielsweise dieses DOM:

<div style="width: 0;">
  <span style="color: blue; position: relative;">Hi</span> <b>there</b>.
</div>

Die Property width ist auf „0“ gesetzt, sodass die Zeile zwischen „Hallo“ und „da.“ umgebrochen wird.

Wenn der Kontext für die Inline-Formatierung für diese Situation als Baum dargestellt wird, sieht er so aus:

{
  "Line box": {
    "Box <span>": {
      "Text": "Hi"
    }
  },
  "Line box": {
    "Box <b>": {
      "Text": "There"
    }
  },
  {
    "Text": "."
  }
}

Die einfache Liste sieht so aus:

  • (Linienfeld, 2)
  • (Box <span>, 1)
  • (Text „Hallo“, 0)
  • (Linienfeld, 3)
  • (Feld <b>, 1)
  • (Text „there“, 0)
  • (Text ".", 0)

Es gibt viele Nutzer dieser Datenstruktur: APIs für Barrierefreiheit und Geometrie wie getClientRects und contenteditable. Für jede Anwendung gelten unterschiedliche Anforderungen. Diese Komponenten greifen über einen praktischen Cursor auf die flache Datenstruktur zu.

Der Cursor hat APIs wie MoveToNext, MoveToNextLine und CursorForChildren. Diese Cursordarstellung ist aus mehreren Gründen sehr leistungsfähig für Textinhalte:

  • Die Iteration in der Tiefensuche ist sehr schnell. Diese Methode wird sehr oft verwendet, weil sie den Caret-Bewegungen ähnelt. Da es sich um eine flache Liste handelt, wird bei der Tiefensuche nur der Array-Offset erhöht, was schnelle Iterationen und Speicherlokalität ermöglicht.
  • Er bietet eine Breitensuche, die beispielsweise beim Malen des Hintergrunds von Zeilen- und Inline-Boxen erforderlich ist.
  • Wenn Sie die Anzahl der Nachkommen kennen, können Sie schnell zum nächsten Geschwisterelement springen. Dazu müssen Sie nur den Array-Offset um diese Zahl erhöhen.

Immobilienbäume

Das DOM ist ein Elementbaum (mit Textknoten) und mit CSS können verschiedene Stile auf Elemente angewendet werden.

Das kann auf vier Arten geschehen:

  • Layout:Eingaben für den Algorithmus für Layouteinschränkungen.
  • Paint:Gibt an, wie das Element gezeichnet und gerastert werden soll (nicht seine untergeordneten Elemente).
  • Visuell:Raster-/Zeichneneffekte, die auf den untergeordneten DOM-Knoten angewendet werden, z. B. Transformationen, Filter und Clipping.
  • Scrollen:Achsenorientiertes und Abrunden von Ecken sowie Zuschneiden und Scrollen des enthaltenen untergeordneten Baums.

Eigenschaftsbäume sind Datenstrukturen, die erklären, wie visuelle und Scrolleffekte auf DOM-Elemente angewendet werden. Sie bieten die Möglichkeit, Fragen wie diese zu beantworten: Wo befindet sich ein bestimmtes DOM-Element relativ zum Bildschirm, unter Berücksichtigung seiner Layoutgröße und -position? Und: Welche Abfolge von GPU-Vorgängen sollte verwendet werden, um visuelle und Scrolleffekte anzuwenden?

Visuelle und Scroll-Effekte im Web sind in ihrer vollen Pracht sehr kompliziert. Die wichtigste Aufgabe von Property-Bäumen besteht also darin, diese Komplexität in eine einzelne Datenstruktur umzuwandeln, die ihre Struktur und Bedeutung genau darstellt und gleichzeitig die restliche Komplexität des DOM und CSS entfernt. So können wir Algorithmen für das Zusammenführen und Scrollen mit viel größerer Zuverlässigkeit implementieren. Wichtig ist insbesondere:

  • Potenziell fehleranfällige Geometrien und andere Berechnungen können an einem Ort zentralisiert werden.
  • Die Komplexität des Erstellens und Aktualisierens von Property-Bäumen wird in einer einzigen Rendering-Pipeline-Phase isoliert.
  • Es ist viel einfacher und schneller, Property-Bäume an verschiedene Threads und Prozesse zu senden als den vollständigen DOM-Status. Dadurch können sie für viele Anwendungsfälle verwendet werden.
  • Je mehr Anwendungsfälle es gibt, desto mehr Vorteile können wir durch aufeinander aufbauendes Geometrie-Caching erzielen, da sie die Caches der anderen wiederverwenden können.

RenderingNG verwendet Eigenschaftsbäume für viele Zwecke, unter anderem für:

  • Trennung von Compositing und Paint sowie von Compositing und dem Hauptthread.
  • Optimale Komposition / Zeichenstrategie bestimmen
  • Geometrie von IntersectionObserver messen
  • Vermeiden Sie die Arbeit mit nicht sichtbaren Elementen und GPU-Texturkacheln.
  • Farbe und Raster effizient und genau ungültig machen.
  • Messen des Layout-Shifts und des Largest Contentful Paint in Core Web Vitals.

Jedes Webdokument hat vier separate Attributbäume: „transform“, „clip“, „effect“ und „scroll“.(*) Der Transform-Baum steht für CSS-Transformierungen und Scrollen. (Eine Scroll-Transformation wird als 2D-Transformationsmatrix dargestellt.) Der Clipbaum stellt Overflow-Clips dar. Die Effektstruktur stellt alle anderen visuellen Effekte dar: Deckkraft, Filter, Masken, Mischmodi und andere Arten von Clips wie Clippfad. Der Scrollbaum enthält Informationen zum Scrollen, z. B. wie Scrollelemente verkettet werden. Er ist erforderlich, um das Scrollen im Compositor-Thread auszuführen. Jeder Knoten in einem Property-Baum stellt einen Scroll- oder visuellen Effekt dar, der von einem DOM-Element angewendet wird. Wenn das mehrere Auswirkungen hat, kann es in jedem Baum mehr als einen Attributbaumknoten für dasselbe Element geben.

Die Topologie jedes Baums ist eine spärliche Darstellung des DOM. Wenn es beispielsweise drei DOM-Elemente mit Überlauf-Clips gibt, gibt es auch drei Clipbaumknoten. Die Struktur des Clipbaums folgt der Beziehung zwischen den enthaltenden Blöcken der Überlauf-Clips. Es gibt auch Verbindungen zwischen den Bäumen. Diese Links geben die relative DOM-Hierarchie und somit die Reihenfolge der Anwendung der Knoten an. Wenn sich eine Transformation auf einem DOM-Element beispielsweise unter einem anderen DOM-Element mit einem Filter befindet, wird die Transformation natürlich vor dem Filter angewendet.

Jedes DOM-Element hat einen Eigenschaftsbaumstatus, der ein Viertupel (Transform, Clip, Effekt, Scroll) ist, das die nächsten übergeordneten Clip-, Transform- und Effektbaumknoten angibt, die auf dieses Element angewendet werden. Das ist sehr praktisch, da wir anhand dieser Informationen genau wissen, welche Clips, Transformationen und Effekte auf dieses Element angewendet werden und in welcher Reihenfolge. So wissen wir, wo es sich auf dem Bildschirm befindet und wie es gezeichnet werden soll.

Beispiel

(Quelle)

<html>
  <div style="overflow: scroll; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
  id="one" srcdoc="iframe one"></iframe>
  </div>
  <iframe style="top:200px;
      transform: scale(1.1) translateX(200px)" id=two
      srcdoc="iframe two"></iframe>
</html>

Im folgenden Beispiel (das sich geringfügig von dem in der Einführung unterscheidet) sind die wichtigsten Elemente der generierten Property-Hierarchien aufgeführt:

Beispiel für die verschiedenen Elemente im Property-Baum

Listen und Farbblöcke anzeigen

Ein Displayelement enthält Low-Level-Zeichnenbefehle (siehe hier), die mit Skia gerastert werden können. Anzeigeelemente sind in der Regel einfach und benötigen nur wenige Zeichenbefehle, z. B. zum Zeichnen eines Rahmens oder Hintergrunds. Der Paint Tree Walk durchläuft die Layoutstruktur und die zugehörigen Fragmente gemäß der CSS-Painting-Reihenfolge, um eine Liste der Anzeigeelemente zu erstellen.

Beispiel:

Ein blaues Feld mit den Worten „Hallo Welt“ in einem grünen Rechteck.

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="blue" style="width:100px;
  height:100px; background:blue;
  position:absolute;
  top:0; left:0; z-index:-1;">
</div>

Dieser HTML- und CSS-Code erzeugt die folgende Anzeigeliste, wobei jede Zelle ein Anzeigeelement ist:

Hintergrund der Ansicht #blue (Hintergrund) #green (Hintergrund) #green Inline-Text
drawRect mit einer Größe von 800 x 600 Pixeln und der Farbe Weiß. drawRect mit der Größe 100 × 100 an Position 0,0 und der Farbe Blau. drawRect mit der Größe 80 × 18 an der Position 8,8 und der Farbe Grün. drawTextBlob mit der Position 8,8 und dem Text „Hallo Welt“.

Die Liste der angezeigten Artikel ist in umgekehrter Reihenfolge angeordnet. Im obigen Beispiel befindet sich das grüne div-Element in der DOM-Reihenfolge vor dem blauen div-Element. Die CSS-Farbreihenfolge erfordert jedoch, dass das blaue div-Element mit negativem Z-Index vor dem grünen div-Element (Schritt 3) dargestellt wird (Schritt 4.1). Die Displayelemente entsprechen ungefähr den atomaren Schritten der CSS-Anordnungsanweisung. Ein einzelnes DOM-Element kann zu mehreren Anzeigeelementen führen, z. B. dass #green ein Anzeigeelement für den Hintergrund und ein weiteres Anzeigeelement für den Inline-Text hat. Diese Detaillierung ist wichtig, um die gesamte Komplexität der CSS-Anweisung für die Malreihenfolge darzustellen, z. B. das Interleaving, das durch einen negativen Rand entsteht:

Ein grünes Rechteck, das teilweise von einem grauen Feld überlagert wird, und die Worte „Hallo Welt“

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="gray" style="width:35px; height:20px;
  background:gray;margin-top:-10px;"></div>

Dies führt zur folgenden Anzeigeliste, in der jede Zelle ein Anzeigenelement ist:

Hintergrund der Ansicht #green (Hintergrund) #gray (Hintergrund) #green Inline-Text
drawRect mit einer Größe von 800 x 600 Pixeln und der Farbe Weiß. drawRect mit einer Größe von 80 × 18 Pixeln an der Position 8,8 und der Farbe Grün. drawRect mit den Maßen 35 × 20 an Position 8,16 und grau. drawTextBlob mit der Position 8,8 und dem Text „Hallo Welt“.

Die Liste der angezeigten Artikel wird gespeichert und bei späteren Aktualisierungen wiederverwendet. Wenn sich ein Layoutobjekt während des Durchlaufs des Paint-Baums nicht geändert hat, werden seine Darstellungselemente aus der vorherigen Liste kopiert. Eine weitere Optimierung basiert auf einer Eigenschaft der CSS-Anweisung für die Malreihenfolge: Stapelkontexte werden atomar gemalt. Wenn sich innerhalb eines Stapelkontexts kein Layoutobjekt geändert hat, überspringt die Paint Tree Walk den Stapelkontext und kopiert die gesamte Reihenfolge der Anzeigeelemente aus der vorherigen Liste.

Der aktuelle Property-Baumstatus wird während des Durchlaufens des Baums beibehalten und die Liste der Anzeigenelemente wird in „Chunks“ von Anzeigenelementen gruppiert, die denselben Property-Baumstatus haben. Das wird im folgenden Beispiel veranschaulicht:

Ein rosafarbener Kasten mit einem geneigten orangefarbenen Kasten.

<div id="scroll" style="background:pink; width:100px;
   height:100px; overflow:scroll;
   position:absolute; top:0; left:0;">
    Hello world
    <div id="orange" style="width:75px; height:200px;
      background:orange; transform:rotateZ(25deg);">
        I'm falling
    </div>
</div>

Dies führt zur folgenden Anzeigeliste, in der jede Zelle ein Anzeigenelement ist:

Hintergrund der Ansicht #scroll (Hintergrund) #scroll Inline-Text #orange (Hintergrund) #orange Inline-Text
drawRect mit einer Größe von 800 x 600 Pixeln und der Farbe Weiß. drawRect mit einer Größe von 100 × 100 Pixeln an der Position 0,0 und der Farbe Rosa. drawTextBlob mit der Position 0,0 und dem Text „Hallo Welt“. drawRect mit der Größe 75 × 200 an Position 0,0 und der Farbe Orange. drawTextBlob mit der Position 0,0 und dem Text „Ich falle“

Der Transform-Eigenschaftsbaum und die Paint-Chunks würden dann so aussehen (vereinfacht dargestellt):

Ein Bild der vorhergehenden Tabelle: die ersten beiden Zellen in Block 1, die dritte in Block 2 und die letzten beiden Zellen in Block 3.

Die sortierte Liste der Paint-Chunks, also Gruppen von Darstellungselementen und ein Property-Baumstatus, sind die Eingaben für den Ebenen-Schritt der Rendering-Pipeline. Die gesamte Liste der Farbblöcke könnte zu einer einzigen zusammengesetzten Ebene zusammengeführt und zusammen gerastert werden. Dafür wäre jedoch jedes Mal eine aufwendige Rasterung erforderlich, wenn der Nutzer scrollt. Für jeden Malblock könnte eine zusammengesetzte Ebene erstellt und einzeln gerastert werden, um eine erneute Rasterung zu vermeiden. Das würde jedoch schnell den GPU-Speicher erschöpfen. Beim Layern müssen Kompromisse zwischen GPU-Speicher und Kostensenkung bei Änderungen eingegangen werden. Generell empfiehlt es sich, Chunks standardmäßig zusammenzuführen und keine Paint-Chunks zusammenzuführen, deren Attributstruktur sich voraussichtlich im Compositor-Thread ändern wird, z. B. beim Scrollen oder bei Transformationsanimationen im Compositor-Thread.

Das vorherige Beispiel sollte idealerweise zwei zusammengesetzte Ebenen ergeben:

  • Eine zusammengesetzte Ebene mit den Zeichenbefehlen mit einer Größe von 800 × 600 Pixeln:
    1. drawRect mit einer Größe von 800 x 600 Pixeln und der Farbe Weiß
    2. drawRect mit der Größe 100 × 100 an Position 0,0 und der Farbe Pink
  • Eine zusammengesetzte Ebene im Format 144 × 224, die die Zeichenbefehle enthält:
    1. drawTextBlob mit Position 0,0 und dem Text „Hello world“
    2. übersetzen 0,18
    3. rotateZ(25deg)
    4. drawRect mit einer Größe von 75 x 200 Pixeln an der Position 0,0 und der Farbe Orange
    5. drawTextBlob mit der Position 0,0 und dem Text „Ich falle“

Wenn der Nutzer #scroll scrollt, wird die zweite zusammengesetzte Ebene verschoben, aber es ist keine Rasterung erforderlich.

Für das Beispiel aus dem vorherigen Abschnitt zu Property-Bäumen gibt es sechs Paint-Chunks. Zusammen mit den Status der Eigenschaften „transform“, „clip“, „effect“ und „scroll“ sind das:

  • Dokumenthintergrund: Dokument scrollen, Dokument-Clip, Stamm, Dokument scrollen.
  • Horizontale, vertikale und Bildlaufecke für Div (drei separate Paint-Chunks): Dokument-Scrollen, Dokument-Clip, #one-Unkenntlichmachung, Dokument-Scrollen.
  • Iframe #one: #one drehen, Overflow-Scroll-Clip, #one unkenntlich machen, Div-Scroll.
  • IFrame #two: #two-Skalierung, Dokument-Clip, Stamm, Dokument-Scrollen.

Compositor-Frames: Oberflächen, Renderingflächen und GPU-Texturkacheln

Die Browser- und Rendering-Prozesse verwalten die Rasterung von Inhalten und reichen dann Frames des Renderers an den Viz-Prozess zur Darstellung auf dem Bildschirm weiter. Mit Compositor-Frames wird dargestellt, wie rasterisierte Inhalte zusammengefügt und effizient mit der GPU gerendert werden.

Ansichten

Theoretisch könnte ein Renderer- oder Browserprozess-Compositor Pixel in eine einzelne Textur in der vollen Größe des Renderer-Viewports rastern und diese Textur an Viz senden. Zur Anzeige müsste der Display-Compositor nur die Pixel aus dieser einzelnen Textur an die entsprechende Position im Frame-Buffer kopieren (z. B. auf den Bildschirm). Wenn dieser Compositor jedoch nur ein einziges Pixel aktualisieren möchte, muss er den gesamten Viewport neu rastern und eine neue Textur an Viz senden.

Stattdessen wird der Viewport in Kacheln unterteilt. Eine separate GPU-Texturkachel unterstützt jede Kachel mit den Rasterpixeln für einen Teil des Darstellungsbereichs. Der Renderer kann dann einzelne Kacheln aktualisieren oder sogar die Position der vorhandenen Kacheln auf dem Bildschirm ändern. Wenn Sie beispielsweise auf einer Website scrollen, werden die Positionen der vorhandenen Kacheln nach oben verschoben und nur gelegentlich muss für Inhalte weiter unten auf der Seite eine neue Kachel gerastert werden.

Vier Kacheln
Auf diesem Bild ist ein sonniger Tag mit vier Kacheln zu sehen. Wenn Sie scrollen, wird eine fünfte Kachel angezeigt. Eine der Kacheln hat zufällig nur eine Farbe (Himmelblau) und oben befinden sich ein Video und ein iFrame.

Quads und Oberflächen

GPU-Textur-Quader sind eine spezielle Art von Quader, was nur ein ausgefallener Name für eine bestimmte Texturkategorie ist. Ein Quad identifiziert die Eingabetextur und gibt an, wie sie transformiert und visuelle Effekte darauf angewendet werden sollen. Normale Inhaltskacheln haben beispielsweise eine Transformation, die ihre X- und Y-Position im Kachelnetzwerk angibt.

GPU-Texturkacheln

Diese gerasterten Kacheln werden in einem Renderpass verpackt, einer Liste von Quads. Der Renderingdurchlauf enthält keine Pixelinformationen. Stattdessen enthält er eine Anleitung dazu, wo und wie jedes Quad gezeichnet wird, um die gewünschte Pixelausgabe zu erzeugen. Für jede GPU-Texturkachel gibt es ein Draw Quad. Der Display-Compositor muss nur die Liste der Quads durchgehen und jedes mit den angegebenen visuellen Effekten zeichnen, um die gewünschte Pixelausgabe für den Rendering-Pass zu erzeugen. Das Zusammensetzen von Draw-Quads für einen Rendering-Pass kann effizient auf der GPU erfolgen, da die zulässigen visuellen Effekte sorgfältig ausgewählt werden, damit sie direkt auf GPU-Funktionen abgebildet werden.

Neben den gerasterten Kacheln gibt es weitere Arten von Draw Quads. Es gibt beispielsweise Quader mit durchgehender Farbe, die überhaupt nicht von einer Textur unterstützt werden, oder Textur-Quader für nicht gekachelte Texturen wie Video oder Canvas.

Es ist auch möglich, dass ein zusammengesetzter Frame einen weiteren zusammengesetzten Frame einbetten kann. Der Browser-Compositor erstellt beispielsweise einen Compositor-Frame mit der Browser-Benutzeroberfläche und einem leeren Rechteck, in das der Inhalt des Render-Compositors eingebettet wird. Ein weiteres Beispiel sind websiteisolierte Iframes. Diese Einbettung erfolgt über Oberflächen.

Wenn ein Compositor einen Compositor-Frame einreicht, wird dieser mit einer Kennung versehen, die als Surface-ID bezeichnet wird. So können andere Compositor-Frames ihn per Verweis einbetten. Der neueste Renderer-Frame, der mit einer bestimmten Surface-ID gesendet wurde, wird von Viz gespeichert. Ein anderer Renderer-Frame kann sich dann später über ein Surface-Draw-Quad darauf beziehen. So weiß Viz, was gezeichnet werden soll. Hinweis: Quads für die Oberflächendarstellung enthalten nur Oberflächen-IDs und keine Texturen.

Zwischenrenderpässe

Für einige visuelle Effekte, z. B. viele Filter oder erweiterte Überblendungsmodi, müssen zwei oder mehr Quads in eine Zwischentextur gezeichnet werden. Anschließend wird die Zwischentextur in einen Zielpuffer auf der GPU (oder möglicherweise in eine andere Zwischentextur) gezeichnet und gleichzeitig der visuelle Effekt angewendet. Dazu enthält ein Komposition-Frame eine Liste von Rendering-Pässen. Es gibt immer einen Root-Render-Pass, der zuletzt gezeichnet wird und dessen Ziel dem Frame-Zwischenspeicher entspricht. Es können auch mehr vorhanden sein.

Die Möglichkeit mehrerer Renderpässe erklärt den Namen „Renderpass“. Jeder Durchlauf muss sequenziell in mehreren Durchläufen auf der GPU ausgeführt werden, während ein einzelner Durchlauf in einer einzigen massiv parallelen GPU-Berechnung abgeschlossen werden kann.

Aggregation

Mehrere zusammengesetzte Frames werden an Viz gesendet und müssen zusammen auf den Bildschirm gezeichnet werden. Dies wird durch eine Aggregationsphase erreicht, die sie in einen einzelnen, aggregierten Compositor-Frame konvertiert. Bei der Aggregation werden die Surface-Draw-Quads durch die angegebenen Frames des Renderers ersetzt. Außerdem können Sie unnötige Zwischentextur oder Inhalte außerhalb des Bildschirms optimieren. Beispielsweise benötigt der Kompositionsframe für einen isolierten IFrame einer Website in vielen Fällen keine eigene Zwischentextur und kann über geeignete Draw-Quads direkt in den Frame-Puffer gezeichnet werden. In der Aggregationsphase werden solche Optimierungen ermittelt und auf der Grundlage globaler Informationen angewendet, auf die einzelne Render-Kompositoren nicht zugreifen können.

Beispiel

Hier sind die zusammengesetzten Frames, die das Beispiel vom Anfang dieses Posts darstellen.

  • foo.com/index.html-Oberfläche: id=0
    • Renderübergabe 0:Bis zur Ausgabe zeichnen.
      • Renderpass-Zeichnen-Quad: Mit 3 Pixeln Weichzeichnung zeichnen und in Renderpass 0 zuschneiden.
        • Renderpass 1:
          • Zeichnen Sie Quads für den Kachelinhalt des #one-Iframes mit X- und Y-Positionen für jeden.
      • Quad für die Oberflächendarstellung: mit der ID 2, gezeichnet mit Skalierungs- und Verschiebungstransformation.
  • Oberfläche der Browseroberfläche: ID=1
    • Renderpass 0:Zeichnen in die Ausgabe.
      • Quads für die Browseroberfläche zeichnen (auch gekachelte)
  • bar.com/index.html surface: ID=2
    • Renderpass 0:Zeichnen in die Ausgabe.
      • Zeichnen Sie Quadrate für den Inhalt des #two-Iframes mit X- und Y-Positionen für jedes Quadrat.

Illustrationen von Una Kravets