WebGL best practices

This article offers suggestions and tips to improve your WebGL content. Following these suggestions can help improve your web application's compatibility with more devices and browsers, as well as increase its performance.

Things to avoid

  • Always make sure that your application runs without generating any WebGL errors, as returned by getError(). On Firefox, by default, WebGL errors generate a unique JavaScript warning, visible in the Web Console, suggesting that you flip the webgl.verbose preference. Setting webgl.verbose causes every WebGL error, and some other WebGL issues, to be reported as a JavaScript warning with a descriptive message. You don't really want your web app spewing stuff into the user's console, do you? Of course you don't.
  • You should never use #ifdef GL_ES in your WebGL shaders; although some early examples used this, it's not necessary, since this condition is always true in WebGL shaders.
  • Don't explicitly require highp precision in fragment shaders, unless you really need to. Try using mediump instead. Using highp precision in fragment shaders will prevent your content from working on most current mobile hardware. 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.

General performance tips

  • Anything that requires syncing the CPU and GPU sides is potentially very slow, so if possible you should try to avoid doing that in your main rendering loops. This includes the following WebGL calls: getError(), readPixels(), and finish(). WebGL getter calls such as getParameter() and getUniformLocation() should be considered slow too, so try to cache their results in a JavaScript variable.
  • 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.
  • 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.
  • 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.
  • If you use drawElements(), try to always consume the whole element array buffer. That is if you care about performance, don't call drawElements() on only a part of your element array buffer: keep first=0 and count=the size of the element array buffer. The reason for this is that the browser has to validate that all the indices are in range, which is easy enough to cache when you're using the whole element array buffer, but is much harder to cache for arbitrary sub-arrays.
  • For the same reasons, updating an element array buffer with bufferData() or bufferSubData() causes the next drawElement() call to be slow, because the cache is invalidated.

See also

Tags (2)

Edit tags

Attachments (0)


Attach file