Ce n’est probablement pas une surprise que tous les programmes WebGL ne fonctionnent pas sur tous les appareils ou navigateurs.
Voici une liste de la plupart des problèmes que vous pourriez rencontrer d’emblée
Un GPU haut de gamme fonctionne probablement 100x plus vite qu’un GPU bas de gamme. La seule façon de contourner ça que je connaisse est soit de viser bas, soit de donner à l’utilisateur des options comme le font la plupart des applications PC de bureau où il peut choisir entre performance ou fidélité.
De même, un GPU haut de gamme pourrait avoir 12 à 24 gig de RAM alors qu’un GPU bas de gamme a probablement moins de 1 gig. (Je suis vieux donc il est étonnant pour moi que bas de gamme = 1 gig puisque j’ai commencé à programmer sur des machines avec 16k à 64k de mémoire 😜)
WebGL a diverses fonctionnalités minimum supportées, mais votre appareil local pourrait supporter plus que ce minimum, ce qui signifie que ça échouera sur d’autres appareils qui en supportent moins.
Les exemples incluent :
La taille maximale de texture autorisée
2048 ou 4096 semble être des limites raisonnables. Au moins en 2020, il semble que 99% des appareils supportent 4096 mais seulement 50% supportent > 4096.
Note : la taille maximale de texture est la dimension maximale que le GPU peut traiter. Ça ne signifie pas que ce GPU a assez de mémoire pour cette dimension au carré (pour une texture 2D) ou au cube (pour une texture 3D). Par exemple, certains GPU ont une taille max de 16384. Mais une texture 3D de 16384 de chaque côté nécessiterait 16 téraoctets de mémoire !!!
Le nombre maximum d’attributs de sommets dans un seul programme
Dans WebGL1, le minimum supporté est 8. Dans WebGL2, c’est 16. Si vous en utilisez plus, votre code échouera sur une machine avec seulement le minimum.
Le nombre maximum de vecteurs d’uniforms
Ceux-ci sont spécifiés séparément pour les vertex shaders et les fragment shaders.
Dans WebGL1, c’est 128 pour les vertex shaders et 16 pour les fragment shaders. Dans WebGL2, c’est 256 pour les vertex shaders et 224 pour les fragment shaders.
Notez que les uniforms peuvent être “empaquetés” donc le nombre ci-dessus est combien de vec4s
peuvent être utilisés. En théorie, vous pourriez avoir 4x le nombre d’uniforms float.
Mais il y a un algorithme qui les fait rentrer. Vous pouvez imaginer l’espace comme
un tableau avec 4 colonnes, une ligne pour chacun des vecteurs d’uniforms maximum ci-dessus.
+-+-+-+-+
| | | | | <- un vec4
| | | | | |
| | | | | |
| | | | | V
| | | | | lignes max de vecteurs d'uniforms
| | | | |
| | | | |
| | | | |
...
D’abord les vec4s sont alloués avec un mat4 étant 4 vec4s. Ensuite les vec3s sont
placés dans l’espace restant. Puis les vec2s suivis des floats. Donc imaginez qu’on ait 1
mat4, 2 vec3s, 2 vec2s et 3 floats
+-+-+-+-+
|m|m|m|m| <- le mat4 prend 4 lignes
|m|m|m|m|
|m|m|m|m|
|m|m|m|m|
|3|3|3| | <- les 2 vec3s prennent 2 lignes
|3|3|3| |
|2|2|2|2| <- les 2 vec2s tiennent dans 1 ligne
|f|f|f| | <- les 3 floats tiennent dans une ligne
...
De plus, un tableau d’uniforms est toujours vertical, donc par exemple si le maximum
de vecteurs d’uniforms autorisés est 16, vous ne pouvez pas avoir un tableau float de 17 éléments,
et en fait si vous aviez un seul vec4 cela prendrait une ligne entière donc il ne reste
que 15 lignes, ce qui signifie que le plus grand tableau que vous pouvez avoir serait de 15
éléments.
Mon conseil cependant est de ne pas compter sur un empaquetage parfait. Bien que la spécification dise que l’algorithme ci-dessus est requis pour passer, il y a trop de combinaisons à tester pour que tous les drivers passent. Soyez juste conscient si vous approchez de la limite.
Note : les varyings et attributs ne peuvent pas être empaquetés.
Les vecteurs de varyings maximum.
WebGL1 le minimum est 8. WebGL2 c’est 16.
Si vous en utilisez plus, votre code ne fonctionnera pas sur une machine avec seulement le minimum.
Les unités de texture maximum
Il y a 3 valeurs ici.
| WebGL1 | WebGL2 | |
|---|---|---|
| min unités de texture qui existent | 8 | 32 |
| min unités de texture qu'un vertex shader peut référencer | 0! | 16 |
| min unités de texture qu'un fragment shader peut référencer | 8 | 16 |
Il est important de noter le 0 pour un vertex shader dans WebGL1. Notez que ce n’est probablement pas la fin du monde. Apparemment, ~97% de tous les appareils supportent au moins 4. Mais vous pourriez vouloir vérifier afin de soit informer l’utilisateur que votre application ne va pas fonctionner pour lui, soit vous pouvez vous rabattre sur d’autres shaders.
Il existe d’autres limites également. Pour les rechercher, vous appelez gl.getParameter avec
les valeurs suivantes.
| MAX_TEXTURE_SIZE | taille max d'une texture |
| MAX_VERTEX_ATTRIBS | nombre d'attributs que vous pouvez avoir |
| MAX_VERTEX_UNIFORM_VECTORS | nombre d'uniforms vec4 qu'un vertex shader peut avoir |
| MAX_VARYING_VECTORS | nombre de varyings que vous avez |
| MAX_COMBINED_TEXTURE_IMAGE_UNITS | nombre d'unités de texture qui existent |
| MAX_VERTEX_TEXTURE_IMAGE_UNITS | nombre d'unités de texture qu'un vertex shader peut référencer |
| MAX_TEXTURE_IMAGE_UNITS | nombre d'unités de texture qu'un fragment shader peut référencer |
| MAX_FRAGMENT_UNIFORM_VECTORS | nombre d'uniforms vec4 qu'un fragment shader peut avoir |
| MAX_CUBE_MAP_TEXTURE_SIZE | taille max d'un cubemap |
| MAX_RENDERBUFFER_SIZE | taille max d'un renderbuffer |
| MAX_VIEWPORT_DIMS | taille max du viewport |
Ce n’est pas la liste complète. Par exemple, la taille maximale des points et l’épaisseur maximale des lignes, mais vous devriez fondamentalement supposer que l’épaisseur maximale des lignes est 1.0 et que les POINTS ne sont utiles que pour de simples démos où vous ne vous souciez pas des problèmes de découpage.
WebGL2 en ajoute plusieurs autres. Quelques-uns courants sont
| MAX_3D_TEXTURE_SIZE | taille max d'une texture 3D |
| MAX_DRAW_BUFFERS | nombre d'attachements de couleur que vous pouvez avoir |
| MAX_ARRAY_TEXTURE_LAYERS | nombre max de couches dans un tableau de textures 2D |
| MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS | nombre de varyings que vous pouvez émettre vers des buffers séparés lors de l'utilisation du transform feedback |
| MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS | nombre de varyings que vous pouvez émettre quand on les envoie tous vers un seul buffer |
| MAX_COMBINED_UNIFORM_BLOCKS | nombre de blocs d'uniforms que vous pouvez utiliser au total |
| MAX_VERTEX_UNIFORM_BLOCKS | nombre de blocs d'uniforms qu'un vertex shader peut utiliser |
| MAX_FRAGMENT_UNIFORM_BLOCKS | nombre de blocs d'uniforms qu'un fragment shader peut utiliser |
Quelques très anciens appareils mobiles utilisent des depth buffers de 16 bits. Sinon, autant que je sache, 99% des appareils utilisent un depth buffer de 24 bits donc vous n’avez probablement pas à vous inquiéter de ça.
Seuls certains combos format/type sont garantis de fonctionner. D’autres combos sont optionnels. Ceci est couvert dans cet article.
Les framebuffers peuvent avoir 1 ou plusieurs attachements de textures et renderbuffers.
Dans WebGL1, seules 3 combinaisons d’attachements sont garanties de fonctionner.
RGBA, type = UNSIGNED_BYTE comme COLOR_ATTACHMENT0RGBA, type = UNSIGNED_BYTE comme COLOR_ATTACHMENT0 et un
renderbuffer avec format = DEPTH_COMPONENT attaché comme DEPTH_ATTACHMENTRGBA, type = UNSIGNED_BYTE comme COLOR_ATTACHMENT0 et un
renderbuffer avec format = DEPTH_STENCIL attaché comme DEPTH_STENCIL_ATTACHMENTToutes les autres combinaisons dépendent de l’implémentation que vous vérifiez en appelant
gl.checkFramebufferStatus et en voyant s’il retourne FRAMEBUFFER_COMPLETE.
WebGL2 garantit de pouvoir écrire dans beaucoup plus de formats mais a quand même la limite que n’importe quelle combinaison peut échouer ! Votre meilleure chance est peut-être que si tous les attachements de couleur sont du même format si vous en attachez plus d’un.
De nombreuses fonctionnalités de WebGL1 et WebGL2 sont optionnelles. Tout l’intérêt d’avoir une
API appelée getExtension est qu’elle peut échouer si l’extension n’existe pas
et vous devriez donc vérifier cet échec et ne pas supposer aveuglément qu’elle réussira.
Probablement l’extension manquante la plus courante sur WebGL1 et WebGL2 est
OES_texture_float_linear qui est la capacité de filtrer une texture à virgule flottante,
c’est-à-dire la capacité de supporter la définition de TEXTURE_MIN_FILTER et
TEXTURE_MAX_FILTER à autre chose que NEAREST. De nombreux appareils mobiles ne
supportent pas ça.
Dans WebGL1, une autre extension souvent manquante est WEBGL_draw_buffers qui est la
capacité d’attacher plus d’un attachement de couleur à un framebuffer, qui est encore à
environ 70% pour le bureau et presque aucun pour les smartphones (cela semble faux).
Fondamentalement, tout appareil qui peut exécuter WebGL2 devrait également supporter
WEBGL_draw_buffers dans WebGL1, mais quand même, c’est apparemment encore un problème. Si vous
avez besoin de rendre vers plusieurs textures à la fois, votre page nécessite probablement un
GPU haut de gamme de toute façon. Quand même, vous devriez vérifier si l’appareil de l’utilisateur le supporte et
si non, fournir une explication conviviale.
Pour WebGL1, les 3 extensions suivantes semblent presque universellement supportées, donc bien que vous puissiez vouloir avertir l’utilisateur que votre page ne va pas fonctionner si elles sont manquantes, il est probable que cet utilisateur a un appareil extrêmement ancien qui n’allait pas faire fonctionner votre page de toute façon.
Ce sont ANGLE_instance_arrays (la capacité d’utiliser le dessin instancié),
OES_vertex_array_object (la capacité de stocker tout l’état des attributs dans un objet pour pouvoir échanger tout
cet état avec un seul appel de fonction. Voir ceci), et OES_element_index_uint
(la capacité d’utiliser des indices 32 bits UNSIGNED_INT avec drawElements).
Un bug semi-courant est de ne pas rechercher les emplacements des attributs. Par exemple, vous avez un vertex shader comme
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = matrix * position;
v_texcoord = texcoord;
}
Votre code suppose que position sera l’attribut 0 et texcoord sera
l’attribut 1, mais ce n’est pas garanti. Donc ça fonctionne pour vous mais échoue pour quelqu’un
d’autre. Souvent, cela peut être un bug en ce sens que vous n’avez pas fait ça intentionnellement mais
à cause d’une erreur dans le code, les choses fonctionnent quand les emplacements sont d’une façon mais pas
d’une autre.
Il y a 3 solutions.
Toujours rechercher les emplacements.
Assigner des emplacements en appelant gl.bindAttribLocation avant d’appeler gl.linkProgram
WebGL2 seulement, définir les emplacements dans le shader comme dans
#version 300 es
layout(location = 0) vec4 position;
latout(location = 1) vec2 texcoord;
...
La solution 2 semble la plus D.R.Y. alors que la solution 3 semble la plus W.E.T. sauf si vous générez vos textures à l’exécution.
Plusieurs fonctions GLSL ont un comportement indéfini. Par exemple pow(x, y) est
indéfini si x < 0. Il y a une liste plus longue en bas de l’article sur
l’éclairage spot.
En 2020, le plus grand problème ici est que si vous utilisez mediump ou lowp dans vos shaders,
alors sur le bureau le GPU utilisera vraiment highp, mais sur mobile ils seront vraiment
mediump et/ou lowp et vous ne remarquerez donc aucun problème lors du développement sur le bureau.
Voir cet article pour plus de détails.
Les POINTS et LINES dans WebGL peuvent avoir une taille max de 1 et en fait pour les LINES,
c’est maintenant la limite la plus courante. De plus, si les points sont découpés quand leur
centre est en dehors du viewport est défini par l’implémentation. Voir le bas de
cet article.
De même, si le viewport découpe seulement les sommets ou aussi les pixels est indéfini. Les ciseaux découpent toujours les pixels donc activez le test des ciseaux et définissez la taille des ciseaux si vous définissez le viewport plus petit que ce vers quoi vous dessinez et que vous dessinez des LINES ou POINTS.