Von WebGL zu WebGPU

Beaufort
François Beaufort

Als WebGL-Entwickler sind Sie möglicherweise nicht nur einschüchternd, sondern auch aufgeregt, WebGPU zu verwenden. WebGL ist der Nachfolger von WebGL, der die Fortschritte moderner Grafik-APIs ins Web bringt.

Es ist beruhigend zu wissen, dass WebGL und WebGPU viele Kernkonzepte gemeinsam haben. Mit beiden APIs können Sie kleine Programme, sogenannte Shader, auf der GPU ausführen. WebGL unterstützt Vertex- und Fragment-Shader, während WebGPU auch Compute-Shader unterstützt. WebGL verwendet die OpenGL Shading Language (GLSL) und WebGPU die WebGPU Shading Language (WGSL). Obwohl sich die beiden Sprachen unterscheiden, sind die zugrunde liegenden Konzepte größtenteils gleich.

Vor diesem Hintergrund soll Ihnen dieser Artikel auf einige Unterschiede zwischen WebGL und WebGPU eingehen.

Globaler Status

WebGL hat einen großen globalen Status. Einige Einstellungen gelten für alle Renderingvorgänge, z. B. welche Texturen und Puffer gebunden werden. Sie legen diesen globalen Status fest, indem Sie verschiedene API-Funktionen aufrufen. Er bleibt wirksam, bis Sie ihn ändern. Der globale Status in WebGL ist eine große Fehlerquelle, da leicht vergessen werden kann, eine globale Einstellung zu ändern. Außerdem erschwert der globale Status die Codefreigabe, da Entwickler darauf achten müssen, den globalen Status nicht versehentlich auf eine Weise zu ändern, die andere Teile des Codes beeinflusst.

WebGPU ist eine zustandslose API und behält keinen globalen Zustand bei. Stattdessen wird das Konzept einer Pipeline verwendet, um den gesamten Renderingstatus zu kapseln, der in WebGL global war. Eine Pipeline enthält Informationen wie Zusammenführung, Topologie und Attribute, die verwendet werden sollen. Eine Pipeline ist unveränderlich. Wenn Sie einige Einstellungen ändern möchten, müssen Sie eine weitere Pipeline erstellen. WebGPU verwendet außerdem Befehlsencoder, um Befehle zusammenzufassen und in der Reihenfolge auszuführen, in der sie aufgezeichnet wurden. Dies ist beispielsweise bei der Schattenzuordnung nützlich, bei der die Anwendung in einem einzigen Durchlauf der Objekte mehrere Befehlsstreams aufzeichnen kann, einen für die Schattenkarte jedes Lichts.

Zusammenfassend lässt sich noch einmal sagen, dass das Erstellen robuster, zusammensetzbarer Bibliotheken und Anwendungen durch das globale Zustandsmodell von WebGL die Erstellung robuster, zusammensetzbarer Bibliotheken und Anwendungen erschwert und somit die Menge an Status deutlich reduziert hat, die Entwickler beim Senden von Befehlen an die GPU verfolgen mussten.

Nicht mehr synchronisieren

Bei GPUs ist es in der Regel ineffizient, Befehle synchron zu senden und darauf zu warten, da dies die Pipeline leeren und Blasen verursachen kann. Dies gilt insbesondere für WebGPU und WebGL, die eine Multi-Prozess-Architektur verwenden, bei der der GPU-Treiber in einem anderen Prozess als JavaScript ausgeführt wird.

In WebGL ist beispielsweise für den Aufruf von gl.getError() ein synchrones IPC vom JavaScript-Prozess zum GPU-Prozess und zurück erforderlich. Dies kann zu einem Infofeld auf der CPU-Seite führen, wenn die beiden Prozesse miteinander kommunizieren.

Um diese Blasen zu vermeiden, ist WebGPU vollständig asynchron konzipiert. Das Fehlermodell und alle anderen Vorgänge finden asynchron statt. Wenn Sie beispielsweise eine Textur erstellen, scheint der Vorgang sofort erfolgreich zu sein, auch wenn es sich bei der Textur tatsächlich um einen Fehler handelt. Sie können den Fehler nur asynchron erkennen. Dieses Design sorgt dafür, dass die prozessübergreifende Kommunikation blasenfrei ist und Anwendungen eine zuverlässige Leistung bieten.

Compute-Shader

Compute-Shader sind Programme, die auf der GPU ausgeführt werden, um allgemeine Berechnungen durchzuführen. Sie sind nur in WebGPU und nicht in WebGL verfügbar.

Im Gegensatz zu Vertex- und Fragment-Shadern sind sie nicht auf die Grafikverarbeitung beschränkt und können für eine Vielzahl von Aufgaben wie maschinelles Lernen, physikalische Simulation und wissenschaftliches Rechnen verwendet werden. Compute-Shader werden parallel von Hunderten oder sogar Tausenden von Threads ausgeführt, was sie für die Verarbeitung großer Datasets sehr effizient macht. Weitere Informationen zu GPU-Computing und weiteren Informationen finden Sie in diesem ausführlichen Artikel zu WebGPU.

Videoframe wird verarbeitet

Die Verarbeitung von Videoframes mit JavaScript und WebAssembly hat einige Nachteile: die Kosten für das Kopieren der Daten vom GPU- in den CPU-Speicher und die begrenzte Parallelität, die mit Workern und CPU-Threads erreicht werden kann. WebGPU hat diese Einschränkungen nicht. Sie eignet sich aufgrund der engen Integration in die WebCodecs API hervorragend für die Verarbeitung von Videobildern.

Das folgende Code-Snippet zeigt, wie Sie einen VideoFrame als externe Textur in eine WebGPU importieren und verarbeiten. Sie können diese Demo ausprobieren.

// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...

(function render() {
  const videoFrame = new VideoFrame(video);
  applyFilter(videoFrame);
  requestAnimationFrame(render);
})();

function applyFilter(videoFrame) {
  const texture = device.importExternalTexture({ source: videoFrame });
  const bindgroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: texture }],
  });
  // Finally, submit commands to GPU
}

Standardmäßiges Portabilität von Anwendungen

WebGPU erzwingt die Anfrage von limits. Standardmäßig gibt requestDevice() ein GPU-Gerät zurück, das möglicherweise nicht mit den Hardwarefunktionen des physischen Geräts übereinstimmt, sondern einen angemessenen und niedrigsten gemeinsamen Nenner aller GPUs. Da Entwickler die Gerätelimits anfordern müssen, sorgt WebGPU dafür, dass Anwendungen auf so vielen Geräten wie möglich ausgeführt werden.

Canvas-Handhabung

WebGL verwaltet das Canvas automatisch, nachdem Sie einen WebGL-Kontext erstellt und Kontextattribute wie alpha, antialias, colorSpace, depth, keepDrawingBuffer oder stencil angegeben haben.

Bei WebGPU müssen Sie den Canvas hingegen selbst verwalten. Um Antialiasing in WebGPU zu erreichen, würden Sie beispielsweise eine Multisample-Textur erstellen und mit ihr rendern. Anschließend würden Sie die Multisample-Textur in eine normale Textur auflösen und diese Textur auf das Canvas zeichnen. Durch diese manuelle Verwaltung können Sie von einem einzigen GPUDevice-Objekt aus beliebig viele Canvases ausgeben. Im Gegensatz dazu kann mit WebGL nur ein Kontext pro Canvas erstellt werden.

Sehen Sie sich die WebGPU Multiple Canvases-Demo an.

Beachte bitte, dass die Anzahl der WebGL-Canvases pro Seite in Browsern derzeit begrenzt ist. Derzeit können in Chrome und Safari nur bis zu 16 WebGL-Canvases gleichzeitig verwendet werden. In Firefox können bis zu 200 Canvases erstellt werden. Andererseits ist die Anzahl der WebGPU-Canvases pro Seite unbegrenzt.

Screenshot mit maximaler Anzahl von WebGL-Canvases in den Browsern Safari, Chrome und Firefox
Die maximale Anzahl von WebGL-Canvases in Safari, Chrome und Firefox (von links nach rechts) – Demo.

Hilfreiche Fehlermeldungen

WebGPU bietet einen Aufrufstack für jede von der API zurückgegebene Nachricht. So sehen Sie schnell, an welcher Stelle im Code der Fehler aufgetreten ist. Dies ist bei der Fehlersuche und -behebung hilfreich.

WebGPU-Fehlermeldungen sind nicht nur ein Aufrufstack, sondern auch leicht zu verstehen und zu beheben. Die Fehlermeldungen enthalten in der Regel eine Beschreibung des Fehlers und Vorschläge zur Fehlerbehebung.

Mit WebGPU können Sie für jedes WebGPU-Objekt ein benutzerdefiniertes label bereitstellen. Dieses Label wird dann vom Browser in GPU-Fehlermeldungen, Konsolenwarnungen und Browser-Entwicklertools verwendet.

Von Namen zu Indexen

In WebGL sind viele Dinge durch Namen verbunden. Sie können beispielsweise eine einheitliche Variable namens myUniform in GLSL deklarieren und ihren Standort mit gl.getUniformLocation(program, 'myUniform') abrufen. Dies ist praktisch, da Sie einen Fehler erhalten, wenn Sie den Namen der einheitlichen Variablen falsch eingeben.

Bei WebGPU ist alles vollständig durch Byte-Offset oder Index verbunden (oft als Standort bezeichnet). Es liegt in Ihrer Verantwortung, die Speicherorte des Codes in WGSL und JavaScript zu synchronisieren.

Mipmap-Generierung

In WebGL können Sie den 0-mip einer Textur erstellen und dann gl.generateMipmap() aufrufen. Alle anderen Mip-Ebenen werden dann von WebGL generiert.

In WebGPU müssen Sie Mipmaps selbst generieren. Hierfür gibt es keine integrierte Funktion. Weitere Informationen zu dieser Entscheidung finden Sie in der Diskussion zu den Spezifikationen. Sie können praktische Bibliotheken wie webgpu-utils verwenden, um Mipmaps zu generieren, oder sich selbst damit vertraut machen.

Speicherpuffer und Speichertexturen

Einheitliche Zwischenspeicher werden sowohl von WebGL als auch von WebGPU unterstützt und ermöglichen Ihnen, konstante Parameter begrenzter Größe an Shader zu übergeben. Speicherpuffer, die einheitlichen Puffern sehr ähnlich sehen, werden nur von WebGPU unterstützt und sind leistungsfähiger und flexibler als einheitliche Puffer.

  • Daten zu Speicherpuffern, die an Shader übergeben werden, können viel größer sein als einheitliche Zwischenspeicher. Obwohl einheitliche Pufferbindungen laut Spezifikation bis zu 64 KB groß sein können (siehe maxUniformBufferBindingSize) , beträgt die maximale Größe einer Speicherpufferbindung in WebGPU mindestens 128 MB (siehe maxStorageBufferBindingSize).

  • Speicherpuffer sind beschreibbar und unterstützen einige atomare Vorgänge, während einheitliche Puffer nur schreibgeschützt sind. Dadurch können neue Algorithmenklassen implementiert werden.

  • Speicherpufferbindungen unterstützen Arrays mit Laufzeitgröße und sorgen so für flexiblere Algorithmen. Im Shader müssen einheitliche Pufferarraygrößen angegeben werden.

Speichertexturen werden nur in WebGPU unterstützt und sind für Texturen, was Speicherpuffer sind, für einheitliche Puffer. Sie sind flexibler als normale Texturen und unterstützen Schreib- und Lesevorgänge mit zufälligem Zugriff.

Puffer- und Texturänderungen

In WebGL können Sie einen Puffer oder eine Textur erstellen und dann beispielsweise die Größe mit gl.bufferData() bzw. gl.texImage2D() ändern.

In WebGPU sind Puffer und Texturen unveränderlich. Das bedeutet, dass Sie ihre Größe, ihre Verwendung oder ihr Format nicht mehr ändern können, nachdem sie erstellt wurden. Sie können nur deren Inhalt ändern.

Unterschiede bei der Raumkonvention

In WebGL liegt der Abstand zwischen -1 und 1. Bei WebGPU liegt der Clipbereich zwischen 0 und 1. Das bedeutet, dass Objekte mit einem z-Wert von 0 am weitesten der Kamera entfernt sind, während Objekte mit einem z-Wert von 1 am weitesten entfernt sind.

Abbildung von Z-Clip-Bereichen in WebGL und WebGPU.
Z-Clipspace-Bereiche in WebGL und WebGPU.

WebGL verwendet die OpenGL-Konvention, wobei die Y-Achse nach oben und die Z-Achse in Richtung des Betrachters zeigt. WebGPU verwendet die Metal-Konvention, bei der die Y-Achse nach unten und die Z-Achse außerhalb des Bildschirms liegt. Beachten Sie, dass die Richtung der Y-Achse in Framebuffer-Koordinaten, Viewport-Koordinaten und Fragment-/Pixel-Koordinaten nach unten liegt. Im Clipbereich ist die Richtung der Y-Achse wie bei WebGL weiterhin oben.

Danksagungen

Vielen Dank an Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell und Rachel Andrew für die Rezension dieses Artikels.

Außerdem empfehle ich WebGPUFundamentals.org, um die Unterschiede zwischen WebGPU und WebGL genauer zu untersuchen.