WebGPU: computação gráfica e GPGPU direto no browser sem plugins
1. O que é WebGPU e por que substitui WebGL
WebGPU é uma API gráfica moderna e de baixo nível que permite acesso direto à GPU diretamente no navegador, sem necessidade de plugins ou instalações adicionais. Ela surge como sucessora do WebGL, que se baseava no legado OpenGL ES 2.0/3.0 e apresentava limitações significativas: drivers instáveis, ausência de compute shaders, modelo de estado global e overhead de validação em tempo real.
A nova API foi projetada para espelhar as arquiteturas modernas de GPU — Vulkan, Metal e Direct3D 12 — oferecendo controle explícito sobre recursos, filas de comando e sincronização. Seus casos de uso vão desde renderização 3D avançada até simulações físicas, processamento de imagens, filtros em tempo real e até inferência de modelos de machine learning no navegador.
2. Arquitetura fundamental: GPUAdapter, Device, Queue e Pipeline
A arquitetura do WebGPU segue um modelo em camadas:
- GPUAdapter: representa uma placa de vídeo ou driver gráfico disponível. Permite verificar recursos (limites de buffers, texturas, features como compute shaders) e solicitar um dispositivo.
- GPUDevice: é o objeto central que cria todos os recursos da GPU — buffers, texturas, bind groups, pipelines. Cada device opera de forma isolada.
- GPUQueue: fila de comandos onde submetemos operações. A sincronização é feita com
mapAsyncpara leitura de buffers e comfencespara coordenar múltiplas submissões. - Shader Modules e Pipelines: shaders são compilados a partir de código WGSL em módulos. Pipelines combinam esses módulos com configurações de estado (rasterização, blend, profundidade).
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const queue = device.queue;
3. WGSL (WebGPU Shading Language): a linguagem de shaders do futuro
WGSL é a linguagem de shaders padrão do WebGPU. Sua sintaxe é inspirada em Rust e SPIR-V, com tipos seguros e sem ponteiros explícitos. Diferente de GLSL/HLSL, o modelo de memória é mais restrito e seguro, prevenindo acessos fora dos limites.
Exemplo de shader de vértice e fragmento para um triângulo colorido:
// Vertex shader
@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4f {
var pos = array<vec2f, 3>(
vec2f(0.0, 0.5),
vec2f(-0.5, -0.5),
vec2f(0.5, -0.5)
);
return vec4f(pos[idx], 0.0, 1.0);
}
// Fragment shader
@fragment
fn fs_main() -> @location(0) vec4f {
return vec4f(0.0, 0.5, 1.0, 1.0);
}
4. Renderização gráfica: do triângulo ao pipeline completo
Para renderizar um quadrado texturizado, configuramos a swap chain (canvas), criamos buffers de vértices com coordenadas UV e um sampler para filtrar a textura.
// Configuração do canvas
const context = canvas.getContext('webgpu');
context.configure({
device: device,
format: navigator.gpu.getPreferredCanvasFormat(),
alphaMode: 'premultiplied'
});
// Buffer de vértices com posição e UV
const vertices = new Float32Array([
-0.5, -0.5, 0.0, 0.0,
0.5, -0.5, 1.0, 0.0,
0.5, 0.5, 1.0, 1.0,
-0.5, 0.5, 0.0, 1.0
]);
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
});
new Float32Array(vertexBuffer.getMappedRange()).set(vertices);
vertexBuffer.unmap();
O pipeline de renderização combina vertex shader, rasterização, fragment shader e blend. Cada frame submetemos um render pass que desenha os vértices na textura alvo.
5. Compute Shaders e GPGPU (General-Purpose GPU)
Compute shaders permitem executar cálculos arbitrários na GPU, organizados em grupos de trabalho (workgroups) e invocações (threads). Exemplo clássico é a soma de vetores em paralelo:
// WGSL compute shader para soma de vetores
@compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) id: vec3u) {
let idx = id.x;
if (idx < arrayLength(&a)) {
result[idx] = a[idx] + b[idx];
}
}
Aplicações práticas incluem simulação de partículas, FFT, filtros de imagem (blur, detecção de bordas) e inferência de redes neurais leves. A leitura/escrita é feita via storage buffers e storage textures.
6. Gerenciamento de memória e desempenho
O gerenciamento eficiente de memória é crucial:
- Buffers mapeados (staging): para upload/download de dados entre CPU e GPU.
- Device-local buffers: alocados diretamente na VRAM para máxima performance.
- Uniform vs Storage buffers: uniform para dados pequenos e constantes por draw call; storage para grandes volumes de dados mutáveis.
- Bind Groups: organizam recursos (buffers, texturas, samplers) em layouts que o pipeline consome.
Dicas de otimização:
- Reutilize pipelines e bind groups sempre que possível.
- Use double buffering para evitar stalls entre CPU e GPU.
- Reduza validação desnecessária com device.createRenderPipelineAsync.
7. Comparação com alternativas e limitações atuais
WebGPU vs WebGL 2.0: WebGPU oferece até 3x mais desempenho em cenários compute-heavy, suporte nativo a compute shaders, bindless resources e depuração superior via Chrome DevTools. WebGL ainda é mais amplamente suportado em dispositivos antigos.
WebGPU vs WASM + WASI: WebAssembly com WASI permite executar código nativo no navegador, mas sem acesso direto à GPU. WebGPU complementa WASM fornecendo aceleração gráfica e paralela.
Limitações atuais: suporte restrito a Chrome/Edge (desktop e Android) e Firefox (em desenvolvimento). Ausência de ray tracing nativo (extensão em discussão). Sem suporte a tessellation shaders e mesh shaders por enquanto.
Futuro: extensões como ray tracing, mesh shaders e suporte a Node.js via bibliotecas Dawn (Google) e wgpu (Mozilla) estão em andamento.
8. Exemplo completo integrado: simulação de N-corpos com GPU
Abaixo, a estrutura de uma simulação gravitacional de N-corpos executando compute shader a cada frame:
<!DOCTYPE html>
<html>
<body>
<canvas id="gpuCanvas"></canvas>
<script>
// 1. Inicialização
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.getElementById('gpuCanvas');
const context = canvas.getContext('webgpu');
context.configure({
device: device,
format: 'bgra8unorm'
});
// 2. Buffers de posição e velocidade (N partículas)
const N = 1024;
const posBuffer = device.createBuffer({ /* size, usage, mappedAtCreation */ });
const velBuffer = device.createBuffer({ /* size, usage, mappedAtCreation */ });
// 3. Compute shader para forças gravitacionais
const computeShader = `
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
let i = id.x;
if (i >= ${N}u) return;
var force = vec2f(0.0);
for (var j = 0u; j < ${N}u; j = j + 1u) {
let diff = pos[i] - pos[j];
let distSq = max(dot(diff, diff), 0.01);
force = force - (diff / (distSq * sqrt(distSq))) * mass[j];
}
vel[i] = vel[i] + force * dt;
pos[i] = pos[i] + vel[i] * dt;
}
`;
// 4. Pipeline de renderização (pontos coloridos)
const renderPipeline = device.createRenderPipeline({
vertex: { module: vertexModule, entryPoint: 'main' },
fragment: { module: fragmentModule, entryPoint: 'main', targets: [{ format: 'bgra8unorm' }] }
});
// 5. Loop de animação
function frame() {
const commandEncoder = device.createCommandEncoder();
// Compute pass
const computePass = commandEncoder.beginComputePass();
computePass.setPipeline(computePipeline);
computePass.setBindGroup(0, bindGroup);
computePass.dispatchWorkgroups(Math.ceil(N / 64));
computePass.end();
// Render pass
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [...] });
renderPass.setPipeline(renderPipeline);
renderPass.setVertexBuffer(0, posBuffer);
renderPass.draw(N);
renderPass.end();
queue.submit([commandEncoder.finish()]);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>
</body>
</html>
O loop submete um compute pass para atualizar posições e velocidades, seguido de um render pass que desenha pontos coloridos na tela. A cada frame, a GPU processa N² interações gravitacionais em paralelo.
Referências
- WebGPU Specification (W3C) — Documentação oficial da especificação WebGPU, incluindo todos os tipos, métodos e estados da API.
- WebGPU Shading Language (WGSL) Specification — Especificação completa da linguagem de shaders WGSL, com tipos, funções e atributos.
- Dawn: WebGPU Implementation (Google) — Implementação de referência do WebGPU em C++, usada pelo Chrome e disponível como biblioteca standalone.
- WebGPU Fundamentals (webgpufundamentals.org) — Tutorial prático com exemplos interativos de renderização, compute shaders e boas práticas.
- Learn WebGPU (github.com/eliemichel/LearnWebGPU) — Curso completo e gratuito sobre WebGPU com código-fonte em C++ e WGSL.
- WebGPU Compute Shaders: N-Body Simulation (toji.dev) — Demonstração funcional de simulação de N-corpos com compute shaders, com código-fonte completo.
- Mozilla wgpu: WebGPU in Rust — Implementação nativa do WebGPU em Rust, suportada pelo Firefox e utilizável em aplicações desktop.