diff --git a/3rdparty/js-test-pre.js b/3rdparty/js-test-pre.js
index 2127176d47510203774f3bcddb44e0bc6907db1b..66a84190680e861de3592ff1b5470863b6b89065 100644
--- a/3rdparty/js-test-pre.js
+++ b/3rdparty/js-test-pre.js
@@ -20,7 +20,9 @@
 ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
 */
-
+function output(msg) {
+    //console.log(msg)
+}
 function areArraysEqual(_a, _b)
 {
     try {
@@ -61,9 +63,9 @@ function stringify(v)
 function evalAndLog(_a)
 {
     if (typeof _a != "string")
-        debug("WARN: tryAndLog() expects a string argument");
+        output("WARN: tryAndLog() expects a string argument");
     // Log first in case things go horribly wrong or this causes a sync event.
-    debug(_a);
+    output(_a);
     var _av;
     try {
         _av = eval(_a);
@@ -75,7 +77,7 @@ function evalAndLog(_a)
 function shouldBe(_a, _b)
 {
     if (typeof _a != "string" || typeof _b != "string")
-        debug("WARN: shouldBe() expects string arguments");
+        output("WARN: shouldBe() expects string arguments");
     var exception;
     var _av;
     try {
@@ -85,15 +87,15 @@ function shouldBe(_a, _b)
     }
     var _bv = eval(_b);
     if (exception) {
-        console.log(_a + " should be " + _bv + ". Threw exception " + exception);
+        output(_a + " should be " + _bv + ". Threw exception " + exception);
         return false;//testFailed(_a + " should be " + _bv + ". Threw exception " + exception);
     } else if (isResultCorrect(_av, _bv)) {
         return true;//testPassed(_a + " is " + _b);
     } else if (typeof(_av) == typeof(_bv)) {
-        console.log(_a + " should be " + _bv + ". Was " + stringify(_av) + ".");
+        output(_a + " should be " + _bv + ". Was " + stringify(_av) + ".");
         return false;//testFailed(_a + " should be " + _bv + ". Was " + stringify(_av) + ".");
     } else {
-        console.log(_a + " should be " + _bv + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ").");
+        output(_a + " should be " + _bv + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ").");
         return false;//testFailed(_a + " should be " + _bv + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ").");
     }
 }
@@ -148,7 +150,7 @@ function shouldEvaluateTo(actual, expected) {
         retval = shouldBe(actual, stringify(expected));
         if (!retval) return false;
     } else {
-        debug(expected + " is unknown type " + typeof expected);
+        output(expected + " is unknown type " + typeof expected);
         retval = shouldBeTrue(actual, "'" +expected.toString() + "'");
         if (!retval) return false;
     }
@@ -163,12 +165,15 @@ function shouldBeNonZero(_a)
     } catch (e) {
         exception = e;
     }
-    if (exception)
+    if (exception) {
+        output(_a + " should be non-zero. Threw exception " + exception);
         return false;//testFailed(_a + " should be non-zero. Threw exception " + exception);
-    else if (_av != 0)
+    } else if (_av != 0) {
         return true;//testPassed(_a + " is non-zero.");
-    else
+    } else {
+        output(_a + " should be non-zero. Was " + _av);
         return false;//testFailed(_a + " should be non-zero. Was " + _av);
+    }
 }
 function shouldBeNonNull(_a)
 {
@@ -179,12 +184,15 @@ function shouldBeNonNull(_a)
     } catch (e) {
         exception = e;
     }
-    if (exception)
+    if (exception) {
+        output(_a + " should be non-null. Threw exception " + exception);
         return false;//testFailed(_a + " should be non-null. Threw exception " + exception);
-    else if (_av != null)
+    } else if (_av != null) {
         return true;//testPassed(_a + " is non-null.");
-    else
+    } else {
+        output(_a + " should be non-null. Was " + _av);
         return false;//testFailed(_a + " should be non-null. Was " + _av);
+    }
 }
 function shouldBeUndefined(_a)
 {
@@ -195,12 +203,15 @@ function shouldBeUndefined(_a)
     } catch (e) {
         exception = e;
     }
-    if (exception)
+    if (exception) {
+        output(_a + " should be undefined. Threw exception " + exception);
         return false;//testFailed(_a + " should be undefined. Threw exception " + exception);
-    else if (typeof _av == "undefined")
+    } else if (typeof _av == "undefined") {
         return true;//testPassed(_a + " is undefined.");
-    else
+    } else {
+        output(_a + " should be undefined. Was " + _av);
         return false;//testFailed(_a + " should be undefined. Was " + _av);
+    }
 }
 function shouldBeDefined(_a)
 {
@@ -211,16 +222,19 @@ function shouldBeDefined(_a)
     } catch (e) {
         exception = e;
     }
-    if (exception)
+    if (exception) {
+        output(_a + " should be defined. Threw exception " + exception);
         return false;//testFailed(_a + " should be defined. Threw exception " + exception);
-    else if (_av !== undefined)
+    } else if (_av !== undefined) {
         return true;//testPassed(_a + " is defined.");
-    else
+    } else {
+        output(_a + " should be defined. Was " + _av);
         return false;//testFailed(_a + " should be defined. Was " + _av);
+    }
 }
 function shouldBeGreaterThanOrEqual(_a, _b) {
     if (typeof _a != "string" || typeof _b != "string")
-        console.log("WARN: shouldBeGreaterThanOrEqual expects string arguments");
+        output("WARN: shouldBeGreaterThanOrEqual expects string arguments");
     var exception;
     var _av;
     try {
@@ -230,10 +244,10 @@ function shouldBeGreaterThanOrEqual(_a, _b) {
     }
     var _bv = eval(_b);
     if (exception) {
-        console.log(_a + " should be >= " + _b + ". Threw exception " + exception);
+        output(_a + " should be >= " + _b + ". Threw exception " + exception);
         return false;//testFailed(_a + " should be >= " + _b + ". Threw exception " + exception);
     } else if (typeof _av == "undefined" || _av < _bv) {
-        console.log(_a + " should be >= " + _b + ". Was " + _av + " (of type " + typeof _av + ").");
+        output(_a + " should be >= " + _b + ". Was " + _av + " (of type " + typeof _av + ").");
         return false;//testFailed(_a + " should be >= " + _b + ". Was " + _av + " (of type " + typeof _av + ").");
     } else {
         return true;//testPassed(_a + " is >= " + _b);
@@ -252,20 +266,25 @@ function shouldThrow(_a, _e)
     if (_e)
         _ev = eval(_e);
     if (exception) {
-        if (typeof _e == "undefined" || exception == _ev)
+        if (typeof _e == "undefined" || exception == _ev) {
             return true;//testPassed(_a + " threw exception " + exception + ".");
-        else
+        } else {
+            output(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Threw exception " + exception + ".");
             return false;//testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Threw exception " + exception + ".");
-    } else if (typeof _av == "undefined")
+        }
+    } else if (typeof _av == "undefined") {
+        output(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was undefined.");
         return false;//testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was undefined.");
-    else
+    } else {
+        output(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was " + _av + ".");
         return false;//testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was " + _av + ".");
+    }
 }
 function assertMsg(assertion, msg) {
     if (assertion) {
         return true;//testPassed(msg);
     } else {
-        console.log(msg);
+        output(msg);
         return false;//testFailed(msg);
     }
 }
diff --git a/3rdparty/test-eval.js b/3rdparty/test-eval.js
new file mode 100644
index 0000000000000000000000000000000000000000..35a4ef6faca8d4804559aebb3c297e676c8cd349
--- /dev/null
+++ b/3rdparty/test-eval.js
@@ -0,0 +1,30 @@
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+/**
+* Calls eval.
+*
+* This is here so other modules can use "use strict":
+*/
+var TestEval = function(str) {
+    return eval(str);
+};
diff --git a/3rdparty/webgl-test-utils.js b/3rdparty/webgl-test-utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..aafa628a07d16ffb6a3dc17bfee18ce0989944a3
--- /dev/null
+++ b/3rdparty/webgl-test-utils.js
@@ -0,0 +1,2562 @@
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+var WebGLTestUtils = function() {
+    "use strict";
+
+    var output = function(msg) {
+        console.log(msg)
+    };
+
+    /**
+* Wrapped logging function.
+* @param {string} msg The message to log.
+*/
+    var log = function(msg) {
+    };
+    /**
+* Wrapped logging function.
+* @param {string} msg The message to log.
+*/
+    var error = function(msg) {
+        output(msg)
+    };
+    /**
+* Turn off all logging.
+*/
+    var loggingOff = function() {
+    };
+    /**
+* Converts a WebGL enum to a string.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} value The enum value.
+* @return {string} The enum as a string.
+*/
+    var glEnumToString = function(gl, value) {
+        // Optimization for the most common enum:
+        if (value === gl.NO_ERROR) {
+            return "NO_ERROR";
+        }
+        for (var p in gl) {
+            if (gl[p] == value) {
+                return p;
+            }
+        }
+        return "0x" + value.toString(16);
+    };
+    var lastError = "";
+    /**
+* Returns the last compiler/linker error.
+* @return {string} The last compiler/linker error.
+*/
+    var getLastError = function() {
+        return lastError;
+    };
+    /**
+* Whether a haystack ends with a needle.
+* @param {string} haystack String to search
+* @param {string} needle String to search for.
+* @param {boolean} True if haystack ends with needle.
+*/
+    var endsWith = function(haystack, needle) {
+        return haystack.substr(haystack.length - needle.length) === needle;
+    };
+    /**
+* Whether a haystack starts with a needle.
+* @param {string} haystack String to search
+* @param {string} needle String to search for.
+* @param {boolean} True if haystack starts with needle.
+*/
+    var startsWith = function(haystack, needle) {
+        return haystack.substr(0, needle.length) === needle;
+    };
+    /**
+* A vertex shader for a single texture.
+* @type {string}
+*/
+    var simpleTextureVertexShader = [
+                'attribute vec4 vPosition;',
+                'attribute vec2 texCoord0;',
+                'varying vec2 texCoord;',
+                'void main() {',
+                ' gl_Position = vPosition;',
+                ' texCoord = texCoord0;',
+                '}'].join('\n');
+    /**
+* A fragment shader for a single texture.
+* @type {string}
+*/
+    var simpleTextureFragmentShader = [
+                'precision mediump float;',
+                'uniform sampler2D tex;',
+                'varying vec2 texCoord;',
+                'void main() {',
+                ' gl_FragData[0] = texture2D(tex, texCoord);',
+                '}'].join('\n');
+    /**
+* A vertex shader for a single texture.
+* @type {string}
+*/
+    var noTexCoordTextureVertexShader = [
+                'attribute vec4 vPosition;',
+                'varying vec2 texCoord;',
+                'void main() {',
+                ' gl_Position = vPosition;',
+                ' texCoord = vPosition.xy * 0.5 + 0.5;',
+                '}'].join('\n');
+    /**
+* A vertex shader for a uniform color.
+* @type {string}
+*/
+    var simpleColorVertexShader = [
+                'attribute vec4 vPosition;',
+                'void main() {',
+                ' gl_Position = vPosition;',
+                '}'].join('\n');
+    /**
+* A fragment shader for a uniform color.
+* @type {string}
+*/
+    var simpleColorFragmentShader = [
+                'precision mediump float;',
+                'uniform vec4 u_color;',
+                'void main() {',
+                ' gl_FragData[0] = u_color;',
+                '}'].join('\n');
+    /**
+* A vertex shader for vertex colors.
+* @type {string}
+*/
+    var simpleVertexColorVertexShader = [
+                'attribute vec4 vPosition;',
+                'attribute vec4 a_color;',
+                'varying vec4 v_color;',
+                'void main() {',
+                ' gl_Position = vPosition;',
+                ' v_color = a_color;',
+                '}'].join('\n');
+    /**
+* A fragment shader for vertex colors.
+* @type {string}
+*/
+    var simpleVertexColorFragmentShader = [
+                'precision mediump float;',
+                'varying vec4 v_color;',
+                'void main() {',
+                ' gl_FragData[0] = v_color;',
+                '}'].join('\n');
+    /**
+* Creates a simple texture vertex shader.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {!WebGLShader}
+*/
+    var setupSimpleTextureVertexShader = function(gl) {
+        return loadShader(gl, simpleTextureVertexShader, gl.VERTEX_SHADER);
+    };
+    /**
+* Creates a simple texture fragment shader.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {!WebGLShader}
+*/
+    var setupSimpleTextureFragmentShader = function(gl) {
+        return loadShader(
+                    gl, simpleTextureFragmentShader, gl.FRAGMENT_SHADER);
+    };
+    /**
+* Creates a texture vertex shader that doesn't need texcoords.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {!WebGLShader}
+*/
+    var setupNoTexCoordTextureVertexShader = function(gl) {
+        return loadShader(gl, noTexCoordTextureVertexShader, gl.VERTEX_SHADER);
+    };
+    /**
+* Creates a simple vertex color vertex shader.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {!WebGLShader}
+*/
+    var setupSimpleVertexColorVertexShader = function(gl) {
+        return loadShader(gl, simpleVertexColorVertexShader, gl.VERTEX_SHADER);
+    };
+    /**
+* Creates a simple vertex color fragment shader.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {!WebGLShader}
+*/
+    var setupSimpleVertexColorFragmentShader = function(gl) {
+        return loadShader(
+                    gl, simpleVertexColorFragmentShader, gl.FRAGMENT_SHADER);
+    };
+    /**
+* Creates a simple color vertex shader.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {!WebGLShader}
+*/
+    var setupSimpleColorVertexShader = function(gl) {
+        return loadShader(gl, simpleColorVertexShader, gl.VERTEX_SHADER);
+    };
+    /**
+* Creates a simple color fragment shader.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {!WebGLShader}
+*/
+    var setupSimpleColorFragmentShader = function(gl) {
+        return loadShader(
+                    gl, simpleColorFragmentShader, gl.FRAGMENT_SHADER);
+    };
+    /**
+* Creates a program, attaches shaders, binds attrib locations, links the
+* program and calls useProgram.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<!WebGLShader|string>} shaders The shaders to
+* attach, or the source, or the id of a script to get
+* the source from.
+* @param {!Array.<string>} opt_attribs The attribs names.
+* @param {!Array.<number>} opt_locations The locations for the attribs.
+* @param {boolean} opt_logShaders Whether to log shader source.
+*/
+    var setupProgram = function(
+        gl, shaders, opt_attribs, opt_locations, opt_logShaders) {
+        var realShaders = [];
+        var program = gl.createProgram();
+        var shaderCount = 0;
+        for (var ii = 0; ii < shaders.length; ++ii) {
+            var shader = shaders[ii];
+            var shaderType = undefined;
+            if (typeof shader == 'string') {
+                if (endsWith(shader, ".vert")) {
+                    shader = loadShaderFromFile(gl, shader, gl.VERTEX_SHADER, undefined, opt_logShaders);
+                } else if (endsWith(shader, ".frag")) {
+                    shader = loadShaderFromFile(gl, shader, gl.FRAGMENT_SHADER, undefined, opt_logShaders);
+                } else {
+                    shader = loadShader(gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER, undefined, opt_logShaders);
+                }
+            } else if (opt_logShaders) {
+                throw 'Shader source logging requested but no shader source provided';
+            }
+            if (shader) {
+                ++shaderCount;
+                gl.attachShader(program, shader);
+            }
+        }
+        if (shaderCount != 2) {
+            error("Error in compiling shader");
+            return null;
+        }
+        if (opt_attribs) {
+            for (ii = 0; ii < opt_attribs.length; ++ii) {
+                gl.bindAttribLocation(
+                            program,
+                            opt_locations ? opt_locations[ii] : ii,
+                                            opt_attribs[ii]);
+            }
+        }
+        gl.linkProgram(program);
+        // Check the link status
+        var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+        if (!linked) {
+            // something went wrong with the link
+            lastError = gl.getProgramInfoLog (program);
+            error("Error in program linking:" + lastError);
+            gl.deleteProgram(program);
+            return null;
+        }
+        gl.useProgram(program);
+        return program;
+    };
+    /**
+* Creates a simple texture program.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @return {WebGLProgram}
+*/
+    var setupNoTexCoordTextureProgram = function(gl) {
+        var vs = setupNoTexCoordTextureVertexShader(gl);
+        var fs = setupSimpleTextureFragmentShader(gl);
+        if (!vs || !fs) {
+            return null;
+        }
+        var program = setupProgram(
+                    gl,
+                    [vs, fs],
+                    ['vPosition'],
+                    [0]);
+        if (!program) {
+            gl.deleteShader(fs);
+            gl.deleteShader(vs);
+        }
+        gl.useProgram(program);
+        return program;
+    };
+    /**
+* Creates a simple texture program.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} opt_positionLocation The attrib location for position.
+* @param {number} opt_texcoordLocation The attrib location for texture coords.
+* @return {WebGLProgram}
+*/
+    var setupSimpleTextureProgram = function(
+        gl, opt_positionLocation, opt_texcoordLocation) {
+        opt_positionLocation = opt_positionLocation || 0;
+        opt_texcoordLocation = opt_texcoordLocation || 1;
+        var vs = setupSimpleTextureVertexShader(gl);
+        var fs = setupSimpleTextureFragmentShader(gl);
+        if (!vs || !fs) {
+            return null;
+        }
+        var program = setupProgram(
+                    gl,
+                    [vs, fs],
+                    ['vPosition', 'texCoord0'],
+                    [opt_positionLocation, opt_texcoordLocation]);
+        if (!program) {
+            gl.deleteShader(fs);
+            gl.deleteShader(vs);
+        }
+        gl.useProgram(program);
+        return program;
+    };
+    /**
+* Creates a simple vertex color program.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} opt_positionLocation The attrib location for position.
+* @param {number} opt_vertexColorLocation The attrib location
+* for vertex colors.
+* @return {WebGLProgram}
+*/
+    var setupSimpleVertexColorProgram = function(
+        gl, opt_positionLocation, opt_vertexColorLocation) {
+        opt_positionLocation = opt_positionLocation || 0;
+        opt_vertexColorLocation = opt_vertexColorLocation || 1;
+        var vs = setupSimpleVertexColorVertexShader(gl);
+        var fs = setupSimpleVertexColorFragmentShader(gl);
+        if (!vs || !fs) {
+            return null;
+        }
+        var program = setupProgram(
+                    gl,
+                    [vs, fs],
+                    ['vPosition', 'a_color'],
+                    [opt_positionLocation, opt_vertexColorLocation]);
+        if (!program) {
+            gl.deleteShader(fs);
+            gl.deleteShader(vs);
+        }
+        gl.useProgram(program);
+        return program;
+    };
+    /**
+* Creates a simple color program.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} opt_positionLocation The attrib location for position.
+* @return {WebGLProgram}
+*/
+    var setupSimpleColorProgram = function(gl, opt_positionLocation) {
+        opt_positionLocation = opt_positionLocation || 0;
+        var vs = setupSimpleColorVertexShader(gl);
+        var fs = setupSimpleColorFragmentShader(gl);
+        if (!vs || !fs) {
+            return null;
+        }
+        var program = setupProgram(
+                    gl,
+                    [vs, fs],
+                    ['vPosition'],
+                    [opt_positionLocation]);
+        if (!program) {
+            gl.deleteShader(fs);
+            gl.deleteShader(vs);
+        }
+        gl.useProgram(program);
+        return program;
+    };
+    /**
+* Creates buffers for a textured unit quad and attaches them to vertex attribs.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} opt_positionLocation The attrib location for position.
+* @param {number} opt_texcoordLocation The attrib location for texture coords.
+* @return {!Array.<WebGLBuffer>} The buffer objects that were
+* created.
+*/
+    var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) {
+        return setupUnitQuadWithTexCoords(gl, [ 0.0, 0.0 ], [ 1.0, 1.0 ],
+                                          opt_positionLocation, opt_texcoordLocation);
+    };
+    /**
+* Creates buffers for a textured unit quad with specified lower left
+* and upper right texture coordinates, and attaches them to vertex
+* attribs.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the lower left corner.
+* @param {!Array.<number>} upperRightTexCoords The texture coordinates for the upper right corner.
+* @param {number} opt_positionLocation The attrib location for position.
+* @param {number} opt_texcoordLocation The attrib location for texture coords.
+* @return {!Array.<WebGLBuffer>} The buffer objects that were
+* created.
+*/
+    var setupUnitQuadWithTexCoords = function(
+        gl, lowerLeftTexCoords, upperRightTexCoords,
+        opt_positionLocation, opt_texcoordLocation) {
+        return setupQuad(gl, {
+                             positionLocation: opt_positionLocation || 0,
+                             texcoordLocation: opt_texcoordLocation || 1,
+                             lowerLeftTexCoords: lowerLeftTexCoords,
+                             upperRightTexCoords: upperRightTexCoords,
+                         });
+    };
+    /**
+* Makes a quad with various options.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Object} options.
+*
+* scale: scale to multiple unit quad values by. default 1.0.
+* positionLocation: attribute location for position.
+* texcoordLocation: attribute location for texcoords.
+* If this does not exist no texture coords are created.
+* lowerLeftTexCoords: an array of 2 values for the
+* lowerLeftTexCoords.
+* upperRightTexCoords: an array of 2 values for the
+* upperRightTexCoords.
+*/
+    var setupQuad = function(gl, options) {
+        var positionLocation = options.positionLocation || 0;
+        var scale = options.scale || 1;
+        var objects = [];
+        var vertexObject = gl.createBuffer();
+        gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+                                                            1.0 * scale , 1.0 * scale,
+                                                            -1.0 * scale , 1.0 * scale,
+                                                            -1.0 * scale , -1.0 * scale,
+                                                            1.0 * scale , 1.0 * scale,
+                                                            -1.0 * scale , -1.0 * scale,
+                                                            1.0 * scale , -1.0 * scale,]), gl.STATIC_DRAW);
+        gl.enableVertexAttribArray(positionLocation);
+        gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
+        objects.push(vertexObject);
+        if (options.texcoordLocation !== undefined) {
+            var llx = options.lowerLeftTexCoords[0];
+            var lly = options.lowerLeftTexCoords[1];
+            var urx = options.upperRightTexCoords[0];
+            var ury = options.upperRightTexCoords[1];
+            var vertexObject = gl.createBuffer();
+            gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+                                                                urx, ury,
+                                                                llx, ury,
+                                                                llx, lly,
+                                                                urx, ury,
+                                                                llx, lly,
+                                                                urx, lly]), gl.STATIC_DRAW);
+            gl.enableVertexAttribArray(options.texcoordLocation);
+            gl.vertexAttribPointer(options.texcoordLocation, 2, gl.FLOAT, false, 0, 0);
+            objects.push(vertexObject);
+        }
+        return objects;
+    };
+    /**
+* Creates a program and buffers for rendering a textured quad.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} opt_positionLocation The attrib location for
+* position. Default = 0.
+* @param {number} opt_texcoordLocation The attrib location for
+* texture coords. Default = 1.
+* @return {!WebGLProgram}
+*/
+    var setupTexturedQuad = function(
+        gl, opt_positionLocation, opt_texcoordLocation) {
+        var program = setupSimpleTextureProgram(
+                    gl, opt_positionLocation, opt_texcoordLocation);
+        setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation);
+        return program;
+    };
+    /**
+* Creates a program and buffers for rendering a color quad.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} opt_positionLocation The attrib location for position.
+* @return {!WebGLProgram}
+*/
+    var setupColorQuad = function(gl, opt_positionLocation) {
+        opt_positionLocation = opt_positionLocation || 0;
+        var program = setupSimpleColorProgram(gl);
+        setupUnitQuad(gl, opt_positionLocation);
+        return program;
+    };
+    /**
+* Creates a program and buffers for rendering a textured quad with
+* specified lower left and upper right texture coordinates.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the lower left corner.
+* @param {!Array.<number>} upperRightTexCoords The texture coordinates for the upper right corner.
+* @param {number} opt_positionLocation The attrib location for position.
+* @param {number} opt_texcoordLocation The attrib location for texture coords.
+* @return {!WebGLProgram}
+*/
+    var setupTexturedQuadWithTexCoords = function(
+        gl, lowerLeftTexCoords, upperRightTexCoords,
+        opt_positionLocation, opt_texcoordLocation) {
+        var program = setupSimpleTextureProgram(
+                    gl, opt_positionLocation, opt_texcoordLocation);
+        setupUnitQuadWithTexCoords(gl, lowerLeftTexCoords, upperRightTexCoords,
+                                   opt_positionLocation, opt_texcoordLocation);
+        return program;
+    };
+    /**
+* Creates a unit quad with only positions of a given resolution.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} gridRes The resolution of the mesh grid,
+* expressed in the number of quads across and down.
+* @param {number} opt_positionLocation The attrib location for position.
+*/
+    var setupIndexedQuad = function (
+        gl, gridRes, opt_positionLocation, opt_flipOddTriangles) {
+        return setupIndexedQuadWithOptions(gl,
+                                           { gridRes: gridRes,
+                                               positionLocation: opt_positionLocation,
+                                               flipOddTriangles: opt_flipOddTriangles
+                                           });
+    };
+    /**
+* Creates a quad with various options.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Object) options The options. See below.
+* @return {!Array.<WebGLBuffer>} The created buffers.
+* [positions, <colors>, indices]
+*
+* Options:
+* gridRes: number of quads across and down grid.
+* positionLocation: attrib location for position
+* flipOddTriangles: reverse order of vertices of every other
+* triangle
+* positionOffset: offset added to each vertex
+* positionMult: multipier for each vertex
+* colorLocation: attrib location for vertex colors. If
+* undefined no vertex colors will be created.
+*/
+    var setupIndexedQuadWithOptions = function (gl, options) {
+        var positionLocation = options.positionLocation || 0;
+        var objects = [];
+        var gridRes = options.gridRes || 1;
+        var positionOffset = options.positionOffset || 0;
+        var positionMult = options.positionMult || 1;
+        var vertsAcross = gridRes + 1;
+        var numVerts = vertsAcross * vertsAcross;
+        var positions = new Float32Array(numVerts * 3);
+        var indices = new Uint16Array(6 * gridRes * gridRes);
+        var poffset = 0;
+        for (var yy = 0; yy <= gridRes; ++yy) {
+            for (var xx = 0; xx <= gridRes; ++xx) {
+                positions[poffset + 0] = (-1 + 2 * xx / gridRes) * positionMult + positionOffset;
+                positions[poffset + 1] = (-1 + 2 * yy / gridRes) * positionMult + positionOffset;
+                positions[poffset + 2] = 0;
+output(positions[poffset + 0]+","+positions[poffset + 1]+","+positions[poffset + 2])
+                poffset += 3;
+            }
+        }
+        var buf = gl.createBuffer();
+        gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
+output("posloc:"+positionLocation)
+        gl.enableVertexAttribArray(positionLocation);
+        gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
+        objects.push(buf);
+        if (options.colorLocation !== undefined) {
+output("colloc:"+options.colorLocation)
+            var colors = new Float32Array(numVerts * 4);
+            for (yy = 0; yy <= gridRes; ++yy) {
+                for (xx = 0; xx <= gridRes; ++xx) {
+                    if (options.color !== undefined) {
+                        colors[poffset + 0] = options.color[0];
+                        colors[poffset + 1] = options.color[1];
+                        colors[poffset + 2] = options.color[2];
+                        colors[poffset + 3] = options.color[3];
+                    } else {
+                        colors[poffset + 0] = xx / gridRes;
+                        colors[poffset + 1] = yy / gridRes;
+                        colors[poffset + 2] = (xx / gridRes) * (yy / gridRes);
+                        colors[poffset + 3] = (yy % 2) * 0.5 + 0.5;
+                    }
+                    poffset += 4;
+                }
+            }
+            buf = gl.createBuffer();
+            gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+            gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+            gl.enableVertexAttribArray(options.colorLocation);
+            gl.vertexAttribPointer(options.colorLocation, 4, gl.FLOAT, false, 0, 0);
+            objects.push(buf);
+        }
+        var tbase = 0;
+        for (yy = 0; yy < gridRes; ++yy) {
+            var index = yy * vertsAcross;
+            for (xx = 0; xx < gridRes; ++xx) {
+                indices[tbase + 0] = index + 0;
+                indices[tbase + 1] = index + 1;
+                indices[tbase + 2] = index + vertsAcross;
+                indices[tbase + 3] = index + vertsAcross;
+                indices[tbase + 4] = index + 1;
+                indices[tbase + 5] = index + vertsAcross + 1;
+                if (options.flipOddTriangles) {
+                    indices[tbase + 4] = index + vertsAcross + 1;
+                    indices[tbase + 5] = index + 1;
+                }
+                index += 1;
+                tbase += 6;
+            }
+        }
+        buf = gl.createBuffer();
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf);
+        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+        objects.push(buf);
+        return objects;
+    };
+    /**
+* Returns the constructor for a typed array that corresponds to the given
+* WebGL type.
+* @param {!WebGLRenderingContext} gl A WebGLRenderingContext.
+* @param {number} type The WebGL type (eg, gl.UNSIGNED_BYTE)
+* @return {!Constructor} The typed array constructor that
+* corresponds to the given type.
+*/
+    var glTypeToTypedArrayType = function(gl, type) {
+        switch (type) {
+        case gl.BYTE:
+            return window.Int8Array;
+        case gl.UNSIGNED_BYTE:
+            return window.Uint8Array;
+        case gl.SHORT:
+            return window.Int16Array;
+        case gl.UNSIGNED_SHORT:
+        case gl.UNSIGNED_SHORT_5_6_5:
+        case gl.UNSIGNED_SHORT_4_4_4_4:
+        case gl.UNSIGNED_SHORT_5_5_5_1:
+            return window.Uint16Array;
+        case gl.INT:
+            return window.Int32Array;
+        case gl.UNSIGNED_INT:
+            return window.Uint32Array;
+        default:
+            throw 'unknown gl type ' + glEnumToString(gl, type);
+        }
+    };
+    /**
+* Returns the number of bytes per component for a given WebGL type.
+* @param {!WebGLRenderingContext} gl A WebGLRenderingContext.
+* @param {GLenum} type The WebGL type (eg, gl.UNSIGNED_BYTE)
+* @return {number} The number of bytes per component.
+*/
+    var getBytesPerComponent = function(gl, type) {
+        switch (type) {
+        case gl.BYTE:
+        case gl.UNSIGNED_BYTE:
+            return 1;
+        case gl.SHORT:
+        case gl.UNSIGNED_SHORT:
+        case gl.UNSIGNED_SHORT_5_6_5:
+        case gl.UNSIGNED_SHORT_4_4_4_4:
+        case gl.UNSIGNED_SHORT_5_5_5_1:
+            return 2;
+        case gl.INT:
+        case gl.UNSIGNED_INT:
+            return 4;
+        default:
+            throw 'unknown gl type ' + glEnumToString(gl, type);
+        }
+    };
+    /**
+* Returns the number of typed array elements per pixel for a given WebGL
+* format/type combination. The corresponding typed array type can be determined
+* by calling glTypeToTypedArrayType.
+* @param {!WebGLRenderingContext} gl A WebGLRenderingContext.
+* @param {GLenum} format The WebGL format (eg, gl.RGBA)
+* @param {GLenum} type The WebGL type (eg, gl.UNSIGNED_BYTE)
+* @return {number} The number of typed array elements per pixel.
+*/
+    var getTypedArrayElementsPerPixel = function(gl, format, type) {
+        switch (type) {
+        case gl.UNSIGNED_SHORT_5_6_5:
+        case gl.UNSIGNED_SHORT_4_4_4_4:
+        case gl.UNSIGNED_SHORT_5_5_5_1:
+            return 1;
+        case gl.UNSIGNED_BYTE:
+            break;
+        default:
+            throw 'not a gl type for color information ' + glEnumToString(gl, type);
+        }
+        switch (format) {
+        case gl.RGBA:
+            return 4;
+        case gl.RGB:
+            return 3;
+        case gl.LUMINANCE_ALPHA:
+            return 2;
+        case gl.LUMINANCE:
+        case gl.ALPHA:
+            return 1;
+        default:
+            throw 'unknown gl format ' + glEnumToString(gl, format);
+        }
+    };
+    /**
+* Fills the given texture with a solid color.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!WebGLTexture} tex The texture to fill.
+* @param {number} width The width of the texture to create.
+* @param {number} height The height of the texture to create.
+* @param {!Array.<number>} color The color to fill with.
+* where each element is in the range 0 to 255.
+* @param {number} opt_level The level of the texture to fill. Default = 0.
+* @param {number} opt_format The format for the texture.
+*/
+    var fillTexture = function(gl, tex, width, height, color, opt_level, opt_format, opt_type) {
+        opt_level = opt_level || 0;
+        opt_format = opt_format || gl.RGBA;
+        opt_type = opt_type || gl.UNSIGNED_BYTE;
+        var pack = gl.getParameter(gl.UNPACK_ALIGNMENT);
+        var numComponents = color.length;
+        var bytesPerComponent = getBytesPerComponent(gl, opt_type);
+        var rowSize = numComponents * width * bytesPerComponent;
+        var paddedRowSize = Math.floor((rowSize + pack - 1) / pack) * pack;
+        var size = rowSize + (height - 1) * paddedRowSize;
+        size = Math.floor((size + bytesPerComponent - 1) / bytesPerComponent) * bytesPerComponent;
+        var buf = new (glTypeToTypedArrayType(gl, opt_type))(size);
+        for (var yy = 0; yy < height; ++yy) {
+            var off = yy * paddedRowSize;
+            for (var xx = 0; xx < width; ++xx) {
+                for (var jj = 0; jj < numComponents; ++jj) {
+                    buf[off++] = color[jj];
+                }
+            }
+        }
+        gl.bindTexture(gl.TEXTURE_2D, tex);
+        gl.texImage2D(
+                    gl.TEXTURE_2D, opt_level, opt_format, width, height, 0,
+                    opt_format, opt_type, buf);
+    };
+    /**
+* Creates a texture and fills it with a solid color.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} width The width of the texture to create.
+* @param {number} height The height of the texture to create.
+* @param {!Array.<number>} color The color to fill with. A 4 element array
+* where each element is in the range 0 to 255.
+* @return {!WebGLTexture}
+*/
+    var createColoredTexture = function(gl, width, height, color) {
+        var tex = gl.createTexture();
+        fillTexture(gl, tex, width, height, color);
+        return tex;
+    };
+    var ubyteToFloat = function(c) {
+        return c / 255;
+    };
+    var ubyteColorToFloatColor = function(color) {
+        var floatColor = [];
+        for (var ii = 0; ii < color.length; ++ii) {
+            floatColor[ii] = ubyteToFloat(color[ii]);
+        }
+        return floatColor;
+    };
+    /**
+* Sets the "u_color" uniform of the current program to color.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<number> color 4 element array of 0-1 color
+* components.
+*/
+    var setFloatDrawColor = function(gl, color) {
+        var program = gl.getParameter(gl.CURRENT_PROGRAM);
+        var colorLocation = gl.getUniformLocation(program, "u_color");
+        gl.uniform4fv(colorLocation, color);
+    };
+    /**
+* Sets the "u_color" uniform of the current program to color.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<number> color 4 element array of 0-255 color
+* components.
+*/
+    var setUByteDrawColor = function(gl, color) {
+        setFloatDrawColor(gl, ubyteColorToFloatColor(color));
+    };
+    /**
+* Draws a previously setup quad in the given color.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<number>} color The color to draw with. A 4
+* element array where each element is in the range 0 to
+* 1.
+*/
+    var drawFloatColorQuad = function(gl, color) {
+        var program = gl.getParameter(gl.CURRENT_PROGRAM);
+        var colorLocation = gl.getUniformLocation(program, "u_color");
+        gl.uniform4fv(colorLocation, color);
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+    };
+    /**
+* Draws a previously setup quad in the given color.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<number>} color The color to draw with. A 4
+* element array where each element is in the range 0 to
+* 255.
+*/
+    var drawUByteColorQuad = function(gl, color) {
+        drawFloatColorQuad(gl, ubyteColorToFloatColor(color));
+    };
+    /**
+* Draws a previously setupUnitQuad.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+*/
+    var drawUnitQuad = function(gl) {
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+    };
+    /**
+* Clears then Draws a previously setupUnitQuad.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!Array.<number>} opt_color The color to fill clear with before
+* drawing. A 4 element array where each element is in the range 0 to
+* 255. Default [255, 255, 255, 255]
+*/
+    var clearAndDrawUnitQuad = function(gl, opt_color) {
+        opt_color = opt_color || [255, 255, 255, 255];
+        gl.clearColor(
+                    opt_color[0] / 255,
+                    opt_color[1] / 255,
+                    opt_color[2] / 255,
+                    opt_color[3] / 255);
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        drawUnitQuad(gl);
+    };
+    /**
+* Draws a quad previously setup with setupIndexedQuad.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} gridRes Resolution of grid.
+*/
+    var drawIndexedQuad = function(gl, gridRes) {
+        gl.drawElements(gl.TRIANGLES, gridRes * gridRes * 6, gl.UNSIGNED_SHORT, 0);
+    };
+    /**
+* Draws a previously setupIndexedQuad
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number} gridRes Resolution of grid.
+* @param {!Array.<number>} opt_color The color to fill clear with before
+* drawing. A 4 element array where each element is in the range 0 to
+* 255. Default [255, 255, 255, 255]
+*/
+    var clearAndDrawIndexedQuad = function(gl, gridRes, opt_color) {
+        opt_color = opt_color || [255, 255, 255, 255];
+        gl.clearColor(
+                    opt_color[0] / 255,
+                    opt_color[1] / 255,
+                    opt_color[2] / 255,
+                    opt_color[3] / 255);
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        drawIndexedQuad(gl, gridRes);
+    };
+    /**
+* Clips a range to min, max
+* (Eg. clipToRange(-5,7,0,20) would return {value:0,extent:2}
+* @param {number} value start of range
+* @param {number} extent extent of range
+* @param {number} min min.
+* @param {number} max max.
+* @return {!{value:number,extent:number} The clipped value.
+*/
+    var clipToRange = function(value, extent, min, max) {
+        if (value < min) {
+            extent -= min - value;
+            value = min;
+        }
+        var end = value + extent;
+        if (end > max) {
+            extent -= end - max;
+        }
+        if (extent < 0) {
+            value = max;
+            extent = 0;
+        }
+        return {value:value, extent: extent};
+    };
+    /**
+* Determines if the passed context is an instance of a WebGLRenderingContext
+* or later variant (like WebGL2RenderingContext)
+* @param {CanvasRenderingContext} ctx The context to check.
+*/
+    var isWebGLContext = function(ctx) {
+        //        if (ctx instanceof WebGLRenderingContext)
+        //            return true;
+        //        if ('WebGL2RenderingContext' in window && ctx instanceof WebGL2RenderingContext)
+        //            return true;
+        //        return false;
+        return true;
+    };
+    /**
+* Checks that a portion of a canvas or the currently attached framebuffer is 1 color.
+* @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The
+* WebGLRenderingContext or 2D context to use.
+* @param {number} x left corner of region to check.
+* @param {number} y bottom corner of region to check in case of checking from
+* a GL context or top corner in case of checking from a 2D context.
+* @param {number} width width of region to check.
+* @param {number} height width of region to check.
+* @param {!Array.<number>} color The color expected. A 4 element array where
+* each element is in the range 0 to 255.
+* @param {number} opt_errorRange Optional. Acceptable error in
+* color checking. 0 by default.
+* @param {!function()} sameFn Function to call if all pixels
+* are the same as color.
+* @param {!function()} differentFn Function to call if a pixel
+* is different than color
+* @param {!function()} logFn Function to call for logging.
+* @param {Uint8Array} opt_readBackBuf typically passed to reuse existing
+* buffer while reading back pixels.
+*/
+    var checkCanvasRectColor = function(gl, x, y, width, height, color, opt_errorRange, sameFn, differentFn, logFn, opt_readBackBuf) {
+        if (isWebGLContext(gl) && !gl.getParameter(gl.FRAMEBUFFER_BINDING)) {
+            // We're reading the backbuffer so clip.
+            var xr = clipToRange(x, width, 0, gl.canvas.width);
+            var yr = clipToRange(y, height, 0, gl.canvas.height);
+            if (!xr.extent || !yr.extent) {
+                logFn("checking rect: effective width or height is zero");
+                sameFn();
+                return;
+            }
+            x = xr.value;
+            y = yr.value;
+            width = xr.extent;
+            height = yr.extent;
+        }
+        var errorRange = opt_errorRange || 0;
+        if (!errorRange.length) {
+            errorRange = [errorRange, errorRange, errorRange, errorRange]
+        }
+        var buf;
+        if (isWebGLContext(gl)) {
+            buf = opt_readBackBuf ? opt_readBackBuf : new Uint8Array(width * height * 4);
+            gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+        } else {
+            buf = gl.getImageData(x, y, width, height).data;
+        }
+        for (var i = 0; i < width * height; ++i) {
+//for (var i = 0; i < width; ++i) {
+            var offset = i * 4;
+            for (var j = 0; j < color.length; ++j) {
+//output(i+":"+buf[offset + j])
+                if (Math.abs(buf[offset + j] - color[j]) > errorRange[j]) {
+                    var was = buf[offset + 0].toString();
+                    for (j = 1; j < color.length; ++j) {
+                        was += ", " + buf[offset + j];
+                    }
+                    differentFn('at (' + (x + (i % width)) + ', ' + (y + Math.floor(i / width)) +
+                                ') expected: ' + color + ' was ' + was);
+                    return false;
+                }
+            }
+        }
+        //sameFn();
+        return true;
+    };
+
+    /**
+* Checks that a portion of a canvas or the currently attached framebuffer is 1 color.
+* @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The
+* WebGLRenderingContext or 2D context to use.
+* @param {number} x left corner of region to check.
+* @param {number} y bottom corner of region to check in case of checking from
+* a GL context or top corner in case of checking from a 2D context.
+* @param {number} width width of region to check.
+* @param {number} height width of region to check.
+* @param {!Array.<number>} color The color expected. A 4 element array where
+* each element is in the range 0 to 255.
+* @param {string} opt_msg Message to associate with success. Eg
+* ("should be red").
+* @param {number} opt_errorRange Optional. Acceptable error in
+* color checking. 0 by default.
+*/
+    var checkCanvasRect = function(gl, x, y, width, height, color, opt_msg, opt_errorRange) {
+        return checkCanvasRectColor(
+                    gl, x, y, width, height, color, opt_errorRange,
+                    function() {
+                        var msg = opt_msg;
+                        if (msg === undefined)
+                            msg = "should be " + color.toString();
+                        return true;//testPassed(msg);
+                    },
+                    output,//testFailed,
+                    output);
+    };
+    /**
+* Checks that an entire canvas or the currently attached framebuffer is 1 color.
+* @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The
+* WebGLRenderingContext or 2D context to use.
+* @param {!Array.<number>} color The color expected. A 4 element array where
+* each element is in the range 0 to 255.
+* @param {string} msg Message to associate with success. Eg ("should be red").
+* @param {number} errorRange Optional. Acceptable error in
+* color checking. 0 by default.
+*/
+    var checkCanvas = function(gl, color, msg, errorRange) {
+        return checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, color, msg, errorRange);
+    };
+    /**
+* Checks a rectangular area both inside the area and outside
+* the area.
+* @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The
+* WebGLRenderingContext or 2D context to use.
+* @param {number} x left corner of region to check.
+* @param {number} y bottom corner of region to check in case of checking from
+* a GL context or top corner in case of checking from a 2D context.
+* @param {number} width width of region to check.
+* @param {number} height width of region to check.
+* @param {!Array.<number>} innerColor The color expected inside
+* the area. A 4 element array where each element is in the
+* range 0 to 255.
+* @param {!Array.<number>} outerColor The color expected
+* outside. A 4 element array where each element is in the
+* range 0 to 255.
+* @param {!number} opt_edgeSize: The number of pixels to skip
+* around the edges of the area. Defaut 0.
+* @param {!{width:number, height:number}} opt_outerDimensions
+* The outer dimensions. Default the size of gl.canvas.
+*/
+    var checkAreaInAndOut = function(gl, x, y, width, height, innerColor, outerColor, opt_edgeSize, opt_outerDimensions) {
+        var outerDimensions = opt_outerDimensions || { width: gl.canvas.width, height: gl.canvas.height };
+        var edgeSize = opt_edgeSize || 0;
+        checkCanvasRect(gl, x + edgeSize, y + edgeSize, width - edgeSize * 2, height - edgeSize * 2, innerColor);
+        checkCanvasRect(gl, 0, 0, x - edgeSize, outerDimensions.height, outerColor);
+        checkCanvasRect(gl, x + width + edgeSize, 0, outerDimensions.width - x - width - edgeSize, outerDimensions.height, outerColor);
+        checkCanvasRect(gl, 0, 0, outerDimensions.width, y - edgeSize, outerColor);
+        checkCanvasRect(gl, 0, y + height + edgeSize, outerDimensions.width, outerDimensions.height - y - height - edgeSize, outerColor);
+    };
+    /**
+* Loads a texture, calls callback when finished.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} url URL of image to load
+* @param {function(!Image): void} callback Function that gets called after
+* image has loaded
+* @return {!WebGLTexture} The created texture.
+*/
+    var loadTexture = function(gl, url, callback) {
+        var texture = gl.createTexture();
+        gl.bindTexture(gl.TEXTURE_2D, texture);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+        var image = new Image();
+        image.onload = function() {
+            gl.bindTexture(gl.TEXTURE_2D, texture);
+            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
+            callback(image);
+        };
+        image.src = url;
+        return texture;
+    };
+    /**
+* Checks whether the bound texture has expected dimensions. One corner pixel
+* of the texture will be changed as a side effect.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!WebGLTexture} texture The texture to check.
+* @param {number} width Expected width.
+* @param {number} height Expected height.
+* @param {GLenum} opt_format The texture's format. Defaults to RGBA.
+* @param {GLenum} opt_type The texture's type. Defaults to UNSIGNED_BYTE.
+*/
+    var checkTextureSize = function(gl, width, height, opt_format, opt_type) {
+        opt_format = opt_format || gl.RGBA;
+        opt_type = opt_type || gl.UNSIGNED_BYTE;
+        var numElements = getTypedArrayElementsPerPixel(gl, opt_format, opt_type);
+        var buf = new (glTypeToTypedArrayType(gl, opt_type))(numElements);
+        var errors = 0;
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, width - 1, height - 1, 1, 1, opt_format, opt_type, buf);
+        if (gl.getError() != gl.NO_ERROR) {
+            testFailed("Texture was smaller than the expected size " + width + "x" + height);
+            ++errors;
+        }
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, width - 1, height, 1, 1, opt_format, opt_type, buf);
+        if (gl.getError() == gl.NO_ERROR) {
+            testFailed("Texture was taller than " + height);
+            ++errors;
+        }
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, width, height - 1, 1, 1, opt_format, opt_type, buf);
+        if (gl.getError() == gl.NO_ERROR) {
+            testFailed("Texture was wider than " + width);
+            ++errors;
+        }
+        if (errors == 0) {
+            testPassed("Texture had the expected size " + width + "x" + height);
+        }
+    };
+    /**
+* Makes a shallow copy of an object.
+* @param {!Object) src Object to copy
+* @return {!Object} The copy of src.
+*/
+    var shallowCopyObject = function(src) {
+        var dst = {};
+        for (var attr in src) {
+            if (src.hasOwnProperty(attr)) {
+                dst[attr] = src[attr];
+            }
+        }
+        return dst;
+    };
+    /**
+* Checks if an attribute exists on an object case insensitive.
+* @param {!Object) obj Object to check
+* @param {string} attr Name of attribute to look for.
+* @return {string?} The name of the attribute if it exists,
+* undefined if not.
+*/
+    var hasAttributeCaseInsensitive = function(obj, attr) {
+        var lower = attr.toLowerCase();
+        for (var key in obj) {
+            if (obj.hasOwnProperty(key) && key.toLowerCase() == lower) {
+                return key;
+            }
+        }
+    };
+    /**
+* Returns a map of URL querystring options
+* @return {Object?} Object containing all the values in the URL querystring
+*/
+    var getUrlOptions = function() {
+        var options = {};
+        var s = window.location.href;
+        var q = s.indexOf("?");
+        var e = s.indexOf("#");
+        if (e < 0) {
+            e = s.length;
+        }
+        var query = s.substring(q + 1, e);
+        var pairs = query.split("&");
+        for (var ii = 0; ii < pairs.length; ++ii) {
+            var keyValue = pairs[ii].split("=");
+            var key = keyValue[0];
+            var value = decodeURIComponent(keyValue[1]);
+            options[key] = value;
+        }
+        return options;
+    };
+    /**
+* Creates a webgl context.
+* @param {!Canvas|string} opt_canvas The canvas tag to get
+* context from. If one is not passed in one will be
+* created. If it's a string it's assumed to be the id of a
+* canvas.
+* @param {Object} opt_attributes Context attributes.
+* @param {!number} opt_version Version of WebGL context to create
+* @return {!WebGLRenderingContext} The created context.
+*/
+    var create3DContext = function(opt_canvas, opt_attributes, opt_version) {
+        if (window.initTestingHarness) {
+            window.initTestingHarness();
+        }
+        var attributes = shallowCopyObject(opt_attributes || {});
+        if (!hasAttributeCaseInsensitive(attributes, "antialias")) {
+            attributes.antialias = false;
+        }
+        if (!opt_version) {
+            opt_version = parseInt(getUrlOptions().webglVersion, 10) || 1;
+        }
+        opt_canvas = opt_canvas || document.createElement("canvas");
+        if (typeof opt_canvas == 'string') {
+            opt_canvas = document.getElementById(opt_canvas);
+        }
+        var context = null;
+        var names;
+        switch (opt_version) {
+        case 2:
+            names = ["webgl2", "experimental-webgl2"]; break;
+        default:
+            names = ["webgl", "experimental-webgl"]; break;
+        }
+        for (var i = 0; i < names.length; ++i) {
+            try {
+                context = opt_canvas.getContext(names[i], attributes);
+            } catch (e) {
+            }
+            if (context) {
+                break;
+            }
+        }
+        if (!context) {
+            testFailed("Unable to fetch WebGL rendering context for Canvas");
+        }
+        return context;
+    }
+    /**
+* Wraps a WebGL function with a function that throws an exception if there is
+* an error.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} fname Name of function to wrap.
+* @return {function} The wrapped function.
+*/
+    var createGLErrorWrapper = function(context, fname) {
+        return function() {
+            var rv = context[fname].apply(context, arguments);
+            var err = context.getError();
+            if (err != context.NO_ERROR)
+                throw "GL error " + glEnumToString(context, err) + " in " + fname;
+            return rv;
+        };
+    };
+    /**
+* Creates a WebGL context where all functions are wrapped to throw an exception
+* if there is an error.
+* @param {!Canvas} canvas The HTML canvas to get a context from.
+* @return {!Object} The wrapped context.
+*/
+    function create3DContextWithWrapperThatThrowsOnGLError(canvas) {
+        var context = create3DContext(canvas);
+        var wrap = {};
+        for (var i in context) {
+            try {
+                if (typeof context[i] == 'function') {
+                    wrap[i] = createGLErrorWrapper(context, i);
+                } else {
+                    wrap[i] = context[i];
+                }
+            } catch (e) {
+                error("createContextWrapperThatThrowsOnGLError: Error accessing " + i);
+            }
+        }
+        wrap.getError = function() {
+            return context.getError();
+        };
+        return wrap;
+    };
+    /**
+* Tests that an evaluated expression generates a specific GL error.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number|Array.<number>} glErrors The expected gl error or an array of expected errors.
+* @param {string} evalStr The string to evaluate.
+*/
+    var shouldGenerateGLError = function(gl, glErrors, evalStr) {
+        var exception;
+        try {
+            eval(evalStr);
+        } catch (e) {
+            exception = e;
+        }
+        if (exception) {
+            output(evalStr + " threw exception " + exception);
+            return false; //testFailed(evalStr + " threw exception " + exception);
+        } else {
+            return glErrorShouldBe(gl, glErrors, "after evaluating: " + evalStr);
+        }
+    };
+    /**
+* Tests that the first error GL returns is the specified error.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {number|Array.<number>} glErrors The expected gl error or an array of expected errors.
+* @param {string} opt_msg Optional additional message.
+*/
+    var glErrorShouldBe = function(gl, glErrors, opt_msg) {
+        if (!glErrors.length) {
+            glErrors = [glErrors];
+        }
+        opt_msg = opt_msg || "";
+        var err = gl.getError();
+        var ndx = glErrors.indexOf(err);
+        var errStrs = [];
+        for (var ii = 0; ii < glErrors.length; ++ii) {
+            errStrs.push(glEnumToString(gl, glErrors[ii]));
+        }
+        var expected = errStrs.join(" or ");
+        if (ndx < 0) {
+            var msg = "getError expected" + ((glErrors.length > 1) ? " one of: " : ": " + expected + ". Was " + glEnumToString(gl, err) + " : " + opt_msg);
+            output(msg)
+            return false;//testFailed(msg + expected + ". Was " + glEnumToString(gl, err) + " : " + opt_msg);
+        } else {
+            //var msg = "getError was " + ((glErrors.length > 1) ? "one of: " : "expected value: ") + expected + " : " + opt_msg;
+            //output(msg)
+            return true;//testPassed(msg + expected + " : " + opt_msg);
+        }
+    };
+    /**
+* Links a WebGL program, throws if there are errors.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!WebGLProgram} program The WebGLProgram to link.
+* @param {function(string): void) opt_errorCallback callback for errors.
+*/
+    var linkProgram = function(gl, program, opt_errorCallback) {
+        var errFn = opt_errorCallback || testFailed;
+        // Link the program
+        gl.linkProgram(program);
+        // Check the link status
+        var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+        if (!linked) {
+            // something went wrong with the link
+            var error = gl.getProgramInfoLog (program);
+            errFn("Error in program linking:" + error);
+            gl.deleteProgram(program);
+        }
+    };
+    /**
+* Loads text from an external file. This function is synchronous.
+* @param {string} url The url of the external file.
+* @param {!function(bool, string): void} callback that is sent a bool for
+* success and the string.
+*/
+    var loadTextFileAsync = function(url, callback) {
+        log ("loading: " + url);
+        var error = 'loadTextFileSynchronous failed to load url "' + url + '"';
+        var request;
+        if (window.XMLHttpRequest) {
+            request = new XMLHttpRequest();
+            if (request.overrideMimeType) {
+                request.overrideMimeType('text/plain');
+            }
+        } else {
+            throw 'XMLHttpRequest is disabled';
+        }
+        try {
+            request.open('GET', url, true);
+            request.onreadystatechange = function() {
+                if (request.readyState == 4) {
+                    var text = '';
+                    // HTTP reports success with a 200 status. The file protocol reports
+                    // success with zero. HTTP does not use zero as a status code (they
+                    // start at 100).
+                    // https://developer.mozilla.org/En/Using_XMLHttpRequest
+                    var success = request.status == 200 || request.status == 0;
+                    if (success) {
+                        text = request.responseText;
+                    }
+                    log("loaded: " + url);
+                    callback(success, text);
+                }
+            };
+            request.send(null);
+        } catch (e) {
+            log("failed to load: " + url);
+            callback(false, '');
+        }
+    };
+    /**
+* Recursively loads a file as a list. Each line is parsed for a relative
+* path. If the file ends in .txt the contents of that file is inserted in
+* the list.
+*
+* @param {string} url The url of the external file.
+* @param {!function(bool, Array<string>): void} callback that is sent a bool
+* for success and the array of strings.
+*/
+    var getFileListAsync = function(url, callback) {
+        var files = [];
+        var getFileListImpl = function(url, callback) {
+            var files = [];
+            if (url.substr(url.length - 4) == '.txt') {
+                loadTextFileAsync(url, function() {
+                    return function(success, text) {
+                        if (!success) {
+                            callback(false, '');
+                            return;
+                        }
+                        var lines = text.split('\n');
+                        var prefix = '';
+                        var lastSlash = url.lastIndexOf('/');
+                        if (lastSlash >= 0) {
+                            prefix = url.substr(0, lastSlash + 1);
+                        }
+                        var fail = false;
+                        var count = 1;
+                        var index = 0;
+                        for (var ii = 0; ii < lines.length; ++ii) {
+                            var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+                            if (str.length > 4 &&
+                                    str[0] != '#' &&
+                                    str[0] != ";" &&
+                                    str.substr(0, 2) != "//") {
+                                var names = str.split(/ +/);
+                                new_url = prefix + str;
+                                if (names.length == 1) {
+                                    new_url = prefix + str;
+                                    ++count;
+                                    getFileListImpl(new_url, function(index) {
+                                        return function(success, new_files) {
+                                            log("got files: " + new_files.length);
+                                            if (success) {
+                                                files[index] = new_files;
+                                            }
+                                            finish(success);
+                                        };
+                                    }(index++));
+                                } else {
+                                    var s = "";
+                                    var p = "";
+                                    for (var jj = 0; jj < names.length; ++jj) {
+                                        s += p + prefix + names[jj];
+                                        p = " ";
+                                    }
+                                    files[index++] = s;
+                                }
+                            }
+                        }
+                        finish(true);
+                        function finish(success) {
+                            if (!success) {
+                                fail = true;
+                            }
+                            --count;
+                            log("count: " + count);
+                            if (!count) {
+                                callback(!fail, files);
+                            }
+                        }
+                    }
+                }());
+            } else {
+                files.push(url);
+                callback(true, files);
+            }
+        };
+        getFileListImpl(url, function(success, files) {
+            // flatten
+            var flat = [];
+            flatten(files);
+            function flatten(files) {
+                for (var ii = 0; ii < files.length; ++ii) {
+                    var value = files[ii];
+                    if (typeof(value) == "string") {
+                        flat.push(value);
+                    } else {
+                        flatten(value);
+                    }
+                }
+            }
+            callback(success, flat);
+        });
+    };
+    /**
+* Gets a file from a file/URL.
+* @param {string} file the URL of the file to get.
+* @return {string} The contents of the file.
+*/
+    var readFile = function(file) {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", file, false);
+        xhr.send();
+        return xhr.responseText.replace(/\r/g, "");
+    };
+    var readFileList = function(url) {
+        var files = [];
+        if (url.substr(url.length - 4) == '.txt') {
+            var lines = readFile(url).split('\n');
+            var prefix = '';
+            var lastSlash = url.lastIndexOf('/');
+            if (lastSlash >= 0) {
+                prefix = url.substr(0, lastSlash + 1);
+            }
+            for (var ii = 0; ii < lines.length; ++ii) {
+                var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+                if (str.length > 4 &&
+                        str[0] != '#' &&
+                        str[0] != ";" &&
+                        str.substr(0, 2) != "//") {
+                    var names = str.split(/ +/);
+                    if (names.length == 1) {
+                        new_url = prefix + str;
+                        files = files.concat(readFileList(new_url));
+                    } else {
+                        var s = "";
+                        var p = "";
+                        for (var jj = 0; jj < names.length; ++jj) {
+                            s += p + prefix + names[jj];
+                            p = " ";
+                        }
+                        files.push(s);
+                    }
+                }
+            }
+        } else {
+            files.push(url);
+        }
+        return files;
+    };
+    /**
+* Loads a shader.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} shaderSource The shader source.
+* @param {number} shaderType The type of shader.
+* @param {function(string): void) opt_errorCallback callback for errors.
+* @param {boolean} opt_logShaders Whether to log shader source.
+* @param {string} opt_shaderLabel Label that identifies the shader source in
+* the log.
+* @param {string} opt_url URL from where the shader source was loaded from.
+* If opt_logShaders is set, then a link to the source file will also be
+* added.
+* @return {!WebGLShader} The created shader.
+*/
+    var loadShader = function(
+        gl, shaderSource, shaderType, opt_errorCallback, opt_logShaders,
+        opt_shaderLabel, opt_url) {
+        var errFn = opt_errorCallback || error;
+        // Create the shader object
+        var shader = gl.createShader(shaderType);
+        if (shader == null) {
+            errFn("*** Error: unable to create shader '"+shaderSource+"'");
+            return null;
+        }
+        // Load the shader source
+        gl.shaderSource(shader, shaderSource);
+        var err = gl.getError();
+        if (err != gl.NO_ERROR) {
+            errFn("*** Error loading shader '" + shader + "':" + glEnumToString(gl, err));
+            return null;
+        }
+        // Compile the shader
+        gl.compileShader(shader);
+        if (opt_logShaders) {
+            var label = shaderType == gl.VERTEX_SHADER ? 'vertex shader' : 'fragment_shader';
+            if (opt_shaderLabel) {
+                label = opt_shaderLabel + ' ' + label;
+            }
+            addShaderSources(
+                        gl, document.getElementById('console'), label, shader, shaderSource, opt_url);
+        }
+        // Check the compile status
+        var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+        if (!compiled) {
+            // Something went wrong during compilation; get the error
+            lastError = gl.getShaderInfoLog(shader);
+            errFn("*** Error compiling " + glEnumToString(gl, shaderType) + " '" + shader + "':" + lastError);
+            gl.deleteShader(shader);
+            return null;
+        }
+        return shader;
+    }
+    /**
+* Loads a shader from a URL.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {file} file The URL of the shader source.
+* @param {number} type The type of shader.
+* @param {function(string): void) opt_errorCallback callback for errors.
+* @param {boolean} opt_logShaders Whether to log shader source.
+* @return {!WebGLShader} The created shader.
+*/
+    var loadShaderFromFile = function(
+        gl, file, type, opt_errorCallback, opt_logShaders) {
+        var shaderSource = readFile(file);
+        return loadShader(gl, shaderSource, type, opt_errorCallback,
+                          opt_logShaders, undefined, file);
+    };
+    /**
+* Gets the content of script.
+* @param {string} scriptId The id of the script tag.
+* @return {string} The content of the script.
+*/
+    var getScript = function(scriptId) {
+        var shaderScript = document.getElementById(scriptId);
+        if (!shaderScript) {
+            throw("*** Error: unknown script element " + scriptId);
+        }
+        return shaderScript.text;
+    };
+    /**
+* Loads a shader from a script tag.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} scriptId The id of the script tag.
+* @param {number} opt_shaderType The type of shader. If not passed in it will
+* be derived from the type of the script tag.
+* @param {function(string): void) opt_errorCallback callback for errors.
+* @param {boolean} opt_logShaders Whether to log shader source.
+* @return {!WebGLShader} The created shader.
+*/
+    var loadShaderFromScript = function(
+        gl, scriptId, opt_shaderType, opt_errorCallback, opt_logShaders) {
+        var shaderSource = "";
+        var shaderScript = document.getElementById(scriptId);
+        if (!shaderScript) {
+            throw("*** Error: unknown script element " + scriptId);
+        }
+        shaderSource = shaderScript.text;
+        if (!opt_shaderType) {
+            if (shaderScript.type == "x-shader/x-vertex") {
+                opt_shaderType = gl.VERTEX_SHADER;
+            } else if (shaderScript.type == "x-shader/x-fragment") {
+                opt_shaderType = gl.FRAGMENT_SHADER;
+            } else {
+                throw("*** Error: unknown shader type");
+                return null;
+            }
+        }
+        return loadShader(gl, shaderSource, opt_shaderType, opt_errorCallback,
+                          opt_logShaders);
+    };
+    var loadStandardProgram = function(gl) {
+        var program = gl.createProgram();
+        gl.attachShader(program, loadStandardVertexShader(gl));
+        gl.attachShader(program, loadStandardFragmentShader(gl));
+        gl.bindAttribLocation(program, 0, "a_vertex");
+        gl.bindAttribLocation(program, 1, "a_normal");
+        linkProgram(gl, program);
+        return program;
+    };
+    /**
+* Loads shaders from files, creates a program, attaches the shaders and links.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} vertexShaderPath The URL of the vertex shader.
+* @param {string} fragmentShaderPath The URL of the fragment shader.
+* @param {function(string): void) opt_errorCallback callback for errors.
+* @return {!WebGLProgram} The created program.
+*/
+    var loadProgramFromFile = function(
+        gl, vertexShaderPath, fragmentShaderPath, opt_errorCallback) {
+        var program = gl.createProgram();
+        var vs = loadShaderFromFile(
+                    gl, vertexShaderPath, gl.VERTEX_SHADER, opt_errorCallback);
+        var fs = loadShaderFromFile(
+                    gl, fragmentShaderPath, gl.FRAGMENT_SHADER, opt_errorCallback);
+        if (vs && fs) {
+            gl.attachShader(program, vs);
+            gl.attachShader(program, fs);
+            linkProgram(gl, program, opt_errorCallback);
+        }
+        if (vs) {
+            gl.deleteShader(vs);
+        }
+        if (fs) {
+            gl.deleteShader(fs);
+        }
+        return program;
+    };
+    /**
+* Loads shaders from script tags, creates a program, attaches the shaders and
+* links.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} vertexScriptId The id of the script tag that contains the
+* vertex shader.
+* @param {string} fragmentScriptId The id of the script tag that contains the
+* fragment shader.
+* @param {function(string): void) opt_errorCallback callback for errors.
+* @return {!WebGLProgram} The created program.
+*/
+    var loadProgramFromScript = function loadProgramFromScript(
+        gl, vertexScriptId, fragmentScriptId, opt_errorCallback) {
+        var program = gl.createProgram();
+        gl.attachShader(
+                    program,
+                    loadShaderFromScript(
+                        gl, vertexScriptId, gl.VERTEX_SHADER, opt_errorCallback));
+        gl.attachShader(
+                    program,
+                    loadShaderFromScript(
+                        gl, fragmentScriptId, gl.FRAGMENT_SHADER, opt_errorCallback));
+        linkProgram(gl, program, opt_errorCallback);
+        return program;
+    };
+    /**
+* Loads shaders from source, creates a program, attaches the shaders and
+* links.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!WebGLShader} vertexShader The vertex shader.
+* @param {!WebGLShader} fragmentShader The fragment shader.
+* @param {function(string): void) opt_errorCallback callback for errors.
+* @return {!WebGLProgram} The created program.
+*/
+    var createProgram = function(gl, vertexShader, fragmentShader, opt_errorCallback) {
+        var program = gl.createProgram();
+        gl.attachShader(program, vertexShader);
+        gl.attachShader(program, fragmentShader);
+        linkProgram(gl, program, opt_errorCallback);
+        return program;
+    };
+    /**
+* Loads shaders from source, creates a program, attaches the shaders and
+* links.
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} vertexShader The vertex shader source.
+* @param {string} fragmentShader The fragment shader source.
+* @param {function(string): void) opt_errorCallback callback for errors.
+* @param {boolean} opt_logShaders Whether to log shader source.
+* @return {!WebGLProgram} The created program.
+*/
+    var loadProgram = function(
+        gl, vertexShader, fragmentShader, opt_errorCallback, opt_logShaders) {
+        var program;
+        var vs = loadShader(
+                    gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback, opt_logShaders);
+        var fs = loadShader(
+                    gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback, opt_logShaders);
+        if (vs && fs) {
+            program = createProgram(gl, vs, fs, opt_errorCallback)
+        }
+        if (vs) {
+            gl.deleteShader(vs);
+        }
+        if (fs) {
+            gl.deleteShader(fs);
+        }
+        return program;
+    };
+    /**
+* Loads shaders from source, creates a program, attaches the shaders and
+* links but expects error.
+*
+* GLSL 1.0.17 10.27 effectively says that compileShader can
+* always succeed as long as linkProgram fails so we can't
+* rely on compileShader failing. This function expects
+* one of the shader to fail OR linking to fail.
+*
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} vertexShaderScriptId The vertex shader.
+* @param {string} fragmentShaderScriptId The fragment shader.
+* @return {WebGLProgram} The created program.
+*/
+    var loadProgramFromScriptExpectError = function(
+        gl, vertexShaderScriptId, fragmentShaderScriptId) {
+        var vertexShader = loadShaderFromScript(gl, vertexShaderScriptId);
+        if (!vertexShader) {
+            return null;
+        }
+        var fragmentShader = loadShaderFromScript(gl, fragmentShaderScriptId);
+        if (!fragmentShader) {
+            return null;
+        }
+        var linkSuccess = true;
+        var program = gl.createProgram();
+        gl.attachShader(program, vertexShader);
+        gl.attachShader(program, fragmentShader);
+        linkSuccess = true;
+        linkProgram(gl, program, function() {
+            linkSuccess = false;
+        });
+        return linkSuccess ? program : null;
+    };
+    var getActiveMap = function(gl, program, typeInfo) {
+        var numVariables = gl.getProgramParameter(program, gl[typeInfo.param]);
+        var variables = {};
+        for (var ii = 0; ii < numVariables; ++ii) {
+            var info = gl[typeInfo.activeFn](program, ii);
+            variables[info.name] = {
+                name: info.name,
+                size: info.size,
+                type: info.type,
+                location: gl[typeInfo.locFn](program, info.name)
+            };
+        }
+        return variables;
+    };
+    /**
+* Returns a map of attrib names to info about those
+* attribs.
+*
+* eg:
+* { "attrib1Name":
+* {
+* name: "attrib1Name",
+* size: 1,
+* type: gl.FLOAT_MAT2,
+* location: 0
+* },
+* "attrib2Name[0]":
+* {
+* name: "attrib2Name[0]",
+* size: 4,
+* type: gl.FLOAT,
+* location: 1
+* },
+* }
+*
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {WebGLProgram} The program to query for attribs.
+* @return the map.
+*/
+    var getAttribMap = function(gl, program) {
+        return getActiveMap(gl, program, {
+                                param: "ACTIVE_ATTRIBUTES",
+                                activeFn: "getActiveAttrib",
+                                locFn: "getAttribLocation"
+                            });
+    };
+    /**
+* Returns a map of uniform names to info about those uniforms.
+*
+* eg:
+* { "uniform1Name":
+* {
+* name: "uniform1Name",
+* size: 1,
+* type: gl.FLOAT_MAT2,
+* location: WebGLUniformLocation
+* },
+* "uniform2Name[0]":
+* {
+* name: "uniform2Name[0]",
+* size: 4,
+* type: gl.FLOAT,
+* location: WebGLUniformLocation
+* },
+* }
+*
+* @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {WebGLProgram} The program to query for uniforms.
+* @return the map.
+*/
+    var getUniformMap = function(gl, program) {
+        return getActiveMap(gl, program, {
+                                param: "ACTIVE_UNIFORMS",
+                                activeFn: "getActiveUniform",
+                                locFn: "getUniformLocation"
+                            });
+    };
+    var basePath;
+    var getBasePath = function() {
+        if (!basePath) {
+            var expectedBase = "webgl-test-utils.js";
+            var scripts = document.getElementsByTagName('script');
+            for (var script, i = 0; script = scripts[i]; i++) {
+                var src = script.src;
+                var l = src.length;
+                if (src.substr(l - expectedBase.length) == expectedBase) {
+                    basePath = src.substr(0, l - expectedBase.length);
+                }
+            }
+        }
+        return basePath;
+    };
+    var loadStandardVertexShader = function(gl) {
+        return loadShaderFromFile(
+                    gl, getBasePath() + "vertexShader.vert", gl.VERTEX_SHADER);
+    };
+    var loadStandardFragmentShader = function(gl) {
+        return loadShaderFromFile(
+                    gl, getBasePath() + "fragmentShader.frag", gl.FRAGMENT_SHADER);
+    };
+    /**
+* Loads an image asynchronously.
+* @param {string} url URL of image to load.
+* @param {!function(!Element): void} callback Function to call
+* with loaded image.
+*/
+    var loadImageAsync = function(url, callback) {
+        var img = document.createElement('img');
+        img.onload = function() {
+            callback(img);
+        };
+        img.src = url;
+    };
+    /**
+* Loads an array of images.
+* @param {!Array.<string>} urls URLs of images to load.
+* @param {!function(!{string, img}): void} callback. Callback
+* that gets passed map of urls to img tags.
+*/
+    var loadImagesAsync = function(urls, callback) {
+        var count = 1;
+        var images = { };
+        function countDown() {
+            --count;
+            if (count == 0) {
+                log("loadImagesAsync: all images loaded");
+                callback(images);
+            }
+        }
+        function imageLoaded(url) {
+            return function(img) {
+                images[url] = img;
+                log("loadImagesAsync: loaded " + url);
+                countDown();
+            }
+        }
+        for (var ii = 0; ii < urls.length; ++ii) {
+            ++count;
+            loadImageAsync(urls[ii], imageLoaded(urls[ii]));
+        }
+        countDown();
+    };
+    /**
+* Returns a map of key=value values from url.
+* @return {!Object.<string, number>} map of keys to values.
+*/
+    var getUrlArguments = function() {
+        var args = {};
+        try {
+            var s = window.location.href;
+            var q = s.indexOf("?");
+            var e = s.indexOf("#");
+            if (e < 0) {
+                e = s.length;
+            }
+            var query = s.substring(q + 1, e);
+            var pairs = query.split("&");
+            for (var ii = 0; ii < pairs.length; ++ii) {
+                var keyValue = pairs[ii].split("=");
+                var key = keyValue[0];
+                var value = decodeURIComponent(keyValue[1]);
+                args[key] = value;
+            }
+        } catch (e) {
+            throw "could not parse url";
+        }
+        return args;
+    };
+    /**
+* Makes an image from a src.
+* @param {string} src Image source URL.
+* @param {function} onload Callback to call when the image has finised loading.
+* @param {function} onerror Callback to call when an error occurs.
+* @return {!Image} The created image.
+*/
+    var makeImage = function(src, onload, onerror) {
+        var img = document.createElement('img');
+        if (onload) {
+            img.onload = onload;
+        }
+        if (onerror) {
+            img.onerror = onerror;
+        } else {
+            img.onerror = function() {
+                log("WARNING: creating image failed; src: " + this.src);
+            };
+        }
+        if (src) {
+            img.src = src;
+        }
+        return img;
+    }
+    /**
+* Makes an image element from a canvas.
+* @param {!HTMLCanvas} canvas Canvas to make image from.
+* @param {function} onload Callback to call when the image has finised loading.
+* @param {string} imageFormat Image format to be passed to toDataUrl().
+* @return {!Image} The created image.
+*/
+    var makeImageFromCanvas = function(canvas, onload, imageFormat) {
+        return makeImage(canvas.toDataURL(imageFormat), onload);
+    };
+    /**
+* Makes a video element from a src.
+* @param {string} src Video source URL.
+* @param {function} onerror Callback to call when an error occurs.
+* @return {!Video} The created video.
+*/
+    var makeVideo = function(src, onerror) {
+        var vid = document.createElement('video');
+        if (onerror) {
+            vid.onerror = onerror;
+        } else {
+            vid.onerror = function() {
+                log("WARNING: creating video failed; src: " + this.src);
+            };
+        }
+        if (src) {
+            vid.src = src;
+        }
+        return vid;
+    }
+    /**
+* Inserts an image with a caption into 'element'.
+* @param {!HTMLElement} element Element to append image to.
+* @param {string} caption caption to associate with image.
+* @param {!Image) img image to insert.
+*/
+    var insertImage = function(element, caption, img) {
+        var div = document.createElement("div");
+        div.appendChild(img);
+        var label = document.createElement("div");
+        label.appendChild(document.createTextNode(caption));
+        div.appendChild(label);
+        element.appendChild(div);
+    };
+    /**
+* Inserts a 'label' that when clicked expands to the pre formatted text
+* supplied by 'source'.
+* @param {!HTMLElement} element element to append label to.
+* @param {string} label label for anchor.
+* @param {string} source preformatted text to expand to.
+* @param {string} opt_url URL of source. If provided a link to the source file
+* will also be added.
+*/
+    var addShaderSource = function(element, label, source, opt_url) {
+        var div = document.createElement("div");
+        var s = document.createElement("pre");
+        s.className = "shader-source";
+        s.style.display = "none";
+        var ol = document.createElement("ol");
+        //s.appendChild(document.createTextNode(source));
+        var lines = source.split("\n");
+        for (var ii = 0; ii < lines.length; ++ii) {
+            var line = lines[ii];
+            var li = document.createElement("li");
+            li.appendChild(document.createTextNode(line));
+            ol.appendChild(li);
+        }
+        s.appendChild(ol);
+        var l = document.createElement("a");
+        l.href = "show-shader-source";
+        l.appendChild(document.createTextNode(label));
+        l.addEventListener('click', function(event) {
+            if (event.preventDefault) {
+                event.preventDefault();
+            }
+            s.style.display = (s.style.display == 'none') ? 'block' : 'none';
+            return false;
+        }, false);
+        div.appendChild(l);
+        if (opt_url) {
+            var u = document.createElement("a");
+            u.href = opt_url;
+            div.appendChild(document.createTextNode(" "));
+            u.appendChild(document.createTextNode("(" + opt_url + ")"));
+            div.appendChild(u);
+        }
+        div.appendChild(s);
+        element.appendChild(div);
+    };
+    /**
+* Inserts labels that when clicked expand to show the original source of the
+* shader and also translated source of the shader, if that is available.
+* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {!HTMLElement} element element to append label to.
+* @param {string} label label for anchor.
+* @param {WebGLShader} shader Shader to show the sources for.
+* @param {string} shaderSource Original shader source.
+* @param {string} opt_url URL of source. If provided a link to the source file
+* will also be added.
+*/
+    var addShaderSources = function(
+        gl, element, label, shader, shaderSource, opt_url) {
+        addShaderSource(element, label, shaderSource, opt_url);
+        var debugShaders = gl.getExtension('WEBGL_debug_shaders');
+        if (debugShaders && shader) {
+            var translatedSource = debugShaders.getTranslatedShaderSource(shader);
+            if (translatedSource != '') {
+                addShaderSource(element, label + ' translated for driver', translatedSource);
+            }
+        }
+    };
+    /**
+* Sends shader information to the server to be dumped into text files
+* when tests are run from within the test-runner harness.
+* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
+* @param {string} url URL of current.
+* @param {string} passMsg Test description.
+* @param {object} vInfo Object containing vertex shader information.
+* @param {object} fInfo Object containing fragment shader information.
+*/
+    var dumpShadersInfo = function(gl, url, passMsg, vInfo, fInfo) {
+        var shaderInfo = {};
+        shaderInfo.url = url;
+        shaderInfo.testDescription = passMsg;
+        shaderInfo.vLabel = vInfo.label;
+        shaderInfo.vShouldCompile = vInfo.shaderSuccess;
+        shaderInfo.vSource = vInfo.source;
+        shaderInfo.fLabel = fInfo.label;
+        shaderInfo.fShouldCompile = fInfo.shaderSuccess;
+        shaderInfo.fSource = fInfo.source;
+        shaderInfo.vTranslatedSource = null;
+        shaderInfo.fTranslatedSource = null;
+        var debugShaders = gl.getExtension('WEBGL_debug_shaders');
+        if (debugShaders) {
+            if (vInfo.shader)
+                shaderInfo.vTranslatedSource = debugShaders.getTranslatedShaderSource(vInfo.shader);
+            if (fInfo.shader)
+                shaderInfo.fTranslatedSource = debugShaders.getTranslatedShaderSource(fInfo.shader);
+        }
+        var dumpShaderInfoRequest = new XMLHttpRequest();
+        dumpShaderInfoRequest.open('POST', "/dumpShaderInfo", true);
+        dumpShaderInfoRequest.setRequestHeader("Content-Type", "text/plain");
+        dumpShaderInfoRequest.send(JSON.stringify(shaderInfo));
+    };
+    // Add your prefix here.
+    var browserPrefixes = [
+                "",
+                "MOZ_",
+                "OP_",
+                "WEBKIT_"
+            ];
+    /**
+* Given an extension name like WEBGL_compressed_texture_s3tc
+* returns the name of the supported version extension, like
+* WEBKIT_WEBGL_compressed_teture_s3tc
+* @param {string} name Name of extension to look for.
+* @return {string} name of extension found or undefined if not
+* found.
+*/
+    var getSupportedExtensionWithKnownPrefixes = function(gl, name) {
+        var supported = gl.getSupportedExtensions();
+        for (var ii = 0; ii < browserPrefixes.length; ++ii) {
+            var prefixedName = browserPrefixes[ii] + name;
+            if (supported.indexOf(prefixedName) >= 0) {
+                return prefixedName;
+            }
+        }
+    };
+    /**
+* Given an extension name like WEBGL_compressed_texture_s3tc
+* returns the supported version extension, like
+* WEBKIT_WEBGL_compressed_teture_s3tc
+* @param {string} name Name of extension to look for.
+* @return {WebGLExtension} The extension or undefined if not
+* found.
+*/
+    var getExtensionWithKnownPrefixes = function(gl, name) {
+        for (var ii = 0; ii < browserPrefixes.length; ++ii) {
+            var prefixedName = browserPrefixes[ii] + name;
+            var ext = gl.getExtension(prefixedName);
+            if (ext) {
+                return ext;
+            }
+        }
+    };
+    /**
+* Returns possible prefixed versions of an extension's name.
+* @param {string} name Name of extension. May already include a prefix.
+* @return {Array.<string>} Variations of the extension name with known
+* browser prefixes.
+*/
+    var getExtensionPrefixedNames = function(name) {
+        var unprefix = function(name) {
+            for (var ii = 0; ii < browserPrefixes.length; ++ii) {
+                if (browserPrefixes[ii].length > 0 &&
+                        name.substring(0, browserPrefixes[ii].length).toLowerCase() ===
+                        browserPrefixes[ii].toLowerCase()) {
+                    return name.substring(browserPrefixes[ii].length);
+                }
+            }
+            return name;
+        }
+        var unprefixed = unprefix(name);
+        var variations = [];
+        for (var ii = 0; ii < browserPrefixes.length; ++ii) {
+            variations.push(browserPrefixes[ii] + unprefixed);
+        }
+        return variations;
+    };
+    var replaceRE = /\$\((\w+)\)/g;
+    /**
+* Replaces strings with property values.
+* Given a string like "hello $(first) $(last)" and an object
+* like {first:"John", last:"Smith"} will return
+* "hello John Smith".
+* @param {string} str String to do replacements in.
+* @param {...} 1 or more objects containing properties.
+*/
+    var replaceParams = function(str) {
+        var args = arguments;
+        return str.replace(replaceRE, function(str, p1, offset, s) {
+            for (var ii = 1; ii < args.length; ++ii) {
+                if (args[ii][p1] !== undefined) {
+                    return args[ii][p1];
+                }
+            }
+            throw "unknown string param '" + p1 + "'";
+        });
+    };
+    var upperCaseFirstLetter = function(str) {
+        return str.substring(0, 1).toUpperCase() + str.substring(1);
+    };
+    /**
+* Gets a prefixed property. For example,
+*
+* var fn = getPrefixedProperty(
+* window,
+* "requestAnimationFrame");
+*
+* Will return either:
+* "window.requestAnimationFrame",
+* "window.oRequestAnimationFrame",
+* "window.msRequestAnimationFrame",
+* "window.mozRequestAnimationFrame",
+* "window.webKitRequestAnimationFrame",
+* undefined
+*
+* the non-prefixed function is tried first.
+*/
+    var propertyPrefixes = ["", "moz", "ms", "o", "webkit"];
+    var getPrefixedProperty = function(obj, propertyName) {
+        for (var ii = 0; ii < propertyPrefixes.length; ++ii) {
+            var prefix = propertyPrefixes[ii];
+            var name = prefix + propertyName;
+            log(name);
+            var property = obj[name];
+            if (property) {
+                return property;
+            }
+            if (ii == 0) {
+                propertyName = upperCaseFirstLetter(propertyName);
+            }
+        }
+        return undefined;
+    };
+    var _requestAnimFrame;
+    /**
+* Provides requestAnimationFrame in a cross browser way.
+*/
+    var requestAnimFrame = function(callback) {
+        if (!_requestAnimFrame) {
+            _requestAnimFrame = getPrefixedProperty(window, "requestAnimationFrame") ||
+                    function(callback, element) {
+                        return window.setTimeout(callback, 1000 / 70);
+                    };
+        }
+        log("requestAnimFrame: document.hidden = " + document.hidden);
+        _requestAnimFrame.call(window, callback);
+    };
+    var _cancelAnimFrame;
+    /**
+* Provides cancelAnimationFrame in a cross browser way.
+*/
+    var cancelAnimFrame = function(request) {
+        if (!_cancelAnimFrame) {
+            _cancelAnimFrame = getPrefixedProperty(window, "cancelAnimationFrame") ||
+                    window.clearTimeout;
+        }
+        _cancelAnimFrame.call(window, request);
+    };
+    /**
+* Provides requestFullScreen in a cross browser way.
+*/
+    var requestFullScreen = function(element) {
+        var fn = getPrefixedProperty(element, "requestFullScreen");
+        if (fn) {
+            fn.call(element);
+        }
+    };
+    /**
+* Provides cancelFullScreen in a cross browser way.
+*/
+    var cancelFullScreen = function() {
+        var fn = getPrefixedProperty(document, "cancelFullScreen");
+        if (fn) {
+            fn.call(document);
+        }
+    };
+    var fullScreenStateName;
+    //    (function() {
+    //        var fullScreenStateNames = [
+    //                    "isFullScreen",
+    //                    "fullScreen",
+    //                ];
+    //        for (var ii = 0; ii < fullScreenStateNames.length; ++ii) {
+    //            var propertyName = fullScreenStateNames[ii];
+    //            for (var jj = 0; jj < propertyPrefixes.length; ++jj) {
+    //                var prefix = propertyPrefixes[jj];
+    //                if (prefix.length) {
+    //                    propertyName = upperCaseFirstLetter(propertyName);
+    //                    fullScreenStateName = prefix + propertyName;
+    //                    if (document[fullScreenStateName] !== undefined) {
+    //                        return;
+    //                    }
+    //                }
+    //            }
+    //            fullScreenStateName = undefined;
+    //        }
+    //    }());
+    /**
+* @return {boolean} True if fullscreen mode is active.
+*/
+    var getFullScreenState = function() {
+        log("fullscreenstatename:" + fullScreenStateName);
+        log(document[fullScreenStateName]);
+        return document[fullScreenStateName];
+    };
+    /**
+* @param {!HTMLElement} element The element to go fullscreen.
+* @param {!function(boolean)} callback A function that will be called
+* when entering/exiting fullscreen. It is passed true if
+* entering fullscreen, false if exiting.
+*/
+    var onFullScreenChange = function(element, callback) {
+        propertyPrefixes.forEach(function(prefix) {
+            var eventName = prefix + "fullscreenchange";
+            log("addevent: " + eventName);
+            document.addEventListener(eventName, function(event) {
+                log("event: " + eventName);
+                callback(getFullScreenState());
+            });
+        });
+    };
+    /**
+* @param {!string} buttonId The id of the button that will toggle fullscreen
+* mode.
+* @param {!string} fullscreenId The id of the element to go fullscreen.
+* @param {!function(boolean)} callback A function that will be called
+* when entering/exiting fullscreen. It is passed true if
+* entering fullscreen, false if exiting.
+* @return {boolean} True if fullscreen mode is supported.
+*/
+    var setupFullscreen = function(buttonId, fullscreenId, callback) {
+        if (!fullScreenStateName) {
+            return false;
+        }
+        var fullscreenElement = document.getElementById(fullscreenId);
+        onFullScreenChange(fullscreenElement, callback);
+        var toggleFullScreen = function(event) {
+            if (getFullScreenState()) {
+                cancelFullScreen(fullscreenElement);
+            } else {
+                requestFullScreen(fullscreenElement);
+            }
+            event.preventDefault();
+            return false;
+        };
+        var buttonElement = document.getElementById(buttonId);
+        buttonElement.addEventListener('click', toggleFullScreen);
+        return true;
+    };
+    /**
+* Waits for the browser to composite the web page.
+* @param {function()} callback A function to call after compositing has taken
+* place.
+*/
+    var waitForComposite = function(callback) {
+        var frames = 5;
+        var countDown = function() {
+            if (frames == 0) {
+                log("waitForComposite: callback");
+                callback();
+            } else {
+                log("waitForComposite: countdown(" + frames + ")");
+                --frames;
+                requestAnimFrame.call(window, countDown);
+            }
+        };
+        countDown();
+    };
+    /**
+* Runs an array of functions, yielding to the browser between each step.
+* If you want to know when all the steps are finished add a last step.
+* @param {!Array.<function(): void>} steps. Array of functions.
+*/
+    var runSteps = function(steps) {
+        if (!steps.length) {
+            return;
+        }
+        // copy steps so they can't be modifed.
+        var stepsToRun = steps.slice();
+        var currentStep = 0;
+        var runNextStep = function() {
+            stepsToRun[currentStep++]();
+            if (currentStep < stepsToRun.length) {
+                setTimeout(runNextStep, 1);
+            }
+        };
+        runNextStep();
+    };
+    /**
+* Starts playing a video and waits for it to be consumable.
+* @param {!HTMLVideoElement} video An HTML5 Video element.
+* @param {!function(!HTMLVideoElement): void>} callback Function to call when
+* video is ready.
+*/
+    var startPlayingAndWaitForVideo = function(video, callback) {
+        var gotPlaying = false;
+        var gotTimeUpdate = false;
+        var maybeCallCallback = function() {
+            if (gotPlaying && gotTimeUpdate && callback) {
+                callback(video);
+                callback = undefined;
+                video.removeEventListener('playing', playingListener, true);
+                video.removeEventListener('timeupdate', timeupdateListener, true);
+            }
+        };
+        var playingListener = function() {
+            gotPlaying = true;
+            maybeCallCallback();
+        };
+        var timeupdateListener = function() {
+            // Checking to make sure the current time has advanced beyond
+            // the start time seems to be a reliable heuristic that the
+            // video element has data that can be consumed.
+            if (video.currentTime > 0.0) {
+                gotTimeUpdate = true;
+                maybeCallCallback();
+            }
+        };
+        video.addEventListener('playing', playingListener, true);
+        video.addEventListener('timeupdate', timeupdateListener, true);
+        video.loop = true;
+        video.play();
+    };
+    var getHost = function(url) {
+        url = url.replace("\\", "/");
+        var pos = url.indexOf("://");
+        if (pos >= 0) {
+            url = url.substr(pos + 3);
+        }
+        var parts = url.split('/');
+        return parts[0];
+    }
+    // This function returns the last 2 words of the domain of a URL
+    // This is probably not the correct check but it will do for now.
+    var getBaseDomain = function(host) {
+        var parts = host.split(":");
+        var hostname = parts[0];
+        var port = parts[1] || "80";
+        parts = hostname.split(".");
+        if(parts.length < 2)
+            return hostname + ":" + port;
+        var tld = parts[parts.length-1];
+        var domain = parts[parts.length-2];
+        return domain + "." + tld + ":" + port;
+    }
+    var runningOnLocalhost = function() {
+        return window.location.hostname.indexOf("localhost") != -1 ||
+                window.location.hostname.indexOf("127.0.0.1") != -1;
+    }
+    var getLocalCrossOrigin = function() {
+        var domain;
+        if (window.location.host.indexOf("localhost") != -1) {
+            domain = "127.0.0.1";
+        } else {
+            domain = "localhost";
+        }
+        var port = window.location.port || "80";
+        return window.location.protocol + "//" + domain + ":" + port
+    }
+    var getRelativePath = function(path) {
+        var relparts = window.location.pathname.split("/");
+        relparts.pop(); // Pop off filename
+        var pathparts = path.split("/");
+        var i;
+        for (i = 0; i < pathparts.length; ++i) {
+            switch (pathparts[i]) {
+            case "": break;
+            case ".": break;
+            case "..":
+                relparts.pop();
+                break;
+            default:
+                relparts.push(pathparts[i]);
+                break;
+            }
+        }
+        return relparts.join("/");
+    }
+    var setupImageForCrossOriginTest = function(img, imgUrl, localUrl, callback) {
+        window.addEventListener("load", function() {
+            if (typeof(img) == "string")
+                img = document.querySelector(img);
+            if (!img)
+                img = new Image();
+            img.addEventListener("load", callback, false);
+            img.addEventListener("error", callback, false);
+            if (runningOnLocalhost())
+                img.src = getLocalCrossOrigin() + getRelativePath(localUrl);
+            else
+                img.src = getUrlOptions().imgUrl || imgUrl;
+        }, false);
+    }
+    return {
+        addShaderSource: addShaderSource,
+        addShaderSources: addShaderSources,
+        cancelAnimFrame: cancelAnimFrame,
+        create3DContext: create3DContext,
+        create3DContextWithWrapperThatThrowsOnGLError:
+        create3DContextWithWrapperThatThrowsOnGLError,
+        checkAreaInAndOut: checkAreaInAndOut,
+        checkCanvas: checkCanvas,
+        checkCanvasRect: checkCanvasRect,
+        checkCanvasRectColor: checkCanvasRectColor,
+        checkTextureSize: checkTextureSize,
+        clipToRange: clipToRange,
+        createColoredTexture: createColoredTexture,
+        createProgram: createProgram,
+        clearAndDrawUnitQuad: clearAndDrawUnitQuad,
+        clearAndDrawIndexedQuad: clearAndDrawIndexedQuad,
+        drawUnitQuad: drawUnitQuad,
+        drawIndexedQuad: drawIndexedQuad,
+        drawUByteColorQuad: drawUByteColorQuad,
+        drawFloatColorQuad: drawFloatColorQuad,
+        dumpShadersInfo: dumpShadersInfo,
+        endsWith: endsWith,
+        fillTexture: fillTexture,
+        getBytesPerComponent: getBytesPerComponent,
+        getExtensionPrefixedNames: getExtensionPrefixedNames,
+        getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes,
+        getFileListAsync: getFileListAsync,
+        getLastError: getLastError,
+        getPrefixedProperty: getPrefixedProperty,
+        getScript: getScript,
+        getSupportedExtensionWithKnownPrefixes: getSupportedExtensionWithKnownPrefixes,
+        getTypedArrayElementsPerPixel: getTypedArrayElementsPerPixel,
+        getUrlArguments: getUrlArguments,
+        getUrlOptions: getUrlOptions,
+        getAttribMap: getAttribMap,
+        getUniformMap: getUniformMap,
+        glEnumToString: glEnumToString,
+        glErrorShouldBe: glErrorShouldBe,
+        glTypeToTypedArrayType: glTypeToTypedArrayType,
+        hasAttributeCaseInsensitive: hasAttributeCaseInsensitive,
+        insertImage: insertImage,
+        loadImageAsync: loadImageAsync,
+        loadImagesAsync: loadImagesAsync,
+        loadProgram: loadProgram,
+        loadProgramFromFile: loadProgramFromFile,
+        loadProgramFromScript: loadProgramFromScript,
+        loadProgramFromScriptExpectError: loadProgramFromScriptExpectError,
+        loadShader: loadShader,
+        loadShaderFromFile: loadShaderFromFile,
+        loadShaderFromScript: loadShaderFromScript,
+        loadStandardProgram: loadStandardProgram,
+        loadStandardVertexShader: loadStandardVertexShader,
+        loadStandardFragmentShader: loadStandardFragmentShader,
+        loadTextFileAsync: loadTextFileAsync,
+        loadTexture: loadTexture,
+        log: log,
+        loggingOff: loggingOff,
+        makeImage: makeImage,
+        makeImageFromCanvas: makeImageFromCanvas,
+        makeVideo: makeVideo,
+        error: error,
+        shallowCopyObject: shallowCopyObject,
+        setupColorQuad: setupColorQuad,
+        setupProgram: setupProgram,
+        setupQuad: setupQuad,
+        setupIndexedQuad: setupIndexedQuad,
+        setupIndexedQuadWithOptions: setupIndexedQuadWithOptions,
+        setupSimpleColorFragmentShader: setupSimpleColorFragmentShader,
+        setupSimpleColorVertexShader: setupSimpleColorVertexShader,
+        setupSimpleColorProgram: setupSimpleColorProgram,
+        setupSimpleTextureFragmentShader: setupSimpleTextureFragmentShader,
+        setupSimpleTextureProgram: setupSimpleTextureProgram,
+        setupSimpleTextureVertexShader: setupSimpleTextureVertexShader,
+        setupSimpleVertexColorFragmentShader: setupSimpleVertexColorFragmentShader,
+        setupSimpleVertexColorProgram: setupSimpleVertexColorProgram,
+        setupSimpleVertexColorVertexShader: setupSimpleVertexColorVertexShader,
+        setupNoTexCoordTextureProgram: setupNoTexCoordTextureProgram,
+        setupNoTexCoordTextureVertexShader: setupNoTexCoordTextureVertexShader,
+        setupTexturedQuad: setupTexturedQuad,
+        setupTexturedQuadWithTexCoords: setupTexturedQuadWithTexCoords,
+        setupUnitQuad: setupUnitQuad,
+        setupUnitQuadWithTexCoords: setupUnitQuadWithTexCoords,
+        setFloatDrawColor: setFloatDrawColor,
+        setUByteDrawColor: setUByteDrawColor,
+        startPlayingAndWaitForVideo: startPlayingAndWaitForVideo,
+        startsWith: startsWith,
+        shouldGenerateGLError: shouldGenerateGLError,
+        readFile: readFile,
+        readFileList: readFileList,
+        replaceParams: replaceParams,
+        requestAnimFrame: requestAnimFrame,
+        runSteps: runSteps,
+        waitForComposite: waitForComposite,
+        // fullscreen api
+        setupFullscreen: setupFullscreen,
+        getHost: getHost,
+        getBaseDomain: getBaseDomain,
+        runningOnLocalhost: runningOnLocalhost,
+        getLocalCrossOrigin: getLocalCrossOrigin,
+        getRelativePath: getRelativePath,
+        setupImageForCrossOriginTest: setupImageForCrossOriginTest,
+        none: false
+    };
+}();
diff --git a/tests/auto/qmltest/canvas3d/tst_conformance_attribs.js b/tests/auto/qmltest/canvas3d/tst_conformance_attribs.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e644e6902e3300e99c33bf0f8e3b7e60f55c42a
--- /dev/null
+++ b/tests/auto/qmltest/canvas3d/tst_conformance_attribs.js
@@ -0,0 +1,1003 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtCanvas3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+Qt.include("../../../../3rdparty/js-test-pre.js")
+Qt.include("../../../../3rdparty/webgl-test-utils.js")
+Qt.include("../../../../3rdparty/gl-matrix.js")
+
+"use strict";
+
+var gl, gl2;
+var width, height;
+
+var testfunction;
+
+var glVertexShader, glFragmentShader;
+var vertexshader, fragmentshader;
+
+var maxVertexAttributes;
+
+function initGL(canvas, antialias, depth, vertex, fragment, w, h, test) {
+    gl = canvas.getContext("attributeTests", {depth:depth, antialias:antialias});
+    gl.viewport(0, 0, w, h);
+    gl.clearColor(0, 0, 0, 1);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+
+    initShaders(vertex, fragment);
+
+    width = w;
+    height = h;
+
+    maxVertexAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+
+    testfunction = test;
+}
+
+function renderGL(canvas) {
+    return testfunction();
+}
+
+function initGL2(canvas, w, h) {
+    gl2 = canvas.getContext("attributeTests");
+    gl2.viewport(0, 0, w, h);
+    gl2.clearColor(0, 0, 0, 1);
+    gl2.clear(gl2.COLOR_BUFFER_BIT);
+
+    width = w;
+    height = h;
+}
+
+function initShaders(vertex, fragment)
+{
+    vertexshader = vertex;
+    fragmentshader = fragment;
+    if (vertex)
+        glVertexShader = compileShader(vertex, gl.VERTEX_SHADER);
+    if (fragment)
+        glFragmentShader = compileShader(fragment, gl.FRAGMENT_SHADER);
+}
+
+function compileShader(str, type) {
+    var shader = gl.createShader(type);
+    gl.shaderSource(shader, str);
+    gl.compileShader(shader);
+
+    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+        console.log("JS:Shader compile failed");
+        console.log(gl.getShaderInfoLog(shader));
+        return null;
+    }
+
+    return shader;
+}
+
+// WebGL Conformance tests
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+//
+// gl-bindAttribLocation-aliasing.html
+//
+function testbindattriblocationaliasing() {
+    var typeInfo = [
+                { type: 'float', asVec4: 'vec4(0.0, $(var), 0.0, 1.0)' },
+                { type: 'vec2', asVec4: 'vec4($(var), 0.0, 1.0)' },
+                { type: 'vec3', asVec4: 'vec4($(var), 1.0)' },
+                { type: 'vec4', asVec4: '$(var)' },
+            ];
+    var retval = true;
+    // Test all type combinations of a_1 and a_2.
+    typeInfo.forEach(function(typeInfo1) {
+        typeInfo.forEach(function(typeInfo2) {
+            output('attribute_1: ' + typeInfo1.type + ' attribute_2: ' + typeInfo2.type);
+            var replace = {
+                type_1: typeInfo1.type,
+                type_2: typeInfo2.type,
+                gl_Position_1: testbindattriblocationaliasing_replaceParams(typeInfo1.asVec4, {var: 'a_1'}),
+                gl_Position_2: testbindattriblocationaliasing_replaceParams(typeInfo2.asVec4, {var: 'a_2'})
+            };
+            var strVertexShader = testbindattriblocationaliasing_replaceParams(
+                        replaceAttribVertexShader, replace);
+            glVertexShader = compileShader(strVertexShader, gl.VERTEX_SHADER);
+            retval = assertMsg(glVertexShader !== null, "Vertex shader compiled successfully.");
+            if (!retval) return false;
+            // Bind both a_1 and a_2 to the same position and verify the link fails.
+            // Do so for all valid positions available.
+            for (var l = 0; l < maxVertexAttributes; l++) {
+                var glProgram = gl.createProgram();
+                gl.bindAttribLocation(glProgram, l, 'a_1');
+                gl.bindAttribLocation(glProgram, l, 'a_2');
+                gl.attachShader(glProgram, glVertexShader);
+                gl.attachShader(glProgram, glFragmentShader);
+                gl.linkProgram(glProgram);
+                retval = assertMsg(!gl.getProgramParameter(glProgram, gl.LINK_STATUS),
+                                   "Link should fail when both types are aliased to location " + l);
+                if (!retval) return false;
+            }
+        });
+    });
+    return retval;
+}
+
+var replaceAttribVertexShader = [
+            'precision mediump float;',
+            'attribute $(type_1) a_1;',
+            'attribute $(type_2) a_2;',
+            'void main() {',
+            'gl_Position = $(gl_Position_1) + $(gl_Position_2);',
+            '}'].join('\n');
+
+var testbindattriblocationaliasing_replaceParams = function(str) {
+    var args = arguments;
+    return str.replace(testbindattriblocationaliasing_replaceRE, function(str, p1, offset, s) {
+        for (var ii = 1; ii < args.length; ++ii) {
+            if (args[ii][p1] !== undefined) {
+                return args[ii][p1];
+            }
+        }
+        throw "unknown string param '" + p1 + "'";
+    });
+};
+
+var testbindattriblocationaliasing_replaceRE = /\$\((\w+)\)/g;
+
+//
+// gl-bindAttribLocation-matrix.html
+//
+function testbindattriblocationmatrix_loadVertexShader(numMatrixDimensions) {
+    var strVertexShader =
+            'attribute mat' + numMatrixDimensions + ' matrix;\n' +
+            'attribute vec' + numMatrixDimensions + ' vector;\n' +
+            'void main(void) { gl_Position = vec4(vector*matrix';
+    // Ensure the vec4 has the correct number of dimensions in order to be assignable
+    // to gl_Position.
+    for (var ii = numMatrixDimensions; ii < 4; ++ii) {
+        strVertexShader += ",0.0";
+    }
+    strVertexShader += ");}\n";
+    return compileShader(strVertexShader, gl.VERTEX_SHADER);
+}
+
+function testbindattriblocationmatrix_createAndLinkProgram(vertexShader, matrixLocation,
+                                                           vectorLocation) {
+    var glProgram = gl.createProgram();
+    gl.bindAttribLocation(glProgram, matrixLocation, 'matrix');
+    gl.bindAttribLocation(glProgram, vectorLocation, 'vector');
+    gl.attachShader(glProgram, vertexShader);
+    gl.attachShader(glProgram, glFragmentShader);
+    gl.linkProgram(glProgram);
+    return gl.getProgramParameter(glProgram, gl.LINK_STATUS);
+}
+
+function testbindattriblocationmatrix() {
+    var retval = true;
+    output('MAX_VERTEX_ATTRIBS is ' + maxVertexAttributes);
+    retval = (maxVertexAttributes >= 4);
+    if (!retval) return false;
+
+    for (var mm = 2; mm <= 4; ++mm) {
+        output('Testing ' + mm + ' dimensional matrices');
+        glVertexShader = testbindattriblocationmatrix_loadVertexShader(mm);
+        // Per the WebGL spec: "LinkProgram will fail if the attribute bindings assigned
+        // by bindAttribLocation do not leave enough space to assign a location for an
+        // active matrix attribute which requires multiple contiguous generic attributes."
+        // We will test this by placing the vector after the matrix attribute such that there
+        // is not enough room for the matrix. Vertify the link operation fails.
+        // Run the test for each available attribute slot. Go to maxAttributes-mm to leave enough room
+        // for the matrix itself. Leave another slot open for the vector following the matrix.
+        for (var pp = 0; pp <= maxVertexAttributes - mm - 1; ++pp) {
+            // For each matrix dimension, bind the vector right after the matrix such that we leave
+            // insufficient room for the matrix. Verify doing this will fail the link operation.
+            for (var ll = 0; ll < mm; ++ll) {
+                var vectorLocation = pp + ll;
+                retval = assertMsg(!testbindattriblocationmatrix_createAndLinkProgram(
+                                       glVertexShader, pp, vectorLocation),
+                                   "Matrix with location " + pp + " and vector with location " + vectorLocation + " should not link.");
+                if (!retval) return false;
+            }
+            // Ensure that once we have left enough room for the matrix, the program links successfully.
+            vectorLocation = pp + ll;
+            retval = assertMsg(testbindattriblocationmatrix_createAndLinkProgram(
+                                   glVertexShader, pp, vectorLocation),
+                               "Matrix with location " + pp + " and vector with location " + vectorLocation + " should link.");
+            if (!retval) return false;
+        }
+    }
+    return retval;
+}
+
+//
+// gl-disabled-vertex-attrib.html
+//
+function testdisabledvertexattrib() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+    for (var ii = 0; ii < maxVertexAttributes; ++ii) {
+        var colorLocation = (ii + 1) % maxVertexAttributes;
+        var positionLocation = colorLocation ? 0 : 1;
+        if (positionLocation !== 0) {
+            // We need to use a new 3d context for testing attrib 0
+            // since we've already effected attrib 0 on other tests.
+            console.log(gl)
+            gl = gl2;
+            console.log(gl)
+        }
+        output("testing attrib: " + colorLocation);
+        var program = wtu.setupProgram(
+                    gl,
+                    [disabledVertexShader, disabledFragmentShader],
+                    ['a_position', 'a_color'],
+                    [positionLocation, colorLocation]);
+        var gridRes = 1;
+        wtu.setupIndexedQuad(gl, gridRes, positionLocation);
+        wtu.clearAndDrawIndexedQuad(gl, gridRes);
+        retval = wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+        if (!retval) return false;
+    }
+    retval = wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+    return retval;
+}
+
+var disabledVertexShader = [
+            'attribute vec4 a_position;',
+            'attribute vec4 a_color;',
+            'varying vec4 v_color;',
+            'bool isCorrectColor(vec4 v) {',
+            ' return v.x == 0.0 && v.y == 0.0 && v.z == 0.0 && v.w == 1.0;',
+            '}',
+            'void main() {',
+            ' gl_Position = a_position;',
+            ' v_color = isCorrectColor(a_color) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);',
+            '}'].join('\n');
+
+var disabledFragmentShader = [
+            'precision mediump float;',
+            'varying vec4 v_color;',
+            'void main() {',
+            ' gl_FragColor = v_color;',
+            '}'].join('\n');
+
+//
+// gl-enable-vertex-attrib.html
+//
+function testenablevertexattrib() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+    var program = wtu.setupProgram(
+                gl,
+                [simpleColorVertexShader, enableFragmentShader],
+                ["vPosition"]);
+    var vertexObject = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    gl.bufferData(gl.ARRAY_BUFFER,
+                  new Float32Array([ 0,0.5,0, -0.5,-0.5,0, 0.5,-0.5,0 ]),
+                  gl.STATIC_DRAW);
+    gl.enableVertexAttribArray(0);
+    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+    gl.enableVertexAttribArray(3);
+    retval = wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+    if (!retval) return false;
+    gl.drawArrays(gl.TRIANGLES, 0, 3);
+    retval = wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+    return retval;
+}
+
+var enableFragmentShader = [
+            'void main() {',
+            ' gl_FragColor = vec4(1.0,0.0,0.0,1.0);',
+            '}'].join('\n');
+
+//
+// gl-matrix-attributes.html
+//
+function testmatrixattributes() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+    glFragmentShader = compileShader(simpleColorFragmentShader, gl.FRAGMENT_SHADER);
+    // Test mat2, mat3 and mat4.
+    for (var mm = 2; mm <= 4; ++mm) {
+        // Add maxVertexAttributes number of attributes by saving enough room in the attribute
+        // list for a matrix of mm dimensions. All of the other attribute slots will be
+        // filled with vectors.
+        var numVectors = maxVertexAttributes - mm;
+        for (var pp = 1; pp <= numVectors + 1; ++pp) {
+            //output('Test ' + mm + ' dimensional matrix at position ' + pp);
+            var glProgram = testmatrixattributes_prepareMatrixProgram(wtu, numVectors, mm, pp);
+            retval = (glProgram !== null);
+            if (!retval) return false;
+            var attribMatrix = gl.getAttribLocation(glProgram, 'matrix');
+            //output('Matrix is at attribute location ' + attribMatrix);
+            retval = (attribMatrix > -1);
+            if (!retval) return false;
+            // Per the spec, when an attribute is a matrix attribute, getAttribLocation
+            // returns the index of the first component of the matrix. The implementation must
+            // leave sufficient room for all the components. Here we ensure none of the vectors
+            // in the shader are assigned attribute locations that belong to the matrix.
+            for (var vv = 1; vv <= numVectors; ++vv) {
+                var strVector = 'vec_' + vv
+                var attribVector = gl.getAttribLocation(glProgram, strVector);
+                // Begin with the first attribute location where the matrix begins and ensure
+                // the vector's attribute location is not assigned to the matrix. Loop until
+                // we've checked all of the attribute locations that belong to the matrix.
+                for (var ii = attribMatrix; ii < attribMatrix + mm; ++ii) {
+                    var testStr = strVector + ' attribute location: ' + attribVector + '. Should not be ' + ii;
+                    if (attribVector === ii) {
+                        output(testStr);
+                        return false;//testFailed(testStr);
+                    }
+                }
+            }
+        }
+    }
+    return retval;
+}
+
+// testmatrixattributes_prepareMatrixProgram creates a program with glFragmentShader as the fragment shader.
+// The vertex shader has numVector number of vectors and a matrix with numMatrixDimensions
+// dimensions at location numMatrixPosition in the list of attributes.
+// Ensures that every vector and matrix is used by the program.
+// Returns a valid program on successful link; null on link failure.
+function testmatrixattributes_prepareMatrixProgram(wtu, numVectors, numMatrixDimensions,
+                                                   numMatrixPosition) {
+    // Add the matrix and vector attribute declarations. Declare the vectors
+    // to have the same number of components as the matrix so we can perform
+    // operations on them when we assign to gl_Position later on.
+    var strVertexShader = "";
+    for (var ii = 1; ii <= numVectors; ++ii) {
+        if (numMatrixPosition === ii) {
+            strVertexShader += "attribute mat" + numMatrixDimensions + " matrix;\n";
+        }
+        strVertexShader += "attribute vec" + numMatrixDimensions + " vec_" + ii + ";\n";
+    }
+    // numMatrixPosition will be one past numVectors if the caller wants it to be
+    // last. Hence, we need this check outside the loop as well as inside.
+    if (numMatrixPosition === ii) {
+        strVertexShader += "attribute mat" + numMatrixDimensions + " matrix;\n";
+    }
+    // Add the body of the shader. Add up all of the vectors and multiply by the matrix.
+    // The operations we perform do not matter. We just need to ensure that all the vector and
+    // matrix attributes are used.
+    strVertexShader += "void main(void) { \ngl_Position = vec4((";
+    for (ii = 1; ii <= numVectors; ++ii) {
+        if (ii > 1) {
+            strVertexShader += "+"
+        }
+        strVertexShader += "vec_" + ii;
+    }
+    strVertexShader += ")*matrix";
+    // Ensure the vec4 has the correct number of dimensions in order to be assignable
+    // to gl_Position.
+    for (ii = numMatrixDimensions; ii < 4; ++ii) {
+        strVertexShader += ",0.0";
+    }
+    strVertexShader += ");}\n";
+    // Load the shader, attach it to a program, and return the link results
+    glVertexShader = compileShader(strVertexShader, gl.VERTEX_SHADER);
+    var strTest = 'Load shader with ' + numVectors + ' vectors and 1 matrix';
+    if (glVertexShader !== null) {
+        //testPassed(strTest);
+        var glProgram = gl.createProgram();
+        gl.attachShader(glProgram, glVertexShader);
+        gl.attachShader(glProgram, glFragmentShader);
+        gl.linkProgram(glProgram);
+        if (gl.getProgramParameter(glProgram, gl.LINK_STATUS)) {
+            var retval = wtu.glErrorShouldBe(gl, gl.NO_ERROR, 'linkProgram');
+            if (!retval) return null;
+            return glProgram;
+        }
+    } else {
+        output(strTest);
+        //testFailed(strTest);
+    }
+    return null;
+}
+
+//
+// gl-vertex-attrib-render.html
+//
+function testvertexattribrender() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+
+    var program = wtu.setupProgram(
+                gl,
+                [vertexattribVertexShader, vertexattribFragmentShader],
+                ['p', 'a']);
+    gl.enableVertexAttribArray(gl.p);
+    var pos = gl.createBuffer();
+    pos.type = gl.FLOAT;
+    pos.size = 2;
+    pos.num = 4;
+    gl.bindBuffer(gl.ARRAY_BUFFER, pos);
+    gl.bufferData(gl.ARRAY_BUFFER,
+                  new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
+                  gl.STATIC_DRAW);
+    gl.vertexAttribPointer(0, pos.size, pos.type, false, 0, 0);
+    //output('Test vertexAttrib[1..4]fv by setting different combinations that add up to 1.5 and use that when rendering.');
+    var vals = [[0.5], [0.1,0.4], [0.2,-0.2,0.5], [-1.0,0.3,0.2,2.0]];
+    for (var j = 0; j < 4; ++j) {
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        gl['vertexAttrib' + (j+1) + 'fv'](1, vals[j]);
+        gl.drawArrays(gl.TRIANGLE_STRIP, 0, pos.num);
+        if (!testvertexattribrender_checkRedPortion(gl, width, width * 0.7, width * 0.8)) {
+            output('Attribute of size ' + (j+1) + ' was not set correctly');
+            return false;//testFailed('Attribute of size ' + (j+1) + ' was not set correctly');
+        }
+    }
+
+    return retval;
+}
+
+function testvertexattribrender_checkRedPortion(gl, w, low, high) {
+    var buf = new Uint8Array(w * w * 4);
+    gl.readPixels(0, 0, w, w, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+    var i = 0;
+    for (; i < w; ++i) {
+        if (buf[i * 4 + 0] === 255 && buf[i * 4 + 1] === 0
+                && buf[i * 4 + 2] === 0 && buf[i * 4 + 3] === 255) {
+            break;
+        }
+    }
+    return low <= i && i <= high;
+}
+
+var vertexattribVertexShader = [
+            'attribute vec4 a;',
+            'attribute vec2 p;',
+            'void main() {',
+            ' gl_Position = vec4(p.x + a.x + a.y + a.z + a.w, p.y, 0.0, 1.0);',
+            '}'].join('\n');
+
+var vertexattribFragmentShader = [
+            'precision mediump float;',
+            'void main() {',
+            ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);',
+            '}'].join('\n');
+
+//
+// gl-vertex-attrib-zero-issues.html
+//
+function testvertexattribzeroissues() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+
+    var p0 = testvertexattribzeroissues_setup(wtu, 0);
+    var p3 = testvertexattribzeroissues_setup(wtu, 3);
+    if (p0 === null || p3 === null)
+        return false;
+    testvertexattribzeroissues_setupVerts(60000);
+    for (var ii = 0; ii < 5; ++ii) {
+        // test drawing with attrib 0
+        gl.useProgram(p0);
+        gl.enableVertexAttribArray(0);
+        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+        gl.drawElements(gl.TRIANGLES, 60000, gl.UNSIGNED_SHORT, 0);
+        retval = wtu.glErrorShouldBe(
+                    gl, gl.NO_ERROR,
+                    "drawing using attrib 0 with 60000 verts");
+        if (!retval) return false;
+        retval = wtu.checkCanvas(gl, [0, 255, 0, 255], "canvas should be green");
+        if (!retval) return false;
+        gl.disableVertexAttribArray(0);
+        // test drawing without attrib 0
+        gl.useProgram(p3);
+        gl.enableVertexAttribArray(3);
+        gl.vertexAttribPointer(3, 3, gl.FLOAT, false, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+        gl.drawArrays(gl.TRIANGLES, 0, 60000);
+        retval = wtu.glErrorShouldBe(
+                    gl, gl.NO_ERROR,
+                    "drawing using attrib 3 with 60000 verts");
+        if (!retval) return false;
+        retval = wtu.checkCanvas(gl, [0, 255, 0, 255], "canvas should be green");
+        if (!retval)
+        gl.disableVertexAttribArray(3);
+        // This second test of drawing without attrib0 unconvered a bug in chrome
+        // where after the draw without attrib0 the attrib 0 emulation code disabled
+        // attrib 0 and it was never re-enabled so this next draw failed.
+        gl.useProgram(p3);
+        gl.enableVertexAttribArray(3);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+        gl.drawElements(gl.TRIANGLES, 60000, gl.UNSIGNED_SHORT, 0);
+        retval = wtu.glErrorShouldBe(
+                    gl, gl.NO_ERROR,
+                    "drawing using attrib 3 with 60000 verts");
+        if (!retval) return false;
+        retval = wtu.checkCanvas(gl, [0, 255, 0, 255], "canvas should be green");
+        if (!retval) return false;
+        gl.disableVertexAttribArray(3);
+    }
+
+    return retval;
+}
+
+function testvertexattribzeroissues_setup(wtu, attribIndex) {
+    var program = wtu.setupProgram(
+                gl,
+                [simpleColorVertexShader, vertexattribzeroFragmentShader],
+                ['vPosition'],
+                [attribIndex]);
+    if (attribIndex === gl.getAttribLocation(program, 'vPosition'))
+        return program;
+    else
+        return null;
+}
+
+function testvertexattribzeroissues_setupVerts(numVerts) {
+    var verts = [
+                1.0, 1.0, 0.0,
+                -1.0, 1.0, 0.0,
+                -1.0, -1.0, 0.0,
+                1.0, 1.0, 0.0,
+                -1.0, -1.0, 0.0,
+                1.0, -1.0, 0.0
+            ];
+    var positions = new Float32Array(numVerts * 3);
+    var indices = new Uint16Array(numVerts);
+    for (var ii = 0; ii < numVerts; ++ii) {
+        var ndx = ii % 6;
+        var dst = ii * 3;
+        var src = ndx * 3;
+        for (var jj = 0; jj < 3; ++jj) {
+            positions[dst + jj] = verts[src + jj];
+        }
+        indices[ii] = ii;
+    }
+    var vertexObject = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
+    var indexBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+}
+
+var vertexattribzeroFragmentShader = [
+            'void main() {',
+            ' gl_FragColor = vec4(0.0,1.0,0.0,1.0);',
+            '}'].join('\n');
+
+//
+// gl-vertex-attrib.html
+//
+function testvertexattrib() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+
+    for (var ii = 0; ii < maxVertexAttributes; ++ii) {
+        // These don't work (QTBUG-45043)
+//        gl.vertexAttrib1fv(ii, [1]);
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '1');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '0');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '0');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '1');
+//        if (!retval) return false;
+//        gl.vertexAttrib2fv(ii, [1, 2]);
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '1');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '2');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '0');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '1');
+//        if (!retval) return false;
+//        gl.vertexAttrib3fv(ii, [1, 2, 3]);
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '1');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '2');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '3');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '1');
+//        if (!retval) return false;
+//        gl.vertexAttrib4fv(ii, [1, 2, 3, 4]);
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '1');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '2');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '3');
+//        if (!retval) return false;
+//        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '4');
+//        if (!retval) return false;
+        gl.vertexAttrib1f(ii, 5);
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '5');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '0');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '0');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '1');
+        if (!retval) return false;
+        gl.vertexAttrib2f(ii, 6, 7);
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '6');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '7');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '0');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '1');
+        if (!retval) return false;
+        gl.vertexAttrib3f(ii, 7, 8, 9);
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '7');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '8');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '9');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '1');
+        if (!retval) return false;
+        gl.vertexAttrib4f(ii, 6, 7, 8, 9);
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[0]', '6');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[1]', '7');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[2]', '8');
+        if (!retval) return false;
+        retval = shouldBe('gl.getVertexAttrib(' + ii + ', gl.CURRENT_VERTEX_ATTRIB)[3]', '9');
+        if (!retval) return false;
+    }
+    retval = wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+    return retval;
+}
+
+//
+// gl-vertexattribpointer-offsets.html
+//
+function testvertexattribpointeroffsets() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+
+    gl.clearColor(0, 0, 0, 0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+
+    var program = wtu.setupProgram(
+                gl,
+                [simpleColorVertexShader, attribvertexoffsetFragmentShader],
+                ["vPosition"]);
+    var tests = [
+                { data: new Float32Array([ 0, 1, 0, 1, 0, 0, 0, 0, 0 ]),
+                    type: gl.FLOAT,
+                    componentSize: 4,
+                    normalize: false,
+                },
+                { data: new Float32Array([ 0, 1, 0, 1, 0, 0, 0, 0, 0 ]),
+                    type: gl.FLOAT,
+                    componentSize: 4,
+                    normalize: false,
+                },
+                { data: new Uint16Array([ 0, 32767, 0, 32767, 0, 0, 0, 0, 0 ]),
+                    type: gl.SHORT,
+                    componentSize: 2,
+                    normalize: true,
+                },
+                { data: new Uint16Array([ 0, 65535, 0, 65535, 0, 0, 0, 0, 0 ]),
+                    type: gl.UNSIGNED_SHORT,
+                    componentSize: 2,
+                    normalize: true,
+                },
+                { data: new Uint16Array([ 0, 1, 0, 1, 0, 0, 0, 0, 0 ]),
+                    type: gl.UNSIGNED_SHORT,
+                    componentSize: 2,
+                    normalize: false,
+                },
+                { data: new Uint16Array([ 0, 1, 0, 1, 0, 0, 0, 0, 0 ]),
+                    type: gl.SHORT,
+                    componentSize: 2,
+                    normalize: false,
+                },
+                { data: new Uint8Array([ 0, 127, 0, 127, 0, 0, 0, 0, 0 ]),
+                    type: gl.BYTE,
+                    componentSize: 1,
+                    normalize: true,
+                },
+                { data: new Uint8Array([ 0, 255, 0, 255, 0, 0, 0, 0, 0 ]),
+                    type: gl.UNSIGNED_BYTE,
+                    componentSize: 1,
+                    normalize: true,
+                },
+                { data: new Uint8Array([ 0, 1, 0, 1, 0, 0, 0, 0, 0 ]),
+                    type: gl.BYTE,
+                    componentSize: 1,
+                    normalize: false,
+                },
+                { data: new Uint8Array([ 0, 1, 0, 1, 0, 0, 0, 0, 0 ]),
+                    type: gl.UNSIGNED_BYTE,
+                    componentSize: 1,
+                    normalize: false,
+                }
+            ];
+    var vertexObject = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    gl.bufferData(gl.ARRAY_BUFFER, 1024, gl.STATIC_DRAW);
+    gl.enableVertexAttribArray(0);
+    var colorLoc = gl.getUniformLocation(program, "color");
+    var kNumVerts = 3;
+    var kNumComponents = 3;
+    var count = 0;
+    for (var tt = 0; tt < tests.length; ++tt) {
+        var test = tests[tt];
+        for (var oo = 0; oo < 3; ++oo) {
+            for (var ss = 0; ss < 3; ++ss) {
+                var offset = (oo + 1) * test.componentSize;
+                var color = (count % 2) ? [1, 0, 0, 1] : [0, 1, 0, 1];
+                var stride = test.componentSize * kNumComponents + test.componentSize * ss;
+                output("check with " + wtu.glEnumToString(gl, test.type) + " at offset: "
+                       + offset + " with stride:" + stride + " normalize: " + test.normalize);
+                gl.uniform4fv(colorLoc, color);
+                var data = new Uint8Array(test.componentSize * kNumVerts * kNumComponents
+                                          + stride * (kNumVerts - 1));
+                var view = new Uint8Array(test.data.buffer);
+                var size = test.componentSize * kNumComponents;
+                for (var jj = 0; jj < kNumVerts; ++jj) {
+                    var off1 = jj * size;
+                    var off2 = jj * stride;
+                    for (var zz = 0; zz < size; ++zz) {
+                        data[off2 + zz] = view[off1 + zz];
+                    }
+                }
+                gl.bufferSubData(gl.ARRAY_BUFFER, offset, data);
+                gl.vertexAttribPointer(0, 3, test.type, test.normalize, stride, offset);
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawArrays(gl.TRIANGLES, 0, 3);
+                var buf = new Uint8Array(width * height * 4);
+                gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+                var black = [0, 0, 0, 0];
+                var other = [color[0] * 255, color[1] * 255, color[2] * 255, color[3] * 255];
+                var otherMsg = "should be " + ((count % 2) ? "red" : "green")
+                // The following tests assume width and height are both 50
+                retval = wtu.checkCanvasRect(gl, 0, 0, 1, 1, black, "should be black", 0);
+                if (!retval) return false;
+                retval = wtu.checkCanvasRect(gl, 0, 49, 1, 1, black, "should be black", 0);
+                if (!retval) return false;
+                retval = wtu.checkCanvasRect(gl, 26, 40, 1, 1, other, otherMsg, 0);
+                if (!retval) return false;
+                retval = wtu.checkCanvasRect(gl, 26, 27, 1, 1, other, otherMsg, 0);
+                if (!retval) return false;
+                retval = wtu.checkCanvasRect(gl, 40, 27, 1, 1, other, otherMsg, 0);
+                if (!retval) return false;
+                ++count;
+            }
+        }
+    }
+
+    return retval;
+}
+
+var attribvertexoffsetFragmentShader = [
+            'precision mediump float;',
+            'uniform vec4 color;',
+            'void main() {',
+            ' gl_FragColor = color;',
+            '}'].join('\n');
+
+//
+// gl-vertexattribpointer.html
+//
+function testvertexattribpointer() {
+    var wtu = WebGLTestUtils;
+    var retval = true;
+
+    if (!gl.FIXED) {
+        gl.FIXED = 0x140C;
+    }
+
+    gl.vertexAttribPointer(0, 3, gl.FLOAT, 0, 0, 12);
+    retval = wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION,
+                        "vertexAttribPointer should fail if no buffer is bound");
+    if (!retval) return false;
+    var vertexObject = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(0), gl.STATIC_DRAW);
+    gl.vertexAttribPointer(0, 1, gl.INT, 0, 0, 0);
+    retval = wtu.glErrorShouldBe(gl, gl.INVALID_ENUM,
+                        "vertexAttribPointer should not support INT");
+    if (!retval) return false;
+    gl.vertexAttribPointer(0, 1, gl.UNSIGNED_INT, 0, 0, 0);
+    retval = wtu.glErrorShouldBe(gl, gl.INVALID_ENUM,
+                        "vertexAttribPointer should not support UNSIGNED_INT");
+    if (!retval) return false;
+    gl.vertexAttribPointer(0, 1, gl.FIXED, 0, 0, 0);
+    retval = wtu.glErrorShouldBe(gl, gl.INVALID_ENUM,
+                        "vertexAttribPointer should not support FIXED");
+    if (!retval) return false;
+    var types = [
+                { type:gl.BYTE, bytesPerComponent: 1 },
+                { type:gl.UNSIGNED_BYTE, bytesPerComponent: 1 },
+                { type:gl.SHORT, bytesPerComponent: 2 },
+                { type:gl.UNSIGNED_SHORT, bytesPerComponent: 2 },
+                { type:gl.FLOAT, bytesPerComponent: 4 },
+            ];
+    for (var ii = 0; ii < types.length; ++ii) {
+        var info = types[ii];
+        for (var size = 1; size <= 4; ++size) {
+            output("checking: " + wtu.glEnumToString(gl, info.type) + " with size " + size);
+            var bytesPerElement = size * info.bytesPerComponent;
+            var offsetSet = [
+                        0,
+                        1,
+                        info.bytesPerComponent - 1,
+                        info.bytesPerComponent,
+                        info.bytesPerComponent + 1,
+                        info.bytesPerComponent * 2];
+            for (var jj = 0; jj < offsetSet.length; ++jj) {
+                var offset = offsetSet[jj];
+                for (var kk = 0; kk < offsetSet.length; ++kk) {
+                    var stride = offsetSet[kk];
+                    var err = gl.NO_ERROR;
+                    var reason = ""
+                    if (offset % info.bytesPerComponent != 0) {
+                        reason = "because offset is bad";
+                        err = gl.INVALID_OPERATION;
+                    }
+                    if (stride % info.bytesPerComponent != 0) {
+                        reason = "because stride is bad";
+                        err = gl.INVALID_OPERATION;
+                    }
+                    retval = testvertexattribpointer_checkVertexAttribPointer(
+                                wtu, gl, err, reason, size, info.type, false, stride, offset);
+                    if (!retval) return false;
+                }
+                stride = Math.floor(255 / info.bytesPerComponent) * info.bytesPerComponent;
+                if (offset === 0) {
+                    retval = testvertexattribpointer_checkVertexAttribPointer(
+                                wtu, gl, gl.NO_ERROR, "at stride limit",
+                                size, info.type, false, stride, offset);
+                    if (!retval) return false;
+                    retval = testvertexattribpointer_checkVertexAttribPointer(
+                                wtu, gl, gl.INVALID_VALUE, "over stride limit",
+                                size, info.type, false,
+                                stride + info.bytesPerComponent, offset);
+                    if (!retval) return false;
+                }
+            }
+        }
+    }
+
+    return retval;
+}
+
+var testvertexattribpointer_checkVertexAttribPointer = function(
+    wtu, gl, err, reason, size, type, normalize, stride, offset) {
+    gl.vertexAttribPointer(0, size, type, normalize, stride, offset);
+    return wtu.glErrorShouldBe(gl, err,
+                               "gl.vertexAttribPointer(0, " + size +
+                               ", gl." + wtu.glEnumToString(gl, type) +
+                               ", " + normalize +
+                               ", " + stride +
+                               ", " + offset +
+                               ") should " + (err === gl.NO_ERROR ? "succeed " : "fail ") + reason);
+}
+
+/**
+* A vertex shader for a single texture.
+* @type {string}
+*/
+var simpleTextureVertexShader = [
+            'attribute vec4 vPosition;',
+            'attribute vec2 texCoord0;',
+            'varying vec2 texCoord;',
+            'void main() {',
+            ' gl_Position = vPosition;',
+            ' texCoord = texCoord0;',
+            '}'].join('\n');
+/**
+* A fragment shader for a single texture.
+* @type {string}
+*/
+var simpleTextureFragmentShader = [
+            'precision mediump float;',
+            'uniform sampler2D tex;',
+            'varying vec2 texCoord;',
+            'void main() {',
+            ' gl_FragData[0] = texture2D(tex, texCoord);',
+            '}'].join('\n');
+/**
+* A vertex shader for a single texture.
+* @type {string}
+*/
+var noTexCoordTextureVertexShader = [
+            'attribute vec4 vPosition;',
+            'varying vec2 texCoord;',
+            'void main() {',
+            ' gl_Position = vPosition;',
+            ' texCoord = vPosition.xy * 0.5 + 0.5;',
+            '}'].join('\n');
+/**
+* A vertex shader for a uniform color.
+* @type {string}
+*/
+var simpleColorVertexShader = [
+            'attribute vec4 vPosition;',
+            'void main() {',
+            ' gl_Position = vPosition;',
+            '}'].join('\n');
+/**
+* A fragment shader for a uniform color.
+* @type {string}
+*/
+var simpleColorFragmentShader = [
+            'precision mediump float;',
+            'uniform vec4 u_color;',
+            'void main() {',
+            ' gl_FragData[0] = u_color;',
+            '}'].join('\n');
+/**
+* A vertex shader for vertex colors.
+* @type {string}
+*/
+var simpleVertexColorVertexShader = [
+            'attribute vec4 vPosition;',
+            'attribute vec4 a_color;',
+            'varying vec4 v_color;',
+            'void main() {',
+            ' gl_Position = vPosition;',
+            ' v_color = a_color;',
+            '}'].join('\n');
+/**
+* A fragment shader for vertex colors.
+* @type {string}
+*/
+var simpleVertexColorFragmentShader = [
+            'precision mediump float;',
+            'varying vec4 v_color;',
+            'void main() {',
+            ' gl_FragData[0] = v_color;',
+            '}'].join('\n');
diff --git a/tests/auto/qmltest/canvas3d/tst_conformance_attribs.qml b/tests/auto/qmltest/canvas3d/tst_conformance_attribs.qml
new file mode 100644
index 0000000000000000000000000000000000000000..5249f4be7dc55812588514dc3d2a87e6c71a9e8d
--- /dev/null
+++ b/tests/auto/qmltest/canvas3d/tst_conformance_attribs.qml
@@ -0,0 +1,327 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtCanvas3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.2
+import QtCanvas3D 1.0
+import QtTest 1.0
+
+import "tst_conformance_attribs.js" as Content
+
+// Covers the following WebGL conformance attribute tests:
+// gl-bindAttribLocation-aliasing.html
+// gl-bindAttribLocation-matrix.html
+// gl-disabled-vertex-attrib.html
+// gl-enable-vertex-attrib.html
+// gl-matrix-attributes.html
+// gl-vertex-attrib-render.html
+// gl-vertex-attrib-zero-issues.html
+// gl-vertex-attrib.html
+// gl-vertexattribpointer-offsets.html
+// gl-vertexattribpointer.html
+
+// Doesn't cover the following attribute tests:
+// -
+
+Item {
+    id: top
+    height: 200
+    width: 200
+
+    property var canvas3d: null
+    property var canvas3d2: null
+    property var activeContent: Content
+    property bool renderOk: false
+    property bool antialiasOn: false
+    property bool depthOn: false
+    property var vertexShader: null
+    property var fragmentShader: null
+    property var testfunction: null
+    property bool testresult: false
+
+    function createCanvas(antialias, depth, vertex, fragment, w, h) {
+        // reset state flags
+        renderOk = false
+        testresult = false
+        antialiasOn = antialias
+        depthOn = depth
+        vertexShader = vertex
+        fragmentShader = fragment
+        var width = w
+        var height = h
+        canvas3d = Qt.createQmlObject("
+        import QtQuick 2.2
+        import QtCanvas3D 1.0
+        Canvas3D {
+            onInitGL: activeContent.initGL(canvas3d, antialiasOn, depthOn, vertexShader,
+                                           fragmentShader, width, height, testfunction)
+            onRenderGL: {
+                if (!renderOk) // Render only once
+                    testresult = activeContent.renderGL(canvas3d)
+                renderOk = true
+            }
+        }", top)
+        canvas3d.width = w
+        canvas3d.height = h
+    }
+
+    function createCanvas2(w, h) {
+        var width = w
+        var height = h
+        canvas3d2 = Qt.createQmlObject("
+        import QtQuick 2.2
+        import QtCanvas3D 1.0
+        Canvas3D {
+            onInitGL: activeContent.initGL2(canvas3d2, width, height)
+        }", top)
+        canvas3d2.anchors.fill = top
+    }
+
+    /*
+    ** Copyright (c) 2012 The Khronos Group Inc.
+    **
+    ** Permission is hereby granted, free of charge, to any person obtaining a
+    ** copy of this software and/or associated documentation files (the
+    ** "Materials"), to deal in the Materials without restriction, including
+    ** without limitation the rights to use, copy, modify, merge, publish,
+    ** distribute, sublicense, and/or sell copies of the Materials, and to
+    ** permit persons to whom the Materials are furnished to do so, subject to
+    ** the following conditions:
+    **
+    ** The above copyright notice and this permission notice shall be included
+    ** in all copies or substantial portions of the Materials.
+    **
+    ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+    */
+
+    // gl-bindAttribLocation-aliasing.html
+    // QTBUG-44955
+    /*
+    TestCase {
+        // This test verifies combinations of valid, active attribute types cannot be bound to the same location with bindAttribLocation.
+        name: "Canvas3D_conformance_bindAttribLocation_aliasing"
+        when: windowShown
+
+        function test_bindAttribLocation_aliasing() {
+            testfunction = Content.testbindattriblocationaliasing
+            createCanvas(false, false,
+                         null,
+                         activeContent.simpleColorFragmentShader,
+                         8, 8)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+    */
+
+    // gl-bindAttribLocation-matrix.html
+    // QTBUG-44955
+    /*
+    TestCase {
+        // This test verifies that vectors placed via bindAttribLocation right after matricies will fail if there is insufficient room for the matrix.
+        name: "Canvas3D_conformance_bindAttribLocation_matrix"
+        when: windowShown
+
+        function test_bindAttribLocation_matrix() {
+            testfunction = Content.testbindattriblocationmatrix
+            createCanvas(false, false,
+                         activeContent.simpleColorVertexShader,
+                         activeContent.simpleColorFragmentShader,
+                         8, 8)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+    */
+
+    // gl-disabled-vertex-attrib.html
+    // QTBUG-45075
+    // Readpixels returns the correct value only for the pixels on the first row for some reason.
+    // Might be an issue with two contexts that are required to run this.
+    /*
+    TestCase {
+        // This test verifies that vectors placed via bindAttribLocation right after matricies will fail if there is insufficient room for the matrix.
+        name: "Canvas3D_conformance_disabled_vertex_attrib"
+        when: windowShown
+
+        function test_disabled_vertex_attrib() {
+            createCanvas2(50, 50)
+            waitForRendering(canvas3d2)
+            testfunction = Content.testdisabledvertexattrib
+            createCanvas(false, false, null, null, 50, 50)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+            canvas3d2.destroy()
+        }
+    }
+    */
+
+    // gl-enable-vertex-attrib.html
+    // QTBUG-45074
+    /*
+    TestCase {
+        // This tests that turning on attribs that have no buffer bound fails to draw.
+        name: "Canvas3D_conformance_enable_vertex_attrib"
+        when: windowShown
+
+        function test_enable_vertex_attrib() {
+            testfunction = Content.testenablevertexattrib
+            createCanvas(false, false, null, null, 50, 50)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+    */
+
+    // gl-matrix-attributes.html
+    TestCase {
+        // This test ensures that matrix attribute locations do not clash with other shader attributes.
+        name: "Canvas3D_conformance_matrix_attributes"
+        when: windowShown
+
+        function test_matrix_attributes() {
+            testfunction = Content.testmatrixattributes
+            createCanvas(false, false, null, null, 8, 8)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+
+    // gl-vertex-attrib-render.html
+    // QTBUG-45126 (Only fails on Windows Angle tests)
+    /*
+    TestCase {
+        // This test verifies that using constant attributes works.
+        name: "Canvas3D_conformance_vertex_attrib_render"
+        when: windowShown
+
+        function test_vertex_attrib_render() {
+            testfunction = Content.testvertexattribrender
+            createCanvas(false, true, null, null, 50, 50)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+    */
+
+    // gl-vertex-attrib-zero-issues.html
+    // QTBUG-45175
+    /*
+    TestCase {
+        //  Test some of the issues of the difference between attrib 0 on OpenGL vs WebGL.
+        name: "Canvas3D_conformance_vertex_attrib_zero_issues"
+        when: windowShown
+
+        function test_vertex_attrib_zero_issues() {
+            testfunction = Content.testvertexattribzeroissues
+            createCanvas(false, false, null, null, 50, 50)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+    */
+
+    // gl-vertex-attrib.html
+    // QTBUG-45043 (vertexAttrib...fv doesn't work). Failing parts are commented out in tst_conformance_attribs.js
+    // QTBUG-45175
+    /*
+    TestCase {
+        // This test ensures WebGL implementations vertexAttrib can be set and read.
+        name: "Canvas3D_conformance_vertex_attrib"
+        when: windowShown
+
+        function test_vertex_attrib() {
+            testfunction = Content.testvertexattrib
+            createCanvas(false, false, null, null, 2, 2)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+    */
+
+    // gl-vertexattribpointer-offsets.html
+    TestCase {
+        // This test ensures vertexattribpointer offsets work.
+        name: "Canvas3D_conformance_vertex_attrib_pointer_offsets"
+        when: windowShown
+
+        function test_vertex_attrib_pointer_offsets() {
+            testfunction = Content.testvertexattribpointeroffsets
+            createCanvas(false, false, null, null, 50, 50)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+
+    // gl-vertexattribpointer.html
+    TestCase {
+        // This test checks vertexAttribPointer behaviors in WebGL.
+        name: "Canvas3D_conformance_vertex_attrib_pointer"
+        when: windowShown
+
+        function test_vertex_attrib_pointer() {
+            testfunction = Content.testvertexattribpointer
+            createCanvas(false, false, null, null, 2, 2)
+            waitForRendering(canvas3d)
+            tryCompare(top, "renderOk", true)
+            verify(testresult)
+            canvas3d.destroy()
+        }
+    }
+}
diff --git a/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.js b/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.js
index d5afae3337f44bab7a58f9332ea91405e2720b3e..806f54c61a9bea37a235bf9ff047cfba346a20c4 100644
--- a/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.js
+++ b/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.js
@@ -35,6 +35,9 @@
 ****************************************************************************/
 
 Qt.include("../../../../3rdparty/js-test-pre.js")
+Qt.include("../../../../3rdparty/test-eval.js")
+
+"use strict";
 
 var gl;
 
@@ -86,7 +89,7 @@ function running(str) {
 }
 
 function output(str) {
-    //console.log(str);
+    //output(str);
 }
 
 function pass(str) {
@@ -130,7 +133,7 @@ var subArray;
 function testSlice() {
     function test(subBuf, starts, size) {
         byteLength = size;
-        subBuffer = eval(subBuf);
+        subBuffer = TestEval(subBuf);
         subArray = new Int8Array(subBuffer);
         assertEq(subBuf, subBuffer.byteLength, byteLength);
         for (var i = 0; i < size; ++i)
@@ -444,14 +447,14 @@ function testIntegralArrayTruncationBehavior(type, name, unsigned) {
 //
 function testGetWithOutOfRangeIndices(type, name) {
     var retval = true;
-    console.log('Testing ' + name + ' GetWithOutOfRangeIndices');
+    output('Testing ' + name + ' GetWithOutOfRangeIndices');
     // See below for declaration of this global variable
-    array = new type([2, 3]);
-    retval = shouldBeUndefined("array[2]");//(array[2] === undefined);
+    var array = new type([2, 3]);
+    retval = (array[2] === undefined);
     if (retval)
-        retval = shouldBeUndefined("array[-1]");//(array[-1] === undefined);
+        retval = (array[-1] === undefined);
     if (retval)
-        retval = shouldBeUndefined("array[0x20000000]");//(array[0x20000000] === undefined);
+        retval = (array[0x20000000] === undefined);
     return retval;
 }
 
@@ -594,7 +597,7 @@ function negativeTestSubarray(type, name) {
     running('negativeTest ' + name + ' Subarray');
     try {
         var array = new type([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
-        subarray = array.subarray(5, 11);
+        var subarray = array.subarray(5, 11);
         if (subarray.length !== 5) {
             return fail();
         }
@@ -604,7 +607,7 @@ function negativeTestSubarray(type, name) {
         }
         return pass();
     } catch (e) {
-        console.log("subarrayfail")
+        output("subarrayfail")
         return fail(e);
     }
 }
@@ -723,7 +726,7 @@ function testConstructionOfHugeArray(type, name, sz) {
     try {
         // Construction of huge arrays must fail because byteLength is
         // an unsigned long
-        array = new type(3000000000);
+        var array = new type(3000000000);
         return fail("Construction of huge " + name + " should throw exception");
     } catch (e) {
         return pass("Construction of huge " + name + " threw exception");
@@ -742,22 +745,21 @@ function testConstructionWithBothArrayBufferAndLength(type, name, elementSizeInB
     }
 }
 
-// These need to be global for shouldBe to see them
-var array;
-var typeSize;
 function testSubarrayWithOutOfRangeValues(type, name, sz) {
-    console.log("Testing subarray of " + name);
+    var array;
+    var typeSize;
+    output("Testing subarray of " + name);
     var retval = true;
     try {
         var buffer = new ArrayBuffer(32);
         array = new type(buffer);
         typeSize = sz;
-        retval = shouldBe("array.length", "32 / typeSize");
+        retval = (array.length === 32 / typeSize);
         try {
             if (retval)
-                retval = shouldBe("array.subarray(4, 0x3FFFFFFF).length", "(32 / typeSize) - 4");
+                retval = (array.subarray(4, 0x3FFFFFFF).length === (32 / typeSize) - 4);
             if (retval)
-                retval = shouldBe("array.subarray(4, -2147483648).length", "0");
+                retval = (array.subarray(4, -2147483648).length === 0);
             // Test subarray() against overflows.
             array = array.subarray(2);
             if (sz > 1) {
@@ -767,7 +769,7 @@ function testSubarrayWithOutOfRangeValues(type, name, sz) {
                 var start = 4294967296 / sz - 2;
                 array = array.subarray(start, start + 1);
                 if (retval)
-                    retval = shouldBe("array.length", "0");
+                    retval = (array.length === 0);
             }
         } catch (e) {
             return fail("Subarray of " + name + " threw exception");
@@ -779,22 +781,22 @@ function testSubarrayWithOutOfRangeValues(type, name, sz) {
 }
 
 function testSubarrayWithDefaultValues(type, name, sz) {
-    console.log("Testing subarray with default inputs of " + name);
+    output("Testing subarray with default inputs of " + name);
     var retval = true;
     try {
         var buffer = new ArrayBuffer(32);
-        array = new type(buffer);
-        typeSize = sz;
-        retval = shouldBe("array.length", "32 / typeSize");
+        var array = new type(buffer);
+        var typeSize = sz;
+        retval = (array.length === 32 / typeSize);
         try {
             if (retval)
-                retval = shouldBe("array.subarray(0).length", "(32 / typeSize)");
+                retval = (array.subarray(0).length === (32 / typeSize));
             if (retval)
-                retval = shouldBe("array.subarray(2).length", "(32 / typeSize) - 2");
+                retval = (array.subarray(2).length === (32 / typeSize) - 2);
             if (retval)
-                retval = shouldBe("array.subarray(-2).length", "2");
+                retval = (array.subarray(-2).length === 2);
             if (retval)
-                retval = shouldBe("array.subarray(-2147483648).length", "(32 / typeSize)");
+                retval = (array.subarray(-2147483648).length === (32 / typeSize));
         } catch (e) {
             return fail("Subarray of " + name + " threw exception");
         }
@@ -844,9 +846,10 @@ function testSettingFromTypedArrayWithOutOfRangeOffset(type, name) {
     }
 }
 
+var setgetarray; // Needs to be global for shouldBe to find it
 function negativeTestGetAndSetMethods(type, name) {
-    array = new type([2, 3]);
-    shouldBeUndefined("array.get");
+    setgetarray = new type([2, 3]);
+    shouldBeUndefined("setgetarray.get");
     var exceptionThrown = false;
     // We deliberately check for an exception here rather than using
     // shouldThrow here because the precise contents of the syntax
@@ -856,7 +859,7 @@ function negativeTestGetAndSetMethods(type, name) {
     } catch (e) {
         exceptionThrown = true;
     }
-    var txt = "array.set(0, 1) ";
+    var txt = "setgetarray.set(0, 1) ";
     if (exceptionThrown) {
         return pass(txt + "threw exception.");
     } else {
diff --git a/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.qml b/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.qml
index 72a21c324231f83393dfdff7ea6f4bbbbcd7f9b4..53a1156292cb222b48522e5551840d6acaf99e4f 100644
--- a/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.qml
+++ b/tests/auto/qmltest/canvas3d/tst_conformance_typedarrays.qml
@@ -149,7 +149,7 @@ Item {
                 verify(Content.testSetFromArray(type, name));
                 verify(Content.negativeTestSetFromArray(type, name));
                 verify(Content.testSubarray(type, name));
-                //verify(Content.negativeTestSubarray(type, name)); // V4VM? Float32Array Subarray: Error: Invalid write to global property "subarray". Something to check in V4VM?
+                verify(Content.negativeTestSubarray(type, name));
                 verify(Content.testSetBoundaryConditions(type,
                                                          name,
                                                          testCase.testValues,
diff --git a/tests/auto/qmltest/qmltest.pro b/tests/auto/qmltest/qmltest.pro
index b33e5c8c56537636b409201233f6937c235a4149..c54931bfd448950f8be19324022bb794df818f34 100644
--- a/tests/auto/qmltest/qmltest.pro
+++ b/tests/auto/qmltest/qmltest.pro
@@ -7,5 +7,5 @@ CONFIG += console
 
 SOURCES += tst_qmltest.cpp
 
-OTHER_FILES += canvas3d/tst_*.qml canvas3d/*.js canvas3d/*.png
+OTHER_FILES += canvas3d/tst_*.qml canvas3d/*.js canvas3d/*.png ../../../3rdparty/*.js
 DEFINES += QUICK_TEST_SOURCE_DIR=\"\\\"$$PWD/canvas3d\\\"\"