paint-brush
Migration de WebGL vers WebGPUpar@dmitrii
7,681 lectures
7,681 lectures

Migration de WebGL vers WebGPU

par Dmitrii Ivashchenko13m2023/12/20
Read on Terminal Reader

Trop long; Pour lire

Ce guide explique la transition de WebGL vers WebGPU, couvrant les principales différences, les concepts de haut niveau et les conseils pratiques. Alors que le WebGPU apparaît comme l'avenir du graphisme Web, cet article offre des informations inestimables aux ingénieurs logiciels et aux chefs de projet.
featured image - Migration de WebGL vers WebGPU
Dmitrii Ivashchenko HackerNoon profile picture

Passer au prochain WebGPU signifie plus que simplement changer d’API graphique. C'est aussi un pas vers l'avenir du graphisme Web. Mais cette migration se déroulera mieux avec de la préparation et de la compréhension – et cet article vous préparera.


Bonjour à tous, je m'appelle Dmitrii Ivashchenko et je suis ingénieur logiciel chez MY.GAMES. Dans cet article, nous discuterons des différences entre WebGL et le prochain WebGPU, et nous expliquerons comment préparer votre projet pour la migration.


Aperçu du contenu

  1. Chronologie de WebGL et WebGPU

  2. L'état actuel du WebGPU et ce qui va arriver

  3. Différences conceptuelles de haut niveau

  4. Initialisation

    • WebGL : le modèle de contexte

    • WebGPU : le modèle de périphérique

  5. Programmes et pipelines

    • WebGL : programme

    • WebGPU : pipeline

  6. Uniformes

    • Uniformes dans WebGL 1

    • Uniformes dans WebGL 2

    • Uniformes dans WebGPU

  7. Shaders

    • Langage Shader : GLSL vs WGSL

    • Comparaison des types de données

    • Structures

    • Déclarations de fonctions

    • Fonctions intégrées

    • Conversion de shader

  8. Différences de convention

  9. Textures

    • Espace de visualisation

    • Espaces de découpe

  10. Trucs et astuces WebGPU

    • Réduisez le nombre de pipelines que vous utilisez.

    • Créer des pipelines à l'avance

    • Utiliser les RenderBundles

  11. Résumé


Chronologie de WebGL et WebGPU

WebGL , comme beaucoup d'autres technologies Web, a des racines qui remontent assez loin dans le passé. Pour comprendre la dynamique et la motivation derrière l'évolution vers WebGPU, il est utile de jeter d'abord un rapide coup d'œil à l'histoire du développement de WebGL :


  • Bureau OpenGL (1993) La version de bureau d'OpenGL fait ses débuts.
  • WebGL 1.0 (2011) : Il s'agissait de la première version stable de WebGL, basée sur OpenGL ES 2.0, lui-même introduit en 2007. Elle offrait aux développeurs Web la possibilité d'utiliser des graphiques 3D directement dans les navigateurs, sans avoir besoin de plugins supplémentaires.
  • WebGL 2.0 (2017) : Introduit six ans après la première version, WebGL 2.0 était basé sur OpenGL ES 3.0 (2012). Cette version a apporté un certain nombre d'améliorations et de nouvelles fonctionnalités, rendant les graphiques 3D sur le Web encore plus puissants.


Ces dernières années, on a assisté à un regain d'intérêt pour les nouvelles API graphiques qui offrent aux développeurs plus de contrôle et de flexibilité :


  • Vulkan (2016) : Créée par le groupe Khronos, cette API multiplateforme est le « successeur » d'OpenGL. Vulkan fournit un accès de niveau inférieur aux ressources matérielles graphiques, permettant des applications hautes performances avec un meilleur contrôle sur le matériel graphique.
  • D3D12 (2015) : Cette API a été créée par Microsoft et est exclusivement pour Windows et Xbox. D3D12 est le successeur du D3D10/11 et offre aux développeurs un contrôle plus approfondi sur les ressources graphiques.
  • Metal (2014) : Créée par Apple, Metal est une API exclusive pour les appareils Apple. Il a été conçu dans un souci de performances maximales sur le matériel Apple.


L'état actuel du WebGPU et ce qui va arriver

Aujourd'hui, WebGPU est disponible sur plusieurs plates-formes telles que Windows, Mac et ChromeOS via les navigateurs Google Chrome et Microsoft Edge, à partir de la version 113. La prise en charge de Linux et Android est attendue dans un avenir proche.


Voici quelques-uns des moteurs qui prennent déjà en charge (ou offrent un support expérimental) pour WebGPU :


  • Babylon JS : Prise en charge complète de WebGPU.
  • ThreeJS : Support expérimental pour le moment.
  • PlayCanvas : En développement, mais avec des perspectives très prometteuses.
  • Unity : Un support très précoce et expérimental du WebGPU a été annoncé dans la version 2023.2 alpha.
  • Cocos Creator 3.6.2 : Supporte officiellement WebGPU, ce qui en fait l'un des pionniers dans ce domaine.
  • Construct : actuellement pris en charge dans la version v113+ pour Windows, macOS et ChromeOS uniquement.



Compte tenu de cela, la transition vers WebGPU ou au moins la préparation de projets pour une telle transition semble être une étape opportune dans un avenir proche.


Différences conceptuelles de haut niveau

Faisons un zoom arrière et examinons certaines des différences conceptuelles de haut niveau entre WebGL et WebGPU, en commençant par l'initialisation.

Initialisation

Lorsque vous commencez à travailler avec des API graphiques, l'une des premières étapes consiste à initialiser l'objet principal pour l'interaction. Ce processus diffère entre WebGL et WebGPU, avec quelques particularités pour les deux systèmes.

WebGL : le modèle de contexte

En WebGL, cet objet est appelé « contexte », qui représente essentiellement une interface permettant de dessiner sur un élément de canevas HTML5. Obtenir ce contexte est assez simple :

 const gl = canvas.getContext('webgl');


Le contexte de WebGL est en fait lié à un canevas spécifique. Cela signifie que si vous devez effectuer un rendu sur plusieurs canevas, vous aurez besoin de plusieurs contextes.

WebGPU : le modèle d'appareil

WebGPU introduit un nouveau concept appelé « appareil ». Cet appareil représente une abstraction GPU avec laquelle vous allez interagir. Le processus d'initialisation est un peu plus complexe qu'en WebGL, mais il offre plus de flexibilité :

 const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); const context = canvas.getContext('webgpu'); context.configure({ device, format: 'bgra8unorm', });


L'un des avantages de ce modèle est qu'un seul appareil peut effectuer le rendu sur plusieurs toiles, voire aucune. Cela offre une flexibilité supplémentaire ; par exemple, un appareil peut contrôler le rendu dans plusieurs fenêtres ou contextes.



Programmes et pipelines

WebGL et WebGPU représentent différentes approches pour gérer et organiser le pipeline graphique.

WebGL : programme

En WebGL, l'accent est mis principalement sur le programme de shader. Le programme combine des shaders de sommets et de fragments, définissant comment les sommets doivent être transformés et comment chaque pixel doit être coloré.

 const program = gl.createProgram(); gl.attachShader(program, vertShader); gl.attachShader(program, fragShader); gl.bindAttribLocation(program, 'position', 0); gl.linkProgram(program);


Étapes pour créer un programme en WebGL :


  1. Création de Shaders : Le code source des shaders est écrit et compilé.
  2. Création du programme : Les shaders compilés sont attachés au programme puis liés.
  3. Utilisation du programme : Le programme est activé avant le rendu.
  4. Transmission de données : Les données sont transmises au programme activé.


Ce processus permet un contrôle graphique flexible, mais peut également être complexe et sujet à des erreurs, en particulier pour les projets volumineux et complexes.

WebGPU : Pipeline

WebGPU introduit le concept de « pipeline » au lieu d'un programme distinct. Ce pipeline combine non seulement des shaders mais également d'autres informations qui, dans WebGL, sont établies sous forme d'états. Ainsi, créer un pipeline dans WebGPU semble plus complexe :

 const pipeline = device.createRenderPipeline({ layout: 'auto', vertex: { module: shaderModule, entryPoint: 'vertexMain', buffers: [{ arrayStride: 12, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }] }], }, fragment: { module: shaderModule, entryPoint: 'fragmentMain', targets: [{ format, }], }, });


Étapes pour créer un pipeline dans WebGPU :


  1. Définition du shader : le code source du shader est écrit et compilé, de la même manière que dans WebGL.
  2. Création de pipeline : les shaders et autres paramètres de rendu sont combinés dans un pipeline.
  3. Utilisation du pipeline : Le pipeline est activé avant le rendu.


Alors que WebGL sépare chaque aspect du rendu, WebGPU tente d'encapsuler davantage d'aspects dans un seul objet, rendant le système plus modulaire et flexible. Au lieu de gérer séparément les shaders et les états de rendu, comme cela se fait dans WebGL, WebGPU combine tout en un seul objet pipeline. Cela rend le processus plus prévisible et moins sujet aux erreurs :



Uniformes

Les variables uniformes fournissent des données constantes disponibles pour toutes les instances de shader.

Uniformes dans WebGL 1

Dans WebGL de base, nous avons la possibilité de définir des variables uniform directement via des appels API.


GLSL :

 uniform vec3 u_LightPos; uniform vec3 u_LightDir; uniform vec3 u_LightColor;


Javascript :

 const location = gl.getUniformLocation(p, "u_LightPos"); gl.uniform3fv(location, [100, 300, 500]);


Cette méthode est simple, mais nécessite plusieurs appels API pour chaque variable uniform .

Uniformes dans WebGL 2

Avec l'arrivée de WebGL 2, nous avons désormais la possibilité de regrouper des variables uniform dans des tampons. Bien que vous puissiez toujours utiliser des shaders uniformes distincts, une meilleure option consiste à regrouper différents uniformes dans une structure plus grande à l’aide de tampons uniformes. Ensuite, vous envoyez toutes ces données uniformes au GPU en même temps, de la même manière que vous pouvez charger un tampon de vertex dans WebGL 1. Cela présente plusieurs avantages en termes de performances, tels que la réduction des appels d'API et le fait d'être plus proche du fonctionnement des GPU modernes.


GLSL :

 layout(std140) uniform ub_Params { vec4 u_LightPos; vec4 u_LightDir; vec4 u_LightColor; };


Javascript :

 gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, gl.createBuffer());


Pour lier des sous-ensembles d'un grand tampon uniforme dans WebGL 2, vous pouvez utiliser un appel d'API spécial appelé bindBufferRange . Dans WebGPU, il existe quelque chose de similaire appelé décalages de tampon uniformes dynamiques où vous pouvez transmettre une liste de décalages lors de l'appel de l'API setBindGroup .



Uniformes dans WebGPU

WebGPU nous offre une méthode encore meilleure. Dans ce contexte, les variables uniform individuelles ne sont plus prises en charge et le travail est effectué exclusivement via des tampons uniform .


WGSL :

 [[block]] struct Params { u_LightPos : vec4<f32>; u_LightColor : vec4<f32>; u_LightDirection : vec4<f32>; }; [[group(0), binding(0)]] var<uniform> ub_Params : Params;


Javascript :

 const buffer = device.createBuffer({ usage: GPUBufferUsage.UNIFORM, size: 8 });


Les GPU modernes préfèrent que les données soient chargées dans un seul gros bloc plutôt que dans plusieurs petits. Au lieu de recréer et de relier de petits tampons à chaque fois, envisagez de créer un grand tampon et d'en utiliser différentes parties pour différents appels de tirage. Cette approche peut augmenter considérablement les performances.


WebGL est plus impératif, réinitialisant l'état global à chaque appel et s'efforçant d'être aussi simple que possible. WebGPU, quant à lui, vise à être plus orienté objet et axé sur la réutilisation des ressources, ce qui conduit à l'efficacité.


La transition de WebGL vers WebGPU peut sembler difficile en raison des différences de méthodes. Cependant, commencer par une transition vers WebGL 2 comme étape intermédiaire peut vous simplifier la vie.



Shaders

La migration de WebGL vers WebGPU nécessite des modifications non seulement dans l'API, mais également dans les shaders. La spécification WGSL est conçue pour rendre cette transition fluide et intuitive, tout en conservant l'efficacité et les performances des GPU modernes.

Langage de shader : GLSL vs WGSL

WGSL est conçu pour être un pont entre WebGPU et les API graphiques natives. Comparé à GLSL, WGSL semble un peu plus verbeux, mais la structure reste familière.


Voici un exemple de shader pour la texture :


GLSL :

 sampler2D myTexture; varying vec2 vTexCoord; void main() { return texture(myTexture, vTexCoord); }


WGSL :

 [[group(0), binding(0)]] var mySampler: sampler; [[group(0), binding(1)]] var myTexture: texture_2d<f32>; [[stage(fragment)]] fn main([[location(0)]] vTexCoord: vec2<f32>) -> [[location(0)]] vec4<f32> { return textureSample(myTexture, mySampler, vTexCoord); } 



Comparaison des types de données

Le tableau ci-dessous montre une comparaison des types de données de base et matricielles dans GLSL et WGSL :



La transition de GLSL vers WGSL démontre le désir d'un typage plus strict et d'une définition explicite de la taille des données, ce qui peut améliorer la lisibilité du code et réduire le risque d'erreurs.



Structures

La syntaxe de déclaration des structures a également changé :


GLSL :

 struct Light { vec3 position; vec4 color; float attenuation; vec3 direction; float innerAngle; float angle; float range; };


WGSL :

 struct Light { position: vec3<f32>, color: vec4<f32>, attenuation: f32, direction: vec3<f32>, innerAngle: f32, angle: f32, range: f32, };


L'introduction d'une syntaxe explicite pour déclarer les champs dans les structures WGSL souligne le désir d'une plus grande clarté et simplifie la compréhension des structures de données dans les shaders.



Déclarations de fonction

GLSL :

 float saturate(float x) { return clamp(x, 0.0, 1.0); }


WGSL :

 fn saturate(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }


La modification de la syntaxe des fonctions dans WGSL reflète l'unification de l'approche des déclarations et des valeurs de retour, rendant le code plus cohérent et prévisible.



Fonctions intégrées

Dans WGSL, de nombreuses fonctions GLSL intégrées ont été renommées ou remplacées. Par exemple:



Renommer les fonctions intégrées dans WGSL simplifie non seulement leurs noms, mais les rend également plus intuitives, ce qui peut faciliter le processus de transition pour les développeurs familiarisés avec d'autres API graphiques.



Conversion de nuanceur

Pour ceux qui envisagent de convertir leurs projets de WebGL vers WebGPU, il est important de savoir qu'il existe des outils pour convertir automatiquement GLSL en WGSL, tels que **[Naga](https://github.com/gfx-rs/naga /)**, qui est une bibliothèque Rust pour convertir GLSL en WGSL. Il peut même fonctionner directement dans votre navigateur avec l'aide de WebAssembly.


Voici les points de terminaison pris en charge par Naga :



Différences de convention

Textures

Après la migration, vous pourriez rencontrer une surprise sous la forme d'images inversées. Ceux qui ont déjà porté des applications d'OpenGL vers Direct3D (ou vice versa) ont déjà été confrontés à ce problème classique.


Dans le contexte d'OpenGL et WebGL, les textures sont généralement chargées de telle manière que le pixel de départ corresponde au coin inférieur gauche. Cependant, dans la pratique, de nombreux développeurs chargent les images en commençant par le coin supérieur gauche, ce qui entraîne une erreur d'image inversée. Néanmoins, cette erreur peut être compensée par d’autres facteurs, nivelant finalement le problème.



Contrairement à OpenGL, des systèmes tels que Direct3D et Metal utilisent traditionnellement le coin supérieur gauche comme point de départ pour les textures. Considérant que cette approche semble la plus intuitive pour de nombreux développeurs, les créateurs de WebGPU ont décidé de suivre cette pratique.

Espace de la fenêtre

Si votre code WebGL sélectionne des pixels dans le tampon de trame, préparez-vous au fait que WebGPU utilise un système de coordonnées différent. Vous devrez peut-être appliquer une simple opération "y = 1,0 - y" pour corriger les coordonnées.



Espaces de découpe

Lorsqu'un développeur est confronté à un problème où des objets sont tronqués ou disparaissent plus tôt que prévu, cela est souvent lié à des différences dans le domaine de la profondeur. Il existe une différence entre WebGL et WebGPU dans la manière dont ils définissent la plage de profondeur de l'espace de découpage. Alors que WebGL utilise une plage de -1 à 1, WebGPU utilise une plage de 0 à 1, similaire à d'autres API graphiques telles que Direct3D, Metal et Vulkan. Cette décision a été prise en raison de plusieurs avantages liés à l'utilisation d'une plage de 0 à 1, identifiés lors de l'utilisation d'autres API graphiques.



La principale responsabilité de la transformation des positions de votre modèle en espace de clip incombe à la matrice de projection. Le moyen le plus simple d'adapter votre code est de vous assurer que les résultats de votre matrice de projection sont compris entre 0 et 1. Pour ceux qui utilisent des bibliothèques telles que gl-matrix, il existe une solution simple : au lieu d'utiliser la fonction perspective , vous pouvez utiliser perspectiveZO ; des fonctions similaires sont disponibles pour d’autres opérations matricielles.

 if (webGPU) { // Creates a matrix for a symetric perspective-view frustum // using left-handed coordinates mat4.perspectiveZO(out, Math.PI / 4, ...); } else { // Creates a matrix for a symetric perspective-view frustum // based on the default handedness and default near // and far clip planes definition. mat4.perspective(out, Math.PI / 4, …); }


Cependant, il arrive parfois que vous disposiez d'une matrice de projection existante et que vous ne puissiez pas modifier sa source. Dans ce cas, pour la transformer en une plage de 0 à 1, vous pouvez pré-multiplier votre matrice de projection par une autre matrice qui corrige la plage de profondeur.



Trucs et astuces WebGPU

Voyons maintenant quelques trucs et astuces pour travailler avec WebGPU.

Réduisez le nombre de pipelines que vous utilisez.

Plus vous utilisez de pipelines, plus vous avez de changements d’état et moins de performances ; cela n’est peut-être pas anodin, selon la provenance de vos actifs.

Créer des pipelines à l'avance

Créer un pipeline et l'utiliser immédiatement peut fonctionner, mais cela n'est pas recommandé. Au lieu de cela, créez des fonctions qui reviennent immédiatement et commencez à travailler sur un autre thread. Lorsque vous utilisez le pipeline, la file d'attente d'exécution doit attendre la fin des créations de pipeline en attente. Cela peut entraîner des problèmes de performances importants. Pour éviter cela, veillez à laisser un certain temps entre la création du pipeline et sa première utilisation.


Ou, mieux encore, utilisez les variantes create*PipelineAsync ! La promesse se réalise lorsque le pipeline est prêt à être utilisé, sans aucun blocage.

 device.createComputePipelineAsync({ compute: { module: shaderModule, entryPoint: 'computeMain' } }).then((pipeline) => { const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginComputePass(); passEncoder.setPipeline(pipeline); passEncoder.setBindGroup(0, bindGroup); passEncoder.dispatchWorkgroups(128); passEncoder.end(); device.queue.submit([commandEncoder.finish()]); });

Utiliser les RenderBundles

Les bundles de rendu sont des passes de rendu préenregistrées, partielles et réutilisables. Ils peuvent contenir la plupart des commandes de rendu (à l'exception de choses comme la configuration de la fenêtre d'affichage) et peuvent être "rejoués" ultérieurement dans le cadre d'une passe de rendu réelle.


 const renderPass = encoder.beginRenderPass(descriptor); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.executeBundles([renderBundle]); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.end();


Les bundles de rendu peuvent être exécutés parallèlement aux commandes de passe de rendu habituelles. L’état de réussite du rendu est réinitialisé aux valeurs par défaut avant et après chaque exécution du bundle. Ceci est principalement fait pour réduire la surcharge JavaScript du dessin. Les performances du GPU restent les mêmes quelle que soit l’approche.

Résumé

La transition vers WebGPU signifie plus que simplement changer d’API graphique. C'est également une étape vers l'avenir du graphisme Web, combinant les fonctionnalités et les pratiques réussies de diverses API graphiques. Cette migration nécessite une compréhension approfondie des évolutions techniques et philosophiques, mais les bénéfices sont significatifs.

Ressources et liens utiles :