De WebGL vers WebGPU

François Beaufort
François Beaufort

En tant que développeur WebGL, vous serez peut-être intimidé et enthousiaste à l'idée d'utiliser WebGPU, le successeur de WebGL qui permet d'accéder aux avancées des API graphiques modernes sur le Web.

Il est rassurant de savoir que WebGL et WebGPU ont de nombreux concepts fondamentaux en commun. Les deux API vous permettent d'exécuter de petits programmes appelés nuanceurs sur le GPU. WebGL est compatible avec les nuanceurs de sommets et de fragments, tandis que WebGPU est également compatible avec les nuanceurs de calcul. WebGL utilise le langage OpenGL Shading Language (GLSL), tandis que WebGPU utilise le langage WGSL (WebGPU Shading Language). Bien que les deux langages soient différents, les concepts sous-jacents sont pour la plupart les mêmes.

C'est pourquoi cet article met en évidence les différences entre WebGL et WebGPU, pour vous aider à démarrer.

État global

WebGL présente de nombreux états globaux. Certains paramètres s'appliquent à toutes les opérations de rendu, par exemple les textures et les tampons qui sont liés. Vous définissez cet état global en appelant diverses fonctions d'API. Il reste actif jusqu'à ce que vous le modifiiez. L'état global dans WebGL est une principale source d'erreurs, car il est facile d'oublier de modifier un paramètre global. De plus, l'état global complique le partage de code, car les développeurs doivent veiller à ne pas modifier accidentellement l'état global d'une manière qui affecte d'autres parties du code.

WebGPU est une API sans état qui ne conserve pas d'état global. Au lieu de cela, il utilise le concept de pipeline pour encapsuler tout l'état de rendu global dans WebGL. Un pipeline contient des informations telles que la combinaison, la topologie et les attributs à utiliser. Un pipeline est immuable. Si vous souhaitez modifier certains paramètres, vous devez créer un autre pipeline. WebGPU utilise également des encodeurs de commande pour regrouper les commandes et les exécuter dans l'ordre dans lequel elles ont été enregistrées. Cette fonctionnalité est utile pour le mappage des ombres, par exemple, où, en une seule passe sur les objets, l'application peut enregistrer plusieurs flux de commandes, un pour la carte d'ombres de chaque lumière.

En résumé, comme le modèle d'état global de WebGL rendait difficile et fragile la création d'applications et de bibliothèques composables robustes, WebGPU a considérablement réduit la quantité d'états que les développeurs devaient suivre pour envoyer des commandes au GPU.

Ne plus synchroniser

Sur les GPU, il est généralement inefficace d'envoyer des commandes et de les attendre de manière synchrone, car cela peut vider le pipeline et provoquer des bulles. Cela est particulièrement vrai dans WebGPU et WebGL, qui utilisent une architecture multiprocessus avec le pilote GPU s'exécutant dans un processus distinct de JavaScript.

Dans WebGL, par exemple, l'appel de gl.getError() nécessite une IPC synchrone entre le processus JavaScript et le processus GPU. Cela peut entraîner une bulle côté CPU lorsque les deux processus communiquent.

Pour éviter ces bulles, WebGPU est conçu pour être complètement asynchrone. Le modèle d'erreur et toutes les autres opérations se produisent de manière asynchrone. Par exemple, lorsque vous créez une texture, l'opération semble réussir immédiatement, même si la texture est en réalité une erreur. Vous ne pouvez détecter l'erreur que de manière asynchrone. Cette conception permet d'éviter toute bulle de communication entre les processus et offre aux applications des performances fiables.

Calculer les nuanceurs

Les nuanceurs de calcul sont des programmes qui s'exécutent sur le GPU pour effectuer des calculs à usage général. Elles ne sont disponibles que dans WebGPU, pas dans WebGL.

Contrairement aux nuanceurs de sommets et de fragments, ils ne se limitent pas au traitement graphique et peuvent être utilisés pour une grande variété de tâches, telles que le machine learning, la simulation physique et l'informatique scientifique. Les nuanceurs de calcul sont exécutés en parallèle par des centaines, voire des milliers de threads, ce qui les rend très efficaces pour traiter de grands ensembles de données. Pour en savoir plus sur le calcul GPU et obtenir plus d'informations, consultez cet article complet sur WebGPU.

Traitement des images vidéo

Le traitement d'images vidéo à l'aide de JavaScript et de WebAssembly présente certains inconvénients: le coût de copie des données de la mémoire GPU vers la mémoire CPU, et le parallélisme limité qui peut être obtenu avec les nœuds de calcul et les threads de processeur. WebGPU ne présente pas ces limites. Il est donc parfaitement adapté au traitement des images vidéo grâce à son intégration étroite à l'API WebCodecs.

L'extrait de code suivant montre comment importer un élément VideoFrame en tant que texture externe dans WebGPU et le traiter. Vous pouvez essayer cette démonstration.

// 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é des applications par défaut

WebGPU vous oblige à demander limits. Par défaut, requestDevice() renvoie un GPUDevice qui peut ne pas correspondre aux capacités matérielles de l'appareil physique, mais plutôt à un dénominateur commun raisonnable et plus bas de tous les GPU. En obligeant les développeurs à demander des limites d'appareils, WebGPU s'assure que les applications s'exécutent sur autant d'appareils que possible.

Manipulation du canevas

WebGL gère automatiquement le canevas une fois que vous avez créé un contexte WebGL et fournit des attributs de contexte tels que alpha, antialias, colorSpace, depth, keepDrawingBuffer ou stencil.

En revanche, avec WebGPU, vous devez gérer vous-même le canevas. Par exemple, pour obtenir l'anticrénelage dans WebGPU, vous devez créer une texture multi-échantillon et y effectuer le rendu. Ensuite, vous convertiriez la texture multi-échantillon en une texture standard et dessinerez cette texture sur le canevas. Cette gestion manuelle vous permet d'afficher autant de canevas que vous le souhaitez à partir d'un seul objet GPUDevice. En revanche, WebGL ne peut créer qu'un seul contexte par canevas.

Consultez la démonstration de WebGPU Multiple Canvases.

Par ailleurs, le nombre de canevas WebGL par page est actuellement limité dans les navigateurs. Au moment de la rédaction de ce document, Chrome et Safari ne peuvent utiliser simultanément que 16 canevas WebGL. Firefox peut en créer jusqu'à 200. En revanche, le nombre de canevas WebGPU par page n'est pas limité.

Capture d'écran montrant le nombre maximal de canevas WebGL dans les navigateurs Safari, Chrome et Firefox
Nombre maximal de toiles WebGL dans Safari, Chrome et Firefox (de gauche à droite) : démonstration.

Messages d'erreur utiles

WebGPU fournit une pile d'appel pour chaque message renvoyé par l'API. Vous pouvez ainsi voir rapidement où l'erreur s'est produite dans votre code, ce qui est utile pour le débogage et la correction des erreurs.

En plus d'offrir une pile d'appels, les messages d'erreur WebGPU sont faciles à comprendre et à exploiter. Les messages d'erreur incluent généralement une description de l'erreur et des suggestions pour la corriger.

WebGPU vous permet également de fournir un label personnalisé pour chaque objet WebGPU. Ce libellé est ensuite utilisé par le navigateur dans les messages d'erreur GPU, les avertissements de la console et les outils pour les développeurs du navigateur.

Des noms aux index

Dans WebGL, de nombreux éléments sont reliés par des noms. Par exemple, vous pouvez déclarer une variable uniforme appelée myUniform en GLSL et obtenir son emplacement à l'aide de gl.getUniformLocation(program, 'myUniform'). Cela s'avère pratique, car un message d'erreur s'affiche si vous saisissez mal le nom de la variable uniforme.

En revanche, dans WebGPU, tout est entièrement connecté par décalage d'octets ou index (souvent appelé emplacement). Il est de votre responsabilité de synchroniser les emplacements du code dans WGSL et JavaScript.

Génération de mipmaps

Dans WebGL, vous pouvez créer le niveau 0 mip d'une texture, puis appeler gl.generateMipmap(). WebGL génère alors automatiquement tous les autres niveaux mip.

Dans WebGPU, vous devez générer vous-même des mipmaps. Il n'existe aucune fonction intégrée pour effectuer cette opération. Pour en savoir plus sur cette décision, consultez la discussion sur les spécifications. Vous pouvez utiliser des bibliothèques pratiques comme webgpu-utils pour générer des mipmaps ou apprendre à le faire vous-même.

Tampons et textures de stockage

Les tampons uniformes sont compatibles avec WebGL et WebGPU, et vous permettent de transmettre des paramètres constants de taille limitée aux nuanceurs. Les tampons de stockage, qui ressemblent beaucoup aux tampons de variables uniformes, ne sont compatibles qu'avec WebGPU. Ils sont plus puissants et flexibles que les tampons de variables uniformes.

  • Les données des tampons de stockage transmises aux nuanceurs peuvent être beaucoup plus volumineuses que les tampons de variables uniformes. Bien que la spécification indique que la taille des liaisons de tampons uniformes peut atteindre 64 Ko (voir maxUniformBufferBindingSize) , la taille maximale d'une liaison de tampon de stockage est d'au moins 128 Mo dans WebGPU (voir maxStorageBufferBindingSize).

  • Les tampons de stockage sont accessibles en écriture et acceptent certaines opérations atomiques, tandis que les tampons de variables uniformes sont en lecture seule. Cela permet d'implémenter de nouvelles classes d'algorithmes.

  • Les liaisons de tampons de stockage acceptent les tableaux dimensionnés à l'environnement d'exécution pour des algorithmes plus flexibles, tandis que des tailles de tableaux de tampons uniformes doivent être fournies dans le nuanceur.

Les textures de stockage ne sont acceptées que dans WebGPU, et aux textures correspondent ce que les tampons de stockage sont aux tampons de variables uniformes. Elles sont plus flexibles que les textures standards et prennent en charge les écritures (et les lectures) à accès aléatoire à l'avenir.

Modifications de tampon et de texture

Dans WebGL, vous pouvez créer un tampon ou une texture, puis modifier sa taille à tout moment avec gl.bufferData() et gl.texImage2D() respectivement, par exemple.

Dans WebGPU, les tampons et les textures sont immuables. Cela signifie que vous ne pouvez pas modifier leur taille, leur utilisation ou leur format une fois qu'ils ont été créés. Vous ne pouvez modifier que leur contenu.

Différences de convention d'espace

Dans WebGL, l'espace Z des extraits est compris entre -1 et 1. Dans WebGPU, la plage d'espace des extraits Z est comprise entre 0 et 1. Cela signifie que les objets dont la valeur z est égale à 0 sont les plus proches de la caméra, tandis que ceux dont la valeur z est égale à 1 sont les plus éloignés.

Illustration des plages d'espace des extraits Z dans WebGL et WebGPU.
Plages d'espace des extraits z dans WebGL et WebGPU.

WebGL utilise la convention OpenGL : l'axe Y est orienté vers le haut et l'axe Z vers la visionneuse. WebGPU utilise la convention Metal, où l'axe Y est vers le bas et l'axe Z est à l'extérieur de l'écran. Notez que l'axe Y est descendant dans les coordonnées du framebuffer, de la fenêtre d'affichage et du fragment/pixel. Dans l'espace des extraits, la direction de l'axe Y est toujours en haut, comme dans WebGL.

Remerciements

Merci à Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell et Rachel Andrew d'avoir consulté cet article.

Nous vous recommandons également le site WebGPUFundamentals.org pour un exposé détaillé des différences entre WebGPU et WebGL.