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 gli shader vertex e fragment, mentre WebGPU supporta anche gli shader compute. 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 senza stato 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 quali le modalità di miscelazione, la topologia e gli attributi da utilizzare. Una pipeline è immutabile. Se vuoi modificare alcune impostazioni, devi creare un'altra pipeline. WebGPU utilizza anche gli encoder dei comandi per raggruppare i comandi ed eseguirli nell'ordine in cui sono stati registrati. Questo è utile, ad esempio, nella mappatura delle ombre, dove, in un unico passaggio sugli oggetti, l'applicazione può registrare più stream di comandi, uno per ogni mappa delle ombre della luce.

In sintesi, poiché il modello di stato globale di WebGL rendeva difficile e fragile la creazione di librerie e applicazioni composibili e robuste, WebGPU ha ridotto notevolmente la quantità di stato che gli sviluppatori dovevano monitorare 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 è particolarmente vero in WebGPU e WebGL, che utilizzano un'architettura multiprocesso con il driver della 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. Ciò può causare una bolla lato CPU durante la comunicazione dei due processi.

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 calcolo

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 machine learning, simulazione fisica e calcolo scientifico. Gli shader di calcolo vengono eseguiti in parallelo da centinaia o addirittura migliaia di thread, il che li rende molto efficienti per l'elaborazione di set di dati di grandi dimensioni. Scopri di più sul calcolo GPU e su 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 raggiunto con i thread di lavoro e della CPU. WebGPU non presenta queste limitazioni, il che lo rende ideale per l'elaborazione dei frame 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 obbliga 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 la tela dopo che hai creato un contesto WebGL e fornito attributi del 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. Poi, devi risolvere la texture multisample in una texture normale e disegnarla sulla tela. Questa gestione manuale ti consente di eseguire l'output su quanti canvas vuoi da un singolo oggetto GPUDevice. Al contrario, WebGL può creare un solo contesto per canvas.

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

A proposito, i browser attualmente hanno un limite per il 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
Il 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. I messaggi di errore in genere includono una descrizione dell'errore e suggerimenti su come 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 utilizzare librerie utili come webgpu-utils per generare mipmap o scoprire come farlo autonomamente.

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. Gli storage buffer, che assomigliano molto 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 di buffer di archiviazione supportano gli array di dimensioni di runtime per algoritmi più flessibili, mentre le dimensioni degli array di buffer uniformi devono essere fornite nello shader.

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 ai buffer e alle 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 la creazione. Puoi modificare solo i contenuti.

Differenze nelle convenzioni spaziali

In WebGL, l'intervallo dello spazio di clip 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 clip, la direzione dell'asse Y è ancora verso l'alto 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.