Wichtige Datenstrukturen und ihre Rollen 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

Die vorherigen Beiträge dieser Reihe gaben einen Überblick über die Ziele, wichtigsten Eigenschaften und allgemeinen Komponententeile der RenderingNG-Architektur. Beschäftigen wir uns nun mit den wichtigsten Datenstrukturen, die Ein- und Ausgaben für die Rendering-Pipeline sind.

Diese Datenstrukturen sind:

  • Frame-Bäume, die aus lokalen und Remote-Knoten bestehen, die darstellen, welche Webdokumente in welchem Renderingprozess und welchen Blink-Renderer sich befinden.
  • Der unveränderliche Fragmentbaum stellt die Ausgabe des Algorithmus für die Layouteinschränkung und die Eingabe darin dar.
  • Property-Bäume, die die Transformations-, Clip-, Effekt- und Scroll-Hierarchien eines Webdokuments darstellen und in der gesamten Pipeline verwendet werden.
  • Anzeigelisten und Farbblöcke werden als Eingaben in die Raster- und Ebenenalgorithmen verwendet.
  • Compositor-Frames kapseln Oberflächen, Renderingflächen und GPU-Texturkacheln, die zum Zeichnen mit der GPU verwendet werden.

Bevor wir diese Datenstrukturen durchgehen, möchte ich Ihnen das folgende einfache Beispiel zeigen, das auf einem aus dem vorherigen Beitrag aufbaut. Ich verwende dieses Beispiel in diesem Beitrag, um zu zeigen, wie die Datenstrukturen angewendet werden.

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

Bäume einrahmen

Chrome rendert einen ursprungsübergreifenden Frame manchmal in einem anderen Renderingprozess als der übergeordnete Frame.

Im Beispiel aus der Einführung gibt es insgesamt drei Frames:

Den übergeordneten Frame foo.com mit zwei iFrames.

Mit der Website-Isolierung verwendet Chromium zum Rendern dieser Webseite zwei Rendering-Prozesse. Jeder Renderingprozess verfügt über eine eigene Darstellung der Framestruktur für diese Webseite:

Zwei Frame-Bäume, die die beiden Rendering-Prozesse darstellen.

Ein Frame, der in einem anderen Prozess gerendert wird, wird als Remote-Frame dargestellt. Ein Remote-Frame enthält die Informationen, die mindestens erforderlich sind, um beim Rendern als Platzhalter zu fungieren, z. B. die Abmessungen. Ansonsten enthält der Remote-Frame keine Informationen, die zum Rendern des eigentlichen Inhalts erforderlich sind.

Im Gegensatz dazu stellt ein lokaler Frame einen Frame dar, der die in den vorherigen Beiträgen beschriebene Standard-Rendering-Pipeline durchläuft. Der lokale Frame enthält alle Informationen, die erforderlich sind, um die Daten für diesen Frame (z. B. den DOM-Baum und Stildaten) so umzuwandeln, dass sie gerendert und angezeigt werden können.

Die Rendering-Pipeline arbeitet mit dem Detaillierungsgrad eines lokalen Frame-Baumfragments. Sehen wir uns ein komplizierteres Beispiel mit foo.com als Hauptframe an:

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

Und der folgende bar.com-Subframe:

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

Obwohl es immer noch nur zwei Renderer gibt, gibt es jetzt drei lokale Frame-Baumfragmente, wobei zwei im Renderingprozess für foo.com und einer im Renderingprozess für bar.com enthalten sind:

Darstellung der beiden Renderings und drei Frame-Baumfragmente.

Um einen Compositor-Frame für die Webseite zu erstellen, fordert Viz gleichzeitig einen Compositor-Frame aus dem Stamm-Frame jedes der drei lokalen Frame-Bäume an und aggregiert diesen dann. (Siehe auch den Abschnitt für Compositor-Frames weiter unten in diesem Post.)

Der foo.com-Hauptframe und der foo.com/other-page-Subframe sind Teil desselben Frame-Baums und werden im selben Prozess gerendert. Die beiden Frames haben jedoch immer noch unabhängige Dokumentlebenszyklen, da sie Teil verschiedener lokaler Frame-Baumfragmente sind. Aus diesem Grund ist es nicht möglich, einen Compositor-Frame für beide in einer Aktualisierung zu generieren. Der Renderingprozess hat nicht genügend Informationen, um den für foo.com/other-page generierten Compositor-Frame direkt im Compositor-Frame für den foo.com-Hauptframe zusammenzusetzen. Beispielsweise kann der übergeordnete Frame bar.com, der außerhalb des Prozesses liegt, die Anzeige des iFrames foo.com/other-url beeinträchtigen, indem er den iFrame mit CSS transformiert oder Teile des iFrames mit anderen Elementen im DOM verschließt.

Vermittlungsabfolge für visuelle Attribute

Visuelle Eigenschaften wie der Skalierungsfaktor des Geräts und die Größe des Darstellungsbereichs beeinflussen die gerenderte Ausgabe und müssen zwischen den lokalen Frame-Baumfragmenten synchronisiert werden. Der Stamm jedes lokalen Frame-Baumfragments ist ein Widget-Objekt zugeordnet. Aktualisierungen der visuellen Eigenschaften werden an das Widget des Hauptframes übertragen, bevor von oben nach unten an die verbleibenden Widgets weitergeleitet wird. Das passiert beispielsweise, wenn sich die Größe des Darstellungsbereichs ändert:

Diagramm des im vorherigen Text erläuterten Vorgangs.

Dieser Vorgang erfolgt nicht sofort, daher enthalten die replizierten visuellen Eigenschaften auch ein Synchronisierungstoken. Der Viz-Compositor verwendet dieses Synchronisierungstoken, um zu warten, bis alle lokalen Frame-Baumfragmente einen Compositor-Frame mit dem aktuellen Synchronisierungstoken senden. Bei diesem Prozess wird vermieden, dass Compositor-Frames mit verschiedenen visuellen Eigenschaften kombiniert werden.

Der unveränderliche Fragmentbaum

Der unveränderliche Fragmentbaum ist die Ausgabe der Layoutphase der Renderingpipeline. Sie gibt die Position und Größe aller Elemente auf der Seite an (ohne angewendete Transformationen).

Darstellung der Fragmente in jedem Baum, wobei für ein Fragment angegeben wurde, dass das Layout erforderlich ist.

Jedes Fragment stellt einen Teil eines DOM-Elements dar. Üblicherweise gibt es nur ein Fragment pro Element. Es können aber mehr Fragmente vorhanden sein, wenn es beim Drucken auf verschiedene Seiten oder in einem mehrspaltigen Kontext auf verschiedene Seiten aufgeteilt wird.

Nach dem Layout ist jedes Fragment unveränderlich und wird nie mehr geändert. Außerdem gelten einige zusätzliche Einschränkungen. Was wir nicht tun:

  • Lassen Sie alle „Up“-Verweise in der Baumstruktur zu. Untergeordnete Elemente können nicht auf das übergeordnete Element verweisen.
  • „Bubble“-Daten im Baum (ein Kind liest nur Informationen von seinen untergeordneten, 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 häufig den gesamten Baum neu generieren, was teuer ist.

Bei den meisten Layouts handelt es sich in der Regel um inkrementelle Aktualisierungen, z. B. eine Webanwendung, die einen kleinen Teil der Benutzeroberfläche aktualisiert, wenn der Nutzer auf ein Element klickt. Idealerweise sollte das Layout nur proportional zu dem funktionieren, was sich auf dem Bildschirm tatsächlich geändert hat. Dies erreichen wir, indem wir so viele Teile der vorherigen Struktur wie möglich wiederverwenden. Das bedeutet (in der Regel), dass wir nur den Rücken des Baums neu aufbauen müssen.

Mit diesem unveränderlichen Design können wir in Zukunft interessante Dinge tun, z. B. den unveränderlichen Fragmentbaum bei Bedarf über Thread-Grenzen übergeben (um nachfolgende Phasen in einem anderen Thread auszuführen), mehrere Bäume für eine reibungslose Layoutanimation zu generieren oder parallele spekulative Layouts durchzuführen. Es bietet uns auch das Potenzial des Multithread-Layouts selbst.

Inline-Fragmentelemente

Inline-Inhalte, d. h. überwiegend Text mit Stilen, werden in einer etwas anderen Darstellung dargestellt. Anstelle einer Baumstruktur mit Kästchen und Verweisen werden Inline-Inhalte in einer flachen Liste dargestellt, die den Baum darstellt. Der Hauptvorteil besteht darin, dass eine Darstellung mit einer flachen Liste für Inlines schnell, nützlich für das Untersuchen oder Abfragen von Inline-Datenstrukturen und speichereffizient ist. Dies ist für die Leistung beim Web-Rendering extrem wichtig, da das Rendering von Text sehr komplex ist und ohne eine hohe Optimierung schnell zum langsamsten Teil der Pipeline werden kann.

Ein interessanter historischer Hinweis: Dies ähnelt der bisherigen Darstellung des DOMs in Internet Explorer, da es ursprünglich ähnlich wie ein Texteditor aufgebaut wurde.

Die Liste wird für jeden Inline-Formatierungskontext in der Reihenfolge einer Tiefensuche der Unterstruktur des Inline-Layouts erstellt. Jeder Eintrag in der Liste ist ein Tupel von (Objekt, Anzahl der Nachfolgerelemente). Betrachten Sie zum Beispiel dieses DOM:

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

(Beachten Sie, dass die Eigenschaft width auf 0 gesetzt ist, sodass die Zeile zwischen „Hi“ und „there“ umgebrochen wird.) Wenn der Kontext der Inline-Formatierung für diese Situation als Baumstruktur dargestellt wird, sieht das so aus:

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

Die einfache Liste sieht so aus:

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

Diese Datenstruktur wird von vielen Nutzern verwendet: Accessibility APIs und Geometrie-APIs wie getClientRects und contenteditable. Für jede Methode gelten unterschiedliche Anforderungen. Diese Komponenten greifen über einen Convenience-Cursor auf die flache Datenstruktur zu.

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

  • Die Iteration in der Reihenfolge der Tiefensuche geht sehr schnell. Diese Methode wird sehr oft verwendet, da sie den Caret-Bewegungen ähnelt. Da es sich um eine flache Liste handelt, erhöht die Tiefensuche nur den Array-Offset, was schnelle Iterationen und eine hohe Speicherlokalität ermöglicht.
  • Sie ermöglicht eine breite Suche, die beispielsweise erforderlich ist, wenn der Hintergrund von Linien- und Inline-Feldern gestrichen wird.
  • Wenn Sie die Anzahl der Nachfolger kennen, ist der Wechsel zum nächsten gleichgeordneten Element schnell (inkrementieren Sie einfach den Array-Offset um diese Zahl).

Grundstücksbäume

Wie Sie wissen, ist das DOM eine Baumstruktur von Elementen (plus Textknoten), und CSS können verschiedene Stile auf Elemente anwenden.

Diese gibt es hauptsächlich in vier Geschmacksrichtungen:

  • Layout:Eingaben für den Layoutbeschränkungsalgorithmus.
  • Paint:Hier wird festgelegt, wie das Element (jedoch nicht seine Nachfolgerelemente) gemalt und gerastert wird.
  • Bild: Raster-/Zeicheneffekte, die auf die DOM-Unterstruktur angewendet werden, z. B. Transformationen, Filter und Beschnitt.
  • Scrollen: Achsenausrichtung und abgerundete Ecken, Zuschneiden und Scrollen der enthaltenen Unterstruktur

Property-Bäume sind Datenstrukturen, die erklären, wie visuelle Effekte und Scroll-Effekte auf DOM-Elemente angewendet werden. Sie bieten die Möglichkeit, Fragen wie die folgenden Fragen zu beantworten: Wo ist ein bestimmtes DOM-Element aufgrund seiner Layoutgröße und -position relativ zum Bildschirm? Und: In welcher Abfolge von GPU-Vorgängen sollten visuelle und Scroll-Effekte angewendet werden?

Visuelle und Scroll-Effekte im Web sind in ihrer vollen Pracht sehr kompliziert. Das Wichtigste bei Property-Bäumen ist also, diese Komplexität in eine einzelne Datenstruktur umzuwandeln, die ihre Struktur und Bedeutung genau abbildet. Gleichzeitig wird der Rest der Komplexität von DOM und CSS beseitigt. Dadurch können wir Algorithmen für das Erstellen und Scrollen viel zuverlässiger implementieren. Wichtig ist insbesondere:

  • Potenziell fehleranfällige Geometrie und andere Berechnungen können an einem Ort zentralisiert werden.
  • Die Komplexität der Erstellung und Aktualisierung von Immobilienbäumen ist in eine Rendering-Pipeline-Phase unterteilt.
  • Es ist viel einfacher und schneller, Property-Bäume an verschiedene Threads und Prozesse zu senden als mit dem vollständigen DOM-Status. Dadurch können sie für viele Anwendungsfälle verwendet werden.
  • Je mehr Anwendungsfälle es gibt, desto mehr Erfolge können wir durch Geo-Caching erhalten, das oben aufgesetzt ist, da die Caches der anderen wiederverwendet werden können.

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

  • Compositing von Paint trennen und compositing vom Hauptthread.
  • Eine optimale Zusammenstellungs-/Zeichenstrategie festlegen
  • Die IntersectionObserver-Geometrie wird gemessen.
  • Vermeiden Sie Arbeiten für nicht sichtbare Elemente und GPU-Texturkacheln.
  • Farbe und Raster werden effizient und präzise entwertet.
  • Layout Shift und Largest Contentful Paint werden in Core Web Vitals gemessen.

Jedes Webdokument hat vier verschiedene Eigenschaftsstrukturen: transform, Clip, Effekt und scrollen.(*) Der Transformationsbaum steht für CSS-Transformationen und Scrollen. (Die Scroll-Transformation wird als 2D-Transformationsmatrix dargestellt.) Der Clip-Baum stellt Überlaufclips dar. Der Effektbaum repräsentiert alle anderen visuellen Effekte: Deckkraft, Filter, Masken, Mischmodi und andere Arten von Clips wie „clip-path“. Der Scrollbaum stellt Informationen zum Scrollen dar, z. B. wie Scrollvorgänge verkettet werden. Er ist erforderlich, um Scrollen im Compositor-Thread auszuführen. Jeder Knoten in einer Eigenschaftsstruktur repräsentiert einen Scrolleffekt oder einen visuellen Effekt, der von einem DOM-Element angewendet wird. Wenn es mehrere Auswirkungen hat, kann es mehrere Knoten in der Eigenschaftsstruktur für dasselbe Element geben.

Die Topologie der einzelnen Baumstrukturen ist wie eine dünnbesetzte Darstellung des DOM. Wenn beispielsweise drei DOM-Elemente mit Überlaufclips vorhanden sind, gibt es drei Knoten in der Baumstruktur. Die Struktur der Baumstruktur folgt der enthaltenden Blockbeziehung zwischen den Überlaufclips. Es gibt auch Verbindungen zwischen den Bäumen. Diese Links geben die relative DOM-Hierarchie und damit die Reihenfolge der Anwendung der Knoten an. Wenn beispielsweise eine Transformation für ein DOM-Element unter einem anderen DOM-Element mit einem Filter liegt, wird sie natürlich vor dem Filter angewendet.

Jedes DOM-Element hat einen Eigenschaftsbaumstatus, d. h. ein 4-Tupel (Transformation, Clip, Effekt, Scrollen), das den nächstgelegenen Ancestor-Clip, Transformation und Effektbaumknoten angibt, der auf dieses Element angewendet wird. Dies ist sehr praktisch, da wir mit diesen Informationen genau die Liste der Clips, Transformationen und Effekte kennen, die auf dieses Element angewendet werden, und in welcher Reihenfolge. So wissen wir, wo es sich auf dem Bildschirm befindet und wie es gezeichnet wird.

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 obigen Beispiel, das sich leicht vom Beispiel in der Einführung unterscheidet, finden Sie hier die Schlüsselelemente der generierten Property-Bäume:

Beispiel für die verschiedenen Elemente im Eigenschaftenbaum

Listen anzeigen und Stücke malen

Ein Anzeigeelement enthält Low-Level-Zeichenbefehle (siehe hier), die mit Skia gerastert werden können. Anzeigeelemente sind in der Regel einfach und beinhalten nur wenige Zeichenbefehle, wie z. B. das Zeichnen eines Rahmens oder Hintergrunds. Der Paint-Baum wird in der CSS-Painting-Reihenfolge über den Layout-Baum und die zugehörigen Fragmente iteriert, um eine Liste mit Anzeigeelementen 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 würde die folgende Anzeigeliste erzeugen, wobei jede Zelle ein Anzeigeelement ist:

Hintergrund der Ansicht Hintergrund: #blue Hintergrund: #green #green Inline-Text
drawRect mit der Größe 800 × 600 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 Position 8,8 und der Farbe Grün. drawTextBlob mit Position 8,8 und dem Text „Hello world“.

Die Liste der präsentierten Artikel ist von vorn nach vorne geordnet. Im Beispiel oben befindet sich das grüne „div“-Element vor dem blauen „div“-Element in der DOM-Reihenfolge. Für die CSS-Farbreihenfolge muss aber der negative Z-Index mit blauem „div“-Element vor dem grünen „div“-Element (Schritt 3) gezeichnet werden (Schritt 4.1). Anzeigeelemente entsprechen ungefähr den atomaren Schritten der CSS-Farbauftragsspezifikation. Ein einzelnes DOM-Element kann zu mehreren Anzeigeelementen führen, etwa dazu, dass #green ein Anzeigeelement für den Hintergrund und ein weiteres Anzeigeelement für den Inline-Text hat. Dieser Detaillierungsgrad ist wichtig, um die gesamte Komplexität der CSS-Farbauftragsspezifikation darzustellen, z. B. durch die Verschachtelung durch eine negative Marge:

Ein grünes Rechteck mit einem teilweise überlagerten grauen Kasten und den Worten „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>

Daraus ergibt sich die folgende Anzeigeliste, wobei jede Zelle ein Anzeigeelement darstellt:

Hintergrund der Ansicht Hintergrund: #green Hintergrund: #gray #green Inline-Text
drawRect mit der Größe 800 × 600 und der Farbe Weiß. drawRect mit der Größe 80 × 18 an Position 8,8 und der Farbe Grün. drawRect mit der Größe 35 × 20 an Position 8,16 und der Farbe Grau. drawTextBlob mit Position 8,8 und dem Text „Hello world“.

Die Liste der Anzeigeartikel wird gespeichert und bei späteren Aktualisierungen wiederverwendet. Wenn sich ein Layoutobjekt während des Paint-Baums nicht geändert hat, werden seine Anzeigeelemente aus der vorherigen Liste kopiert. Eine weitere Optimierung basiert auf einer Eigenschaft der CSS-Farbauftragsspezifikation: Das Stapeln von Kontexten wird in kleinstmöglichen Schritten dargestellt. Wenn sich in einem Stapelkontext kein Layoutobjekt geändert hat, wird der Stapelkontext bei der Erstellung des Paint-Baums übersprungen und es wird die gesamte Sequenz der Anzeigeelemente aus der vorherigen Liste kopiert.

Der aktuelle Status der Eigenschaftsstruktur wird während der Paint-Baumstruktur beibehalten und die Liste der Anzeigeelemente wird in "Blöcken" von Anzeigeelementen mit demselben Baumstrukturstatus gruppiert. Das wird im folgenden Beispiel veranschaulicht:

Ein rosafarbenes Feld mit einem geneigten orangefarbenen Rahmen.

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

Daraus ergibt sich die folgende Anzeigeliste, wobei jede Zelle ein Anzeigeelement darstellt:

Hintergrund der Ansicht Hintergrund: #scroll #scroll Inline-Text Hintergrund: #orange #orange Inline-Text
drawRect mit der Größe 800 × 600 und der Farbe Weiß. drawRect mit der Größe 100 × 100 an Position 0,0 und der Farbe Pink. drawTextBlob mit Position 0,0 und dem Text „Hello world“. drawRect mit der Größe 75 × 200 an Position 0,0 und der Farbe Orange. drawTextBlob mit Position 0,0 und dem Text „Ich falle“.

Der Eigenschaftsbaum für Transformationen und die Paint-Chunks würden dann (der Einfachheit halber vereinfacht):

Ein Bild der vorherigen Tabelle, die ersten beiden Zellen in Chunk 1, die dritte in Chunk 2, die letzten beiden Zellen in Block 3.

Die geordnete Liste von Paint-Chunks, bei denen es sich um Gruppen von Anzeigeelementen und den Zustand eines Eigenschaftenbaums handelt, sind die Eingaben für den Schritt zur Ebenenerstellung der Rendering-Pipeline. Die gesamte Liste der Farbblöcke könnte in einer einzigen zusammengesetzten Ebene zusammengeführt und zusammen gerastert werden. Dafür wäre jedoch bei jedem Scrollen des Nutzers ein teures Rastern erforderlich. Für jeden Farbblock könnte eine zusammengesetzte Ebene erstellt und einzeln gerastert werden, um jede erneute Rasterung zu vermeiden. Dies würde jedoch den GPU-Arbeitsspeicher schnell erschöpfen. Beim Layerize-Schritt müssen Kompromisse zwischen GPU-Arbeitsspeicher und Kostensenkungen gefunden werden, wenn sich etwas ändert. Ein guter allgemeiner Ansatz besteht darin, Blöcke standardmäßig zusammenzuführen und keine Paint-Chunks mit Eigenschaftenbaumstatus zusammenzuführen, die sich voraussichtlich im Compositor-Thread ändern, z. B. bei Animationen für Compositor-Thread-Scrolling oder Compositor-Thread-Transformation.

Im vorherigen Beispiel sollten idealerweise zwei zusammengesetzte Schichten erzeugt werden:

  • Eine zusammengesetzte Ebene mit einer Größe von 800 × 600, die die Zeichenbefehle enthält:
    1. drawRect mit der Größe 800 × 600 und der Farbe Weiß
    2. drawRect mit der Größe 100 × 100 an Position 0,0 und der Farbe Pink
  • Eine 144 x 224 zusammengesetzte Ebene, 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 der Größe 75 × 200 an Position 0,0 und der Farbe Orange
    5. drawTextBlob mit Position 0,0 und dem Text „Ich falle“

Scrollt der Nutzer durch #scroll, wird die zweite zusammengesetzte Ebene verschoben, allerdings ist keine Rasterung erforderlich.

In unserem Beispiel aus dem vorherigen Abschnitt zu Immobilienbäumen gibt es sechs Farbblöcke. Zusammen mit ihren Status (Transformieren, Ausschneiden, Effekt, Scrollen) sind sie:

  • Dokumenthintergrund: Scrollen, Dokumentclip, Stamm, Dokumentscroll
  • Horizontale, vertikale und Scroll-Ecke für „div“ (drei separate Farbblöcke): Scrolling von Dokumenten, Dokumentenclip, #one-Weichzeichnen, Scrollen des Dokuments.
  • iFrame #one: #one drehen, Überlauf-Scrollclip, #one weichzeichnen, div-Scrollen.
  • iFrame-#two: #two-Skalierung, Dokumentclip, Stamm, Dokument-Scrollen.

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

Wie im vorherigen Post erläutert (siehe hier), verwalten Browser- und Renderingprozesse das Rastern von Inhalten und senden dann Compositor-Frames zur Darstellung auf dem Bildschirm an den Visualisierungsprozess. Mit Compositor-Frames stellt RenderingNG dar, wie Rasterinhalte zusammengefügt und mithilfe der GPU effizient gezeichnet werden.

Ansichten

Theoretisch könnte ein Rendering- oder Browser-Kompositor-Tool Pixel in einer einzelnen Textur in voller Größe des Darstellungsbereichs des Renderers rastern und diese Textur an Viz senden. Um sie anzuzeigen, müsste der Display-Compositor nur die Pixel aus dieser einzelnen Textur an die entsprechende Position im Framepuffer (z. B. den Bildschirm) kopieren. Wenn dieser Kompositor jedoch auch nur ein einzelnes Pixel aktualisieren möchte, müsste er den gesamten Darstellungsbereich neu rastern und eine neue Textur an Viz senden.

Stattdessen wird der Darstellungsbereich in Kacheln unterteilt. Eine separate GPU-Texturkachel hinter jeder Kachel mit den Rasterpixeln für einen Teil des Darstellungsbereichs. Der Renderer kann dann einzelne Kacheln aktualisieren oder sogar einfach die Position der vorhandenen Kacheln auf dem Bildschirm ändern. Beim Scrollen auf einer Website verschiebt sich beispielsweise die Position vorhandener Kacheln nach oben und nur gelegentlich muss eine neue Kachel für Inhalte weiter unten auf der Seite gerastert werden.

Vier Kacheln.

Das Bild oben zeigt einen sonnigen Tag mit vier Kacheln. Beim Scrollen wird eine fünfte Kachel eingeblendet. Eine der Kacheln hat nur eine Farbe (himmelblau) und darüber befinden sich ein Video und ein iFrame. Das bringt uns zum nächsten Thema.

Quads und Oberflächen

GPU-Texturkacheln sind eine spezielle Art von Quad, das lediglich ein origineller Name für die eine oder andere Texturkategorie ist. Ein Viereck identifiziert die Eingabetextur und zeigt an, wie sie transformiert und mit visuellen Effekten versehen wird. Reguläre Inhaltskacheln haben beispielsweise eine Transformation, die ihre x- und y-Position im Kachelraster angibt.

GPU-Texturkacheln.

Diese Rasterkacheln sind in einen Renderingpass zusammengefasst, bei dem es sich um eine Liste von Quadraten handelt. Die Renderingkarte enthält keine Pixelinformationen. Stattdessen enthält sie Anweisungen, wo und wie die einzelnen Quadrate gezeichnet werden, um die gewünschte Pixelausgabe zu erzeugen. Für jede GPU-Texturkachel gibt es ein Zeichen-Quadrat. Der Displaycompositor muss lediglich die Liste der Quadrate durchlaufen und jedes Element mit den angegebenen visuellen Effekten zeichnen, um die gewünschte Pixelausgabe für den Renderingdurchlauf zu erzeugen. Das Zusammensetzen von Zeichen-Quadraten für einen Rendering-Pass kann effizient auf der GPU durchgeführt werden, da die zulässigen visuellen Effekte sorgfältig ausgewählt wurden, damit sie GPU-Funktionen direkt zugeordnet werden können.

Neben Rasterkacheln gibt es noch weitere Typen von Zeichenquadraten. So gibt es beispielsweise Zeichenquadrate für Vollfarbe, die gar nicht durch eine Textur gestützt werden, oder Texturquadrate für nicht gekachelte Texturen wie Video oder Canvas.

Es ist auch möglich, dass ein Compositor-Frame einen anderen Compositor-Frame einbettet. Der Browser-Compositor erzeugt beispielsweise einen Compositor-Frame mit der Browser-UI und ein leeres Rechteck, in das der Rendering-Compositor-Inhalt eingebettet wird. Ein weiteres Beispiel sind websiteisolierte iFrames. Diese Einbettung erfolgt über Oberflächen.

Wenn ein Compositor einen Compositor-Frame sendet, erhält er eine Kennung, die als Oberflächen-ID bezeichnet wird. So können andere Compositor-Frames diesen durch Verweis einbetten. Der neueste Compositor-Frame, der mit einer bestimmten Oberflächen-ID eingereicht wurde, wird von Viz gespeichert. Ein anderer Compositor-Frame kann dann später über ein Zeichen-Quadrat der Oberfläche darauf verweisen, sodass Viz weiß, was gezeichnet werden soll. Beachten Sie, dass Quadrate zum Zeichnen von Oberflächen nur Oberflächen-IDs und keine Texturen enthalten.

Mittlere Renderingkarten

Für einige visuelle Effekte, z. B. viele Filter oder erweiterte Mischmodi, müssen zwei oder mehr Quadrate auf einer Zwischentextur gezeichnet werden. Dann wird die Zwischentextur in einen Zielpuffer auf der GPU (oder möglicherweise in eine andere Zwischentextur) gezogen und gleichzeitig den visuellen Effekt angewendet. Um dies zu ermöglichen, enthält ein Compositor-Frame tatsächlich eine Liste von Renderingkarten. Es gibt immer einen Stamm-Rendering-Pass, der zuletzt gezeichnet wird und dessen Ziel dem Frame-Zwischenspeicher entspricht. Es können auch weitere vorhanden sein.

Die Möglichkeit mehrerer Rendering-Passagen erklärt den Namen "Render Pass". Jeder Durchlauf muss auf der GPU in mehreren „Durchgängen“ nacheinander ausgeführt werden. Ein einzelner Durchlauf kann hingegen in einer einzigen massiv parallelen GPU-Berechnung ausgeführt werden.

Aggregation

Mehrere Compositor-Frames werden an Viz gesendet und müssen gemeinsam auf dem Bildschirm gezeichnet werden. Dies wird durch eine Aggregationsphase erreicht, die sie in einen einzelnen, aggregierten Compositor-Frame umwandelt. Bei der Aggregation werden Quadrate zum Zeichnen der Oberfläche durch die angegebenen Compositor-Frames ersetzt. Es ist auch eine Gelegenheit, unnötige Zwischentexturen oder nicht sichtbare Inhalte zu entfernen. Beispielsweise benötigt der Compositor-Frame für einen von der Website isolierten iFrame in vielen Fällen keine eigene Zwischentextur und kann über entsprechende Zeichenquadrate direkt in den Frame-Zwischenspeicher gezogen werden. In der Aggregationsphase werden solche Optimierungen anhand von globalem Wissen durchgeführt und angewendet, das einzelnen Rendering-Kompositoren nicht zugänglich ist.

Beispiel

Hier sind die tatsächlichen Compositor-Frames, die das Beispiel vom Anfang dieses Posts darstellen.

  • foo.com/index.html-Oberfläche: id=0
    • Renderingübergabe 0: Zeichnen Sie in die Ausgabe.
      • Render-Pass-Zeichen-Quad: Zeichnen mit 3-Pixel-Unschärfe und Clip in Rendering-Pass 0.
        • Renderingkarte 1:
          • Zeichnen Sie Quadrate für die Kachelinhalte des iFrames #one mit jeweils x- und y-Positionen.
      • Surface Draw Quad: mit ID 2, gezeichnet mit Maßstab und Translate-Transformation.
  • Benutzeroberfläche des Browsers: ID=1
    • Renderingübergabe 0: Zeichnen Sie in die Ausgabe.
      • Quadrate für Browser-Benutzeroberfläche zeichnen (auch gekachelt)
  • bar.com/index.html-Oberfläche: ID=2
    • Renderingübergabe 0: Zeichnen Sie in die Ausgabe.
      • Zeichnen Sie Quadrate für die Inhalte des iFrames #two mit jeweils x- und y-Positionen.

Fazit

Vielen Dank, dass Sie sich die Zeit zum Lesen dieser E-Mail genommen haben. Damit haben wir die beiden letzten Posts beendet und den Überblick über RenderingNG beendet. Als Nächstes befassen wir uns eingehend mit den Herausforderungen und der Technologie in vielen Unterkomponenten der Rendering-Pipeline von Anfang bis Ende. Diese Funktion wird demnächst verfügbar sein.

Illustrationen von Una Kravets.