Da WebGL a WebGPU

Francesco Beaufort
François Beaufort

In qualità di sviluppatore WebGL, potresti essere intimorita 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 consentono di eseguire sulla GPU dei piccoli programmi chiamati shabbyr. WebGL supporta gli Shader vertex e fragment, mentre WebGPU supporta anche gli handle di calcolo. WebGL utilizza il linguaggio OpenGL Shading Language (GLSL), mentre WebGPU si avvale del WebGPU Shading Language (WGSL). Sebbene le due lingue siano diverse, i concetti di base sono per lo più gli stessi.

Alla luce di questo, questo articolo evidenzia 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 associati. Questo stato globale viene impostato chiamando varie funzioni API e rimane attivo finché non lo modifichi. Lo stato globale di 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 un modo che influisca su altre parti del codice.

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

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

Non eseguire più la sincronizzazione

Sulle GPU, in genere è inefficiente inviare comandi e attenderli in modo sincrono, poiché ciò può svuotare la pipeline e causare bolle. Ciò è particolarmente vero in 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 un fumetto sul lato CPU mentre i due processi comunicano.

Per evitare questo tipo di bolle, la WebGPU è progettata per essere completamente asincrona. Il modello di errore e tutte le altre operazioni avvengono in modo asincrono. Ad esempio, quando crei una texture, l'operazione sembra riuscire immediatamente, anche se in realtà si tratta di un errore. Puoi scoprire l'errore solo in modo asincrono. Questo design consente l'assenza di bolle di comunicazione tra processi e offre alle applicazioni prestazioni affidabili.

Shader di calcolo

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

A differenza dei vertex e fragment Shader, questi strumenti non si limitano all'elaborazione grafica e possono essere utilizzati per un'ampia varietà di attività, come il machine learning, la simulazione fisica e l'informatica scientifica. Gli handle di computing 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 maggiori dettagli in questo articolo completo su WebGPU.

Elaborazione frame video

L'elaborazione di frame video utilizzando JavaScript e WebAssembly presenta alcuni svantaggi: il costo della copia dei dati dalla memoria della GPU alla memoria della CPU e il parallelismo limitato che può essere ottenuto con i worker e i thread della CPU. WebGPU non ha queste limitazioni ed è quindi ideale per l'elaborazione di 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 dispositivo GPU che potrebbe non corrispondere alle funzionalità hardware del dispositivo fisico, ma piuttosto un minimo comune denominatore 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 delle stampe su tela

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

WebGPU, invece, richiede la gestione autonoma della tela. Ad esempio, per ottenere l'antialiasing in WebGPU, devi creare una trama multicampione e eseguirne il rendering. Successivamente, dovrai risolvere la texture multicampione in una texture regolare e disegnarla sulla tela. Questa gestione manuale consente di eseguire output su tutti i canvas che vuoi da un singolo oggetto GPUDevice. Al contrario, WebGL può creare un solo contesto per canvas.

Guarda la demo di WebGPU Multiple Canvases.

Tieni presente che attualmente i browser hanno un limite al numero di canvas WebGL per pagina. Al momento della scrittura, Chrome e Safari possono utilizzare solo fino a 16 canvas WebGL contemporaneamente, mentre Firefox può crearne fino a 200. D'altra parte, non esiste alcun 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 di WebGPU sono anche facili da capire e da usare. I messaggi di errore in genere includono una descrizione dell'errore e suggerimenti su come correggerlo.

WebGPU ti consente inoltre di fornire un elemento label personalizzato per ogni oggetto WebGPU. Questa etichetta viene quindi utilizzata dal browser nei messaggi GPUError, 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 denominata myUniform in GLSL e recuperarne la posizione utilizzando gl.getUniformLocation(program, 'myUniform'). Questo è utile perché ricevi un messaggio di errore se digiti in modo errato il nome della variabile Uniform.

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

Generazione Mipmap

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

In WebGPU, devi generare tu le mipmap. Non esiste una funzione integrata per eseguire questa operazione. Per ulteriori informazioni sulla decisione, consulta la discussione sulle specifiche. Puoi utilizzare pratiche librerie come webgpu-utils per generare mipmap o imparare a farlo da te.

Buffer di archiviazione e texture di archiviazione

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

  • I dati nel buffer di archiviazione passati agli mesh 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 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.

  • Le associazioni di buffer di archiviazione supportano gli array di dimensioni runtime per algoritmi più flessibili, mentre le dimensioni uniformi degli array di buffer devono essere indicate nello mesh.

Le texture di archiviazione sono supportate solo in WebGPU e riguardano le texture ciò che sono i buffer di archiviazione a quelli uniformi. Sono più flessibili delle texture normali e supportano le scritture ad accesso casuale (e le letture in futuro).

Modifiche a buffer e texture

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

In WebGPU, buffer e texture sono immutabili. Ciò significa che non puoi modificarne le dimensioni, l'utilizzo o il formato dopo averli creati. Puoi modificarne 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 è compreso tra 0 e 1. Ciò significa che gli oggetti con valore z pari a 0 sono i più vicini alla fotocamera, mentre gli oggetti con valore z pari a 1 sono i più lontani.

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

WebGL utilizza la convenzione OpenGL, in cui l'asse Y è rivolto verso l'alto e l'asse Z è rivolto verso il visualizzatore. WebGPU utilizza la convenzione Metal, in cui l'asse Y è abbassato e l'asse Z è fuori dallo schermo. Tieni presente che la direzione dell'asse Y è verso il basso nella coordinata del framebuffer, nell'area visibile e nella coordinata frammento/pixel. Nell'area di ritaglio, la direzione dell'asse Y è ancora in alto come in WebGL.

Ringraziamenti

Grazie a Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell e Rachel Andrew per la recensione di questo articolo.

Consiglio inoltre WebGPUFundamentals.org per un'analisi approfondita delle differenze tra WebGPU e WebGL.