Publicado em 20 de julho de 2023. Última atualização: 27 de agosto de 2025
Para desenvolvedores da Web, a WebGPU é uma API de gráficos da Web que oferece acesso unificado e rápido a GPUs. A WebGPU expõe recursos de hardware modernos e permite operações de renderização e computação em uma GPU, semelhante ao Direct3D 12, Metal e Vulkan.
Embora seja verdade, essa história está incompleta. A 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 significativo foi desenvolvido em paralelo: a API C webgpu.h. Esse arquivo de cabeçalho C lista todos os procedimentos e estruturas de dados disponíveis do WebGPU. Ela serve como uma camada de abstração de hardware independente de plataforma, permitindo criar aplicativos específicos da plataforma ao fornecer uma interface consistente em diferentes plataformas.
Neste documento, você vai aprender a escrever um pequeno app em C++ usando 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 da área de trabalho com ajustes mínimos no seu código.

Como funciona?
Para conferir o aplicativo concluído, acesse o repositório do app multiplataforma WebGPU (em inglês).
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. Em segundo plano, ele usa webgpu.h do WebGPU como uma camada de abstração de hardware independente de plataforma por 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 na API JavaScript. Em plataformas específicas, como macOS ou Windows, esse projeto pode ser criado com base no Dawn, a implementação multiplataforma do WebGPU do Chromium. Vale a pena mencionar que wgpu-native, uma implementação em Rust de 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 que o código-fonte é 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")
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, use o Dawn, a implementação multiplataforma do WebGPU 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 git ao seu repositório. Os comandos a seguir buscam em uma subpasta "dawn/".
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Em seguida, anexe ao arquivo CMakeLists.txt
da seguinte forma:
- A opção
DAWN_FETCH_DEPENDENCIES
do CMake busca todas as dependências do Dawn. - A opção
DAWN_BUILD_MONOLITHIC_LIBRARY
do CMake agrupa todos os componentes do Dawn em uma única biblioteca. - A subpasta
dawn/
está incluída no destino. - Seu app vai depender dos destinos
webgpu_dawn
,webgpu_glfw
eglfw
para que você possa usá-los no arquivomain.cpp
mais tarde.
…
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)
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 conveniência permite escrever código independente da plataforma para gerenciamento de janelas.
Para abrir uma janela chamada "WebGPU window" com uma resolução de 512x512, atualize o arquivo main.cpp
da seguinte maneira. Observe que glfwWindowHint()
é usado aqui para solicitar nenhuma inicialização específica da API de gráficos.
#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();
}
Recriar e executar o app como antes agora resulta em uma janela vazia. Você está progredindo!

Receber 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
usada para a mesma finalidade. Para facilitar, 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() {
static const auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
wgpu::InstanceDescriptor instanceDesc{.requiredFeatureCount = 1,
.requiredFeatures = &kTimedWaitAny};
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 atribua o retorno de chamada de resultado a adapter
. Em seguida, chame adapter.RequestDevice()
e atribua o retorno de chamada 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);
}
Desenhar um triângulo
A cadeia de troca não é exposta na API JavaScript porque o navegador cuida dela. Em C++, é necessário criar manualmente. Mais uma vez, para facilitar, 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 é um bom momento 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 a 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);
}
Recriar o app com o CMake e executá-lo agora resulta no tão esperado triângulo vermelho em uma janela. Faça uma pausa, você merece.

Compilar para WebAssembly
Agora vamos analisar 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++ em 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.
- O
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 links de destino
emdawnwebgpu_cpp
ativa a compatibilidade com WebGPU no Emscripten. Sem ele, o arquivomain.cpp
não pode acessar o arquivowebgpu/webgpu_cpp.h
. - A opção de link do 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 para usar a implementação JavaScript integrada da 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()
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 suave adequada que se alinha 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 é adicionar o script shell emcmake
mágico aos comandos cmake
. Desta 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, registre problemas do WebGPU para Emscripten e problemas do Dawn com sugestões e perguntas.
Recursos
Se quiser, confira o código-fonte deste app.
Se quiser saber mais sobre como criar aplicativos 3D nativos em C++ do zero com WebGPU, confira a documentação do Learn WebGPU para C++ e os exemplos nativos do WebGPU do Dawn.
Se você tiver interesse em Rust, também poderá conhecer a biblioteca gráfica wgpu baseada em 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.