A GPU do usuário estava ali o tempo todo — o WebGPU finalmente deixa a web usá-la para computar, não só desenhar

Apostila completa de WebGPU & GPU Compute no Navegador

WebGPU é a API gráfica moderna da web — sucessora do WebGL — e, pela primeira vez no navegador, dá acesso de primeira classe a compute shaders: rodar milhares de threads em paralelo na GPU para ML, simulação, processamento de imagem e visualização massiva. Esta apostila cobre o modelo mental (adapter/device/queue), a linguagem WGSL, compute e render pipelines, performance e memória, ML no navegador, os casos de uso, e a produção — fallback, device lost, limites e debugging.

10 módulosadapter · device · queueWGSL · workgroupscompute · renderML na web · fallbackExercícios com gabarito
MÓDULO 01 · BÁSICO

O que é WebGPU e por que

Objetivo: entender o que WebGPU é (e não é), por que substitui o WebGL, o que compute shaders desbloqueiam, o suporte atual, e quando usar cada tecnologia.

1.1 O que é

1.2 Por que substitui o WebGL

1.3 Suporte (panorama 2026)

1.4 Quando usar o quê

Precisa de…Use
UI, animações, layout, efeitos levesCSS / Canvas 2D (ver CSS Avançado: Animações & SVG, Motion de Interface)
3D com uma engine, alcance máximo hojeThree.js / Babylon (que têm backend WebGPU e fallback WebGL) — ver Computação Gráfica & Three.js
Compute pesado (ML, simulação, milhões de pontos), render customizado, controle totalWebGPU direto
ML no navegador sem escrever kernelstransformers.js / WebLLM / ONNX Runtime Web (usam WebGPU por baixo) — ver IA Local & SLMs
💡 A regra que organiza a apostila

WebGPU brilha quando você precisa processar muitos dados em paralelo ou renderizar de um jeito que uma engine não faz. Se uma lib de alto nível (Three.js, transformers.js) resolve, use a lib — escrever WebGPU cru é poderoso e trabalhoso. E sempre com fallback: nem todo dispositivo tem.

💼 Mercado de trabalho

Perguntas de abertura: "O que WebGPU adiciona sobre o WebGL?" (compute shaders de verdade, menos overhead de driver, estado explícito, multithreading, API moderna sobre Vulkan/Metal/D3D12), "Quando você usaria WebGPU cru em vez de Three.js?" (compute pesado, render muito customizado, controle total; a engine para o 3D comum), "Qual o estado de suporte?" (Chrome/Edge estável, Safari enviado, Firefox em rollout — sempre feature-detect + fallback).

✏️ Exercício 1 — Escolha a ferramenta

Para cada tarefa, escolha CSS/Canvas2D, Three.js, WebGPU cru ou uma lib de ML, e justifique: (a) um hover com partículas atrás de um botão; (b) um viewer de modelo 3D de produto; (c) uma simulação de fluido em tempo real cobrindo a tela; (d) rodar um modelo de embeddings no cliente; (e) um scatter plot com 5 milhões de pontos que dá pan/zoom a 60 fps.

Gabarito (uma boa resposta): (a) Canvas 2D (ou CSS) — poucas partículas, leve; WebGPU seria exagero. (b) Three.js — 3D comum, a engine resolve com glTF e tem fallback. (c) WebGPU (compute) — simulação de fluido é o caso de compute shader por excelência; WebGL faria com gambiarra. (d) lib de ML (transformers.js / ONNX Runtime Web) que usa WebGPU internamente — não escreva o kernel. (e) WebGPU — 5M de pontos com interação a 60 fps exige um vertex buffer grande e talvez um compute pass para culling/agregação; Canvas 2D e até Three.js sofrem nessa escala.

MÓDULO 02 · BÁSICO

O modelo mental

Objetivo: adapter → device → queue; a natureza assíncrona e "gravar e submeter"; buffers, textures, bind groups, layouts e pipelines.

2.1 Inicialização

const adapter = await navigator.gpu?.requestAdapter();      // escolhe uma GPU física
if (!adapter) throw new Error('WebGPU indisponível');  // → fallback
const device = await adapter.requestDevice();               // o "handle" com que você trabalha
const queue = device.queue;                                 // para submeter comandos e escrever buffers

device.lost.then(info => { /* GPU resetou — recriar tudo (Módulo 9) */ });

2.2 "Nada acontece imediatamente"

Você não "manda a GPU calcular agora". Você:

  1. Cria recursos (buffers, textures) e sobe dados.
  2. Cria pipelines (compilam os shaders e fixam o estado).
  3. Cria um command encoder, abre passes (compute ou render), grava chamadas (setPipeline, setBindGroup, dispatch/draw).
  4. encoder.finish() → um command buffer.
  5. queue.submit([commandBuffer]) a GPU executa, de forma assíncrona.

Ler resultado de volta é outra operação assíncrona (buffer.mapAsync) — e cara (Módulo 4).

2.3 Recursos

2.4 Bind groups e pipelines

💼 Mercado de trabalho

Perguntas: "Descreva o fluxo de uma operação em WebGPU" (adapter → device → criar recursos e pipeline → encoder → pass → gravar comandos → finish → queue.submit; execução assíncrona), "O que é um bind group?" (amarra recursos concretos aos slots que o shader declara, conforme um layout), "Por que criar pipeline uma vez só?" (compilar shader e fixar estado é caro).

✏️ Exercício 2 — Ordem das operações

Ordene os passos para dobrar cada elemento de um array de 1 milhão de floats na GPU e ler o resultado: (a) queue.submit; (b) criar o pipeline de compute com o shader; (c) encoder.beginComputePass + setPipeline + setBindGroup + dispatchWorkgroups; (d) criar buffers (input STORAGE+COPY_DST, output STORAGE+COPY_SRC, staging MAP_READ+COPY_DST); (e) queue.writeBuffer do input; (f) copiar output → staging no encoder; (g) staging.mapAsync + ler; (h) criar bind group.

Gabarito: d → b → h → e → (novo encoder) → c → f → a → g. Ou seja: cria os buffers (d); cria o pipeline com o shader WGSL (b); cria o bind group amarrando input/output (h); sobe os dados do input (e); abre o command encoder, faz o compute pass (c), e ainda no encoder copia o output para o buffer staging (f); encoder.finish() e queue.submit (a); depois staging.mapAsync(GPUMapMode.READ), lê o getMappedRange(), e unmap() (g). O readback (f+g) é o passo caro — em produção você evitaria se o resultado for consumido por outro shader.

MÓDULO 03 · BÁSICO

WGSL: a linguagem de shader

Objetivo: os tipos, os atributos (@group/@binding, @workgroup_size), os builtins, storage vs uniform, e o modelo de threads em workgroups.

3.1 Tipos

3.2 Um compute shader mínimo

@group(0) @binding(0) var<storage, read>        input:  array<f32>;
@group(0) @binding(1) var<storage, read_write>  output: array<f32>;
@group(0) @binding(2) var<uniform>             params: vec4<f32>;   // pequeno, constante

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
  let i = gid.x;
  if (i >= arrayLength(&input)) { return; }   // guarda de borda
  output[i] = input[i] * params.x + params.y;
}

3.3 Bindings

3.4 O modelo de threads

3.5 Diferenças do GLSL

💼 Mercado de trabalho

Perguntas: "O que é um workgroup e como você escolhe o tamanho?" (grupo de threads que compartilham memória e sincronizam; múltiplo de 32/64, total ≤ ~256, ajustar por benchmark), "storage vs uniform buffer?" (grande de dados vs pequeno e constante, mais rápido, com limite), "O que é divergência e por que custa?" (threads vizinhos tomando ramos diferentes serializam — SIMT), "Por que a guarda if (i >= n) return?" (o dispatch cobre mais threads que elementos).

✏️ Exercício 3 — Leia o shader

Explique o que este shader faz e um problema nele: @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) g: vec3u){ for (var j=0u; j<arrayLength(&data); j++){ data[g.x] = data[g.x] + data[j]; } }

Gabarito (uma boa resposta): a intenção parece ser somar todos os elementos e acumular em data[g.x]. Problemas graves: (1) @workgroup_size(1) — usa 1 thread por workgroup, desperdiça a GPU (deveria ser 64+); (2) cada thread percorre o array inteiro (O(n) por thread, O(n²) total) — é uma redução mal feita; (3) escreve em data[g.x] enquanto outros threads leem data[j]data race, resultado não determinístico; (4) modificar o buffer que você está lendo, sem barreira. O jeito certo de somar um array na GPU é uma redução paralela: cada workgroup soma seu pedaço em var<workgroup> com workgroupBarrier(), grava um parcial, e um segundo passe soma os parciais (ou atomicAdd num acumulador). Ver Módulo 4.

MÓDULO 04 · INTERMEDIÁRIO

Compute shaders na prática

Objetivo: o dispatch, o padrão "um thread por elemento", o custo do readback, os padrões paralelos (redução, prefix sum) e a memória de workgroup.

4.1 Dispatch

4.2 O readback é caro

4.3 Padrões paralelos

4.4 Memória de workgroup

var<workgroup> tile: array<f32, 64>;

@compute @workgroup_size(64)
fn reduce(@builtin(local_invocation_id) lid: vec3u,
          @builtin(workgroup_id) wid: vec3u) {
  let i = wid.x * 64u + lid.x;
  tile[lid.x] = select(0.0, input[i], i < arrayLength(&input));
  workgroupBarrier();
  var stride = 32u;
  loop {
    if (stride == 0u) { break; }
    if (lid.x < stride) { tile[lid.x] += tile[lid.x + stride]; }
    workgroupBarrier();
    stride = stride / 2u;
  }
  if (lid.x == 0u) { partial[wid.x] = tile[0]; }
}
⚠️ Erros comuns em compute

Readback a cada frame (destrói a performance). @workgroup_size(1). Esquecer a guarda de borda (lê/escreve fora do array). Data race em scatter sem atomic. workgroupBarrier() dentro de um if divergente (comportamento indefinido — a barreira precisa ser alcançada por todos). Buffer sem a usage flag certa (erro de validação). Uniform buffer grande demais (use storage).

💼 Mercado de trabalho

Perguntas: "Por que o readback é caro e como evitar?" (sincroniza CPU↔GPU; deixe o resultado no buffer para o próximo shader consumir — "compute produz, render consome"), "Como você soma um array de 10M na GPU?" (redução em árvore com memória de workgroup + barreiras, dois passes; ou atomicAdd), "Para que serve var<workgroup>?" (memória rápida compartilhada pelos threads do workgroup — evita reler da global em stencils/reduções), "Onde a barreira não pode estar?" (dentro de ramo divergente).

✏️ Exercício 4 — Blur de imagem

Descreva o compute shader para um box blur 5×5 de uma imagem: o dispatch, os bindings, o uso (ou não) de memória de workgroup, e como o resultado seria consumido por um render pass sem readback.

Gabarito (uma boa resposta): Bindings: texture_2d<f32> de entrada + sampler (ou textureLoad direto), texture_storage_2d<rgba8unorm, write> de saída, e um uniform com o tamanho da imagem/kernel. Dispatch: workgroup 8×8, dispatchWorkgroups(ceil(W/8), ceil(H/8)); cada thread computa um pixel de saída. Ingênuo: cada thread lê 25 texels da entrada e faz a média — simples, funciona, relê muito. Otimizado: carregar um tile (10×10 = área do workgroup + borda de 2) em var<workgroup> com todos os threads cooperando, workgroupBarrier(), e cada thread lê os 25 do tile em memória rápida — menos acessos à global. Ou separar em dois passes 1D (horizontal + vertical) para 10 leituras em vez de 25. Consumo sem readback: a textura de saída do compute é usada como textura de entrada de um render pass simples (um quad em tela cheia com um fragment shader que a amostra) — os dados nunca voltam para a CPU.

MÓDULO 05 · INTERMEDIÁRIO

Render pipeline

Objetivo: o pipeline vertex+fragment, vertex buffers, uniforms, texturas, o render pass, e a relação com Three.js.

5.1 O caminho de um triângulo

5.2 O render pass

const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
  colorAttachments: [{
    view: context.getCurrentTexture().createView(),  // o canvas
    clearValue: { r:0, g:0, b:0, a:1 }, loadOp: 'clear', storeOp: 'store',
  }],
});
pass.setPipeline(renderPipeline);
pass.setBindGroup(0, uniformsBindGroup);
pass.setVertexBuffer(0, vbo);
pass.draw(vertexCount);
pass.end();
device.queue.submit([encoder.finish()]);

5.3 O canvas

5.4 Recursos de render

5.5 E o Three.js?

O Three.js tem um renderer WebGPU (WebGPURenderer) e um sistema de nós (TSL) que gera WGSL. Para a maioria dos apps 3D, use a engine — você ganha o fallback WebGL, o carregamento de modelos, a iluminação. Escreva WebGPU cru quando precisa de um pipeline que a engine não expõe, de compute integrado ao render, ou de controle total de performance. Ver Computação Gráfica & Three.js e 3D em Tempo Real & Motion Graphics para Web.

💼 Mercado de trabalho

Perguntas: "O caminho de um vértice até o pixel em WebGPU?" (vertex buffer + layout → vertex shader (clip space) → rasterização → fragment shader (cor) → color attachment), "O que o render pipeline fixa?" (shaders, vertex layout, formato do alvo, blend, depth, topologia), "Como você desenha no canvas a cada frame?" (getCurrentTexture como alvo dentro do rAF), "Quando WebGPU cru vs Three.js?".

✏️ Exercício 5 — Full-screen effect

Você quer um efeito de pós-processamento (ex.: vinheta + grão) sobre uma cena já renderizada numa textura. Descreva o render pipeline: geometria, shaders, bindings, e por que essa é a estrutura de quase todo pós-processamento.

Gabarito (uma boa resposta): Geometria: nenhuma de verdade — um "triângulo em tela cheia" gerado no vertex shader a partir de @builtin(vertex_index) (3 vértices que cobrem o clip space; nem precisa de vertex buffer). Vertex shader: emite as 3 posições e as UVs correspondentes. Fragment shader: recebe a UV, amostra a texture_2d da cena (binding 0) com um sampler (binding 1), e aplica o efeito — multiplica pela vinheta (função da distância ao centro) e soma um grão (ruído baseado na UV + tempo, vindo de um uniform). Saída: a cor final. Bindings: a textura da cena, o sampler, e um uniform com tempo/resolução/parâmetros. Por que é o padrão: quase todo pós-processamento (blur, bloom, color grading, DOF, FXAA) é "para cada pixel da imagem, leia a(s) textura(s) de entrada e compute a saída" — um fragment shader sobre um quad/triângulo full-screen, encadeável em vários passes.

MÓDULO 06 · INTERMEDIÁRIO

Performance e memória

Objetivo: minimizar submits e mudanças de estado, reusar buffers, alinhar dados, escolher o tamanho de workgroup, e medir com timestamps.

6.1 As alavancas

6.2 Alinhamento

6.3 Tamanho de workgroup

6.4 Medir

6.5 Criação de recursos

💼 Mercado de trabalho

Perguntas: "Como você reduz o custo de um frame WebGPU?" (um submit por frame, menos mudanças de pipeline/bind group, reuso de recursos, sem readback, instancing/indirect), "Por que um vec3 num uniform pode ler errado?" (alinhamento a 16 bytes; usar layout correto ou storage buffer), "Como você mede a performance de um compute pass?" (timestamp queries — não performance.now ao redor do submit assíncrono), "Que tamanho de workgroup?" (múltiplo de 32/64, medir).

✏️ Exercício 6 — Otimize o loop

Um app de simulação de partículas está a 25 fps. O código, por frame, cria os buffers de posição/velocidade, cria o compute pipeline, faz o dispatch, faz readback das posições para a CPU, e então desenha as partículas lendo de um array JS. Liste as correções em ordem de impacto.

Gabarito (uma boa resposta): (1) Não criar buffers e pipeline por frame — criar uma vez no boot (com createComputePipelineAsync) e reusar; usar dois buffers de posição (ping-pong) que ficam na GPU. (2) Eliminar o readback — o buffer de posições atualizado pelo compute é usado diretamente como vertex buffer (ou storage lido no vertex shader) do render pass, sem voltar para a CPU nem para um array JS. Isso sozinho costuma dobrar/triplicar o fps. (3) Um único submit por frame contendo o compute pass + o render pass. (4) Instancing para desenhar as N partículas com um draw(6, N) (dois triângulos por partícula) em vez de N draws. (5) Ajustar o workgroup size (testar 64/128/256) e usar timestamp queries para ver onde vai o tempo. (6) Se ainda apertado, reduzir precisão (f16) e o número de partículas visíveis (culling no compute).

MÓDULO 07 · AVANÇADO

ML no navegador com WebGPU

Objetivo: por que WebGPU acelera inferência, o que é um kernel de matmul, os limites de memória da GPU do usuário, e WebNN como alternativa de alto nível.

7.1 Por que WebGPU para ML

7.2 O kernel de matmul (a ideia)

7.3 Memória

7.4 WebNN — a alternativa de alto nível

💼 Mercado de trabalho

Perguntas: "Por que WebGPU acelera ML no navegador?" (inferência é matmul em massa — compute shaders com milhares de threads; antes era WASM/CPU ou WebGL-gambiarra), "O que torna um kernel de matmul rápido?" (tiling com memória de workgroup para reusar dados, f16, quantização), "O que limita o tamanho do modelo no navegador?" (memória da GPU do usuário e maxBufferSize — fatiar em buffers; quantizar), "O que é a WebNN?" (grafo de NN que o SO executa na NPU/GPU/CPU — alternativa de alto nível ao WebGPU para ML).

✏️ Exercício 7 — Rodar um modelo no cliente

Você quer rodar um modelo de classificação de imagem (~50 MB) 100% no navegador. Descreva a abordagem, o papel do WebGPU, o fallback, e os limites que você comunicaria ao usuário.

Gabarito (uma boa resposta): usar transformers.js ou ONNX Runtime Web com o modelo em ONNX (ou o formato da lib), quantizado se possível. A lib usa o backend WebGPU quando navigator.gpu existe (rápido); se não, cai para WASM (CPU) — funciona, mais lento. Baixar o modelo em segundo plano com barra de progresso e cachear (Cache Storage) para a segunda visita ser instantânea. Limites a comunicar/tratar: a primeira visita baixa ~50 MB; em dispositivos sem WebGPU a inferência pode levar segundos (mostrar "processando"); em celulares fracos, considerar rodar só a versão quantizada ou oferecer processar no servidor com consentimento; medir o tempo real em um Android mediano, não no desktop; se WebNN estiver disponível, a lib pode usá-la e ser mais eficiente. Ver IA Local & SLMs para o padrão geral de IA on-device.

MÓDULO 08 · AVANÇADO

Casos e padrões

Objetivo: os usos onde WebGPU se paga — simulação, processamento de imagem/vídeo, visualização massiva, física — e o padrão "compute produz, render consome".

8.1 Os casos

CasoPor que WebGPU
Simulação de partículas / N-body / boidsmilhares a milhões de agentes atualizados em paralelo por frame
Fluidos, fumaça, águagrades densas com passes de advecção/pressão — compute puro
Processamento de imagem/vídeofiltros, detecção de bordas, segmentação, efeitos em tempo real sobre cada pixel/frame
Visualização de dados massivamilhões de pontos/linhas com pan/zoom a 60 fps; agregação e culling no compute (ver DataVis Avançada)
Física (colisões, soft bodies, cloth)muitos elementos, muitas restrições resolvidas iterativamente
Geração proceduralterreno, ruído, marching cubes na GPU
ML (Módulo 7)matmul em massa
Áudio (às vezes)análise/síntese pesada fora do audio thread

8.2 O padrão "compute produz, render consome"

8.3 Interação e agregação

💼 Mercado de trabalho

Perguntas: "Dê exemplos onde WebGPU se justifica" (simulação de partículas/fluidos, processamento de imagem/vídeo, viz de milhões de pontos, física, ML), "Explique 'compute produz, render consome'" (o compute pass atualiza um buffer/textura que o render pass no mesmo submit consome — sem readback), "O que é ping-pong de buffers e para que serve?" (dois buffers alternados para estado que evolui entre frames sem read/modify/write no mesmo buffer).

✏️ Exercício 8 — Boids (flocking)

Projete a arquitetura WebGPU de uma simulação de 100 mil "boids" (pássaros) com regras de separação/alinhamento/coesão, renderizados como triângulos, a 60 fps. Descreva buffers, passes, e como evita readback.

Gabarito (uma boa resposta): Buffers: dois buffers de estado (posição + velocidade) para ping-pong, cada um array<Boid> de 100k (storage). Um uniform com os pesos das regras, o raio de visão, o dt, e os limites do mundo. Compute pass: workgroup 64, dispatchWorkgroups(ceil(100000/64)); cada thread lê o boid i do buffer "atual", percorre os vizinhos (ingênuo: todos — O(n²), pesado a 100k; melhor: um grid espacial — outro compute pass que faz o binning dos boids em células, e cada boid só olha as células vizinhas), calcula a aceleração pelas 3 regras, integra velocidade e posição, e escreve no buffer "próximo". Render pass (mesmo submit): um draw(3, 100000) com instancing; o vertex shader lê a posição/velocidade do boid instance_index do buffer "próximo" (storage), orienta o triângulo pela direção da velocidade, e emite os 3 vértices. Sem readback: o buffer que o compute escreveu é lido direto pelo vertex shader; nada volta para a CPU. Trocar o par de buffers a cada frame. Se ainda lento, o grid espacial é obrigatório, e/ou reduzir o número de vizinhos amostrados.

MÓDULO 09 · MUITO AVANÇADO

Produção

Objetivo: feature detection e fallback, "device lost", limites do adapter, segurança e fingerprinting, bateria/térmico, e debugging.

9.1 Detecção e fallback

if (!navigator.gpu) { usarFallback(); }               // WebGL2 ou CPU/WASM
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { usarFallback(); }

9.2 Device lost

9.3 Limites do adapter

9.4 Segurança e privacidade

9.5 Bateria e térmico

9.6 Debugging

💼 Mercado de trabalho

Perguntas: "O que é 'device lost' e como você lida?" (a GPU pode resetar; todos os recursos ficam inválidos — estruture o código para recriar tudo via uma função init), "Por que consultar os limites do adapter?" (variam muito por dispositivo — iGPU de celular vs desktop; não hard-code), "WebGPU tem preocupações de privacidade?" (fingerprinting via modelo/limites/features — mitigado mas presente; HTTPS obrigatório), "Como você depura um compute shader?" (validação do WebGPU, error scopes, escrever num buffer de debug e ler de volta).

✏️ Exercício 9 — Checklist de produção

Você vai enviar uma feature de "visualização interativa de 2M de pontos" com WebGPU para um site com público amplo (desktop e mobile). Monte o checklist de produção.

Gabarito (uma boa resposta): Compatibilidade: feature-detect navigator.gpu + requestAdapter; fallback para uma versão WebGL2 (ou uma viz agregada/estática em Canvas 2D) quando indisponível; testar em Chrome, Safari, Firefox nas versões-alvo. Limites: consultar adapter.limits — se maxStorageBufferBindingSize não comporta 2M de pontos num buffer, fatiar; ajustar o workgroup size ao maxComputeInvocationsPerWorkgroup. Device lost: uma função init(device) que reconstrói todos os recursos; registrar device.lost e re-inicializar. Performance: um submit por frame, sem readback (culling/agregação no compute, render consome o buffer), instancing, timestamp queries no dev; alvo 60 fps na iGPU e 30+ no celular mediano — degradar (menos pontos, sem hover) se não alcança. Energia: pausar o rAF quando document.hidden; modo de baixa qualidade opcional. Robustez: error scopes ao redor da inicialização com mensagem amigável se falhar; não travar a página se o shader não compila. Build: WGSL como módulos/strings no bundle; pipelines criados async no boot. Acessibilidade: a viz não pode ser o único jeito de acessar os dados — oferecer uma tabela/resumo; respeitar prefers-reduced-motion para animações.

MÓDULO 10 · CARREIRA

Mercado de trabalho: roadmap, entrevistas e portfólio

Objetivo: converter o conteúdo dos módulos em contratação — onde a habilidade é usada, um plano de estudo, um banco de perguntas e projetos que geram entrevista.

10.1 Onde WebGPU pesa

10.2 Roadmap de estudo (5–7 semanas)

SemanasFocoPrática
1Modelo mental e WGSL (Módulos 2–3)Seguir o webgpufundamentals.org; desenhar um triângulo e um quad texturizado do zero
2Compute (Módulo 4)Um compute que dobra um array; uma redução paralela; um blur de imagem com tile
3Render pipeline (Módulo 5)Uma cena 3D simples (cubo girando, uniforms de MVP, depth); um efeito full-screen
4Compute + render juntos (Módulo 8)Partículas com ping-pong, "compute produz render consome", sem readback
5Performance (Módulo 6)Instrumentar com timestamp queries; otimizar o app de partículas; medir na iGPU
6ML e produção (Módulos 7, 9)Rodar um modelo com transformers.js (backend WebGPU); adicionar fallback e device-lost handling
7PortfólioPublicar as demos (com fallback) e um write-up técnico

10.3 Banco de perguntas (com a resposta que aprova)

Júnior/pleno — "O que WebGPU adiciona sobre WebGL?"

Compute shaders de verdade (WGSL), menos overhead de driver (comandos gravados e submetidos em lote, estado explícito via pipelines/bind groups), suporte a multithreading, e uma API moderna mapeada para Vulkan/Metal/D3D12. É o mesmo salto que essas APIs foram sobre OpenGL.

Pleno — "Descreva o fluxo de uma operação em WebGPU."

adapter → device → queue. Criar recursos (buffers/texturas) e pipelines (compilam shaders, fixam estado). Um command encoder abre passes (compute ou render), grava setPipeline/setBindGroup/dispatch ou draw, finish() vira um command buffer, queue.submit executa — assíncrono. Ler de volta é outra operação assíncrona (mapAsync) e cara.

Pleno — "O que é um workgroup e como escolher o tamanho?"

Um grupo de threads (invocations) que compartilham var<workgroup> e podem sincronizar com workgroupBarrier(). Você faz dispatchWorkgroups(gx,gy,gz) e cada thread sabe seu global_invocation_id. Tamanho: múltiplo do wave size (32 NVIDIA, 64 AMD), 64 como default, total ≤ ~256, e medir — grande demais reduz occupancy.

Pleno — "Por que o readback CPU↔GPU é caro e como evitar?"

Ele sincroniza e tem latência — mata o pipeline se feito por frame. Evita-se deixando o resultado do compute num buffer/textura que o render pass (ou o próximo compute) consome direto, no mesmo submit — "compute produz, render consome". Para estado que evolui, ping-pong de dois buffers.

Sénior — "Como você leva uma feature WebGPU para produção com público amplo?"

Feature-detect + fallback (WebGL2 ou WASM/CPU ou degradar a feature); consultar adapter.limits e adaptar (iGPU de celular tem limites bem menores); tratar device.lost recriando todos os recursos via uma função init; um submit por frame sem readback; timestamp queries para medir; pausar quando a aba não está visível; testar em iGPU e celular; error scopes com mensagem amigável.

Sénior — "Por que WebGPU acelera ML no navegador?"

Inferência de redes é matmul em massa — o trabalho ideal para milhares de threads em compute shaders. Antes era WASM (CPU, lento) ou WebGL (gambiarra). Libs como transformers.js, WebLLM e ONNX Runtime Web usam o backend WebGPU. Os limites são a memória da GPU do usuário e maxBufferSize — daí a quantização e o fatiamento de modelos grandes. A WebNN é a alternativa de alto nível (o SO escolhe NPU/GPU/CPU).

Armadilha — "Vou escrever tudo em WebGPU cru para performance"

Para 3D comum, Three.js/Babylon (que têm backend WebGPU e fallback) entregam quase a mesma performance com uma fração do esforço e do risco. WebGPU cru se justifica em compute pesado, render muito customizado, ou quando você precisa integrar compute e render de forma que a engine não expõe. E sempre com fallback — nem todo dispositivo tem.

10.4 Projetos de portfólio que geram entrevista

  1. Simulação de partículas/boids (âncora): 100k+ agentes, compute + render no mesmo submit, ping-pong, sem readback, 60 fps, com fallback e device-lost handling — e um write-up da arquitetura e das otimizações.
  2. Pipeline de processamento de imagem: uma cadeia de efeitos (blur separável, detecção de bordas, color grading) via compute, com timestamps mostrando o custo de cada passe.
  3. Viz de milhões de pontos: scatter/mapa com pan/zoom a 60 fps, culling e agregação no compute, com uma tabela/resumo acessível como alternativa.
  4. Kernel de matmul + comparação: um matmul ingênuo e um com tiling, medidos, comparados com a CPU (WASM) — mostra que você entende a GPU.
  5. Modelo de ML no cliente: transformers.js com backend WebGPU + fallback WASM, com o estudo de download/cache e latência em vários dispositivos.

10.5 Fontes para continuar

🏁 Síntese final da apostila

Cinco ideias sustentam o WebGPU: (1) é render + compute no navegador com uma API moderna — o WebGL só desenhava; (2) o modelo é "grave comandos e submeta" (adapter → device → queue; nada é imediato) e o readback é caro — deixe os dados na GPU; (3) WGSL organiza o trabalho em workgroups de threads (múltiplo de 32/64) com memória compartilhada e barreiras; (4) o padrão vencedor é "compute produz, render consome" no mesmo submit, com ping-pong para estado; (5) em produção, feature-detect + fallback, tratar device lost, consultar os limites do adapter, e medir com timestamp queries — nem todo dispositivo tem, e os que têm são muito diferentes.