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, o sucessor 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. A 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 basicamente os mesmos.

Pensando nisso, este artigo destaca algumas diferenças entre o WebGL e o 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. Você define esse estado global chamando várias funções de API, e ele permanece em vigor até que você o mude. 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 qual mesclagem, topologia 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 tornava a criação de bibliotecas e aplicativos robustos e combináveis difícil e frágil, 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 é especialmente verdadeiro no WebGPU e no WebGL, que usam uma arquitetura de vários processos com o driver de GPU em execução 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 essas bolhas, 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 sem bolhas e oferece desempenho confiável aos aplicativos.

Sombreadores de computação

Os sombreadores de computação são programas executados na GPU para realizar 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 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 da CPU. A WebGPU não tem essas limitações, o que a torna uma ótima opção para processar frames de vídeo, graças à 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 exemplo, para conseguir o antialiasing na WebGPU, você precisa criar uma textura de amostragem múltipla e renderizá-la. Em seguida, você resolveria a textura de várias amostras para uma textura regular e renderizaria essa textura na tela. Esse gerenciamento manual permite que você envie a saída para quantas telas quiser usando um único objeto GPUDevice. Em contraste, a 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, no WebGPU, tudo é totalmente conectado por deslocamento ou índice de byte (muitas vezes chamado de localização). É sua responsabilidade manter os locais do código em WGSL e JavaScript sincronizados.

Geração de mipmap

No WebGL, é possível criar um mip de nível 0 de uma textura e, em seguida, 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 a especificação para saber mais sobre a decisão. Você pode usar bibliotecas úteis, como webgpu-utils, para gerar mipmaps ou aprender a criar.

Armazenamento de buffers e texturas

Os buffers uniformes têm suporte da WebGL e da WebGPU e permitem transmitir parâmetros constantes de tamanho limitado para shaders. 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 de buffers de armazenamento transmitidos para sombreadores podem ser muito maiores que os buffers uniformes. 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 comuns e oferecem suporte a gravações de acesso aleatório (e leituras no futuro).

Mudanças de buffer e 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 varia de -1 a 1. Na 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 do Metal, em que o eixo Y está para baixo e o eixo Z está fora da tela. A direção do eixo Y está para baixo nas coordenadas do frame buffer, da viewport e 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.