Van WebGL tot WebGPU

François Beaufort
François Beaufort

Als WebGL-ontwikkelaar bent u misschien zowel geïntimideerd als enthousiast om WebGPU te gaan gebruiken, de opvolger van WebGL die de vooruitgang van moderne grafische API's naar het web brengt.

Het is geruststellend om te weten dat WebGL en WebGPU veel kernconcepten delen. Met beide API's kunt u kleine programma's, shaders genaamd, op de GPU uitvoeren. WebGL ondersteunt vertex- en fragment-shaders, terwijl WebGPU ook compute-shaders ondersteunt. WebGL gebruikt de OpenGL Shading Language (GLSL), terwijl WebGPU de WebGPU Shading Language (WGSL) gebruikt. Hoewel de twee talen verschillend zijn, zijn de onderliggende concepten grotendeels hetzelfde.

Met dat in gedachten belicht dit artikel enkele verschillen tussen WebGL en WebGPU, om u op weg te helpen.

Mondiale staat

WebGL heeft veel mondiale status . Sommige instellingen zijn van toepassing op alle weergavebewerkingen, zoals welke texturen en buffers gebonden zijn. U stelt deze globale status in door verschillende API-functies aan te roepen, en deze blijft van kracht totdat u deze wijzigt. De globale status in WebGL is een grote bron van fouten , omdat u gemakkelijk vergeet een globale instelling te wijzigen. Bovendien maakt de globale status het delen van code moeilijk, omdat ontwikkelaars moeten oppassen dat ze de globale status niet per ongeluk veranderen op een manier die andere delen van de code beïnvloedt.

WebGPU is een staatloze API en onderhoudt geen mondiale status. In plaats daarvan gebruikt het het concept van een pijplijn om alle weergavestatussen in te kapselen die globaal waren in WebGL. Een pijplijn bevat informatie zoals welke overvloeiing, topologie en attributen moeten worden gebruikt. Een pijplijn is onveranderlijk. Als u bepaalde instellingen wilt wijzigen, moet u een andere pijplijn maken. WebGPU maakt ook gebruik van opdracht-encoders om opdrachten samen te voegen en uit te voeren in de volgorde waarin ze zijn opgenomen. Dit is bijvoorbeeld handig bij het in kaart brengen van schaduwen, waarbij de toepassing in één enkele beweging over de objecten meerdere commandostromen kan opnemen, één voor de schaduwkaart van elk licht.

Samenvattend: omdat het mondiale staatsmodel van WebGL het maken van robuuste, samenstelbare bibliotheken en applicaties moeilijk en kwetsbaar maakte, heeft WebGPU de hoeveelheid status die ontwikkelaars nodig hadden om bij te houden tijdens het verzenden van opdrachten naar de GPU aanzienlijk verminderd.

Synchroniseer niet meer

Op GPU's is het doorgaans inefficiënt om opdrachten synchroon te verzenden en erop te wachten, omdat dit de pijplijn kan doorspoelen en luchtbellen kan veroorzaken. Dit geldt vooral voor WebGPU en WebGL, die een architectuur met meerdere processen gebruiken, waarbij het GPU-stuurprogramma in een afzonderlijk proces van JavaScript wordt uitgevoerd.

In WebGL vereist het aanroepen van gl.getError() bijvoorbeeld een synchrone IPC van het JavaScript-proces naar het GPU-proces en terug. Dit kan een luchtbel aan de CPU-kant veroorzaken terwijl de twee processen communiceren.

Om deze bubbels te voorkomen, is WebGPU ontworpen om volledig asynchroon te zijn. Het foutmodel en alle andere bewerkingen gebeuren asynchroon. Wanneer u bijvoorbeeld een textuur maakt, lijkt de bewerking onmiddellijk te slagen, zelfs als de textuur in werkelijkheid een fout is. U kunt de fout alleen asynchroon ontdekken. Dit ontwerp zorgt ervoor dat de communicatie tussen processen vrij blijft en zorgt voor betrouwbare prestaties van applicaties.

Bereken shaders

Compute shaders zijn programma's die op de GPU draaien om algemene berekeningen uit te voeren. Ze zijn alleen beschikbaar in WebGPU, niet in WebGL.

In tegenstelling tot vertex- en fragment-shaders zijn ze niet beperkt tot grafische verwerking en kunnen ze worden gebruikt voor een breed scala aan taken, zoals machine learning, natuurkundige simulatie en wetenschappelijk computergebruik. Compute shaders worden parallel uitgevoerd door honderden of zelfs duizenden threads, waardoor ze zeer efficiënt zijn voor het verwerken van grote datasets. Lees meer over GPU-computing en meer details in dit uitgebreide artikel over WebGPU .

Videoframeverwerking

Het verwerken van videoframes met behulp van JavaScript en WebAssembly heeft enkele nadelen: de kosten voor het kopiëren van de gegevens van GPU-geheugen naar CPU-geheugen, en de beperkte parallelliteit die kan worden bereikt met werkers en CPU-threads. WebGPU heeft deze beperkingen niet, waardoor het uitstekend geschikt is voor het verwerken van videoframes dankzij de nauwe integratie met de WebCodecs API.

Het volgende codefragment laat zien hoe u een VideoFrame als externe textuur in WebGPU importeert en verwerkt. U kunt deze demo uitproberen.

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

Standaard portabiliteit van applicaties

WebGPU dwingt u om limits aan te vragen. Standaard retourneert requestDevice() een GPUDevice die mogelijk niet overeenkomt met de hardwaremogelijkheden van het fysieke apparaat, maar eerder een redelijke en kleinste gemene deler van alle GPU's is. Door van ontwikkelaars te eisen dat ze apparaatlimieten aanvragen, zorgt WebGPU ervoor dat applicaties op zoveel mogelijk apparaten kunnen worden uitgevoerd.

Doekbehandeling

WebGL beheert het canvas automatisch nadat u een WebGL-context hebt gemaakt en contextkenmerken hebt opgegeven, zoals alpha, antialias, colorSpace, depth, conserveDrawingBuffer of stencil.

Aan de andere kant vereist WebGPU dat u het canvas zelf beheert. Om bijvoorbeeld anti-aliasing in WebGPU te bereiken, zou u een multisample-textuur maken en ernaar renderen. Vervolgens zou u de multisample-textuur omzetten in een normale textuur en die textuur op het canvas tekenen. Met dit handmatige beheer kunt u vanuit één GPUDevice- object naar zoveel canvassen uitvoeren als u wilt. WebGL kan daarentegen slechts één context per canvas creëren.

Bekijk de WebGPU Multiple Canvases-demo .

Even terzijde: browsers hebben momenteel een limiet op het aantal WebGL-canvas per pagina. Op het moment van schrijven kunnen Chrome en Safari maximaal 16 WebGL-canvas tegelijkertijd gebruiken; Firefox kan er maximaal 200 maken. Aan de andere kant is er geen limiet op het aantal WebGPU-canvas per pagina.

Schermafbeelding met het maximale aantal WebGL-canvas in Safari-, Chrome- en Firefox-browsers
Het maximale aantal WebGL-canvas in Safari, Chrome en Firefox (van links naar rechts) - demo .

Handige foutmeldingen

WebGPU biedt een call-stack voor elk bericht dat vanuit de API wordt geretourneerd. Dit betekent dat u snel kunt zien waar de fout in uw code is opgetreden, wat handig is bij het debuggen en oplossen van fouten.

Naast het bieden van een call-stack, zijn WebGPU-foutmeldingen ook gemakkelijk te begrijpen en bruikbaar. De foutmeldingen bevatten doorgaans een beschrijving van de fout en suggesties voor het oplossen van de fout.

Met WebGPU kunt u ook een aangepast label voor elk WebGPU-object opgeven. Dit label wordt vervolgens door de browser gebruikt in GPUError-berichten, consolewaarschuwingen en browserontwikkelaarstools.

Van namen tot indexen

In WebGL zijn veel dingen met elkaar verbonden door middel van namen. U kunt bijvoorbeeld een uniforme variabele met de naam myUniform declareren in GLSL en de locatie ervan ophalen met behulp van gl.getUniformLocation(program, 'myUniform') . Dit is handig omdat u een foutmelding krijgt als u de naam van de uniforme variabele verkeerd typt.

Aan de andere kant is in WebGPU alles volledig verbonden door byte-offset of index (vaak locatie genoemd). Het is uw verantwoordelijkheid om de locaties voor de code in WGSL en JavaScript gesynchroniseerd te houden.

Mipmap-generatie

In WebGL kunt u mip op niveau 0 van een textuur maken en vervolgens gl.generateMipmap() aanroepen. WebGL genereert dan alle andere mip-niveaus voor u.

In WebGPU moet u zelf mipmaps genereren. Er is geen ingebouwde functie om dit te doen. Zie de spec-discussie voor meer informatie over de beslissing. U kunt handige bibliotheken zoals webgpu-utils gebruiken om mipmaps te genereren of leren hoe u dit zelf kunt doen.

Opslagbuffers en opslagtexturen

Uniforme buffers worden ondersteund door zowel WebGL als WebGPU en stellen u in staat constante parameters van beperkte grootte door te geven aan shaders. Opslagbuffers, die veel op uniforme buffers lijken, worden alleen ondersteund door WebGPU en zijn krachtiger en flexibeler dan uniforme buffers.

  • Opslagbuffergegevens die aan shaders worden doorgegeven, kunnen veel groter zijn dan uniforme buffers. Hoewel volgens de specificatie uniforme bufferbindingen maximaal 64 KB groot kunnen zijn (zie maxUniformBufferBindingSize ), is de maximale grootte van een opslagbufferbinding minimaal 128 MB in WebGPU (zie maxStorageBufferBindingSize ).

  • Opslagbuffers zijn beschrijfbaar en ondersteunen enkele atomaire bewerkingen, terwijl uniforme buffers alleen-lezen zijn. Hierdoor kunnen nieuwe klassen algoritmen worden geïmplementeerd.

  • Bindingen van opslagbuffers ondersteunen arrays van runtime-grootte voor flexibelere algoritmen, terwijl in de shader uniforme bufferarraygroottes moeten worden geboden.

Opslagtexturen worden alleen ondersteund in WebGPU en zijn voor texturen wat opslagbuffers zijn voor uniforme buffers. Ze zijn flexibeler dan gewone texturen en ondersteunen schrijfbewerkingen met willekeurige toegang (en ook leesbewerkingen in de toekomst).

Buffer- en textuurveranderingen

In WebGL kunt u een buffer of textuur maken en vervolgens de grootte ervan op elk gewenst moment wijzigen met bijvoorbeeld respectievelijk gl.bufferData() en gl.texImage2D() .

In WebGPU zijn buffers en texturen onveranderlijk. Dit betekent dat u de grootte, het gebruik of het formaat ervan niet kunt wijzigen nadat ze zijn gemaakt. U kunt alleen de inhoud ervan wijzigen.

Verschillen in ruimteconventies

In WebGL is het bereik van de Z- clipruimte van -1 tot 1. In WebGPU is het bereik van de Z-clipruimte van 0 tot 1. Dit betekent dat objecten met een az-waarde van 0 zich het dichtst bij de camera bevinden, terwijl objecten met een az-waarde van 1 zijn het verst weg.

Illustratie van Z-clipruimtebereiken in WebGL en WebGPU.
Z-clipruimtebereiken in WebGL en WebGPU.

WebGL gebruikt de OpenGL-conventie, waarbij de Y-as omhoog is en de Z-as naar de kijker gericht. WebGPU gebruikt de Metal-conventie, waarbij de Y-as naar beneden is en de Z-as buiten het scherm. Merk op dat de richting van de Y-as omlaag is in framebuffercoördinaat, viewport-coördinaat en fragment/pixelcoördinaat. In de clipruimte is de richting van de Y-as nog steeds omhoog, net als in WebGL.

Dankbetuigingen

Met dank aan Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell en Rachel Andrew voor het beoordelen van dit artikel.

Ik raad ook WebGPUFundamentals.org aan voor een diepgaande duik in de verschillen tussen WebGPU en WebGL.