WebGL and OpenGL Differences

From WebGL Public Wiki
Revision as of 20:06, 27 August 2010 by Bjacob (talk | contribs) (remove my newly added section on completeness: actually the same stuff exists in desktop OpenGL, see 4.1 spec, section 3.9.14)
Jump to navigation Jump to search

WebGL is based on the OpenGL ES 2.0 specification, and retains the semantics of OpenGL ES in order to maximize portability to mobile devices. There are some significant differences in behavior of similar APIs between OpenGL ES 2.0 and the OpenGL API on desktop systems. Many OpenGL programmers are familiar with the semantics on the desktop, and may not know about these differences in behavior. We highlight them here for clarity.

Non-Power of Two Texture Support

While OpenGL 2.0 and later for the desktop offer full support for non-power-of-two (NPOT) textures, OpenGL ES 2.0 and WebGL have only limited NPOT support. The restrictions are defined in Sections 3.8.2, "Shader Execution", and 3.7.11, "Mipmap Generation", of the OpenGL ES 2.0 specification, and are summarized here:

  • generateMipmap(target) generates an INVALID_OPERATION error if the level 0 image of the texture currently bound to target has an NPOT width or height.
  • Sampling an NPOT texture in a shader will produce the RGBA color (0, 0, 0, 1) if:
    • The minification filter is set to anything but NEAREST or LINEAR: in other words, if it uses one of the mipmapped filters.
    • The repeat mode is set to anything but CLAMP_TO_EDGE; repeating NPOT textures are not supported.

If your application doesn't require the REPEAT wrap mode, and can tolerate the lack of mipmaps, then you can simply configure the WebGLTexture object appropriately at creation time:

var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

However, if your application requires the REPEAT wrap mode for correctness, you can easily resize the image to the next largest power of two dimensions using DOM APIs. Here is an example of how to do this. image is an HTML image object that has been fully loaded; its onload handler has already been called.

function createTextureFromImage(image) {
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    if (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height)) {
        // Scale up the texture to the next highest power of two dimensions.
        var canvas = document.createElement("canvas");
        canvas.width = nextHighestPowerOfTwo(image.width);
        canvas.height = nextHighestPowerOfTwo(image.height);
        var ctx = canvas.getContext("2d");
                      0, 0, image.width, image.height,
                      0, 0, canvas.width, canvas.height);
        image = canvas;
    gl.texImage2D(gl.TEXTURE_2D, 0, image);
    gl.bindTexture(gl.TEXTURE_2D, null);
    return texture;

function isPowerOfTwo(x) {
    return (x & (x - 1)) == 0;

function nextHighestPowerOfTwo(x) {
    for (var i = 1; i < 32; i <<= 1) {
        x = x | x >> i;
    return x + 1;

If your application allows, you can also resize your source images offline.

Vertex Attribute 0

On desktop GL, vertex attribute 0 has special semantics. First, it must be enabled as an array, or no geometry will be drawn. Second, it does not have persistent state, so calling glGetVertexAttribfv(0, GL_CURRENT_VERTEX_ATTRIB, ...) generates an OpenGL error.

On OpenGL ES 2.0, vertex attribute 0 has no special semantics.

WebGL follows the OpenGL ES 2.0 convention; all vertex attributes behave identically. This requires implementations on desktop GL to perform a certain amount of emulation, but this was considered to be a small price to pay for consistent behavior.


GLSL texture functions that end in "Lod" (eg texture2DLod) are only permitted in the vertex shader.