Da WebGL para a WebGPU

François Beaufort
François Beaufort

Como desenvolvedor do WebGL, você pode ficar com medo e empolgação ao começar a usar a WebGPU, a sucessora do WebGL que traz os avanços das APIs gráficas modernas para a Web.

É bom saber que o WebGL e o WebGPU compartilham muitos conceitos principais. Ambas as APIs permitem executar pequenos programas chamados shaders 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 linguagem de sombreamento OpenGL (GLSL), enquanto a WebGPU usa a linguagem de sombreamento WebGPU (WGSL). Embora os dois idiomas sejam diferentes, os conceitos subjacentes são praticamente os mesmos.

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

Estado global

O WebGL tem muito estado global. Algumas configurações são aplicadas a todas as operações de renderização, como quais texturas e buffers sã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 fonte importante de erros, porque é fácil esquecer de mudar uma configuração global. Além disso, o estado global dificulta o compartilhamento de código, já que os desenvolvedores precisam ter cuidado para não mudar o estado global acidentalmente de uma forma 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 um pipeline para encapsular todo o estado de renderização que era global no WebGL. Um pipeline contém informações como quais mesclagens, topologias e atributos usar. Um pipeline é imutável. Se você quiser mudar algumas configurações, crie 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 sombra, por exemplo, em que, em uma única passagem pelos objetos, o aplicativo pode registrar vários streams de comando, um para cada mapa de sombra da luz.

Resumindo, como o modelo de estado global do WebGL dificultava a criação de bibliotecas e aplicativos robustos e combináveis, a WebGPU reduziu significativamente a quantidade de estado que os desenvolvedores precisavam acompanhar ao enviar comandos para a GPU.

Não sincronizar mais

Em GPUs, normalmente é ineficiente enviar comandos e aguardar por eles de forma síncrona, já que isso pode limpar o pipeline e causar bolhas. 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() requer um IPC síncrono do processo JavaScript para o processo da GPU e vice-versa. Isso pode causar uma bolha no lado da CPU à medida que 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 bem-sucedida imediatamente, mesmo que a textura seja 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 de fragmentos, eles não são limitados ao processamento gráfico e podem ser usados para uma ampla variedade de tarefas, como aprendizado de máquina, simulação de 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 processar grandes conjuntos de dados. Saiba mais sobre o cálculo de GPU e mais detalhes neste artigo completo sobre o 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 da 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 abaixo mostra como importar e processar uma VideoFrame como uma textura externa na WebGPU. Teste 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 sim a um denominador comum razoável e mais baixo de todas as GPUs. Ao exigir que os desenvolvedores solicitem limites de dispositivo, a WebGPU garante que os aplicativos sejam executados no maior número possível de dispositivos.

Processamento de tela

O WebGL gerencia automaticamente a tela depois que você cria um contexto do WebGL e fornece atributos de contexto, como alfa, 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 que você gere saídas para quantas telas quiser usando 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.

Vale lembrar que os navegadores atualmente têm um limite no número de telas WebGL por página. No momento da escrita, o Chrome e o Safari só podem usar até 16 telas WebGL simultaneamente. O Firefox pode criar até 200. Por outro lado, não há limite para o número de telas WebGPU por página.

Captura de tela com o número máximo de telas do 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

O WebGPU fornece uma pilha de chamadas para cada mensagem retornada pela API. Isso significa que você pode saber 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 geralmente incluem uma descrição do erro e sugestões de como corrigi-lo.

A WebGPU também permite fornecer um label personalizado para cada objeto da WebGPU. Esse rótulo é usado pelo navegador em mensagens de GPUError, avisos do console e ferramentas para desenvolvedores de navegadores.

De nomes para índices

No WebGL, muitas coisas são conectadas por nomes. Por exemplo, é possível declarar uma variável uniforme chamada myUniform no GLSL e receber a localização dela usando gl.getUniformLocation(program, 'myUniform'). Isso é útil porque você vai receber 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 vai gerar todos os outros níveis de mip para você.

No WebGPU, você precisa gerar mipmaps. Não há uma função integrada para fazer isso. Consulte a discussão sobre especificações para saber mais sobre a decisão. Você pode usar bibliotecas úteis, como webgpu-utils, para gerar mipmaps ou aprender a fazê-lo 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 uniformes, têm suporte apenas da WebGPU e são mais poderosos e flexíveis do que os buffers uniformes.

  • Os dados dos buffers de armazenamento transmitidos aos sombreadores podem ser muito maiores do que os buffers de uniforme. Embora a especificação diga que as vinculações de buffers uniformes podem ter até 64 KB (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 podem ser gravados e oferecem suporte a algumas operações atômicas, enquanto os buffers uniformes são 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 execução para algoritmos mais flexíveis, enquanto os tamanhos de matriz de buffer uniforme precisam ser fornecidos no shader.

As texturas de armazenamento têm suporte apenas no WebGPU e são para texturas o que os buffers de armazenamento são para buffers uniformes. 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, é possível criar um buffer ou uma textura e mudar o tamanho 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 mudar o tamanho, o uso ou o formato deles depois que são criados. Só é possível mudar o conteúdo.

Diferenças na convenção de espaço

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

Ilustração dos intervalos de espaço de clipe Z no WebGL e no WebGPU.
Os intervalos de espaço de corte Z no WebGL e no WebGPU.

O WebGL usa a convenção OpenGL, em que o eixo Y está para cima e o eixo Z está em direção ao 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 clipe, 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 WebGPUFundamentals.org para uma análise detalhada das diferenças entre WebGPU e WebGL.