Bonnes pratiques de WebGL

This translation is incomplete. Please help translate this article from English

Cet article fournit des suggestions et des conseils vous permettant d'améliorer votre contenu WebGL. Suivre ces suggestions vous permettra d'améliorer la compatibilité de votre application web avec plus de périphériques et de navigateurs et d'optimiser ses performances.

Choses à éviter

  • Assurez-vous toujours que votre application fonctionne sans générer d'erreurs WebGL, retournées par getError(). Dans Firefox, toutes les erreurs WebGL (jusqu'à une certaine limite) et certains problèmes issus de WebGL sont signalés comme avertissements Javascript ainsi qu'un message de description. Vous ne voulez pas que votre app vomisse dans la console utilisateur ? Évidemment que non.
  • N'utilisez pas #ifdef GL_ES dans vos shaders WebGL; bien que certains exemples l'aient utilisé, ce n'est plus nécessaire puisque cette condition est toujours vraie dans les shaders WebGL.
  • Using highp precision in fragment shaders will prevent your content from working on some older mobile hardware. You can use mediump instead, but be aware that this often results in corrupted rendering due to lack of precision on most mobile devices, and the corruption is not going to be visible on a typical desktop computer. In general, only using highp in both vertex and fragment shaders is safer unless shaders are thoroughly tested on a variety of platforms. Starting in Firefox 11, the WebGL getShaderPrecisionFormat() function is implemented, allowing you to check if highp precision is supported, and more generally letting you query the actual precision of all supported precision qualifiers.

Things to keep in mind

  • Some WebGL capabilities depend on the client. Before relying on them, you should use the WebGL getParameter() function to determine what values are supported on the client. For example, the maximum size of a 2D texture is given by webgl.getParameter(webgl.MAX_TEXTURE_SIZE). Starting in Firefox 10, the webgl.min_capability_mode preference allows simulating minimal values for these capabilities, to test portability.
  • In particular, note that usage of textures in vertex shaders is only possible if webgl.getParameter(webgl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) is greater than zero. Typically, this fails on current mobile hardware.
  • The availability of most WebGL extensions depends on the client. When using WebGL extensions, if possible, try to make them optional by gracefully adapting to the case there they are not supported. Starting in Firefox 10, the webgl.disable-extensions preference allows simulating the absence of all extensions, to test portability.
  • Rendering to a floating-point texture may not be supported, even if the OES_texture_float extension is supported. Typically, this fails on current mobile hardware. To check if this is supported, you have to call the WebGL checkFramebufferStatus() function.
  • Rendering to a canvas can be done at a different resolution than the style sheet will eventually force the canvas to appear at. If struggling with performance you should consider rendering to a low resolution WebGL context and using CSS to upscale its canvas to the size you intend.

Conseils de performance généraux

  • Tout ce qui requiert une synchronisation entre le CPU et le GPU est potentiellement très lent, donc évitez si possible de le faire dans vos boucles principales de rendering. Ceci inclut les appels WebGL suivants : getError(), readPixels(), et finish(). Les appels getter WebGL comme getParameter() et getUniformLocation() doivent également être considérés comme lents, donc efforcez-vous de mettre leurs résultats en cache dans une variable JavaScript.
  • Fewer, larger draw operations will improve performance. If you have 1000 sprites to paint, try to do it as a single drawArrays() or drawElements() call. You can draw degenerate (flat) triangles if you need to draw discontinuous objects as a single drawArrays() call.
  • Fewer state changes will also improve performance. In particular, if you can pack multiple images into a single texture and select them by using the appropriate texture coordinates, that can help you do fewer texture binding changes, which improves performance.
    • In some rare cases, packing greyscale textures which belong together into the color channels of a single texture might help.
  • Smaller textures perform better than larger ones. For this reason, mipmapping can be a performance win.
  • Simpler shaders perform better than complex ones. In particular, if you can remove an if statement from a shader, that will make it run faster. Division and math functions like log() should be considered expensive too.
    • However, nowadays even mobile devices possess powerful GPUs that are capable of running even relatively complex shader programs. Moreover, because shaders are compiled, the eventual machine code that actually runs on the hardware may be highly optimized. What may seem like an expensive function call may in fact compile into only few (or even a single) machine instructions. This is particularly true for GLSL functions that typically operate on vectors, such as normalize(), dot() and mix(). The best advice in that regard is to use the built-in functions, rather than try to implement, for example, one's own version of a dot-product or linear interpolation, which may in fact compile to larger and less optimized machine code. Finally, it is important to keep in mind that GPUs are constructed to do complex mathematical calculations in hardware, and therefore, may support math functions, such as sin(), cos() and other, through dedicated machine instructions.
  • Do as much as you can in the vertex shader, rather than in the fragment shader. Because, per rendering pass, fragment shaders run many more times than vertex shaders, any calculation that can be done on the vertices and then just interpolated among fragments is a performance boon (this interpolation is done "automagically" for you, through the fixed functionality rasterization phase of the OpenGL pipeline). For example, a simple animation of a textured surface can be achieved through a time-dependent transformation of texture coordinates (simplest case is to add a uniform vector to the texture coordinates attribute vector). If visually acceptable, one can transform the texture coordinates in the vertex shader rather than in the fragment shader, to get better performance.
  • Always have vertex attrib 0 array enabled. If you draw with vertex attrib 0 array disabled, you will force the browser to do complicated emulation when running on desktop OpenGL (e.g. on Mac OSX). This is because in desktop OpenGL, nothing gets drawn if vertex attrib 0 is not array-enabled. You can use bindAttribLocation() to force a vertex attribute to use location 0, and use enableVertexAttribArray() to make it array-enabled.