Für Webentwickler ist WebGPU eine Webgrafik-API, die eine einheitliche und schnelle auf GPUs zugreifen können. WebGPU bietet moderne Hardwarefunktionen und ermöglicht Rendering und Rechenvorgängen auf einer GPU, ähnlich wie Direct3D 12, Metal und Vulkan.
Diese Geschichte ist zwar unvollständig. WebGPU ist das Ergebnis einer kollaborativen einschließlich großer Unternehmen wie Apple, Google, Intel, Mozilla und Microsoft Einige von ihnen haben dass WebGPU mehr als eine JavaScript API sein könnte, aber eine plattformübergreifende Grafik API für Entwickler in allen Plattformen – nicht nur im Web
Für den primären Anwendungsfall wurde eine JavaScript API in Chrome 113 eingeführt. Ein weiterer wichtiger Punkt zusammen mit dem Projekt entwickelt: webgpu.h C der API erstellen. In dieser C-Header-Datei sind alle verfügbaren Prozeduren und Datenstrukturen aufgeführt. von WebGPU. Es dient als plattformunabhängige Hardwareabstraktionsebene, können Sie plattformspezifische Anwendungen über eine einheitliche Benutzeroberfläche erstellen. auf verschiedenen Plattformen.
In diesem Dokument erfahren Sie, wie Sie eine kleine C++-App mit WebGPU schreiben, die sowohl im Web als auch auf bestimmten Plattformen ausgeführt wird. Achtung, Sie sehen das gleiche rote Dreieck, das in einem Browserfenster und einem Desktop-Fenster mit minimalen Anpassungen an der Codebasis angezeigt wird.
<ph type="x-smartling-placeholder">Wie funktioniert das?
Die fertige Anwendung finden Sie im Repository der plattformübergreifenden WebGPU-Apps.
Die App ist ein minimalistisches C++-Beispiel, das zeigt, wie mit WebGPU Desktop- und Web-Apps aus einer einzigen Codebasis erstellt werden. Intern verwendet es webgpu.h von WebGPU als plattformunabhängige Hardwareabstraktionsebene über einen C++-Wrapper namens webgpu_cpp.h.
Im Web wurde die Anwendung mit Emscripten erstellt, wobei Webgpu.h mithilfe von bindings zusätzlich zur JavaScript API implementiert wird. Auf bestimmten Plattformen wie macOS oder Windows kann dieses Projekt mit Dawn erstellt werden, der plattformübergreifenden WebGPU-Implementierung von Chromium. Erwähnenswert ist, dass wgpu-native, eine Rust-Implementierung von webgpu.h, ebenfalls vorhanden ist, aber in diesem Dokument nicht verwendet wird.
Erste Schritte
Zuerst benötigen Sie einen C++-Compiler und CMake, um plattformübergreifende Builds standardmäßig zu verarbeiten. Erstellen Sie in einem speziellen Ordner
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. Die letzte Zeile gibt den Namen der ausführbaren Datei an: "app". und der Quellcode lautet main.cpp
.
cmake_minimum_required(VERSION 3.13) # 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 "build/" zu erstellen. Unterordner 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 Dinge auf dem Bildschirm zeichnen möchten.
Morgendämmerung
Zum Zeichnen des Dreiecks kannst du Dawn nutzen, die plattformübergreifende WebGPU-Implementierung von Chromium. Dazu gehört die GLFW-C++-Bibliothek zum Zeichnen auf dem Bildschirm. Eine Möglichkeit zum Herunterladen von Dawn besteht darin, das Projekt Ihrem Repository als Git-Submodul hinzuzufügen. Die folgenden Befehle rufen ihn in der Morgenröte ab/ Unterordner.
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Hängen Sie dann so an die Datei CMakeLists.txt
an:
- Mit der CMake-Option
DAWN_FETCH_DEPENDENCIES
werden alle Dawn-Abhängigkeiten abgerufen. - Der Unterordner „
dawn/
“ ist im Ziel enthalten. - Deine App ist von
dawn::webgpu_dawn
-,glfw
- undwebgpu_glfw
-Zielen abhängig, sodass du diese später in dermain.cpp
-Datei verwenden kannst.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
Fenster öffnen
Jetzt, da „Dawn“ verfügbar ist, kannst du mit GLFW Dinge auf dem Bildschirm zeichnen. Mit dieser in webgpu_glfw
enthaltenen Bibliothek können Sie plattformunabhängigen Code für die Fensterverwaltung schreiben.
So öffnen Sie ein Fenster mit dem Namen „WebGPU-Fenster“ einer Auflösung von 512 x 512, aktualisieren Sie die main.cpp
-Datei wie unten dargestellt. 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 Anwendung neu erstellen und wie zuvor ausführen, bleibt das Fenster leer. Du machst Fortschritte!
<ph type="x-smartling-placeholder">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. Der Einfachheit halber sollten Sie instance
oben in der Datei main.cpp
deklarieren und wgpu::CreateInstance()
innerhalb von main()
aufrufen.
…
#include <webgpu/webgpu_cpp.h>
wgpu::Instance instance;
…
int main() {
instance = wgpu::CreateInstance();
Start();
}
Der Zugriff auf die GPU erfolgt aufgrund der Form der JavaScript API asynchron. Erstellen Sie in C++ zwei Hilfsfunktionen mit den Namen GetAdapter()
und GetDevice()
, die jeweils eine Callback-Funktion mit wgpu::Adapter
und wgpu::Device
zurückgeben.
#include <iostream>
…
void GetAdapter(void (*callback)(wgpu::Adapter)) {
instance.RequestAdapter(
nullptr,
[](WGPURequestAdapterStatus status, WGPUAdapter cAdapter,
const char* message, void* userdata) {
if (status != WGPURequestAdapterStatus_Success) {
exit(0);
}
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
reinterpret_cast<void (*)(wgpu::Adapter)>(userdata)(adapter);
}, reinterpret_cast<void*>(callback));
}
void GetDevice(void (*callback)(wgpu::Device)) {
adapter.RequestDevice(
nullptr,
[](WGPURequestDeviceStatus status, WGPUDevice cDevice,
const char* message, void* userdata) {
wgpu::Device device = wgpu::Device::Acquire(cDevice);
device.SetUncapturedErrorCallback(
[](WGPUErrorType type, const char* message, void* userdata) {
std::cout << "Error: " << type << " - message: " << message;
},
nullptr);
reinterpret_cast<void (*)(wgpu::Device)>(userdata)(device);
}, reinterpret_cast<void*>(callback));
}
Für einen leichteren Zugriff deklarieren Sie oben in der Datei main.cpp
die beiden Variablen wgpu::Adapter
und wgpu::Device
. Aktualisieren Sie die Funktion main()
, um GetAdapter()
aufzurufen, und weisen Sie ihren Ergebnis-Callback adapter
zu. Rufen Sie dann GetDevice()
auf und weisen Sie den Ergebnis-Callback device
zu, bevor Sie Start()
aufrufen.
wgpu::Adapter adapter;
wgpu::Device device;
…
int main() {
instance = wgpu::CreateInstance();
GetAdapter([](wgpu::Adapter a) {
adapter = a;
GetDevice([](wgpu::Device d) {
device = d;
Start();
});
});
}
Ein Dreieck zeichnen
Die Austauschkette wird in der JavaScript API nicht offengelegt, da der Browser dies übernimmt. In C++ müssen Sie sie manuell erstellen. Auch hier sollten Sie der Einfachheit halber oben in der Datei main.cpp
eine Variable wgpu::Surface
deklarieren. Direkt nach dem Erstellen des GLFW-Fensters in Start()
rufen Sie die praktische Funktion wgpu::glfw::CreateSurfaceForWindow()
auf, um ein wgpu::Surface
(ähnlich einem HTML-Canvas) zu erstellen, und konfigurieren Sie es, 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 darzustellen. Dies hat keine sichtbaren Auswirkungen, da noch kein Rendering stattfindet.
#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};
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 Code unten zu erstellen. Für einen leichteren Zugriff deklarieren Sie oben in der Datei main.cpp
eine wgpu::RenderPipeline
-Variable und rufen 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::ShaderModuleWGSLDescriptor wgslDesc{};
wgslDesc.code = shaderCode;
wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
.nextInChain = &wgslDesc};
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 zum Schluss Renderingbefehle an die GPU in der Funktion Render()
, die jeden Frame aufgerufen wird.
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, sehen Sie jetzt das lang erwartete rote Dreieck in einem Fenster. Mach eine Pause – du hast es dir verdient.
<ph type="x-smartling-placeholder">In WebAssembly kompilieren
Werfen wir nun einen Blick auf die minimalen Änderungen, die erforderlich sind, um Ihre vorhandene Codebasis anzupassen, um dieses rote Dreieck in einem Browserfenster zu zeichnen. Auch hier basiert die App auf Emscripten, einem Tool zum Kompilieren von C/C++-Programmen in WebAssembly, dessen bindings webgpu.h auf der JavaScript API implementieren.
CMake-Einstellungen aktualisieren
Sobald Emscripten installiert ist, aktualisieren Sie die CMakeLists.txt
-Build-Datei so.
Sie müssen lediglich den hervorgehobenen Code ändern.
- Mit
set_target_properties
wird „html“ automatisch hinzugefügt Dateiendung der Zieldatei hinzu. Sie generieren also eine Datei namens "app.html" -Datei. - Die Option „
USE_WEBGPU
“ für den App-Link ist erforderlich, um die WebGPU-Unterstützung in Emscripten zu aktivieren. Andernfalls kann die Dateimain.cpp
nicht auf die Dateiwebgpu/webgpu_cpp.h
zugreifen. - Die Option für den App-Link
USE_GLFW
ist hier auch erforderlich, damit Sie Ihren GLFW-Code wiederverwenden können.
cmake_minimum_required(VERSION 3.13) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
if(EMSCRIPTEN)
set_target_properties(app PROPERTIES SUFFIX ".html")
target_link_options(app PRIVATE "-sUSE_WEBGPU=1" "-sUSE_GLFW=3")
else()
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
endif()
Code aktualisieren
Zum Erstellen eines wgpu::surface
in Emscripten ist ein HTML-Canvas-Element erforderlich. Rufen Sie dazu instance.CreateSurface()
auf und geben Sie den #canvas
-Selektor an, damit das entsprechende HTML-Canvas-Element auf der von Emscripten generierten HTML-Seite übereinstimmt.
Rufen Sie anstelle einer while-Schleife emscripten_set_main_loop(Render)
auf, um dafür zu sorgen, dass die Render()
-Funktion mit einer geeigneten Rate aufgerufen wird, die dem Browser und Monitor korrekt entspricht.
#include <GLFW/glfw3.h>
#include <webgpu/webgpu_cpp.h>
#include <iostream>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#else
#include <webgpu/webgpu_glfw.h>
#endif
void Start() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window =
glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);
#if defined(__EMSCRIPTEN__)
wgpu::SurfaceDescriptorFromCanvasHTMLSelector canvasDesc{};
canvasDesc.selector = "#canvas";
wgpu::SurfaceDescriptor surfaceDesc{.nextInChain = &canvasDesc};
surface = instance.CreateSurface(&surfaceDesc);
#else
surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
#endif
InitGraphics();
#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 emcmake
-Shell-Skript voranzustellen. Generieren Sie diesmal die Anwendung in einem build-web
-Unterordner und starten Sie einen HTTP-Server. Öffnen Sie anschließ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
Künftige Änderungen:
- Verbesserungen bei der Stabilisierung der APIs webgpu.h und webgpu_cpp.h.
- Anfängliche Unterstützung für Android und iOS
Bitte melde uns in der Zwischenzeit WebGPU-Probleme für Emscripten und Dawn-Probleme mit Vorschlägen und Fragen.
Ressourcen
Sehen Sie sich auch den Quellcode dieser App an.
Wenn Sie mehr darüber erfahren möchten, wie Sie native 3D-Anwendungen in C++ mit WebGPU von Grund auf neu erstellen, lesen Sie die Dokumentation zu WebGPU in der C++-Dokumentation und Dawn Native WebGPU-Beispiele.
Wenn Sie sich für Rust interessieren, können Sie auch die auf WebGPU basierende wgpu-Grafikbibliothek verwenden. Sehen Sie sich die hello-triangle-Demo an.
Danksagung
Dieser Artikel wurde von Corentin Wallez, Kai Ninomiya und Rachel Andrew gelesen.
Foto von Marc-Olivier Jodoin auf Unsplash