Van WebGL tot WebGPU

François Beaufort
François Beaufort

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

Het is geruststellend om te weten dat WebGL en WebGPU veel kernconcepten delen. Beide API's stellen je in staat om kleine programma's, shaders genaamd, op de GPU te draaien. WebGL ondersteunt vertex- en fragmentshaders, 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 verschillen, zijn de onderliggende concepten grotendeels hetzelfde.

Met dat in gedachten belicht dit artikel enkele verschillen tussen WebGL en WebGPU, zodat u aan de slag kunt.

Wereldstatus

WebGL heeft veel globale statussen . Sommige instellingen zijn van toepassing op alle renderbewerkingen, 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 belangrijke bron van fouten , omdat het gemakkelijk is om te vergeten een globale instelling te wijzigen. Bovendien maakt de globale status het delen van code lastig, omdat ontwikkelaars voorzichtig moeten zijn om de globale status niet per ongeluk te wijzigen op een manier die andere delen van de code beïnvloedt.

WebGPU is een stateless API en houdt geen globale status bij. In plaats daarvan gebruikt het het concept van een pipeline om alle renderingstatus die globaal was in WebGL te encapsuleren. Een pipeline bevat informatie zoals welke blending, topologie en attributen gebruikt moeten worden. Een pipeline is onveranderlijk. Als u bepaalde instellingen wilt wijzigen, moet u een andere pipeline maken. WebGPU gebruikt ook opdracht-encoders om opdrachten te groeperen en uit te voeren in de volgorde waarin ze zijn opgenomen. Dit is bijvoorbeeld handig bij schaduwmapping, waarbij de applicatie in één keer over de objecten meerdere opdrachtstromen kan vastleggen, één voor de schaduwmap van elk licht.

Samenvattend: omdat het globale statusmodel van WebGL het creëren van robuuste, samenstelbare bibliotheken en toepassingen lastig en kwetsbaar maakte, heeft WebGPU de hoeveelheid status die ontwikkelaars moesten bijhouden bij het verzenden van opdrachten naar de GPU, aanzienlijk verminderd.

Niet meer synchroniseren

Op GPU's is het doorgaans inefficiënt om opdrachten te versturen en er synchroon op te wachten, omdat dit de pijplijn kan leegmaken en bubbels kan veroorzaken. Dit geldt met name voor WebGPU en WebGL, die een multiprocesarchitectuur gebruiken waarbij de GPU-driver in een apart proces dan JavaScript draait.

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 bubbel aan de CPU-kant veroorzaken terwijl de twee processen communiceren.

Om deze bubbels te voorkomen, is WebGPU volledig asynchroon ontworpen. Het foutmodel en alle andere bewerkingen vinden asynchroon plaats. Wanneer u bijvoorbeeld een textuur aanmaakt, lijkt de bewerking direct te slagen, zelfs als de textuur in werkelijkheid een fout is. U kunt de fout alleen asynchroon ontdekken. Dit ontwerp zorgt voor bubbelvrije communicatie tussen processen en biedt applicaties betrouwbare prestaties.

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 fragmentshaders 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 rekenen. 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-berekeningen en meer details in dit uitgebreide artikel over WebGPU .

Videoframeverwerking

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

Het volgende codefragment laat zien hoe je een VideoFrame als externe textuur in WebGPU importeert en verwerkt. Je 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 applicatieportabiliteit

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

Canvasbehandeling

WebGL beheert automatisch het canvas nadat u een WebGL-context hebt gemaakt en contextkenmerken hebt opgegeven, zoals alfa, anti-alias, kleurruimte, diepte, preserveDrawingBuffer of stencil.

WebGPU vereist daarentegen dat u het canvas zelf beheert. Om bijvoorbeeld anti-aliasing in WebGPU te bereiken, maakt u een multisample-textuur en rendert u deze. Vervolgens vertaalt u de multisample-textuur naar een normale textuur en tekent u die textuur naar het canvas. Dankzij dit handmatige beheer kunt u vanuit één GPUDevice- object naar zoveel canvassen als u wilt uitvoeren. WebGL daarentegen kan slechts één context per canvas aanmaken.

Bekijk de WebGPU Multiple Canvases demo .

Overigens hebben browsers momenteel een limiet op het aantal WebGL-canvas per pagina. Op het moment van schrijven kunnen Chrome en Safari maximaal 16 WebGL-canvas tegelijk gebruiken; Firefox kan er maximaal 200 aanmaken. Er is echter geen limiet op het aantal WebGPU-canvas per pagina.

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

Nuttige foutmeldingen

WebGPU biedt een call stack voor elk bericht dat door 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 te gebruiken. De foutmeldingen bevatten meestal een beschrijving van de fout en suggesties voor het oplossen ervan.

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

Van namen naar indexen

In WebGL worden veel dingen met namen verbonden. Je kunt bijvoorbeeld een uniforme variabele met de naam myUniform in GLSL declareren en de locatie ervan achterhalen met gl.getUniformLocation(program, 'myUniform') . Dit is handig, want je krijgt een foutmelding als je de naam van de uniforme variabele verkeerd typt.

In WebGPU daarentegen is alles volledig verbonden via byte-offset of index (vaak locatie genoemd). Het is jouw verantwoordelijkheid om de locaties voor de code in WGSL en JavaScript synchroon te houden.

Mipmap-generatie

In WebGL kun je een mip-niveau 0 van een textuur maken en vervolgens gl.generateMipmap() aanroepen. WebGL genereert vervolgens alle andere mip-niveaus voor je.

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

Opslagbuffers en opslagtexturen

Uniforme buffers worden ondersteund door zowel WebGL als WebGPU en stellen u in staat om 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.

  • Gegevens van opslagbuffers die naar shaders worden doorgegeven, kunnen veel groter zijn dan die van uniforme buffers. Hoewel de specificatie aangeeft dat bindingen van uniforme buffers maximaal 64 KB groot kunnen zijn (zie maxUniformBufferBindingSize ), is de maximale grootte van een binding van opslagbuffers minimaal 128 MB in WebGPU (zie maxStorageBufferBindingSize ).

  • Opslagbuffers zijn schrijfbaar en ondersteunen enkele atomaire bewerkingen, terwijl uniforme buffers alleen-lezen zijn. Dit maakt de implementatie van nieuwe algoritmeklassen mogelijk.

  • Bindingen van opslagbuffers ondersteunen arrays van runtime-formaat voor flexibelere algoritmen, terwijl uniforme bufferarraygrootten in de shader moeten worden opgegeven.

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

Buffer- en textuurveranderingen

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

In WebGPU zijn buffers en texturen onveranderlijk. Dit betekent dat je hun grootte, gebruik of formaat niet kunt wijzigen nadat ze zijn aangemaakt. Je kunt alleen hun inhoud wijzigen.

Verschillen in ruimteconventies

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

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

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

Dankbetuigingen

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 analyse van de verschillen tussen WebGPU en WebGL.