Da WebGL para a WebGPU

François Beaufort
François Beaufort

Como desenvolvedor WebGL, você pode se sentir intimidado e animado para começar a usar a WebGPU, a sucessora da WebGL que traz os avanços das APIs gráficas modernas para a Web.

É reconfortante saber que o WebGL e a WebGPU compartilham muitos conceitos fundamentais. As duas APIs permitem que você execute pequenos programas chamados sombreadores na GPU. O WebGL oferece suporte a sombreadores de vértice e fragmento, enquanto a WebGPU também oferece suporte a sombreadores de computação. O WebGL usa a OpenGL Shading Language (GLSL), enquanto a WebGPU usa a WebGPU Shading Language (WGSL). Embora as duas linguagens sejam diferentes, os conceitos subjacentes são basicamente os mesmos.

Pensando nisso, este artigo destaca algumas diferenças entre a WebGL e a WebGPU, para ajudar você a começar.

Estado global

O WebGL tem muitos estados globais (link em inglês). Algumas configurações se aplicam a todas as operações de renderização, como quais texturas e buffers estão vinculados. Para definir esse estado global, chame várias funções da API, e ele permanece em vigor até ser alterado. O estado global no WebGL é uma principal fonte de erros (link em inglês), já que é fácil esquecer de alterar uma configuração global. Além disso, o estado global dificulta o compartilhamento de código, porque os desenvolvedores precisam ter cuidado para não alterar acidentalmente o estado global de maneira que afete outras partes do código.

A WebGPU é uma API sem estado e não mantém um estado global. Em vez disso, ele usa o conceito de pipeline para encapsular todo o estado de renderização que era global no WebGL. Um pipeline contém informações como qual combinação, topologia e atributos usar. Um pipeline é imutável. Se você quiser alterar algumas configurações, precisará criar outro pipeline. A WebGPU também usa codificadores de comando para agrupar comandos e executá-los na ordem em que foram gravados. Isso é útil no mapeamento de sombras, por exemplo, em que, em uma única passagem sobre os objetos, o aplicativo pode gravar vários fluxos de comando, um para o mapa de sombras de cada luz.

Para resumir, como o modelo de estado global do WebGL tornava a criação de aplicativos e bibliotecas combináveis robustas e frágil, a WebGPU reduziu significativamente a quantidade de estados que os desenvolvedores precisavam acompanhar ao enviar comandos à GPU.

Chega de sincronizar

Em GPUs, normalmente é ineficiente enviar comandos e esperar por eles de forma síncrona, porque isso pode limpar o pipeline e causar balões. Isso vale principalmente para a WebGPU e a WebGL, que usam uma arquitetura de processos múltiplos com o driver da GPU sendo executado em um processo separado do JavaScript.

No WebGL, por exemplo, chamar gl.getError() exige uma IPC síncrona do processo JavaScript para o processo da GPU e vice-versa. Isso pode causar uma bolha no lado da CPU quando os dois processos se comunicam.

Para evitar esses balões, a WebGPU foi projetada para ser completamente assíncrona. O modelo de erro e todas as outras operações ocorrem de forma assíncrona. Por exemplo, quando você cria uma textura, a operação parece ser concluída imediatamente, mesmo que a textura seja, na verdade, um erro. Só é possível descobrir o erro de forma assíncrona. Esse design mantém a comunicação entre processos livre de bolhas e oferece aos aplicativos um desempenho confiável.

Sombreadores de computação

Sombreadores de computação são programas executados na GPU para fazer cálculos de uso geral. Elas estão disponíveis apenas na WebGPU, não na WebGL.

Ao contrário dos sombreadores de vértice e fragmento, eles não se limitam ao processamento gráfico e podem ser usados para diversas tarefas, como aprendizado de máquina, simulação física e computação científica. Os sombreadores de computação são executados em paralelo por centenas ou até milhares de linhas de execução, o que os torna muito eficientes para o processamento de grandes conjuntos de dados. Saiba mais sobre computação de GPU e veja mais detalhes neste artigo extenso sobre a WebGPU.

Processamento de frames de vídeo

O processamento de frames de vídeo usando JavaScript e WebAssembly tem algumas desvantagens: o custo de copiar os dados da memória da GPU para a memória da CPU e o paralelismo limitado que pode ser alcançado com workers e linhas de execução de CPU. A WebGPU não tem essas limitações, o que a torna ideal para processar frames de vídeo graças à sua forte integração com a API WebCodecs.

O snippet de código a seguir mostra como importar um VideoFrame como uma textura externa na WebGPU e processá-lo. Confira esta demonstração.

// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...

(function render() {
  const videoFrame = new VideoFrame(video);
  applyFilter(videoFrame);
  requestAnimationFrame(render);
})();

function applyFilter(videoFrame) {
  const texture = device.importExternalTexture({ source: videoFrame });
  const bindgroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: texture }],
  });
  // Finally, submit commands to GPU
}

Portabilidade de aplicativos por padrão

A WebGPU força você a solicitar limits. Por padrão, requestDevice() retorna um GPUDevice que pode não corresponder aos recursos de hardware do dispositivo físico, mas com um denominador comum razoável e mais baixo de todas as GPUs. Ao exigir que os desenvolvedores solicitem limites de dispositivos, a WebGPU garante que os aplicativos sejam executados no maior número possível de dispositivos.

Processamento de telas

O WebGL gerencia automaticamente a tela depois que você cria um contexto WebGL e fornece atributos de contexto, como alpha, antialias, colorSpace, depth, preserveDrawingBuffer ou stencil.

Por outro lado, a WebGPU exige que você gerencie a tela por conta própria. Por exemplo, para conseguir suavização na WebGPU, você precisa criar uma textura multiamostra e renderizá-la. Em seguida, você resolveria a textura multiamostra para uma textura regular e desenha essa textura na tela. Esse gerenciamento manual permite gerar quantas telas quiser a partir de um único objeto GPUDevice. Por outro lado, o WebGL só pode criar um contexto por tela.

Confira a demonstração de várias telas da WebGPU.

Além disso, os navegadores atualmente têm um limite quanto ao número de telas WebGL por página. Atualmente, o Chrome e o Safari só podem usar até 16 canvas WebGL simultaneamente. O Firefox pode criar até 200 deles. Por outro lado, não há limite para o número de telas da WebGPU por página.

Captura de tela mostrando o número máximo de telas WebGL nos navegadores Safari, Chrome e Firefox
O número máximo de telas WebGL no Safari, Chrome e Firefox (da esquerda para a direita): demonstração.

Mensagens de erro úteis

A WebGPU fornece uma pilha de chamadas para cada mensagem retornada da API. Isso significa que é possível ver rapidamente onde o erro ocorreu no código, o que é útil para depurar e corrigir erros.

Além de fornecer uma pilha de chamadas, as mensagens de erro da WebGPU também são fáceis de entender e acionáveis. As mensagens de erro normalmente incluem uma descrição do erro e sugestões de como corrigi-lo.

A WebGPU também permite fornecer uma label personalizada para cada objeto WebGPU. Esse rótulo é usado pelo navegador em mensagens de erro de GPU, avisos do console e ferramentas para desenvolvedores do navegador.

De nomes a índices

No WebGL, muitas coisas são conectadas por nomes. Por exemplo, você pode declarar uma variável uniforme chamada myUniform em GLSL e conseguir a localização dela usando gl.getUniformLocation(program, 'myUniform'). Isso é útil quando você recebe um erro se digitar incorretamente o nome da variável uniforme.

Por outro lado, na WebGPU, tudo é totalmente conectado por deslocamento de byte ou índice (geralmente chamado de local). É sua responsabilidade manter os locais do código na WGSL e no JavaScript sincronizados.

Geração de mipmap

No WebGL, você pode criar um nível 0 de textura e chamar gl.generateMipmap(). O WebGL gera todos os outros níveis de mip para você.

Na WebGPU, você precisa gerar mipmaps por conta própria. Não há uma função integrada para fazer isso. Consulte a discussão sobre especificações para saber mais sobre a decisão. É possível usar bibliotecas práticas, como webgpu-utils, para gerar mipmaps ou aprender a fazer isso por conta própria.

Buffers e texturas de armazenamento

Os buffers uniformes têm suporte ao WebGL e à WebGPU e permitem transmitir parâmetros constantes de tamanho limitado aos sombreadores. Os buffers de armazenamento, que se parecem muito com buffers de uniforme, têm suporte apenas da WebGPU e são mais potentes e flexíveis do que buffers de uniforme.

  • Os dados dos buffers de armazenamento transmitidos aos sombreadores podem ser muito maiores do que os buffers de uniforme. Embora a especificação indique que as vinculações de buffers uniformes podem ter até 64 KB de tamanho (consulte maxUniformBufferBindingSize) , o tamanho máximo de uma vinculação de buffer de armazenamento é de pelo menos 128 MB na WebGPU (consulte maxStorageBufferBindingSize).

  • Os buffers de armazenamento são graváveis e aceitam algumas operações atômicas, enquanto os buffers de uniforme são apenas somente leitura. Isso permite a implementação de novas classes de algoritmos.

  • As vinculações de buffers de armazenamento oferecem suporte a matrizes de tamanho de ambiente de execução para algoritmos mais flexíveis, enquanto tamanhos uniformes de matriz de buffer precisam ser fornecidos no sombreador.

Texturas de armazenamento só têm suporte na WebGPU e são para texturas o que os buffers de armazenamento são para buffers de uniforme. Elas são mais flexíveis do que as texturas regulares, com suporte para gravações de acesso aleatório (e leituras também no futuro).

Mudanças no buffer e na textura

No WebGL, você pode criar um buffer ou uma textura e alterar o tamanho dele a qualquer momento com gl.bufferData() e gl.texImage2D(), respectivamente.

Na WebGPU, os buffers e as texturas são imutáveis. Isso significa que não é possível alterar o tamanho, o uso ou o formato depois de criados. Só é possível alterar o conteúdo.

Diferenças das convenções espaciais

Na WebGL, o intervalo do espaço de corte Z é de -1 a 1. Na WebGPU, o intervalo do espaço de corte Z é de 0 a 1. Isso significa que os objetos com um valor z de 0 estão os mais próximos da câmera, enquanto os objetos com um valor de z de 1 estão os mais distantes.

Ilustração de intervalos de espaço de corte Z no WebGL e na WebGPU.
Intervalos de espaço de corte Z no WebGL e na WebGPU.

O WebGL usa a convenção do OpenGL, em que o eixo Y é para cima e o eixo Z está na direção do espectador. A WebGPU usa a convenção Metal, em que o eixo Y está para baixo e o eixo Z está fora da tela. A direção do eixo Y é para baixo nas coordenadas do framebuffer, das coordenadas da janela de visualização e das coordenadas do fragmento/pixel. No espaço de corte, a direção do eixo Y ainda está para cima, como no WebGL.

Agradecimentos

Agradecemos a Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell e Rachel Andrew por revisar este artigo.

Também recomendo o site WebGPUFundamentals.org (em inglês) para entender as diferenças entre WebGPU e WebGL.