Hier erfahren Sie, wie die Komponente RenderingNG und wie die Rendering-Pipeline sie durchläuft.
Beginnend auf der obersten Ebene sind die Aufgaben des Renderings:
- Rendern Sie Inhalte auf dem Bildschirm in Pixel.
- Visuelle Effekte animieren, mit denen die Inhalte von einem Zustand zum anderen geändert werden können.
- Je nach Eingabe wird gescrollt.
- Eingaben effizient an die richtigen Orte weiterleiten, damit Entwicklerskripts und andere Subsysteme antworten können
Der Inhalt, der gerendert werden soll, besteht aus einer Baumstruktur von Frames für jeden Browsertab sowie Browseroberfläche. Ein Strom von Roheingaben über Touchscreens Mäuse, Tastaturen und andere Hardwaregeräte.
Jeder Frame enthält Folgendes:
- DOM-Status
- CSS
- Canvases
- Externe Ressourcen wie Bilder, Videos, Schriftarten und SVG
Ein Frame ist ein HTML-Dokument und seine URL. Eine Webseite, die in einem Browsertab geladen wird, hat einen Frame auf oberster Ebene, für jeden iFrame, der im Dokument auf oberster Ebene enthalten ist, und ihre rekursiven iFrame-Nachfolgerelemente.
Ein visueller Effekt ist ein grafischer Vorgang, der auf eine Bitmap angewendet wird. wie „Scrollen“, „Transformieren“, „Clips“, „Filtern“, „Deckkraft“ oder „Überblenden“.
Architekturkomponenten
Beim RenderingNG sind diese Aufgaben logisch auf mehrere Phasen und Code aufgeteilt Komponenten. Die Komponenten befinden sich in verschiedenen CPU-Prozessen, -Threads und in diesen Threads untergeordnet werden. Beide tragen entscheidend dazu bei, Zuverlässigkeit, skalierbare Leistung und Erweiterbarkeit für alle Webinhalte.
Struktur der Rendering-Pipeline
<ph type="x-smartling-placeholder">Das Rendern erfolgt in einer Pipeline mit einer Reihe von erstellten Phasen und Artefakten. auf dem Weg zu mehr Erfolg. Jede Phase steht für Code, der eine genau definierte Aufgabe innerhalb zu verbessern. Die Artefakte sind Datenstrukturen die Ein- und Ausgaben der Phasen sind.
Die Phasen sind:
- Animieren:Ändern Sie berechnete Stile und passen Sie Eigenschaftsstrukturen im Laufe der Zeit basierend auf deklarativen Zeitachsen an.
- Stil:CSS wird auf das DOM angewendet und berechnete Stile erstellt.
- Layout:Bestimmen Sie die Größe und Position der DOM-Elemente auf dem Bildschirm. und erstellen Sie die unveränderliche Fragmentstruktur.
- Pre-paint:Berechnen Sie Property-Strukturen und entwerten Vorhandene Anzeigelisten und GPU-Texturkacheln nach Bedarf
- Scrollen:Aktualisieren Sie den Scroll-Offset von Dokumenten und scrollbaren DOM-Elementen, indem Sie Eigenschaftenstrukturen ändern.
- Paint:Eine Anzeigeliste berechnen, in der beschrieben wird, wie GPU-Texturkacheln aus dem DOM gerastert werden.
- Commit:Kopiert Attributstrukturen und die Anzeigeliste in den zusammengesetzten Thread.
- Ebenen erstellen:Hiermit wird die Anzeigeliste zur unabhängigen Rasterung und Animation in eine Liste mit zusammengesetzten Ebenen unterteilt.
- Raster-, Decodierungs- und Paint-Worklets: Wandeln Sie Displaylisten, codierte Bilder bzw. Paint-Worklet-Code in GPU-Texturkacheln
- Aktivieren:Erstellen Sie einen Compositor-Frame, der festlegt, wie GPU-Kacheln gezeichnet und positioniert werden, zusammen mit visuellen Effekten.
- Aggregieren:Kombiniert zusammengesetzte Frames aus allen sichtbaren zusammengesetzten Frames zu einem einzigen globalen zusammengesetzten Frame.
- Draw:Führt den aggregierten Compositor-Frame auf der GPU aus, um Pixel auf dem Bildschirm zu erstellen.
Phasen der Rendering-Pipeline können übersprungen werden, wenn sie nicht benötigt werden. Bei Animationen mit visuellen Effekten und beim Scrollen können beispielsweise Layout, Voranstrich und Paint übersprungen werden. Aus diesem Grund sind Animationen und Scrollen mit gelben und grünen Punkten im Diagramm gekennzeichnet. Wenn Layout, Voranstrich und Farbe für visuelle Effekte übersprungen werden können, können sie vollständig im Compositor-Thread ausgeführt werden und den Hauptthread überspringen.
Das UI-Rendering des Browsers wird hier nicht direkt dargestellt. als vereinfachte Version derselben Pipeline (und in der Implementierung wird ein Großteil des Codes verwendet.) Video (auch nicht direkt abgebildet) wird in der Regel mit unabhängigem Code gerendert, der Frames in GPU-Texturkacheln decodiert die dann in Compositor-Frames und den Zeichenschritt eingesteckt werden.
Prozess- und Thread-Struktur
CPU-Prozesse
Die Verwendung mehrerer CPU-Prozesse sorgt für Leistungs- und Sicherheitsisolierung zwischen Websites und vom Browserstatus, sowie Stabilität und Sicherheitsisolierung von GPU-Hardware.
- Der Renderingprozess rendert, animiert sie, scrollt und leitet die Eingabe für eine aus einer Kombination aus einer Website und einem Tab. Es gibt mehrere Renderingprozesse.
- Durch den Browserprozess werden Eingaben für die Browser-Benutzeroberfläche gerendert, animiert und weitergeleitet. (einschließlich Adressleiste, Tab-Titel und Symbolen) und Routen alle verbleibenden in den entsprechenden Rendering-Prozess ein. Es gibt einen Browser-Prozess.
- Der Viz-Prozess aggregiert die Zusammensetzung aus mehreren Renderingprozessen sowie den Browserprozess. Sie rastert und zeichnet mit GPU. Es gibt einem Visualisierungsprozess.
Unterschiedliche Websites werden immer angezeigt in verschiedenen Renderingprozessen.
Mehrere Browser-Tabs oder -Fenster derselben Website werden normalerweise in unterschiedlichen Renderings angezeigt es sei denn, die Tabs haben einen Bezug zueinander, wie z. B. Öffnen des anderen. Bei hoher Speicherauslastung in Chromium legt Chromium möglicherweise mehrere Tabs fest von derselben Website in denselben Renderingprozess, auch wenn sie nicht miteinander verbunden sind.
Über einen einzigen Browsertab Frames von verschiedenen Websites befinden sich immer in unterschiedlichen Rendering-Prozessen. Frames derselben Website befinden sich jedoch immer im selben Renderingprozess. Aus der Perspektive des Renderings Der wichtige Vorteil mehrerer Renderingprozesse besteht darin, dass websiteübergreifende iFrames und Tabs eine Leistungsisolierung voneinander zu lernen. Darüber hinaus können Ursprünge eine noch mehr Isolation aktivieren.
Es gibt genau einen Visualisierungsprozess für alle Chromium-Produkte, da es normalerweise nur eine GPU und einen Bildschirm zum Zeichnen.
Die Trennung von Viz in einen eigenen Prozess ist gut für die Stabilität bei Fehlern in GPU-Treiber oder -Hardware Sie eignet sich auch für die Sicherheitsisolierung, Das ist wichtig für GPU-APIs wie Vulkan und Sicherheit im Allgemeinen.
Da der Browser viele Tabs und Fenster haben kann, und alle haben Browser-UI-Pixel zum Zeichnen, fragen Sie sich vielleicht, warum es genau einen Browser-Prozess gibt. Der Grund ist, dass jeweils nur einer von ihnen im Fokus ist. Tatsächlich werden nicht sichtbare Browser-Tabs größtenteils deaktiviert und es wird ihr gesamter GPU-Arbeitsspeicher gelöscht. Komplexere Funktionen für das Rendering der Browser-Benutzeroberfläche werden jedoch in Rendering-Prozessen (sogenannte WebUI) enthält. Dies geschieht nicht aus Gründen der Leistungsisolation, um die Nutzerfreundlichkeit des Web-Rendering-Moduls von Chromium zu nutzen.
Auf älteren Android-Geräten: Der Rendering- und Browserprozess werden bei Verwendung in einer WebView gemeinsam genutzt. Dies gilt nicht für Chromium unter Android im Allgemeinen, nur für WebView. Bei WebView wird der Browserprozess auch mit der Einbettungs-App geteilt. und WebView hat nur einen Renderprozess.
Manchmal gibt es auch einen Dienstprogrammprozess zum Decodieren geschützter Videoinhalte. Dieser Prozess ist in den vorherigen Diagrammen nicht dargestellt.
Unterhaltungen
Threads sorgen auch bei langsamen Aufgaben für eine Leistungsisolierung und Reaktionsfähigkeit. Pipelineparallelisierung und mehrfacher Zwischenspeicherung.
- Im Hauptthread werden Skripts, die Rendering-Ereignisschleife, der Dokumentlebenszyklus
Treffertests, Skript-Ereignisweiterleitung und Parsen von HTML, CSS und anderen Datenformaten.
- Hilfsprogramme für den Hauptthread führen Aufgaben wie das Erstellen von Bild-Bitmaps und Blobs aus, die eine Codierung oder Decodierung erfordern.
- Web Worker und eine Rendering-Ereignisschleife für OffscreenCanvas.
- Der Compositor-Thread verarbeitet Eingabeereignisse,
Scrollen und Animationen
von Webinhalten durchführt,
die optimale Schichtung von Webinhalten,
und koordinieren Bilddecodierungen, Farb-Worklets und Rasteraufgaben.
- Compositor Thread Helpers zur Koordination von Viz-Raster-Aufgaben Aufgaben zur Bilddecodierung, Paint-Worklets und Fallback-Raster ausführen.
- Medien-, Demuxer- oder Audioausgabe-Threads decodieren, Video- und Audiostreams verarbeiten und synchronisieren. Denken Sie daran, dass das Video parallel zur Haupt-Rendering-Pipeline ausgeführt wird.
Die Trennung von Haupt- und Zusammensetzungsthreads ist für Leistungsisolation und Scrollen im Hauptthread.
Es gibt nur einen Hauptthread pro Renderingprozess. auch wenn mehrere Tabs oder Frames von derselben Website zum selben Prozess führen können. Es besteht jedoch eine Leistungsisolation bei der Arbeit in verschiedenen Browser-APIs. Zum Beispiel wird die Generierung von Bild-Bitmaps und Blobs in der Canvas API in einem Helper-Thread des Hauptthreads ausgeführt.
Ebenso gibt es nur einen Compositor-Thread pro Renderingprozess. Im Allgemeinen ist es kein Problem, dass es nur eins gibt. weil all die wirklich teuren Vorgänge am Compositor-Thread an Compositor-Worker-Threads oder an den Viz-Prozess delegiert, Diese Arbeit kann parallel mit Eingaberouting, Scrollen oder Animation erfolgen. Compositor-Worker-Threads koordinieren Aufgaben, die im Viz-Prozess ausgeführt werden, aber GPU-Beschleunigung überall aus Gründen außerhalb der Kontrolle von Chromium fehlschlagen kann, wie z. B. Treiberfehler. In diesen Fällen führt der Worker-Thread die Arbeit im Fallback-Modus auf der CPU aus.
Die Anzahl der zusammengesetzten Worker-Threads hängt von den Funktionen des Geräts ab. Zum Beispiel verwenden Computer in der Regel mehr Threads, da sie mehr CPU-Kerne und einen geringeren Akkuverbrauch als Mobilgeräte haben. Dies ist ein Beispiel für das Hoch- und Herunterskalieren.
Die Renderingprozess-Threading-Architektur besteht aus drei verschiedenen Optimierungsmuster:
- Hilfsthreads: Senden Sie Unteraufgaben mit langer Ausführungszeit an zusätzliche Threads, um den Überblick zu behalten. im übergeordneten Thread auf andere, gleichzeitige Anfragen reagieren. Im Hauptthread Hilfs- und compositor-Hilfsthreads sind gute Beispiele für diese Technik.
- Mehrfache Zwischenspeicherung: beim Rendern von neuem Content zuvor gerenderten Content anzeigen, um den Latenz beim Rendern. Der Compositor-Thread verwendet diese Technik.
- Pipeline-Parallelisierung:Rendering-Pipeline an mehreren Stellen ausführen gleichzeitig. So können Scrollen und Animationen schnell sein, auch wenn ein Aktualisierung des Hauptthreads erfolgt, können Scrollen und Animationen parallel ausgeführt werden.
Browserprozess
- Der Rendering- und Compositing-Thread reagiert auf Eingaben in der Browser-UI. leitet andere Eingaben an den richtigen Renderingprozess weiter; Layout und Farbdarstellung der Browser-Benutzeroberfläche.
- Helfer für Rendering und Zusammensetzung von Threads Bilddecodierungsaufgaben und Fallback-Raster- oder -Decodierungsaufgaben ausführen.
Der Browserprozess zum Rendern und Aufbau-Thread ist ähnlich. auf den Code und die Funktionalität eines Renderingprozesses, mit der Ausnahme, dass der Hauptthread und der Compositor-Thread zu einem zusammengeführt werden. In diesem Fall ist nur ein Thread erforderlich, Leistungsisolierung von Aufgaben im langen Hauptthread denn es gibt keine.
Visualisierungsprozess
- Die Raster des GPU-Hauptthreads zeigen Listen und Videoframes in GPU-Texturkacheln an. und zeichnet Compositor-Frames auf den Bildschirm.
- Der Compositor-Thread aggregiert und optimiert die Zusammensetzung aus jedem Renderingprozess. und den Browserprozess in einem einzelnen zusammengesetzten Frame für die Präsentation auf dem Bildschirm.
Raster und Zeichnen laufen sich in der Regel auf demselben Thread ab, da beide GPU-Ressourcen nutzen, und es ist schwierig, die GPU zuverlässig für mehrere Threads zu nutzen, (Ein einfacherer Multithread-Zugriff auf die GPU ist eine Motivation für die Entwicklung des neuen Vulkan-Standard). Für Android WebView gibt es einen separaten Renderingthread auf Betriebssystemebene zum Zeichnen weil WebViews in eine native App eingebettet sind. In Zukunft wird es wahrscheinlich einen solchen Thread auf anderen Plattformen geben.
Der Display-Zusammengesetzte befindet sich in einem anderen Thread, da er jederzeit responsiv sein muss. und nicht durch eine mögliche Verlangsamungsquelle im GPU-Hauptthread blockiert wird. Eine Ursache für die Verlangsamung im GPU-Hauptthread sind Aufrufe von Nicht-Chromium-Code, wie anbieterspezifische GPU-Treiber, die nur schwer vorhersagen können.
Komponentenstruktur
Innerhalb jedes Haupt- oder Compositor-Threads jedes Renderingprozesses gibt es logische Softwarekomponenten, die strukturiert miteinander interagieren.
Renderingprozess – Hauptthreadkomponenten
Im Blink-Renderer:
- Das lokale Framestrukturfragment stellt die Baumstruktur lokaler Frames und das DOM innerhalb der Frames dar.
- Die Komponente DOM und Canvas APIs enthält Implementierungen dieser APIs.
- Der Runner für den Dokumentlebenszyklus führt die Rendering-Pipelineschritte bis zum Commit-Schritt aus.
- Die Komponente Eingabeereignistreffertests und -verteilung führt Treffertests aus, um Herausfinden, auf welches DOM-Element ein Ereignis ausgerichtet ist, und das das Eingabeereignis ausführt Algorithmen und Standardverhalten.
Der Planer und Runner der Rendering-Ereignisschleife entscheidet, was für das Ereignis ausgeführt wird. und wann. Das Rendering wird so geplant, dass es in einem Rhythmus erfolgt, der mit dem Gerät übereinstimmt Display.
Lokale Frame Tree-Fragmente sind etwas kompliziert. Denken Sie daran, dass eine Frame-Struktur rekursiv die Hauptseite und ihre untergeordneten iFrames ist. Ein Frame ist lokal für einen Renderingprozess, wenn er dabei gerendert wird. und ansonsten remote.
Sie können sich die Frames entsprechend ihrem Renderingprozess kolorieren. Im vorherigen Bild sind die grünen Kreise alle Frames in einem Renderingprozess. die orangefarbenen in einer Sekunde und der blaue in einer dritten.
Ein lokales Framebaumfragment ist eine verbundene Komponente derselben Farbe in einer Framestruktur. Im Bild sind vier lokale Rahmenbäume zu sehen: zwei für Standort A, eine für Standort B und eine für Standort C. Jede lokale Frame-Struktur erhält eine eigene Blink-Renderer-Komponente. Der Blink-Renderer einer lokalen Frame-Struktur kann sich im selben Renderingprozess befinden oder nicht. wie bei anderen lokalen Rahmenbäumen. Sie wird wie oben beschrieben durch die Auswahl der Renderingprozesse bestimmt.
Aufbau des Compositor-Threads des Renderingprozesses
Die zusammengesetzten Komponenten des Renderingprozesses umfassen:
- Data-Handler, der eine Liste mit zusammengesetzten Ebenen verwaltet, Listen und Property-Strukturen anzeigt
- Ein Lebenszyklus-Runner, der die Animations-, Scroll-, Composite-, Raster- und decodieren und aktivieren Sie Schritte der Rendering-Pipeline. Denken Sie daran, dass Animieren und Scrollen sowohl im Hauptthread als auch im Compositor vorkommen können.
- Ein Eingabe- und Treffertest-Handler führt eine Eingabeverarbeitung und Treffertests bei der Auflösung von zusammengesetzten Ebenen durch. um festzustellen, ob Scrollbewegungen auf dem Compositor-Thread ausgeführt werden können, und auf welchen Renderingprozess Treffertests ausgerichtet sein sollen.
Beispielarchitektur in der Praxis
In diesem Beispiel gibt es drei Tabs:
Tab 1: foo.com
<html>
<iframe id=one src="foo.com/other-url"></iframe>
<iframe id=two src="bar.com"></iframe>
</html>
Tab 2: bar.com
<html>
…
</html>
Tab 3: baz.com
html
<html>
…
</html>
Der Prozess, der Thread und die Komponentenstruktur für diese Tabs sehen so aus:
Sehen wir uns ein Beispiel für jede der vier Hauptaufgaben des Renderings an. Zur Erinnerung:
- Rendern Sie Inhalte auf dem Bildschirm in Pixel.
- Animieren Sie visuelle Effekte auf die Inhalte von einem Zustand zum anderen.
- Je nach Eingabe wird gescrollt.
- Leiten Sie Eingaben effizient an die richtigen Orte weiter, damit Entwicklerskripts und andere Subsysteme antworten können.
So rendern Sie das geänderte DOM für Tab 1:
- Ein Entwicklerskript ändert das DOM im Renderingprozess für foo.com.
- Der Blink-Renderer teilt dem Compositor mit, dass ein Rendering erforderlich ist.
- Der Compositor teilt Viz mit, dass ein Rendering erfolgen muss.
- Viz signalisiert dem Compositor den Beginn des Renderings.
- Der Compositor leitet das Startsignal an den Blink-Renderer weiter.
- Der Hauptthread-Ereignisschleifen-Runner führt den Dokumentlebenszyklus aus.
- Der Hauptthread sendet das Ergebnis an den zusammengesetzten Thread.
- Der Ausführungsschleife der Ereignisschleife des zusammengesetzten Ereignisses führt den Compositing-Lebenszyklus aus.
- Alle Rasteraufgaben werden für die Rasterung an Viz gesendet. Häufig gibt es mehrere dieser Aufgaben.
- Viz-Rasterinhalte auf der GPU.
- Viz bestätigt den Abschluss der Rasteraufgabe. Hinweis: Chromium wartet häufig nicht, bis das Raster abgeschlossen ist, und verwendet stattdessen ein sogenanntes Synchronisierungstoken das gelöst werden muss, vor Ausführung von Schritt 15 durch Rasteraufgaben.
- Ein zusammengesetzter Frame wird an Viz gesendet.
- Viz aggregiert die Compositor-Frames für den foo.com-Renderingprozess, den iFrame-Renderingprozess von bar.com und die Benutzeroberfläche des Browsers.
- Visualisierung plant ein Remis.
- Viz zeichnet den aggregierten Compositor-Frame auf den Bildschirm.
So animieren Sie einen CSS-Transformationsübergang auf Tab 2:
- Der Compositor-Thread für den Renderingprozess von bar.com markiert eine Animation. in seiner zusammengesetzten Ereignisschleife, indem die vorhandenen Eigenschaftsbäume verändert werden. Anschließend wird der Compositor-Lebenszyklus noch einmal ausgeführt. (Es können Aufgaben Raster- und Decodierungsaufgaben ausgeführt werden, die hier nicht dargestellt sind.)
- Ein zusammengesetzter Frame wird an Viz gesendet.
- Viz aggregiert die Compositor-Frames für den foo.com-Renderingprozess, den bar.com-Renderingprozess und die Browser-Benutzeroberfläche.
- Visualisierung plant ein Remis.
- Viz zeichnet den aggregierten Compositor-Frame auf den Bildschirm.
So scrollen Sie auf Tab 3 durch die Webseite:
- Im Browserprozess wird eine Folge von
input
-Ereignissen (Maus, Berührung oder Tastatur) erfasst. - Jedes Ereignis wird an den zusammengesetzten Thread des Renderingprozesses von baz.com weitergeleitet.
- Der Compositor bestimmt, ob der Hauptthread Informationen zu dem Ereignis benötigt.
- Das Ereignis wird bei Bedarf an den Hauptthread gesendet.
- Der Hauptthread löst
input
-Event-Listener aus (pointerdown
,touchstar
,pointermove
,touchmove
oderwheel
) um festzustellen, ob ListenerpreventDefault
für das Ereignis aufrufen. - Der Hauptthread gibt zurück, ob
preventDefault
an den Compositor aufgerufen wurde. - Andernfalls wird das Eingabeereignis an den Browserprozess zurückgesendet.
- Der Browserprozess konvertiert sie in eine Scroll-Geste, indem sie mit anderen aktuellen Ereignissen kombiniert wird.
- Die Scroll-Geste wird noch einmal an den Compositor-Thread von baz.com gesendet,
- Dort wird das Scrollen angewendet und der Compositor-Thread für „bar.com“.
Render-Prozess markiert eine Animation in seiner Compositor-Ereignisschleife.
Dadurch wird dann der Scroll-Offset in den Eigenschaftenstrukturen mutiert und der Compositor-Lebenszyklus erneut ausgeführt.
Außerdem wird damit der Hauptthread angewiesen, ein
scroll
-Ereignis auszulösen (hier nicht dargestellt). - Ein zusammengesetzter Frame wird an Viz gesendet.
- Viz aggregiert die Compositor-Frames für den foo.com-Renderingprozess, den Renderingprozess von bar.com und die Benutzeroberfläche des Browsers.
- Visualisierung plant ein Remis.
- Viz zeichnet den aggregierten Compositor-Frame auf den Bildschirm.
So leiten Sie ein click
-Ereignis über einen Hyperlink in iFrame 2 auf Tab 1 weiter:
- Ein
input
-Ereignis (Maus, Berührung oder Tastatur) wird im Browserprozess eingefügt. Es wird ein ungefährer Treffertest durchgeführt. um zu bestimmen, dass der iFrame-Renderingprozess von bar.com den Klick empfangen und dorthin gesendet wird. - Der Compositor-Thread für bar.com leitet das
click
-Ereignis an den Hauptthread weiter. für bar.com und plant eine Rendering-Ereignisschleife für die Verarbeitung. - Der Eingabeereignisprozessor für den Hauptthread von bar.com testet, um zu ermitteln,
Ein Klick auf das DOM-Element im iFrame löst ein
click
-Ereignis aus, damit Skripts diese beobachten. WennpreventDefault
nicht angezeigt wird, wird der Hyperlink aufgerufen. - Beim Laden der Zielseite des Hyperlinks wird der neue Status gerendert, mit Schritten, die dem „Geändertes DOM rendern“ ähneln, aus dem vorherigen Beispiel. (Die nachfolgenden Änderungen sind hier nicht enthalten.)
Tipp
Es kann viel Zeit dauern, sich zu merken und zu verinnerlichen, wie das Rendering funktioniert.
Die wichtigste Erkenntnis ist, dass die Rendering-Pipeline durch sorgfältige zwischen Modularisierung und Detailgenauigkeit eigenständigen Komponenten. Diese Komponenten wurden dann parallel Prozesse und Threads zu optimieren, skalierbare Leistung und Erweiterbarkeit möglich.
Jede Komponente spielt eine entscheidende Rolle bei der Umsetzung der Leistung und der Funktionen moderne Web-Apps.
Lesen Sie weiter über die Schlüsseldatenstrukturen. die für RenderingNG genauso wichtig sind wie Codekomponenten.
Illustrationen von Una Kravets