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.
- Displaylisten und Paint-Chunks sind die Eingaben für die Raster- und Ebenenalgorithmen.
- Compositor-Frames umfassen Oberflächen, Rendering-Oberflächen und GPU-Texturkacheln, 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>
Bäume umrahmen
Chrome rendert einen plattformübergreifenden Frame manchmal in einem anderen Rendering-Prozess als den des übergeordneten Frames.
Im Beispielcode gibt es insgesamt drei Frames:
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:
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. den 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.
Sehen wir uns ein etwas komplizierteres Beispiel mit foo.com
als Hauptframe an:
<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
:
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 auch 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 nicht möglich, einen einzigen Compositor-Frame für beide in einem Update 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 externe 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:
Dieser Vorgang dauert einige Zeit. Daher enthalten die replizierten visuellen Eigenschaften auch ein Synchronisierungstoken. Der Viz-Compositor verwendet dieses Synchronisierungstoken, um zu warten, bis alle lokalen Frame-Tree-Fragmente einen Compositor-Frame mit dem aktuellen Synchronisierungstoken gesendet haben. So wird verhindert, dass Frames des Renderers 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).
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 wird 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 „aufblähen“ (ein untergeordnetes Element liest nur Informationen von seinen untergeordneten Elementen, nicht von seinem übergeordneten Element).
Durch diese Einschränkungen können wir ein Fragment für ein nachfolgendes Layout wiederverwenden. Ohne diese Einschränkungen müssten wir den gesamten Baum häufig neu generieren, was teuer ist.
Die meisten Layouts werden in der Regel inkrementell aktualisiert, z. B. in einer Webanwendung, in der ein kleiner Teil der Benutzeroberfläche aktualisiert wird, wenn der Nutzer auf ein Element klickt. Idealerweise sollte das Layout nur in dem Maße angepasst werden, in dem sich etwas auf dem Bildschirm geändert hat. 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. Anstatt einer Baumstruktur mit Feldern und Verweispunkten 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 Web-Rendering-Leistung, da das Rendering 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 vom Typ (Objekt, Anzahl der Nachkommen). 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 flache Liste sieht so aus:
- (Linienfeld, 2)
- (Box <span>, 1)
- (Text „Hallo“, 0)
- (Linienfeld, 3)
- (Box <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 häufig verwendet, da sie den Bewegungen des Carets ä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.
Objektbä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 abgerundetes Zuschneiden und Scrollen des enthaltenen untergeordneten Baums.
Property-Bä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 Scrolleffekte im Web sind in ihrer ganzen 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 zu entfernen. 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 den darüber aufgebauten Geometriespeicher erzielen, da die Caches der einzelnen Anwendungsfälle wiederverwendet werden können.
In RenderingNG werden Property-Bäume für viele Zwecke verwendet, darunter:
- Trennung von Compositing und Paint sowie von Compositing und dem Hauptthread.
- Optimale Komposition / Zeichenstrategie bestimmen
- Geometrie von IntersectionObserver messen
- Vermeidung von Arbeitsaufwand für Elemente außerhalb des Bildschirms 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-Transformationen und Scrollen. (Eine Scroll-Transformation wird als 2D-Transformationsmatrix dargestellt.) Der Clipbaum stellt Overflow-Clips dar. Der Effektbaum stellt alle anderen visuellen Effekte dar: Deckkraft, Filter, Masken, Überblendungsmodi und andere Arten von Clips wie Clip-Pfade. 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 es mehrere Auswirkungen hat, kann es in jedem Baum mehr als einen Knoten für das gleiche 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 damit die Reihenfolge der Anwendung der Knoten an. Wenn sich eine Transformation für ein 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 erfahren 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:
Listen und Farbblöcke anzeigen
Ein Anzeigeelement 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 Durchlauf des Paint-Baums durchläuft den Layoutbaum und die zugehörigen Fragmente gemäß der CSS-Zeichenreihenfolge, um eine Liste der angezeigten Elemente zu erstellen.
Beispiel:
<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>
Mit diesem HTML- und CSS-Code würde die folgende Anzeigeliste erstellt, 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 einer Größe von 100 × 100 Pixeln an der Position 0,0 und der Farbe Blau. |
drawRect mit einer Größe von 80 × 18 Pixeln 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 steht das grüne div in der DOM-Reihenfolge vor dem blauen div. Gemäß der CSS-Malreihenfolge muss das blaue div mit dem negativen Z-Index jedoch vor dem grünen div (Schritt 3) (Schritt 4.1) gerendert werden. Die Displayelemente entsprechen ungefähr den atomaren Schritten der CSS-Anordnungsanweisung. Ein einzelnes DOM-Element kann mehrere Anzeigenelemente ergeben, z. B. ein Anzeigenelement für den Hintergrund und ein anderes für den Inline-Text von #grün. Diese Detaillierung ist wichtig, um die gesamte Komplexität der CSS-Anweisung für die Malreihenfolge darzustellen, z. B. das Überlagern, das durch einen negativen Rand entsteht:
<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 der Größe 35 × 20 an der Position 8,16 und der Farbe 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 Stapelstapels kein Layoutobjekt geändert hat, wird der Stapelstapel beim Durchlaufen des Zeichenbaums übersprungen und die gesamte Sequenz der angezeigten Elemente aus der vorherigen Liste kopiert.
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:
<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 einer Größe von 75 × 200 Pixeln an der 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):
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 Malelemente könnte in einer einzigen zusammengesetzten Ebene zusammengeführt und gemeinsam gerastert werden. Dies würde jedoch jedes Mal, wenn der Nutzer scrollt, eine aufwendige Rasterung erfordern. 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 Transformieren 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:
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
- Eine zusammengesetzte Ebene mit den folgenden Zeichenbefehlen:
drawTextBlob
mit der Position 0,0 und dem Text „Hallo Welt“- übersetzen 0,18
rotateZ(25deg)
drawRect
mit einer Größe von 75 x 200 Pixeln an der Position 0,0 und der Farbe OrangedrawTextBlob
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-Scroll, Dokument-Clip,
#one
-Unkenntlichmachung, Dokument-Scroll. - 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, Rendering-Oberflä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 einer einzelnen 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 gerasterten Pixeln für einen Teil des Viewports. Der Renderer kann dann einzelne Kacheln aktualisieren oder einfach nur 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.
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.
Diese gerasterten Kacheln werden in einem Renderpass verpackt, einer Liste von Quads. Der Renderer-Pass enthält keine Pixelinformationen, sondern Anweisungen dazu, wo und wie jedes Quadrat gezeichnet werden soll, um die gewünschte Pixelausgabe zu erzielen. 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 Kompositionsbereich einen anderen Kompositionsbereich einbettet. 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 Stamm-Renderpass, der zuletzt gezeichnet wird und dessen Ziel dem Frame-Buffer entspricht. Es kann auch weitere geben.
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 Frames des Compositors werden an Viz gesendet und müssen gemeinsam auf dem Bildschirm dargestellt werden. Dies geschieht in einer Aggregationsphase, in der sie in einen einzelnen, zusammengefassten Renderer-Frame umgewandelt werden. 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-Buffer 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 Frames des 3D-Kompositors, die dem Beispiel am Anfang dieses Beitrags entsprechen.
foo.com/index.html
surface: id=0- Renderpass 0:Zeichnen in die Ausgabe.
- 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.
- Zeichnen Sie Quads für den Kachelinhalt des
- Renderpass 1:
- Quad für die Oberflächendarstellung: mit der ID 2, gezeichnet mit Skalierungs- und Verschiebungstransformation.
- Renderpass-Zeichnen-Quad: Mit 3 Pixeln Weichzeichnung zeichnen und in Renderpass 0 zuschneiden.
- Renderpass 0:Zeichnen in die Ausgabe.
- Browser-Benutzeroberfläche: ID=1
- Renderpass 0:Zeichnen in die Ausgabe.
- Quads für die Browseroberfläche zeichnen (auch gekachelte)
- Renderpass 0:Zeichnen in die Ausgabe.
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.
- Zeichnen Sie Quadrate für den Inhalt des
- Renderpass 0:Zeichnen in die Ausgabe.
Illustrationen von Una Kravets