Publicado: 20 de julho de 2023, Última atualização: 17 de junho de 2025
Para desenvolvedores da Web, a WebGPU é uma API de gráficos da Web que oferece acesso unificado e rápido a GPUs. O WebGPU expõe recursos de hardware modernos e permite renderização e operações de computação em uma GPU, semelhante ao Direct3D 12, Metal e Vulkan.
Embora seja verdade, essa história está incompleta. O WebGPU é o resultado de um esforço colaborativo, incluindo grandes empresas, como Apple, Google, Intel, Mozilla e Microsoft. Entre eles, alguns perceberam que a WebGPU poderia ser mais do que uma API JavaScript, mas uma API de gráficos multiplataforma para desenvolvedores em vários ecossistemas, além da Web.
Para atender ao caso de uso principal, uma API JavaScript foi introduzida no Chrome 113. No entanto, outro projeto importante foi desenvolvido junto com ele: a API C webgpu.h. Este arquivo de cabeçalho C lista todos os procedimentos e estruturas de dados disponíveis da WebGPU. Ele serve como uma camada de abstração de hardware independente da plataforma, permitindo que você crie aplicativos específicos da plataforma fornecendo uma interface consistente em diferentes plataformas.
Neste documento, você vai aprender a criar um pequeno app C++ usando a WebGPU que é executado na Web e em plataformas específicas. Alerta de spoiler: você vai receber o mesmo triângulo vermelho que aparece em uma janela do navegador e em uma janela de computador com ajustes mínimos na sua base de código.

Como funciona?
Para conferir o aplicativo completo, confira o repositório do app multiplataforma WebGPU.
O app é um exemplo minimalista em C++ que mostra como usar a WebGPU para criar apps para computador e Web com uma única base de código. Por trás dos bastidores, ele usa o webgpu.h da WebGPU como uma camada de abstração de hardware independente de plataforma usando um wrapper C++ chamado webgpu_cpp.h.
Na Web, o app é criado com base em emdawnwebgpu (Emscripten Dawn WebGPU), que tem vinculações que implementam webgpu.h sobre a API JavaScript. Em plataformas específicas, como macOS ou Windows, esse projeto pode ser criado com base na Dawn, a implementação da WebGPU entre plataformas do Chromium. Vale mencionar que a wgpu-native, uma implementação Rust do webgpu.h, também existe, mas não é usada neste documento.
Primeiros passos
Para começar, você precisa de um compilador C++ e do CMake para processar builds multiplataforma de maneira padrão. Em uma pasta dedicada, crie um
arquivo de origem main.cpp
e um arquivo de build CMakeLists.txt
.
O arquivo main.cpp
precisa conter uma função main()
vazia por enquanto.
int main() {}
O arquivo CMakeLists.txt
contém informações básicas sobre o projeto. A última linha especifica que o nome do executável é "app" e o código-fonte é 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")
Execute cmake -B build
para criar arquivos de build em uma subpasta "build/" e cmake --build build
para criar o app e gerar o arquivo executável.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
O app é executado, mas ainda não há saída, porque você precisa de uma maneira de desenhar coisas na tela.
Acessar Dawn
Para desenhar o triângulo, você pode aproveitar o Dawn, a implementação da WebGPU em várias plataformas do Chromium. Isso inclui a biblioteca C++ GLFW para desenhar na tela. Uma maneira de fazer o download do Dawn é adicioná-lo como um submódulo do Git ao seu repositório. Os comandos a seguir buscam o arquivo em uma subpasta "dawn/".
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Em seguida, anexe ao arquivo CMakeLists.txt
da seguinte maneira:
- A opção
DAWN_FETCH_DEPENDENCIES
do CMake busca todas as dependências do Dawn. - A subpasta
dawn/
está incluída no destino. - O app vai depender dos destinos
dawn::webgpu_dawn
,glfw
ewebgpu_glfw
para que você possa usá-los no arquivomain.cpp
mais tarde.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
Abrir uma janela
Agora que o Dawn está disponível, use o GLFW para desenhar coisas na tela. Essa biblioteca, incluída em webgpu_glfw
para sua conveniência, permite que você escreva código independente de plataforma para gerenciamento de janelas.
Para abrir uma janela chamada "Janela WebGPU" com uma resolução de 512x512, atualize o arquivo main.cpp
conforme abaixo. glfwWindowHint()
é usado aqui para solicitar a não inicialização de uma API de gráficos específica.
#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();
}
A recriação do app e a execução dele como antes agora resultam em uma janela vazia. Você está progredindo.

Conseguir dispositivo de GPU
Em JavaScript, navigator.gpu
é o ponto de entrada para acessar a GPU. Em C++, é necessário criar manualmente uma variável wgpu::Instance
que seja usada para o mesmo propósito. Para sua conveniência, declare instance
na parte de cima do arquivo main.cpp
e chame wgpu::CreateInstance()
dentro de Init()
.
#include <webgpu/webgpu_cpp.h>
…
wgpu::Instance instance;
…
void Init() {
wgpu::InstanceDescriptor instanceDesc{
.capabilities = {.timedWaitAnyEnable = true}};
instance = wgpu::CreateInstance(&instanceDesc);
}
int main() {
Init();
Start();
}
Declare duas variáveis wgpu::Adapter
e wgpu::Device
na parte de cima do arquivo main.cpp
. Atualize a função Init()
para chamar instance.RequestAdapter()
e atribuir o callback de resultado a adapter
. Em seguida, chame adapter.RequestDevice()
e atribua o callback de resultado 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);
}
Desenhe um triângulo
A cadeia de troca não é exposta na API JavaScript, porque o navegador cuida disso. Em C++, é necessário criar manualmente. Mais uma vez, para sua conveniência, declare uma variável wgpu::Surface
na parte de cima do arquivo main.cpp
. Logo após criar a janela GLFW em Start()
, chame a função wgpu::glfw::CreateSurfaceForWindow()
para criar um wgpu::Surface
(semelhante a uma tela HTML) e configure-o chamando a nova função auxiliar ConfigureSurface()
em InitGraphics()
. Você também precisa chamar surface.Present()
para apresentar a próxima textura no loop while. Isso não tem efeito visível, porque ainda não há renderização.
#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();
}
}
Agora é uma boa hora para criar o pipeline de renderização com o código abaixo. Para facilitar o acesso, declare uma variável wgpu::RenderPipeline
na parte de cima do arquivo main.cpp
e chame a função auxiliar CreateRenderPipeline()
em 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(); }
Por fim, envie comandos de renderização para a GPU na função Render()
chamada em cada 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);
}
A recriação do app com o CMake e a execução dele agora resultam no tão esperado triângulo vermelho em uma janela. Faça uma pausa, você merece.

Compilar para o WebAssembly
Vamos conferir as mudanças mínimas necessárias para ajustar a base de código atual e desenhar esse triângulo vermelho em uma janela do navegador. Novamente, o app é criado com base em emdawnwebgpu (Emscripten Dawn WebGPU), que tem vinculações que implementam webgpu.h na API JavaScript. Ele usa o Emscripten, uma ferramenta para compilar programas C/C++ para o WebAssembly.
Atualizar as configurações do CMake
Depois que o Emscripten for instalado, atualize o arquivo de build CMakeLists.txt
da seguinte maneira.
O código destacado é a única coisa que você precisa mudar.
set_target_properties
é usado para adicionar automaticamente a extensão de arquivo "html" ao arquivo de destino. Em outras palavras, você vai gerar um arquivo "app.html".- A biblioteca de link de destino
emdawnwebgpu_cpp
ativa o suporte ao WebGPU no Emscripten. Sem ele, o arquivomain.cpp
não pode acessar o arquivowebgpu/webgpu_cpp.h
. - A opção de vinculação de app
ASYNCIFY=1
permite que o código C++ síncrono interaja com o JavaScript assíncrono. - A opção de link do app
USE_GLFW=3
informa ao Emscripten que ele deve usar a implementação JavaScript integrada da API GLFW 3.
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")
set(DAWN_FETCH_DEPENDENCIES ON)
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 dawn::webgpu_dawn glfw webgpu_glfw)
endif()
Atualizar o código
Em vez de usar um loop while, chame emscripten_set_main_loop(Render)
para garantir que a função Render()
seja chamada em uma taxa adequada e suave que se alinhe corretamente com o navegador e o 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
}
Criar o app com o Emscripten
A única mudança necessária para criar o app com o Emscripten é inserir os comandos cmake
com o script de shell emcmake
mágico. Dessa vez, gere o app em uma subpasta build-web
e inicie um servidor HTTP. Por fim, abra o navegador e acesse 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

A seguir
Confira o que você pode esperar no futuro:
- Melhorias na estabilização das APIs webgpu.h e webgpu_cpp.h.
- Suporte inicial do Dawn para Android e iOS.
Enquanto isso, envie problemas da WebGPU para Emscripten e problemas do Dawn com sugestões e perguntas.
Recursos
Confira o código-fonte deste app.
Se você quiser se aprofundar na criação de aplicativos 3D nativos em C++ do zero com a WebGPU, consulte a documentação de Aprenda a WebGPU para C++ e os exemplos de WebGPU nativa do Dawn.
Se você tem interesse em Rust, também pode conferir a biblioteca gráfica wgpu baseada na WebGPU. Confira a demonstração hello-triangle.
Agradecimentos
Este artigo foi revisado por Corentin Wallez, Kai Ninomiya e Rachel Andrew.
Foto de Marc-Olivier Jodoin no Unsplash.