Da WebGL para a WebGPU

Francisco Beaufort
François Beaufort

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

É reconfortante saber que o WebGL e a WebGPU compartilham muitos conceitos básicos. As duas APIs permitem executar pequenos programas, chamados sombreadores, na GPU. O WebGL oferece suporte a sombreadores de vértice e fragmento, enquanto a WebGPU também suporta 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.

Com isso em mente, 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 se aplicam a todas as operações de renderização, como quais texturas e buffers estão vinculados. Você define esse estado global chamando várias funções da API, e ele permanece em vigor até que você o altere. O estado global na WebGL é uma grande fonte de erros, porque é fácil se 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 mudar acidentalmente o estado global de uma 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 global no WebGL. Um pipeline contém informações como qual combinação, topologia e atributos usar. Um pipeline é imutável. Se você quiser mudar 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 sombra, por exemplo, em que, em uma única passagem sobre os objetos, o aplicativo pode registrar vários fluxos de comando, um para cada mapa de sombra de luz.

Resumindo, como o modelo de estado global do WebGL dificultava e frágil 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 sincronize mais

Em GPUs, normalmente é ineficiente enviar comandos e aguardar eles de forma síncrona, já que isso pode limpar o pipeline e causar balões. Isso é especialmente verdadeiro na WebGPU e WebGL, que usam uma arquitetura de vários processos com o driver da GPU em execução em um processo separado do JavaScript.

Em WebGL, por exemplo, chamar gl.getError() requer uma IPC síncrona do processo JavaScript para o processo da GPU e vice-versa. Isso pode gerar 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 realmente 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 um desempenho confiável dos aplicativos.

Sombreadores de computação

Sombreadores de computação são programas executados na GPU para realizar cálculos de uso geral. Elas estão disponíveis somente na WebGPU, não no 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 machine learning, 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 processar grandes conjuntos de dados. Saiba mais sobre a computação da GPU e veja mais detalhes neste artigo extenso sobre a WebGPU.

Processamento de frames do 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 GPU para a memória 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 o torna ideal para processar frames de vídeo devido à sua 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. 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 um denominador comum mais razoável 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 de dispositivos possível.

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 de várias amostras e renderizá-la. Em seguida, você resolveria a textura de várias amostras como uma textura normal e a desenharia na tela. Esse gerenciamento manual permite que você produza em 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 WebGPU.

Vale lembrar que os navegadores atualmente têm um limite no número de telas WebGL por página. No momento em que este artigo foi escrito, o Google Chrome e o Safari só podem usar até 16 telas WebGL simultaneamente. O Firefox pode criar até 200 telas. Por outro lado, não há limite no 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 você pode ver rapidamente onde ocorreu o erro 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, em avisos do console e nas 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 com o nome myUniform na GLSL e conseguir a localização dela usando gl.getUniformLocation(program, 'myUniform'). Isso é útil porque você recebe um erro se digitar incorretamente o nome da variável uniforme.

Por outro lado, na WebGPU, tudo está totalmente conectado pelo deslocamento de byte ou índice (muitas vezes chamado de localização). É sua responsabilidade manter os locais do código em WGSL e JavaScript sincronizados.

Geração de mipmaps

No WebGL, é possível criar o 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ê.

Na WebGPU, você precisa gerar mipmaps por conta própria. Não há funções integradas para fazer isso. Confira a discussão de especificações para saber mais sobre a decisão. Você pode usar bibliotecas úteis, como webgpu-utils, para gerar mipmaps ou aprender a fazer isso por conta própria.

Buffers de armazenamento e texturas de armazenamento

Buffers uniformes são compatíveis com WebGL e WebGPU e permitem que você transmita parâmetros constantes de tamanho limitado para sombreadores. Os buffers de armazenamento, que se parecem com os uniformes, só têm suporte da WebGPU e são mais potentes e flexíveis do que os uniformes.

  • Os dados de buffers de armazenamento transmitidos aos sombreadores podem ser muito maiores do que buffers uniformes. Embora a especificação indique que vinculações uniformes de buffers 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 são graváveis e dão 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 tempo de execução para algoritmos mais flexíveis, enquanto tamanhos uniformes de matrizes de buffer precisam ser fornecidos no sombreador.

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

Mudanças no buffer e na textura

Em WebGL, é possível criar um buffer ou textura e mudar o tamanho dele a qualquer momento usando gl.bufferData() e gl.texImage2D(), por exemplo.

Na WebGPU, buffers e 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 mudar o conteúdo.

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

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

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

O WebGL usa a convenção OpenGL, em que o eixo Y é para cima e o eixo Z está em direção ao visualizador. A WebGPU usa a convenção de metal, em que o eixo Y fica para baixo e o eixo Z está fora da tela. Observe que a direção do eixo Y está abaixo da coordenada framebuffer, da janela de visualização e de fragmento/pixel. No espaço de corte, a direção do eixo Y ainda é para cima, como em WebGL.

Agradecimentos

Agradecemos a Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell e Rachel Andrew pela revisão deste artigo.

Também recomendo o WebGPUFundamentals.org (link em inglês) para você ver as diferenças entre a WebGPU e o WebGL.