Da WebGL a WebGPU

François Beaufort
François Beaufort

In qualità di sviluppatore WebGL, potresti essere sia spaventato che entusiasta di iniziare a utilizzare WebGPU, il successore di WebGL che porta sul web i progressi delle API di grafica moderne.

È rassicurante sapere che WebGL e WebGPU condividono molti concetti fondamentali. Entrambe le API ti consentono di eseguire piccoli programmi chiamati shader sulla GPU. WebGL supporta Shaper vertex e fragments, mentre WebGPU supporta anche gli strumenti per computing computing. WebGL utilizza OpenGL Shading Language (GLSL), mentre WebGPU utilizza WebGPU Shading Language (WGSL). Sebbene le due lingue siano diverse, i concetti di base sono per lo più gli stessi.

Tenendo presente questo, questo articolo mette in evidenza alcune differenze tra WebGL e WebGPU per aiutarti a iniziare.

Stato globale

WebGL ha molto stato globale. Alcune impostazioni si applicano a tutte le operazioni di rendering, ad esempio quali texture e buffer sono legati. Imposti questo stato globale chiamando varie funzioni dell'API e rimane in vigore finché non lo modifichi. Lo stato globale in WebGL è una principale fonte di errori, in quanto è facile dimenticare di modificare un'impostazione globale. Inoltre, lo stato globale rende difficile la condivisione del codice, poiché gli sviluppatori devono fare attenzione a non modificare accidentalmente lo stato globale in modo da influire su altre parti del codice.

WebGPU è un'API stateless e non mantiene uno stato globale. Utilizza invece il concetto di pipeline per incapsulare tutto lo stato di rendering che era globale in WebGL. Una pipeline contiene informazioni come combinazione, topologia e attributi da utilizzare. Una pipeline è immutabile. Se vuoi modificare alcune impostazioni, devi creare un'altra pipeline. WebGPU utilizza anche encoder di comando per raggruppare i comandi ed eseguirli nell'ordine in cui sono stati registrati. Questo è utile, ad esempio, per la mappatura delle ombre, in cui, in un unico passaggio sugli oggetti, l'applicazione può registrare più stream di comandi, uno per ogni mappa delle ombre della luce.

Ricapitolando, poiché il modello di stato globale di WebGL rendeva difficile e fragile la creazione di librerie e applicazioni robuste e componibili, WebGPU ha ridotto in modo significativo la quantità di stato di cui gli sviluppatori dovevano tenere traccia durante l'invio di comandi alla GPU.

Non sincronizzare più

Sulle GPU, in genere non è efficiente inviare comandi e attenderne la risposta in modo sincrono, in quanto ciò può svuotare la pipeline e causare bolle. Questo vale soprattutto per WebGPU e WebGL, che utilizzano un'architettura multi-processo con il driver GPU in esecuzione in un processo separato da JavaScript.

In WebGL, ad esempio, la chiamata a gl.getError() richiede un IPC sincrono dal processo JavaScript al processo GPU e viceversa. Questo può causare una bolla sul lato CPU quando i due processi comunicano.

Per evitare questi problemi, WebGPU è progettato per essere completamente asincrono. Il modello di errore e tutte le altre operazioni vengono eseguite in modo asincrono. Ad esempio, quando crei una trama, l'operazione sembra riuscire immediatamente, anche se la trama è in realtà un errore. Puoi rilevare l'errore solo in modo asincrono. Questo design evita le bolle di comunicazione tra processi e offre alle applicazioni prestazioni affidabili.

Shader di computing

Gli shader di calcolo sono programmi che vengono eseguiti sulla GPU per eseguire calcoli generici. Sono disponibili solo in WebGPU, non in WebGL.

A differenza degli shader vertex e fragment, non sono limitati all'elaborazione grafica e possono essere utilizzati per un'ampia gamma di attività, come il machine learning, la simulazione fisica e il calcolo scientifico. Gli Shader di computing vengono eseguiti in parallelo da centinaia o anche migliaia di thread, il che li rende molto efficienti per l'elaborazione di set di dati di grandi dimensioni. Scopri di più sul computing GPU e altri dettagli in questo articolo dettagliato su WebGPU.

Elaborazione dei fotogrammi video

L'elaborazione dei frame video utilizzando JavaScript e WebAssembly presenta alcuni svantaggi: il costo della copia dei dati dalla memoria GPU alla memoria della CPU e il parallelismo limitato che può essere ottenuto con worker e thread della CPU. WebGPU non ha queste limitazioni, perciò si adatta perfettamente all'elaborazione di fotogrammi video grazie alla sua stretta integrazione con l'API WebCodecs.

Il seguente snippet di codice mostra come importare un VideoFrame come texture esterna in WebGPU ed elaborarlo. Puoi provare questa demo.

// 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
}

Portabilità delle applicazioni per impostazione predefinita

WebGPU ti forza a richiedere limits. Per impostazione predefinita, requestDevice() restituisce un oggetto GPUDevice che potrebbe non corrispondere alle funzionalità hardware del dispositivo fisico, ma piuttosto a un denominatore comune ragionevole e minimo di tutte le GPU. Richiedendo agli sviluppatori di richiedere limiti di dispositivi, WebGPU garantisce che le applicazioni vengano eseguite sul maggior numero possibile di dispositivi.

Gestione della tela

WebGL gestisce automaticamente il canvas dopo aver creato un contesto WebGL e fornito attributi di contesto come alpha, antialias, colorSpace, depth, PreserveDrawingBuffer o stencil.

WebGPU, invece, richiede di gestire autonomamente la tela. Ad esempio, per ottenere l'antialiasing in WebGPU, devi creare una texture multisample e eseguire il rendering in base a questa. Quindi, risolvi la texture multicampione in una texture regolare e disegna quella texture sulla tela. Questa gestione manuale consente di inviare a tutti i canvas che desideri da un singolo oggetto GPUDevice. Al contrario, WebGL può creare un solo contesto per canvas.

Dai un'occhiata alla demo di più canvas WebGPU.

Tieni presente che al momento i browser hanno un limite al numero di canvas WebGL per pagina. Al momento della stesura di questo articolo, Chrome e Safari possono utilizzare contemporaneamente solo fino a 16 canvas WebGL, mentre Firefox può crearne fino a 200. D'altra parte, non esiste un limite al numero di canvas WebGPU per pagina.

Screenshot che mostra il numero massimo di canvas WebGL nei browser Safari, Chrome e Firefox
Numero massimo di canvas WebGL in Safari, Chrome e Firefox (da sinistra a destra) - demo.

Messaggi di errore utili

WebGPU fornisce uno stack di chiamate per ogni messaggio restituito dall'API. Ciò significa che puoi vedere rapidamente dove si è verificato l'errore nel codice, il che è utile per il debug e la correzione degli errori.

Oltre a fornire uno stack di chiamate, i messaggi di errore WebGPU sono anche facili da comprendere e utili. In genere i messaggi di errore includono una descrizione dell'errore e suggerimenti per correggerlo.

WebGPU ti consente anche di fornire un label personalizzato per ogni oggetto WebGPU. Questa etichetta viene poi utilizzata dal browser nei messaggi GPUError, negli avvisi della console e negli strumenti per sviluppatori del browser.

Dai nomi agli indici

In WebGL, molte cose sono collegate tramite nomi. Ad esempio, puoi dichiarare una variabile uniforme chiamata myUniform in GLSL e ottenere la relativa posizione utilizzando gl.getUniformLocation(program, 'myUniform'). Questo è utile perché ricevi un errore se digiti erroneamente il nome della variabile uniforme.

In WebGPU, invece, tutto è completamente collegato tramite offset in byte o indice (spesso chiamato location). È tua responsabilità mantenere sincronizzate le posizioni del codice in WGSL e JavaScript.

Generazione di MIP map

In WebGL, puoi creare il mip level 0 di una texture e poi chiamare gl.generateMipmap(). WebGL genererà quindi tutti gli altri livelli mip.

In WebGPU, devi generare autonomamente le mipmap. Non esiste una funzione integrata per farlo. Per saperne di più sulla decisione, consulta la discussione sulle specifiche. Puoi usare pratiche librerie come webgpu-utils per generare mipmap o imparare a fare da te.

Buffer di archiviazione e texture di archiviazione

Gli uniform buffer sono supportati sia da WebGL che da WebGPU e ti consentono di passare parametri costanti di dimensioni limitate agli shader. I buffer di archiviazione, che sembrano molto simili ai buffer uniformi, sono supportati solo da WebGPU e sono più potenti e flessibili dei buffer uniformi.

  • I dati dei buffer di archiviazione passati agli shader possono essere molto più grandi dei buffer uniformi. Sebbene le specifiche indichino che le associazioni di buffer uniformi possono avere una dimensione massima di 64 KB (vedi maxUniformBufferBindingSize), la dimensione massima di un'associazione di buffer di archiviazione è di almeno 128 MB in WebGPU (vedi maxStorageBufferBindingSize).

  • I buffer di archiviazione sono scrivibili e supportano alcune operazioni atomiche, mentre i buffer uniformi sono solo di sola lettura. Ciò consente di implementare nuove classi di algoritmi.

  • Le associazioni dei buffer di archiviazione supportano array con dimensioni di runtime per algoritmi più flessibili, mentre nello shaker devono essere fornite dimensioni uniformi degli array di buffer.

Le texture di archiviazione sono supportate solo in WebGPU e sono per le texture ciò che gli storage buffer sono per gli uniform buffer. Sono più flessibili delle texture normali e supportano le scritture con accesso casuale (e in futuro anche le letture).

Modifiche di buffer e texture

In WebGL, puoi creare un buffer o una texture e modificarne le dimensioni in qualsiasi momento, ad esempio con gl.bufferData() e gl.texImage2D().

In WebGPU, i buffer e le texture sono immutabili. Ciò significa che non puoi modificarne le dimensioni, l'utilizzo o il formato dopo averli creati. Puoi modificare solo i contenuti.

Differenze nelle convenzioni spaziali

In WebGL, l'intervallo di spazio clip in Z va da -1 a 1. In WebGPU, l'intervallo dello spazio di clip Z va da 0 a 1. Ciò significa che gli oggetti con un valore z pari a 0 sono i più vicini alla fotocamera, mentre quelli con un valore z pari a 1 sono i più lontani.

Illustrazione degli intervalli dello spazio clip Z in WebGL e WebGPU.
Aree di spazio clip Z in WebGL e WebGPU.

WebGL utilizza la convenzione OpenGL, in cui l'asse Y è rivolto verso l'alto e l'asse Z verso lo spettatore. WebGPU utilizza la convenzione Metal, in cui l'asse Y è rivolto verso il basso e l'asse Z è fuori dallo schermo. Tieni presente che la direzione dell'asse Y è verso il basso nelle coordinate del framebuffer, nelle coordinate dell'area visibile e nelle coordinate del frammento/del pixel. Nello spazio dedicato ai clip, la direzione dell'asse Y è ancora aperta come in WebGL.

Ringraziamenti

Grazie a Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell e Rachel Andrew per aver esaminato questo articolo.

Consiglio anche di visitare il sito WebGPUFundamentals.org per un'analisi approfondita delle differenze tra WebGPU e WebGL.