Da WebGL a WebGPU

François Beaufort
François Beaufort

In qualità di sviluppatore WebGL, potresti essere intimo 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 Shaper vertex e fragments, mentre WebGPU supporta anche gli strumenti per computing computing. WebGL utilizza il linguaggio GLSL (OpenGL Shading Language), mentre WebGPU utilizza WGSL (WebGPU Shading Language). Sebbene le due lingue siano diverse, i concetti di base sono per lo più gli stessi.

Alla luce di ciò, questo articolo evidenzia alcune differenze tra WebGL e WebGPU, per aiutarvi a iniziare.

Stato globale

WebGL ha molti stati globali. Alcune impostazioni si applicano a tutte le operazioni di rendering, ad esempio le texture e i buffer associati. Puoi impostare questo stato globale chiamando varie funzioni dell'API, che rimane in vigore finché non lo modifichi. Lo stato globale di WebGL è una importante 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. Ciò è utile nella mappatura shadow, ad esempio, dove, in un singolo passaggio degli oggetti, l'applicazione può registrare più flussi di comandi, uno per la mappa shadow di ogni 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 eseguire più la sincronizzazione

Nelle GPU, in genere è inefficiente inviare comandi e attendere che vengano inviati in modalità sincrona, poiché ciò può causare il svuotamento della pipeline e la creazione di 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 bolle, la WebGPU è progettata per essere completamente asincrona. Il modello di errore e tutte le altre operazioni si verificano in modo asincrono. Ad esempio, quando si crea 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 di evitare le bollette nella comunicazione tra processi e offre alle applicazioni prestazioni affidabili.

Shaker di computing

Si tratta di programmi che vengono eseguiti sulla GPU per eseguire calcoli per uso generico. Sono disponibili solo in WebGPU, non in WebGL.

A differenza dei vertici e dei frammenti, non si limitano all'elaborazione grafica e possono essere utilizzati per un'ampia varietà di attività, come machine learning, simulazione della fisica e 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 frame 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 e come 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à dell'applicazione per impostazione predefinita

WebGPU ti forza a richiedere limits. Per impostazione predefinita, requestDevice() restituisce un GPUDevice che potrebbe non corrispondere alle funzionalità hardware del dispositivo fisico, ma piuttosto un denominatore comune ragionevole e minimo di tutte le GPU. Richiedendo agli sviluppatori di richiedere i limiti relativi ai 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.

D'altra parte, WebGPU richiede che tu gestisca personalmente il canvas. Ad esempio, per ottenere l'antialiasing in WebGPU, devi creare una texture multicampione ed eseguire il rendering. 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.

Guarda la demo su più canvas di WebGPU.

Tieni presente che al momento i browser hanno un limite al numero di canvas WebGL per pagina. Al momento della scrittura, Chrome e Safari possono utilizzare contemporaneamente fino a 16 canvas WebGL; Firefox ne può creare 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 di WebGPU sono anche facili da capire e utilizzabili. In genere i messaggi di errore includono una descrizione dell'errore e suggerimenti per 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 ottenerne la posizione utilizzando gl.getUniformLocation(program, 'myUniform'). Ciò è utile perché ricevi 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 location). È 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à quindi tutti gli altri livelli di mip per te.

In WebGPU, devi generare manualmente mipmap. Non esiste una funzione integrata per eseguire questa operazione. 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

I buffer uniformi sono supportati sia da WebGL che da WebGPU e consentono di passare parametri costanti di dimensioni limitate agli Shaper. 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 streamr possono essere molto più grandi dei buffer uniformi. Mentre la specifica indica che le associazioni dei 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 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 servono per le texture a quali buffer di archiviazione sono i buffer uniformi. Sono più flessibili delle texture normali e supportano scritture ad accesso casuale (e anche letture in futuro).

Modifiche di buffer e texture

In WebGL, puoi creare un buffer o una texture e modificarne le dimensioni in qualsiasi momento, rispettivamente 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 solo modificarne i contenuti.

Differenze tra le convenzioni degli spazi

In WebGL, l'intervallo di spazio clip in Z va da -1 a 1. In WebGPU, l'intervallo di spazio del 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 degli intervalli di spazio dei clip a Z in WebGL e WebGPU.
Intervalli di spazio dei clip Z in WebGL e WebGPU.

WebGL utilizza la convenzione OpenGL, dove 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 è rivolto verso il basso e l'asse Z è fuori dallo schermo. Tieni presente che la direzione dell'asse Y è abbassata nella coordinata del framebuffer, nella coordinata dell'area visibile e nella coordinata frammento/pixel. Nello spazio dei clip, la direzione dell'asse Y è ancora alta come in WebGL.

Ringraziamenti

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

Ti consiglio anche WebGPUFundamentals.org per un approfondimento delle differenze tra WebGPU e WebGL.