Da WebGL a WebGPU

François Beaufort
François Beaufort

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

È 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 di vertici e frammenti, mentre WebGPU supporta anche gli shader di calcolo. 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 aspetto, questo articolo evidenzia alcune differenze tra WebGL e WebGPU per aiutarti a iniziare.

Stato globale

WebGL ha molte variabili globali. Alcune impostazioni si applicano a tutte le operazioni di rendering, ad esempio quali texture e buffer sono associati. Imposti questo stato globale chiamando varie funzioni API e rimane attivo finché non lo modifichi. Lo stato globale in WebGL è una fonte principale di errori, in quanto è facile dimenticare di modificare un'impostazione globale. Inoltre, lo stato globale rende difficile la condivisione del codice, in quanto 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 globale in WebGL. Una pipeline contiene informazioni quali la combinazione, la topologia e gli attributi da utilizzare. Una pipeline è immutabile. Se vuoi modificare alcune impostazioni, devi creare un'altra pipeline. WebGPU utilizza anche i codificatori di comandi per raggruppare i comandi ed eseguirli nell'ordine in cui sono stati registrati. Ciò è utile, ad esempio, nella mappatura delle ombre, in cui, in un unico passaggio sugli oggetti, l'applicazione può registrare più flussi 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 robuste e componibili, WebGPU ha ridotto significativamente la quantità di stato che gli sviluppatori dovevano tenere traccia durante l'invio di comandi alla GPU.

Nessuna sincronizzazione

Sulle GPU, in genere è inefficiente inviare comandi e attenderli in modo sincrono, in quanto ciò può svuotare la pipeline e causare bolle. Ciò è particolarmente vero in WebGPU e WebGL, che utilizzano un'architettura multi-processo con il driver della GPU in esecuzione in un processo separato da JavaScript.

In WebGL, ad esempio, la chiamata di gl.getError() richiede un IPC sincrono dal processo JavaScript al processo GPU e viceversa. Ciò può causare una bolla sul lato della CPU durante la comunicazione tra i due processi.

Per evitare questi problemi, WebGPU è progettata per essere completamente asincrona. Il modello di errore e tutte le altre operazioni vengono eseguiti in modo asincrono. Ad esempio, quando crei una texture, l'operazione sembra riuscire immediatamente, anche se la texture è in realtà un errore. Puoi rilevare l'errore solo in modo asincrono. Questo design mantiene la comunicazione tra processi priva di bolle 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 di vertici e frammenti, 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 grandi set di dati. Scopri di più sul calcolo GPU e leggi ulteriori dettagli in questo articolo esaustivo 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 CPU e il parallelismo limitato che può essere ottenuto con i worker e i thread 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 costringe a richiedere limits. Per impostazione predefinita, requestDevice() restituisce un GPUDevice che potrebbe non corrispondere alle funzionalità hardware del dispositivo fisico, ma piuttosto a un denominatore comune ragionevole e più basso di tutte le GPU. Richiedendo agli sviluppatori di richiedere limiti per i dispositivi, WebGPU garantisce che le applicazioni vengano eseguite sul maggior numero possibile di dispositivi.

Gestione del canvas

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

WebGPU, invece, richiede di gestire autonomamente il canvas. Ad esempio, per ottenere l'antialiasing in WebGPU, devi creare una texture multisample e il rendering. Quindi, risolvi la texture multisample in una texture normale e disegnala sulla tela. Questa gestione manuale ti consente di eseguire l'output su tutte le tele che vuoi da un singolo oggetto GPUDevice. Al contrario, WebGL può creare un solo contesto per canvas.

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

Come nota a margine, i browser hanno attualmente un limite al numero di canvas WebGL per pagina. Al momento della stesura, Chrome e Safari possono utilizzare contemporaneamente 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 da risolvere. I messaggi di errore in genere includono una descrizione dell'errore e suggerimenti su come risolverlo.

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

Dai nomi agli indici

In WebGL, molti elementi sono collegati tramite nomi. Ad esempio, puoi dichiarare una variabile uniforme chiamata myUniform in GLSL e ottenere la sua posizione utilizzando gl.getUniformLocation(program, 'myUniform'). Questa funzionalità è utile perché viene visualizzato un errore se digiti in modo errato il nome della variabile uniforme.

D'altra parte, in WebGPU tutto è completamente connesso tramite offset di byte o indice (spesso chiamato posizione). È tua responsabilità mantenere sincronizzate le posizioni del codice in WGSL e JavaScript.

Generazione di mipmap

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

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 imparare a farlo autonomamente.

Buffer di archiviazione e texture di archiviazione

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

  • I buffer di archiviazione dei dati passati agli shader possono essere molto più grandi dei buffer uniformi. Sebbene la specifica indichi che i binding dei buffer uniformi possono avere una dimensione massima di 64 KB (vedi maxUniformBufferBindingSize) , la dimensione massima di un binding del 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 di sola lettura. Ciò consente di implementare nuove classi di algoritmi.

  • I binding dei buffer di archiviazione supportano 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 i buffer di archiviazione sono per i buffer uniformi. Sono più flessibili delle texture normali, supportano le scritture ad accesso casuale (e in futuro anche le letture).

Modifiche al buffer e alla texture

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

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 solo modificarne i contenuti.

Differenze nelle convenzioni di spaziatura

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

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

WebGL utilizza la convenzione OpenGL, in cui l'asse Y è verso l'alto e l'asse Z è verso lo spettatore. WebGPU utilizza la convenzione Metal, in cui l'asse Y è 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, dell'area visibile e del frammento/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 consultare WebGPUFundamentals.org per un'analisi approfondita delle differenze tra WebGPU e WebGL.