Veröffentlicht am 20. Juli 2023, zuletzt aktualisiert am 27. August 2025
Für Webentwickler ist WebGPU eine Webgrafik-API, die einheitlichen und schnellen Zugriff auf GPUs bietet. WebGPU macht moderne Hardwarefunktionen verfügbar und ermöglicht Rendering- und Rechenvorgänge auf einer GPU, ähnlich wie Direct3D 12, Metal und Vulkan.
Das stimmt zwar, aber die Geschichte ist unvollständig. WebGPU ist das Ergebnis einer Zusammenarbeit von großen Unternehmen wie Apple, Google, Intel, Mozilla und Microsoft. Einige erkannten, dass WebGPU mehr als nur eine JavaScript-API sein könnte, nämlich eine plattformübergreifende Grafik-API für Entwickler in verschiedenen Ökosystemen, die nicht nur für das Web gedacht ist.
Um den primären Anwendungsfall zu erfüllen, wurde in Chrome 113 eine JavaScript API eingeführt. Es wurde jedoch ein weiteres wichtiges Projekt entwickelt: die C-API webgpu.h. In dieser C-Headerdatei sind alle verfügbaren Prozeduren und Datenstrukturen von WebGPU aufgeführt. Sie dient als plattformunabhängige Hardwareabstraktionsschicht, mit der Sie plattformspezifische Anwendungen erstellen können, indem sie eine einheitliche Schnittstelle für verschiedene Plattformen bietet.
In diesem Dokument erfahren Sie, wie Sie eine kleine C++-Anwendung mit WebGPU schreiben, die sowohl im Web als auch auf bestimmten Plattformen ausgeführt wird. Spoiler: Sie erhalten dasselbe rote Dreieck, das in einem Browserfenster und einem Desktopfenster angezeigt wird, und müssen nur minimale Anpassungen an Ihrem Code vornehmen.

Wie funktioniert das?
Die fertige Anwendung finden Sie im Repository WebGPU cross-platform app.
Die App ist ein minimalistisches C++-Beispiel, das zeigt, wie WebGPU verwendet werden kann, um Desktop- und Web-Apps aus einer einzigen Codebasis zu erstellen. Intern wird webgpu.h von WebGPU als plattformunabhängige Hardwareabstraktionsschicht über einen C++-Wrapper namens webgpu_cpp.h verwendet.
Im Web wird die App mit emdawnwebgpu (Emscripten Dawn WebGPU) erstellt, das Bindungen enthält, die webgpu.h auf der JavaScript API implementieren. Auf bestimmten Plattformen wie macOS oder Windows kann dieses Projekt mit Dawn, der plattformübergreifenden WebGPU-Implementierung von Chromium, erstellt werden. Es gibt auch wgpu-native, eine Rust-Implementierung von webgpu.h, die in diesem Dokument jedoch nicht verwendet wird.
Jetzt starten
Zuerst benötigen Sie einen C++-Compiler und CMake, um plattformübergreifende Builds auf standardisierte Weise zu verarbeiten. Erstellen Sie in einem dedizierten Ordner eine main.cpp
-Quelldatei und eine CMakeLists.txt
-Build-Datei.
Die Datei main.cpp
sollte vorerst eine leere main()
-Funktion enthalten.
int main() {}
Die Datei CMakeLists.txt
enthält grundlegende Informationen zum Projekt. In der letzten Zeile wird angegeben, dass der ausführbare Name „app“ ist und der Quellcode main.cpp
lautet.
cmake_minimum_required(VERSION 3.22) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
Führen Sie cmake -B build
aus, um Build-Dateien in einem Unterordner „build/“ zu erstellen, und cmake --build build
, um die App zu erstellen und die ausführbare Datei zu generieren.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
Die App wird ausgeführt, aber es gibt noch keine Ausgabe, da Sie eine Möglichkeit benötigen, Dinge auf dem Bildschirm darzustellen.
Dawn abrufen
Um das Dreieck zu zeichnen, können Sie Dawn verwenden, die plattformübergreifende WebGPU-Implementierung von Chromium. Dazu gehört die GLFW-C++-Bibliothek zum Zeichnen auf dem Bildschirm. Eine Möglichkeit, Dawn herunterzuladen, besteht darin, es als Git-Submodul zu Ihrem Repository hinzuzufügen. Mit den folgenden Befehlen wird sie in einem Unterordner „dawn/“ abgerufen.
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Hängen Sie dann Folgendes an die Datei CMakeLists.txt
an:
- Mit der CMake-Option
DAWN_FETCH_DEPENDENCIES
werden alle Dawn-Abhängigkeiten abgerufen. - Mit der CMake-Option
DAWN_BUILD_MONOLITHIC_LIBRARY
werden alle Dawn-Komponenten in einer einzigen Bibliothek zusammengefasst. - Der Unterordner
dawn/
ist im Ziel enthalten. - Ihre App hängt von den Zielen
webgpu_dawn
,webgpu_glfw
undglfw
ab, damit Sie sie später in der Dateimain.cpp
verwenden können.
…
set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)
Fenster öffnen
Jetzt, da Dawn verfügbar ist, können Sie mit GLFW Elemente auf dem Bildschirm zeichnen. Diese Bibliothek ist in webgpu_glfw
enthalten und ermöglicht es Ihnen, plattformunabhängigen Code für die Fensterverwaltung zu schreiben.
Wenn Sie ein Fenster mit dem Namen „WebGPU window“ und einer Auflösung von 512 × 512 öffnen möchten, aktualisieren Sie die Datei main.cpp
wie unten beschrieben. Beachten Sie, dass glfwWindowHint()
hier verwendet wird, um keine bestimmte Initialisierung der Grafik-API anzufordern.
#include <GLFW/glfw3.h>
const uint32_t kWidth = 512;
const uint32_t kHeight = 512;
void Start() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window =
glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// TODO: Render a triangle using WebGPU.
}
}
int main() {
Start();
}
Wenn Sie die App neu erstellen und wie zuvor ausführen, wird jetzt ein leeres Fenster angezeigt. Du machst Fortschritte!

GPU-Gerät abrufen
In JavaScript ist navigator.gpu
der Einstiegspunkt für den Zugriff auf die GPU. In C++ müssen Sie manuell eine wgpu::Instance
-Variable erstellen, die für denselben Zweck verwendet wird. Deklarieren Sie instance
der Einfachheit halber oben in der Datei main.cpp
und rufen Sie wgpu::CreateInstance()
in Init()
auf.
#include <webgpu/webgpu_cpp.h>
…
wgpu::Instance instance;
…
void Init() {
static const auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
wgpu::InstanceDescriptor instanceDesc{.requiredFeatureCount = 1,
.requiredFeatures = &kTimedWaitAny};
instance = wgpu::CreateInstance(&instanceDesc);
}
int main() {
Init();
Start();
}
Deklarieren Sie oben in der Datei main.cpp
zwei Variablen: wgpu::Adapter
und wgpu::Device
. Aktualisieren Sie die Funktion Init()
so, dass instance.RequestAdapter()
aufgerufen und der zugehörige Ergebnis-Callback adapter
zugewiesen wird. Rufen Sie dann adapter.RequestDevice()
auf und weisen Sie den zugehörigen Ergebnis-Callback device
zu.
#include <iostream>
#include <dawn/webgpu_cpp_print.h>
…
wgpu::Adapter adapter;
wgpu::Device device;
void Init() {
…
wgpu::Future f1 = instance.RequestAdapter(
nullptr, wgpu::CallbackMode::WaitAnyOnly,
[](wgpu::RequestAdapterStatus status, wgpu::Adapter a,
wgpu::StringView message) {
if (status != wgpu::RequestAdapterStatus::Success) {
std::cout << "RequestAdapter: " << message << "\n";
exit(0);
}
adapter = std::move(a);
});
instance.WaitAny(f1, UINT64_MAX);
wgpu::DeviceDescriptor desc{};
desc.SetUncapturedErrorCallback([](const wgpu::Device&,
wgpu::ErrorType errorType,
wgpu::StringView message) {
std::cout << "Error: " << errorType << " - message: " << message << "\n";
});
wgpu::Future f2 = adapter.RequestDevice(
&desc, wgpu::CallbackMode::WaitAnyOnly,
[](wgpu::RequestDeviceStatus status, wgpu::Device d,
wgpu::StringView message) {
if (status != wgpu::RequestDeviceStatus::Success) {
std::cout << "RequestDevice: " << message << "\n";
exit(0);
}
device = std::move(d);
});
instance.WaitAny(f2, UINT64_MAX);
}
Dreieck zeichnen
Die Swap Chain wird in der JavaScript API nicht verfügbar gemacht, da der Browser sich darum kümmert. In C++ müssen Sie sie manuell erstellen. Deklarieren Sie der Einfachheit halber noch einmal eine wgpu::Surface
-Variable oben in der Datei main.cpp
. Rufen Sie direkt nach dem Erstellen des GLFW-Fensters in Start()
die praktische Funktion wgpu::glfw::CreateSurfaceForWindow()
auf, um ein wgpu::Surface
(ähnlich einem HTML-Canvas) zu erstellen und es zu konfigurieren, indem Sie die neue Hilfsfunktion ConfigureSurface()
in InitGraphics()
aufrufen. Außerdem müssen Sie surface.Present()
aufrufen, um die nächste Textur in der while-Schleife zu präsentieren. Das hat keine sichtbaren Auswirkungen, da noch kein Rendering erfolgt.
#include <webgpu/webgpu_glfw.h>
…
wgpu::Surface surface;
wgpu::TextureFormat format;
void ConfigureSurface() {
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
format = capabilities.formats[0];
wgpu::SurfaceConfiguration config{.device = device,
.format = format,
.width = kWidth,
.height = kHeight,
.presentMode = wgpu::PresentMode::Fifo};
surface.Configure(&config);
}
void InitGraphics() {
ConfigureSurface();
}
void Render() {
// TODO: Render a triangle using WebGPU.
}
void Start() {
…
surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
InitGraphics();
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
}
Jetzt ist ein guter Zeitpunkt, um die Rendering-Pipeline mit dem folgenden Code zu erstellen. Für einen einfacheren Zugriff deklarieren Sie oben in der Datei main.cpp
eine wgpu::RenderPipeline
-Variable und rufen Sie die Hilfsfunktion CreateRenderPipeline()
in InitGraphics()
auf.
wgpu::RenderPipeline pipeline; … const char shaderCode[] = R"( @vertex fn vertexMain(@builtin(vertex_index) i : u32) -> @builtin(position) vec4f { const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1)); return vec4f(pos[i], 0, 1); } @fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(1, 0, 0, 1); } )"; void CreateRenderPipeline() { wgpu::ShaderSourceWGSL wgsl{{.code = shaderCode}}; wgpu::ShaderModuleDescriptor shaderModuleDescriptor{.nextInChain = &wgsl}; wgpu::ShaderModule shaderModule = device.CreateShaderModule(&shaderModuleDescriptor); wgpu::ColorTargetState colorTargetState{.format = format}; wgpu::FragmentState fragmentState{ .module = shaderModule, .targetCount = 1, .targets = &colorTargetState}; wgpu::RenderPipelineDescriptor descriptor{.vertex = {.module = shaderModule}, .fragment = &fragmentState}; pipeline = device.CreateRenderPipeline(&descriptor); } void InitGraphics() { … CreateRenderPipeline(); }
Senden Sie schließlich in der Funktion Render()
, die in jedem Frame aufgerufen wird, Rendering-Befehle an die GPU.
void Render() {
wgpu::SurfaceTexture surfaceTexture;
surface.GetCurrentTexture(&surfaceTexture);
wgpu::RenderPassColorAttachment attachment{
.view = surfaceTexture.texture.CreateView(),
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store};
wgpu::RenderPassDescriptor renderpass{.colorAttachmentCount = 1,
.colorAttachments = &attachment};
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
pass.SetPipeline(pipeline);
pass.Draw(3);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
device.GetQueue().Submit(1, &commands);
}
Wenn Sie die App mit CMake neu erstellen und ausführen, wird endlich das lang ersehnte rote Dreieck in einem Fenster angezeigt. Mach eine Pause – du hast es dir verdient.

In WebAssembly kompilieren
Sehen wir uns nun die minimalen Änderungen an, die erforderlich sind, um das rote Dreieck in einem Browserfenster zu zeichnen. Die App wird wieder mit emdawnwebgpu (Emscripten Dawn WebGPU) erstellt, das Bindungen enthält, die webgpu.h auf der JavaScript API implementieren. Dazu wird Emscripten verwendet, ein Tool zum Kompilieren von C/C++-Programmen in WebAssembly.
CMake-Einstellungen aktualisieren
Nachdem Emscripten installiert wurde, aktualisieren Sie die Build-Datei CMakeLists.txt
so:
Der markierte Code ist das Einzige, was Sie ändern müssen.
- Mit
set_target_properties
wird der Zieldatei automatisch die Dateiendung „html“ hinzugefügt. Mit anderen Worten: Sie generieren eine Datei namens „app.html“. - Die
emdawnwebgpu_cpp
-Ziellinkbibliothek ermöglicht die WebGPU-Unterstützung in Emscripten. Ohne sie kann über Ihremain.cpp
-Datei nicht auf diewebgpu/webgpu_cpp.h
-Datei zugegriffen werden. - Mit der Option für
ASYNCIFY=1
-App-Links kann synchroner C++-Code mit asynchronem JavaScript interagieren. - Die App-Link-Option
USE_GLFW=3
weist Emscripten an, die integrierte JavaScript-Implementierung der GLFW 3 API zu verwenden.
cmake_minimum_required(VERSION 3.22) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
if(EMSCRIPTEN)
set_target_properties(app PROPERTIES SUFFIX ".html")
target_link_libraries(app PRIVATE emdawnwebgpu_cpp webgpu_glfw)
target_link_options(app PRIVATE "-sASYNCIFY=1" "-sUSE_GLFW=3")
else()
target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)
endif()
Code aktualisieren
Rufen Sie anstelle einer while-Schleife emscripten_set_main_loop(Render)
auf, damit die Render()
-Funktion mit einer angemessenen Rate aufgerufen wird, die mit dem Browser und dem Monitor übereinstimmt.
#include <iostream>
#include <GLFW/glfw3.h>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#endif
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <webgpu/webgpu_glfw.h>
void Start() {
…
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop(Render, 0, false);
#else
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
#endif
}
App mit Emscripten erstellen
Die einzige Änderung, die zum Erstellen der App mit Emscripten erforderlich ist, besteht darin, den cmake
-Befehlen das magische Shell-Skript emcmake
voranzustellen. Generieren Sie die App dieses Mal in einem Unterordner build-web
und starten Sie einen HTTP-Server. Öffnen Sie abschließend Ihren Browser und rufen Sie build-web/app.html
auf.
# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web
# Start a HTTP server.
$ npx http-server

Nächste Schritte
Das erwartet Sie in Zukunft:
- Verbesserungen bei der Stabilisierung der APIs webgpu.h und webgpu_cpp.h.
- Erste Unterstützung für Android und iOS.
In der Zwischenzeit können Sie WebGPU-Probleme für Emscripten und Dawn-Probleme mit Vorschlägen und Fragen melden.
Ressourcen
Sie können sich den Quellcode dieser App ansehen.
Wenn Sie mehr über die Erstellung nativer 3D-Anwendungen in C++ mit WebGPU erfahren möchten, sehen Sie sich die Dokumentation zu WebGPU für C++ und die Dawn Native WebGPU-Beispiele an.
Wenn Sie sich für Rust interessieren, können Sie sich auch die auf WebGPU basierende Grafikbibliothek wgpu ansehen. Sehen Sie sich die hello-triangle-Demo an.
Danksagung
Dieser Artikel wurde von Corentin Wallez, Kai Ninomiya und Rachel Andrew geprüft.
Foto von Marc-Olivier Jodoin auf Unsplash.