Pubblicato: 20 luglio 2023, ultimo aggiornamento: 27 agosto 2025
Per gli sviluppatori web, WebGPU è un'API grafica web che fornisce un accesso unificato e rapido alle GPU. WebGPU espone le funzionalità hardware moderne e consente di eseguire operazioni di rendering e calcolo su una GPU, in modo simile a Direct3D 12, Metal e Vulkan.
Anche se è vero, questa storia è incompleta. WebGPU è il risultato di uno sforzo collaborativo, che ha coinvolto importanti aziende come Apple, Google, Intel, Mozilla e Microsoft. Tra questi, alcuni si sono resi conto che WebGPU poteva essere più di un'API JavaScript, ma un'API grafica multipiattaforma per sviluppatori in tutti gli ecosistemi, oltre al web.
Per soddisfare il caso d'uso principale, in Chrome 113 è stata introdotta un'API JavaScript. Tuttavia, è stato sviluppato un altro progetto significativo: l'API C webgpu.h. Questo file di intestazione C elenca tutte le procedure e le strutture di dati disponibili di WebGPU. Funge da livello di astrazione hardware indipendente dalla piattaforma, consentendoti di creare applicazioni specifiche per la piattaforma fornendo un'interfaccia coerente su piattaforme diverse.
In questo documento imparerai a scrivere una piccola app C++ utilizzando WebGPU che viene eseguita sia sul web che su piattaforme specifiche. Attenzione spoiler: otterrai lo stesso triangolo rosso che appare in una finestra del browser e in una finestra del desktop con modifiche minime al tuo codice.

Come funziona?
Per visualizzare l'applicazione completata, consulta il repository dell'app multipiattaforma WebGPU.
L'app è un esempio minimalista in C++ che mostra come utilizzare WebGPU per creare app web e desktop da un unico codebase. Sotto il cofano, utilizza webgpu.h di WebGPU come livello di astrazione hardware indipendente dalla piattaforma tramite un wrapper C++ chiamato webgpu_cpp.h.
Sul web, l'app è basata su emdawnwebgpu (Emscripten Dawn WebGPU), che ha binding che implementano webgpu.h sopra l'API JavaScript. Su piattaforme specifiche come macOS o Windows, questo progetto può essere creato in base a Dawn, l'implementazione multipiattaforma di WebGPU di Chromium. Vale la pena menzionare che esiste anche wgpu-native, un'implementazione Rust di webgpu.h, ma non viene utilizzata in questo documento.
Inizia
Per iniziare, hai bisogno di un compilatore C++ e di CMake per gestire le build multipiattaforma in modo standard. All'interno di una cartella dedicata, crea un file di origine
main.cpp
e un file di build CMakeLists.txt
.
Per il momento, il file main.cpp
deve contenere una funzione main()
vuota.
int main() {}
Il file CMakeLists.txt
contiene informazioni di base sul progetto. L'ultima riga specifica che il nome dell'eseguibile è "app" e il relativo codice sorgente è main.cpp
.
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")
Esegui cmake -B build
per creare i file di build in una sottocartella "build/" e cmake --build build
per creare effettivamente l'app e generare il file eseguibile.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
L'app viene eseguita, ma non è ancora presente alcun output, in quanto è necessario un modo per disegnare elementi sullo schermo.
Get Dawn
Per disegnare il triangolo, puoi sfruttare Dawn, l'implementazione multipiattaforma di WebGPU di Chromium. È inclusa la libreria C++ GLFW per il disegno sullo schermo. Un modo per scaricare Dawn è aggiungerlo come sottomodulo Git al tuo repository. I seguenti comandi lo recuperano in una sottocartella "dawn/".
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Quindi, aggiungi al file CMakeLists.txt
quanto segue:
- L'opzione CMake
DAWN_FETCH_DEPENDENCIES
recupera tutte le dipendenze di Dawn. - L'opzione CMake
DAWN_BUILD_MONOLITHIC_LIBRARY
raggruppa tutti i componenti di Dawn in un'unica libreria. - La sottocartella
dawn/
è inclusa nella destinazione. - La tua app dipenderà dai target
webgpu_dawn
,webgpu_glfw
eglfw
, in modo da poterli utilizzare in un secondo momento nel filemain.cpp
.
…
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)
Aprire una finestra
Ora che Dawn è disponibile, usa GLFW per disegnare elementi sullo schermo. Questa libreria inclusa in webgpu_glfw
per comodità ti consente di scrivere codice indipendente dalla piattaforma per la gestione delle finestre.
Per aprire una finestra denominata "WebGPU window" con una risoluzione di 512x512, aggiorna il file main.cpp
come indicato di seguito. Tieni presente che qui viene utilizzato glfwWindowHint()
per richiedere l'inizializzazione di nessuna API grafica specifica.
#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();
}
La ricreazione dell'app e la sua esecuzione come prima ora comporta una finestra vuota. Stai facendo progressi!

Ottieni dispositivo GPU
In JavaScript, navigator.gpu
è il punto di accesso alla GPU. In C++, devi creare manualmente una variabile wgpu::Instance
utilizzata per lo stesso scopo. Per comodità, dichiara instance
nella parte superiore del file main.cpp
e chiama wgpu::CreateInstance()
all'interno di Init()
.
#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();
}
Dichiara due variabili wgpu::Adapter
e wgpu::Device
nella parte superiore del file main.cpp
. Aggiorna la funzione Init()
per chiamare instance.RequestAdapter()
e assegnare il relativo callback dei risultati a adapter
, quindi chiama adapter.RequestDevice()
e assegna il relativo callback dei risultati a device
.
#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);
}
Disegnare un triangolo
La swap chain non è esposta nell'API JavaScript perché il browser se ne occupa. In C++, devi crearlo manualmente. Ancora una volta, per comodità, dichiara una variabile wgpu::Surface
nella parte superiore del file main.cpp
. Subito dopo aver creato la finestra GLFW in Start()
, chiama la pratica funzione wgpu::glfw::CreateSurfaceForWindow()
per creare un wgpu::Surface
(simile a un canvas HTML) e configurarlo chiamando la nuova funzione helper ConfigureSurface()
in InitGraphics()
. Devi anche chiamare surface.Present()
per presentare la texture successiva nel ciclo while. Ciò non ha alcun effetto visibile, in quanto non è ancora in corso alcun rendering.
#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();
}
}
Ora è un buon momento per creare la pipeline di rendering con il codice riportato di seguito. Per un accesso più semplice, dichiara una variabile wgpu::RenderPipeline
nella parte superiore del file main.cpp
e chiama la funzione helper CreateRenderPipeline()
in InitGraphics()
.
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(); }
Infine, invia i comandi di rendering alla GPU nella funzione Render()
chiamata a ogni frame.
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);
}
La ricompilazione dell'app con CMake e la sua esecuzione ora restituiscono il tanto atteso triangolo rosso in una finestra. Prenditi una pausa, te la meriti.

Compilare in WebAssembly
Diamo un'occhiata ora alle modifiche minime necessarie per adattare la base di codice esistente per disegnare questo triangolo rosso in una finestra del browser. Anche in questo caso, l'app è creata in base a emdawnwebgpu (Emscripten Dawn WebGPU), che dispone di binding che implementano webgpu.h sopra l'API JavaScript. Utilizza Emscripten, uno strumento per compilare programmi C/C++ in WebAssembly.
Aggiorna le impostazioni di CMake
Una volta installato Emscripten, aggiorna il file di build CMakeLists.txt
come segue.
Il codice evidenziato è l'unica cosa che devi modificare.
set_target_properties
viene utilizzato per aggiungere automaticamente l'estensione del file "html" al file di destinazione. In altre parole, genererai un file "app.html".- La libreria di link di destinazione
emdawnwebgpu_cpp
consente il supporto di WebGPU in Emscripten. Senza, il filemain.cpp
non può accedere al filewebgpu/webgpu_cpp.h
. - L'opzione di link all'app
ASYNCIFY=1
consente al codice C++ sincrono di interagire con JavaScript asincrono. - L'opzione
USE_GLFW=3
per i link per app indica a Emscripten di utilizzare la sua implementazione JavaScript integrata dell'API GLFW 3.
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()
Aggiorna il codice
Anziché utilizzare un ciclo while, chiama emscripten_set_main_loop(Render)
per assicurarti che la funzione Render()
venga chiamata a una velocità uniforme adeguata che si allinei correttamente con il browser e il monitor.
#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
}
Crea l'app con Emscripten
L'unica modifica necessaria per creare l'app con Emscripten è anteporre ai comandi cmake
lo script shell emcmake
magico. Questa volta, genera l'app in una sottocartella build-web
e avvia un server HTTP. Infine, apri il browser e visita la pagina build-web/app.html
.
# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web
# Start a HTTP server.
$ npx http-server

Passaggi successivi
Ecco cosa puoi aspettarti in futuro:
- Miglioramenti alla stabilizzazione delle API webgpu.h e webgpu_cpp.h.
- Supporto iniziale di Dawn per Android e iOS.
Nel frattempo, invia segnalazioni di problemi relativi a WebGPU per Emscripten e problemi relativi a Dawn con suggerimenti e domande.
Risorse
Puoi esaminare il codice sorgente di questa app.
Se vuoi approfondire la creazione di applicazioni 3D native in C++ da zero con WebGPU, consulta la documentazione Learn WebGPU for C++ e gli esempi di Dawn Native WebGPU.
Se ti interessa Rust, puoi anche esplorare la libreria grafica wgpu basata su WebGPU. Dai un'occhiata alla demo hello-triangle.
Riconoscimenti
Questo articolo è stato rivisto da Corentin Wallez, Kai Ninomiya e Rachel Andrew.
Foto di Marc-Olivier Jodoin su Unsplash.