Add ToT WebGL conformance tests : part 8

Add conformance/ l~n

BUG=
TEST=
TBR=kbr@chromium.org

Review URL: https://codereview.chromium.org/41503006

git-svn-id: http://src.chromium.org/svn/trunk/deps/third_party/webgl/sdk/tests@230838 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
diff --git a/conformance/limits/00_test_list.txt b/conformance/limits/00_test_list.txt
new file mode 100644
index 0000000..0b342ca
--- /dev/null
+++ b/conformance/limits/00_test_list.txt
@@ -0,0 +1,5 @@
+gl-min-attribs.html
+gl-max-texture-dimensions.html
+gl-min-textures.html
+gl-min-uniforms.html
+
diff --git a/conformance/limits/gl-max-texture-dimensions.html b/conformance/limits/gl-max-texture-dimensions.html
new file mode 100644
index 0000000..3a59502
--- /dev/null
+++ b/conformance/limits/gl-max-texture-dimensions.html
@@ -0,0 +1,131 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL the max advertized texture size is supported.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="example" width="4" height="4" style="width: 40px; height: 30px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+attribute vec2 texCoord0;
+varying vec2 texCoord;
+void main()
+{
+    gl_Position = vPosition;
+    texCoord = texCoord0;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+uniform samplerCube tex;
+varying vec2 texCoord;
+void main()
+{
+    gl_FragColor = textureCube(tex, normalize(vec3(texCoord, 1)));
+}
+</script>
+<script>
+"use strict";
+description(document.title);
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example");
+var program = wtu.setupTexturedQuad(gl);
+
+function shouldBePowerOfTwo(n, name) {
+    var power = Math.round(Math.log(n) / Math.log(2));
+    if (Math.pow(2, power) == n) {
+        testPassed(name + ' is a power of two.');
+    } else {
+        testFailed(name + ' should be a power of two, but was ' + n);
+    }
+}
+
+// Note: It seems like a reasonable assuption that a 1xN texture size should
+// work. Even 1 by 128k is only 512k
+var maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
+debug("advertised max size: " + maxSize);
+debug("verifying max size is power-of-two (implied by GLES 2.0 section 3.7.1)");
+shouldBePowerOfTwo(maxSize, 'Max size');
+var testSize = Math.min(maxSize, 128 * 1024);
+var pixels = new Uint8Array(testSize * 4);
+for (var ii = 0; ii < testSize; ++ii) {
+  var off = ii * 4;
+  pixels[off + 0] = 0;
+  pixels[off + 1] = 255;
+  pixels[off + 2] = 128;
+  pixels[off + 3] = 255;
+}
+var tex = gl.createTexture();
+gl.bindTexture(gl.TEXTURE_2D, tex);
+
+debug("test " + testSize + "x1");
+gl.texImage2D(
+    gl.TEXTURE_2D, 0, gl.RGBA, testSize, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+    pixels);
+gl.generateMipmap(gl.TEXTURE_2D);
+
+wtu.clearAndDrawUnitQuad(gl);
+wtu.checkCanvas(gl, [0, 255, 128, 255],
+                "Should be 0, 255, 128, 255");
+debug("test 1x" + testSize);
+gl.texImage2D(
+    gl.TEXTURE_2D, 0, gl.RGBA, 1, testSize, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+    pixels);
+gl.generateMipmap(gl.TEXTURE_2D);
+
+wtu.clearAndDrawUnitQuad(gl);
+wtu.checkCanvas(gl, [0, 255, 128, 255],
+                "Should be 0, 255, 128, 255");
+
+var program = wtu.setupProgram(
+    gl, ['vshader', 'fshader'], ['vPosition', 'texCoord0'], [0, 1]);
+
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors.");
+
+// NOTE: We can't easily test cube maps because they require width == height
+// and we might not have enough memory for maxSize by maxSize texture.
+
+var successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
+
+
diff --git a/conformance/limits/gl-min-attribs.html b/conformance/limits/gl-min-attribs.html
new file mode 100644
index 0000000..9eed619
--- /dev/null
+++ b/conformance/limits/gl-min-attribs.html
@@ -0,0 +1,108 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL the minimum number of attributes are supported.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="example" width="4" height="4" style="width: 40px; height: 30px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+attribute vec4 v0;
+attribute vec4 v1;
+attribute vec4 v2;
+attribute vec4 v3;
+attribute vec4 v4;
+attribute vec4 v5;
+attribute vec4 v6;
+varying vec4 color;
+void main()
+{
+    gl_Position = vPosition;
+    color = v0 + v1 + v2 + v3 + v4 + v5 + v6;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 color;
+void main()
+{
+    gl_FragColor = color;
+}
+</script>
+<script>
+"use strict";
+description(document.title);
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example");
+var program = wtu.setupTexturedQuad(gl);
+
+var program = wtu.setupProgram(
+    gl,
+    ['vshader', 'fshader'],
+    ['vPosition', 'v0', 'v1', 'v2', 'v3', 'v4', 'v5', 'v6'],
+    [0, 1, 2, 3, 4, 5, 6, 7]);
+
+for (var ii = 0; ii < 7; ++ii) {
+  var v = (ii + 1) / 28;
+  var vertexObject = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+      v, v/2, v/4, v/8,
+      v, v/2, v/4, v/8,
+      v, v/2, v/4, v/8,
+      v, v/2, v/4, v/8,
+      v, v/2, v/4, v/8,
+      v, v/2, v/4, v/8]), gl.STATIC_DRAW);
+  gl.enableVertexAttribArray(ii + 1);
+  gl.vertexAttribPointer(ii + 1, 4, gl.FLOAT, false, 0, 0);
+}
+
+wtu.clearAndDrawUnitQuad(gl);
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
+wtu.checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, [255, 127, 64, 32], "Should render 255,127,64,32 (+/-1)", 1);
+
+var successfullyParsed = true;
+
+</script>
+</body>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
+
+
diff --git a/conformance/limits/gl-min-textures.html b/conformance/limits/gl-min-textures.html
new file mode 100644
index 0000000..a8364b1
--- /dev/null
+++ b/conformance/limits/gl-min-textures.html
@@ -0,0 +1,95 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL the minimum number of uniforms are supported.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="example" width="4" height="4" style="width: 40px; height: 30px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+void main()
+{
+    gl_Position = vPosition;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+#define NUM_TEXTURES 8 // See spec
+precision mediump float;
+uniform sampler2D uni[NUM_TEXTURES];
+void main()
+{
+    vec4 c = vec4(0,0,0,0);
+    for (int ii = 0; ii < NUM_TEXTURES; ++ii) {
+      c += texture2D(uni[ii], vec2(0.5, 0.5));
+    }
+    gl_FragColor = c;
+}
+</script>
+<script>
+"use strict";
+description(document.title);
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example");
+var program = wtu.setupTexturedQuad(gl);
+
+//------------------------------------------------------------------------------
+var program = wtu.setupProgram(
+    gl, ['vshader', 'fshader'], ['vPosition'], [0]);
+
+for (var ii = 0; ii < 8; ++ii) {
+  var loc = gl.getUniformLocation(program, "uni[" + ii + "]");
+  gl.activeTexture(gl.TEXTURE0 + ii);
+  var tex = gl.createTexture();
+  wtu.fillTexture(gl, tex, 1, 1, [32, 16, 8, ii * 9], 0);
+  gl.uniform1i(loc, ii);
+}
+
+wtu.clearAndDrawUnitQuad(gl);
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
+wtu.checkCanvas(gl, [255, 128, 64, 252],
+                "Should render using all texture units", 1);
+
+var successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
+
+
diff --git a/conformance/limits/gl-min-uniforms.html b/conformance/limits/gl-min-uniforms.html
new file mode 100644
index 0000000..7abba8b
--- /dev/null
+++ b/conformance/limits/gl-min-uniforms.html
@@ -0,0 +1,127 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL the minimum number of uniforms are supported.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="example" width="4" height="4" style="width: 40px; height: 30px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+#define NUM_UNIFORMS 128 // See spec
+attribute vec4 vPosition;
+uniform vec4 uni[NUM_UNIFORMS];
+varying vec4 color;
+void main()
+{
+    gl_Position = vPosition;
+    vec4 c = vec4(0,0,0,0);
+    for (int ii = 0; ii < NUM_UNIFORMS; ++ii) {
+      c += uni[ii];
+    }
+    color = c;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 color;
+void main()
+{
+    gl_FragColor = color;
+}
+</script>
+<script id="vshader2" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+void main()
+{
+    gl_Position = vPosition;
+}
+</script>
+
+<script id="fshader2" type="x-shader/x-fragment">
+precision mediump float;
+#define NUM_UNIFORMS 16 // See spec
+uniform vec4 uni[NUM_UNIFORMS];
+void main()
+{
+    vec4 c = vec4(0,0,0,0);
+    for (int ii = 0; ii < NUM_UNIFORMS; ++ii) {
+       c += uni[ii];
+    }
+    gl_FragColor = vec4(c.r, c.g, c.b, c.a / 120.0);
+}
+</script>
+<script>
+"use strict";
+description(document.title);
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example");
+var program = wtu.setupTexturedQuad(gl);
+
+//------------------------------------------------------------------------------
+var program = wtu.setupProgram(gl, ['vshader', 'fshader'], ['vPosition'], [0]);
+
+for (var ii = 0; ii < 128; ++ii) {
+  var loc = gl.getUniformLocation(program, "uni[" + ii + "]");
+  gl.uniform4f(loc, 2/256, 2/512, 2/1024, ii/8128);
+}
+
+wtu.clearAndDrawUnitQuad(gl);
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
+wtu.checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, [255, 127, 64, 255], "Should render 255,127,64,32 (+/-1)", 1);
+
+//------------------------------------------------------------------------------
+var program = wtu.setupProgram(gl, ['vshader2', 'fshader2'], ['vPosition'], [0]);
+
+for (var ii = 0; ii < 16; ++ii) {
+  var loc = gl.getUniformLocation(program, "uni[" + ii + "]");
+  gl.uniform4f(loc, 16/2048, 16/1024, 16/512, ii);
+}
+
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
+wtu.clearAndDrawUnitQuad(gl);
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
+wtu.checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, [32, 64, 127, 255], "Should render 32,64,127,255 (+/-1)", 1);
+
+var successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
+
+
diff --git a/conformance/manual/canvas-clear-on-zero-count-draw.html b/conformance/manual/canvas-clear-on-zero-count-draw.html
new file mode 100644
index 0000000..23efd14
--- /dev/null
+++ b/conformance/manual/canvas-clear-on-zero-count-draw.html
@@ -0,0 +1,112 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Check that the canvas is NOT recomposited after unsucessful draw call</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<style>
+canvas {
+    border: 1px solid black;
+}
+.correct {
+    border: 1px solid black;
+    background-color: #ffffff;
+}
+</style>
+</head>
+<body>
+<pre>
+This test must be run manually.
+
+This test tests that a canvas is cleared
+even when a draw call has a zero count.
+
+You should see three <span class="correct">white rectangles</span>
+with black outlines on success.
+</pre>
+<canvas id='c1'></canvas>
+<canvas id='c2'></canvas>
+<canvas id='c3'></canvas>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+void main() {
+  gl_Position = vec4(0,0,0,1);
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+void main() {
+  discard;
+}
+</script>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var c1 = document.getElementById("c1");
+var c2 = document.getElementById("c2");
+var c3 = document.getElementById("c2");
+var gl1 = wtu.create3DContext(c1);
+var gl2 = wtu.create3DContext(c2);
+var gl3 = wtu.create3DContext(c2);
+gl1.clearColor(0,1,0,1);
+gl1.clear(gl1.COLOR_BUFFER_BIT);
+gl2.clearColor(0,1,0,1);
+gl2.clear(gl2.COLOR_BUFFER_BIT);
+gl3.clearColor(0,1,0,1);
+gl3.clear(gl2.COLOR_BUFFER_BIT);
+
+wtu.waitForComposite(gl1, function() {
+  // test drawArrays
+  gl1.drawArrays(gl1.TRIANGLES, 0, 0);
+  glErrorShouldBe(gl1, gl1.NO_ERROR, "no errors");
+});
+
+wtu.waitForComposite(gl2, function() {
+  // test drawElements
+  var buf = gl2.createBuffer();
+  gl2.bindBuffer(gl2.ELEMENT_ARRAY_BUFFER, buf);
+  gl2.bufferData(gl2.ELEMENT_ARRAY_BUFFER, new Uint8Array(1), gl2.STATIC_DRAW);
+  gl2.drawElements(gl2.TRIANGLES, 0, gl2.UNSIGNED_SHORT, 0);
+  glErrorShouldBe(gl2, gl2.NO_ERROR, "no errors");
+});
+
+wtu.waitForComposite(gl3, function() {
+  // test draw with program.
+  wtu.setupProgram(gl3, ["vshader", "fshader"]);
+  gl1.drawArrays(gl3.TRIANGLES, 0, 0);
+  glErrorShouldBe(gl3, gl2.NO_ERROR, "no errors");
+});
+
+</script>
+</body>
+</html>
diff --git a/conformance/manual/canvas-no-clear-on-readpixels.html b/conformance/manual/canvas-no-clear-on-readpixels.html
new file mode 100644
index 0000000..cef839f
--- /dev/null
+++ b/conformance/manual/canvas-no-clear-on-readpixels.html
@@ -0,0 +1,72 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Check that the canvas is NOT recomposited after calling readPixels</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<style>
+canvas {
+    border: 1px solid black;
+}
+.correct {
+    border: 1px solid black;
+    background-color: #00ff00;
+}
+</style>
+</head>
+<body>
+<pre>
+This test must be run manually.
+
+This test tests that a canvas is NOT cleared
+and recomposited after calling readPixels.
+
+You should see a <span class="correct">green rectangle</span>
+with black a outline on success.
+</pre>
+<canvas id='c'></canvas>
+<div id="console"></div>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var c = document.getElementById("c");
+var gl = wtu.create3DContext(c);
+gl.clearColor(0,1,0,1);
+gl.clear(gl.COLOR_BUFFER_BIT);
+wtu.waitForComposite(gl, function() {
+  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
+  glErrorShouldBe(gl, gl.NO_ERROR, "no errors");
+});
+</script>
+</body>
+</html>
diff --git a/conformance/manual/canvas-no-clear-on-unsuccessful-draw.html b/conformance/manual/canvas-no-clear-on-unsuccessful-draw.html
new file mode 100644
index 0000000..2aa3f6c
--- /dev/null
+++ b/conformance/manual/canvas-no-clear-on-unsuccessful-draw.html
@@ -0,0 +1,85 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Check that the canvas is NOT recomposited after unsucessful draw call</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<style>
+canvas {
+    border: 1px solid black;
+}
+.correct {
+    border: 1px solid black;
+    background-color: #00ff00;
+}
+</style>
+</head>
+<body>
+<pre>
+This test must be run manually.
+
+This test tests that a canvas is NOT cleared
+when a draw call fails.
+
+You should see two <span class="correct">green rectangles</span>
+with black outlines on success.
+</pre>
+<canvas id='c1'></canvas>
+<canvas id='c2'></canvas>
+<div id="console"></div>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var c1 = document.getElementById("c1");
+var c2 = document.getElementById("c2");
+var gl1 = wtu.create3DContext(c1);
+var gl2 = wtu.create3DContext(c2);
+gl1.clearColor(0,1,0,1);
+gl1.clear(gl1.COLOR_BUFFER_BIT);
+gl2.clearColor(0,1,0,1);
+gl2.clear(gl2.COLOR_BUFFER_BIT);
+wtu.waitForComposite(gl1, function() {
+  gl1.drawArrays(gl1.BLEND, 0, 0);
+  glErrorShouldBe(gl1, gl1.INVALID_ENUM, "no errors");
+});
+
+wtu.waitForComposite(gl2, function() {
+  var buf = gl2.createBuffer();
+  gl2.bindBuffer(gl2.ELEMENT_ARRAY_BUFFER, buf);
+  gl2.bufferData(gl2.ELEMENT_ARRAY_BUFFER, new Uint8Array(1), gl2.STATIC_DRAW);
+  gl2.drawElements(gl2.BLEND, 0, gl2.UNSIGNED_SHORT, 0);
+  glErrorShouldBe(gl2, gl2.INVALID_ENUM, "no errors");
+});
+</script>
+</body>
+</html>
diff --git a/conformance/manual/framebuffers-keep-contents-exiting-fullscreen-mode.html b/conformance/manual/framebuffers-keep-contents-exiting-fullscreen-mode.html
new file mode 100644
index 0000000..4dcfd3a
--- /dev/null
+++ b/conformance/manual/framebuffers-keep-contents-exiting-fullscreen-mode.html
@@ -0,0 +1,156 @@
+<!--
+
+/*
+** Copyright (c) 2013 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Check that framebuffers keep contents exiting fullscreen mode.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<style>
+canvas {
+    width: 100%;
+    height: 50px;
+    border: 1px solid black;
+}
+#f {
+    left: 0px;
+    top: 0px;
+}
+#inner {
+}
+input.button {
+    font-size: 36pt;
+}
+.redbox {
+    border: 1px solid black;
+    background-color: red;
+}
+#canvasholder {
+    position: relative;
+}
+#clabel {
+    position: absolute;
+    width: 100%;
+    top: 0px;
+    left: 0px;
+    font-size: 40px;
+    z-index: 10;
+    text-align: center;
+}
+</style>
+</head>
+<body>
+<pre>
+This test must be run manually.
+
+Checks that framebuffers keep their contents going into and out of fullscreen mode.
+
+Through the entire test you should see a <span class="redbox">red rectangle</span>. If it is not <span class="redbox">red</span> in all cases the test has failed.
+</pre>
+<div id="f">
+  <div id="inner">
+    <div id="buttonHolder">
+      <div><input id="button" class="button" type="button" value="Click this button to continue the test"/></div>
+      <div id="canvasholder">
+        <canvas id='c'></canvas>
+        <div id='clabel'>
+          should be red
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var testedFullScreen = false;
+var c = document.getElementById("c");
+var button = document.getElementById("button");
+var buttonHolder = document.getElementById("buttonHolder");
+var gl = wtu.create3DContext(c);
+if (!gl) {
+  testFailed("can't init WebGL");
+}
+
+var checkState = function() {
+  debug("");
+  wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 255, 0, 255]);
+  shouldBeNonNull("gl.getParameter(gl.FRAMEBUFFER_BINDING)");
+  glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+var checkFramebufferState = function(fullscreen) {
+  checkState();
+
+  debug("fullscreen:" + fullscreen);
+
+  if (fullscreen) {
+    // Not sure if this is needed but need to make sure
+    // we at least went fullscreen once.
+    testedFullScreen = true;
+  } else {
+    if (testedFullScreen) {
+      finishTest();
+    }
+  }
+};
+
+var test = function() {
+  if (!wtu.setupFullscreen("button", "f", checkFramebufferState)) {
+    testPassed("Browser does not support fullscreen mode. This is OK");
+    finishTest();
+    return;
+  }
+
+  var fb = gl.createFramebuffer();
+  var tex = gl.createTexture();
+
+  gl.clearColor(1,0,0,1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+
+  gl.clearColor(0, 1, 0, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  checkState();
+};
+test();
+
+</script>
+</body>
+</html>
diff --git a/conformance/misc/00_test_list.txt b/conformance/misc/00_test_list.txt
new file mode 100644
index 0000000..3941fd9
--- /dev/null
+++ b/conformance/misc/00_test_list.txt
@@ -0,0 +1,14 @@
+bad-arguments-test.html
+--min-version 1.0.2 boolean-argument-conversion.html
+--min-version 1.0.2 delayed-drawing.html
+error-reporting.html
+instanceof-test.html
+invalid-passed-params.html
+is-object.html
+null-object-behaviour.html
+functions-returning-strings.html
+object-deletion-behaviour.html
+shader-precision-format.html
+type-conversion-test.html
+uninitialized-test.html
+webgl-specific.html
diff --git a/conformance/misc/bad-arguments-test.html b/conformance/misc/bad-arguments-test.html
new file mode 100644
index 0000000..a762a34
--- /dev/null
+++ b/conformance/misc/bad-arguments-test.html
@@ -0,0 +1,121 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description("Tests calling WebGL APIs with wrong argument types");
+
+var context = wtu.create3DContext();
+var program = wtu.loadStandardProgram(context);
+var shader = wtu.loadStandardVertexShader(context);
+var shouldGenerateGLError = wtu.shouldGenerateGLError;
+
+assertMsg(program != null, "Program Compiled");
+assertMsg(shader != null, "Shader Compiled");
+
+var loc = context.getUniformLocation(program, "u_modelViewProjMatrix");
+assertMsg(loc != null, "getUniformLocation succeeded");
+
+var testArguments = [
+  { value: "foo",
+    throws: true },
+  { value: 0,
+    throws: true },
+  { value: null,
+    throws: false },
+  { value: undefined,
+    throws: false }
+];
+
+var argument;
+
+function shouldBeEmptyString(command) {
+  shouldBe(command, "''");
+}
+
+for (var i = 0; i < testArguments.length; ++i) {
+  var func, func2, func3;
+  if (testArguments[i].throws) {
+    func = shouldThrow;
+    func2 = shouldThrow;
+    func3 = shouldThrow;
+  } else {
+    func = shouldBeUndefined;
+    func2 = shouldBeNull;
+    func3 = shouldBeEmptyString;
+  }
+  argument = testArguments[i].value;
+  func("context.compileShader(argument)");
+  func("context.linkProgram(argument)");
+  func("context.attachShader(program, argument)");
+  func("context.attachShader(argument, shader)");
+  func("context.detachShader(program, argument)");
+  func("context.detachShader(argument, shader)");
+  func("context.useProgram(argument)");
+  func("context.shaderSource(argument, 'foo')");
+  func("context.bindAttribLocation(argument, 0, 'foo')");
+  func("context.bindBuffer(context.ARRAY_BUFFER, argument)");
+  func("context.bindFramebuffer(context.FRAMEBUFFER, argument)");
+  func("context.bindRenderbuffer(context.RENDERBUFFER, argument)");
+  func("context.bindTexture(context.TEXTURE_2D, argument)");
+  func("context.framebufferRenderbuffer(context.FRAMEBUFFER, context.DEPTH_ATTACHMENT, context.RENDERBUFFER, argument)");
+  func("context.framebufferTexture2D(context.FRAMEBUFFER, context.COLOR_ATTACHMENT0, context.TEXTURE_2D, argument, 0)");
+  func("context.uniform2fv(argument, new Float32Array([0.0, 0.0]))");
+  func("context.uniform2iv(argument, new Int32Array([0, 0]))");
+  func("context.uniformMatrix2fv(argument, false, new Float32Array([0.0, 0.0, 0.0, 0.0]))");
+
+  func2("context.getProgramParameter(argument, 0)");
+  func2("context.getShaderParameter(argument, 0)");
+  func2("context.getUniform(argument, loc)");
+  func2("context.getUniform(program, argument)");
+  func2("context.getUniformLocation(argument, 'u_modelViewProjMatrix')");
+
+  func3("context.getProgramInfoLog(argument)");
+  func3("context.getShaderInfoLog(argument)");
+  func3("context.getShaderSource(argument)");
+}
+
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/misc/boolean-argument-conversion.html b/conformance/misc/boolean-argument-conversion.html
new file mode 100644
index 0000000..9df4d1d
--- /dev/null
+++ b/conformance/misc/boolean-argument-conversion.html
@@ -0,0 +1,137 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../../resources/test-eval.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description("Test that conversion of boolean arguments of WebGL functions follows EcmaScript 9.2. ToBoolean");
+debug("");
+debug("When an object is converted to a boolean, it should always evaluate as true. Any valueOf() method should not even get called. See Mozilla bug 727590 where Gecko incorrectly converted such an argument to a Number instead of a Boolean, giving the wrong behavior. See 9.2 and 9.3 in the EcmaScript specification.");
+debug("");
+var gl = wtu.create3DContext();
+var program = wtu.loadStandardProgram(gl);
+var shader = wtu.loadStandardVertexShader(gl);
+var shouldGenerateGLError = wtu.shouldGenerateGLError;
+
+assertMsg(program != null, "Program Compiled");
+assertMsg(shader != null, "Shader Compiled");
+
+var uloc = gl.getUniformLocation(program, "u_modelViewProjMatrix");
+var aloc = gl.getAttribLocation(program, "a_vertex");
+
+gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from initialization.");
+assertMsg(uloc, "Uniform not found");
+assertMsg(aloc >= 0, "Attribute not found");
+
+var boolArg = { valueOf: function() { throw "Converting an Object to a Boolean should just give 'true' without further evaluation"; } }
+
+function shouldNotThrowWithBoolArgs(code) {
+  try {
+    TestEval(code);
+  } catch(e) {
+    testFailed(code + " threw exception: " + e);
+    return;
+  }
+  testPassed(code + " converted its boolean arguments correctly");
+}
+
+shouldNotThrowWithBoolArgs(
+  "gl.colorMask(boolArg, boolArg, boolArg, boolArg)"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.depthMask(boolArg)"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.sampleCoverage(1, boolArg)"
+);
+
+function zeroArray(length) {
+  var a = new Array(length);
+  for (var i = 0; i < length; i++)
+    a[i] = 0;
+  return a;
+}
+
+function zeroFloat32Array(length) {
+  var a = new Float32Array(length);
+  for (var i = 0; i < length; i++)
+    a[i] = 0;
+  return a;
+}
+
+shouldNotThrowWithBoolArgs(
+  "gl.uniformMatrix2fv(uloc, boolArg, zeroFloat32Array(4))"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.uniformMatrix2fv(uloc, boolArg, zeroArray(4))"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.uniformMatrix3fv(uloc, boolArg, zeroFloat32Array(9))"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.uniformMatrix3fv(uloc, boolArg, zeroArray(9))"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.uniformMatrix4fv(uloc, boolArg, zeroFloat32Array(16))"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.uniformMatrix4fv(uloc, boolArg, zeroArray(16))"
+);
+
+shouldNotThrowWithBoolArgs(
+  "gl.vertexAttribPointer(aloc, 4, gl.FLOAT, boolArg, 4, 0)"
+);
+
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/misc/delayed-drawing.html b/conformance/misc/delayed-drawing.html
new file mode 100644
index 0000000..c57eedf
--- /dev/null
+++ b/conformance/misc/delayed-drawing.html
@@ -0,0 +1,85 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Delayed Drawing test.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="example" width="4" height="4" style="width: 40px; height: 30px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example");
+var program = wtu.setupTexturedQuad(gl);
+
+glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
+
+var tex = gl.createTexture();
+wtu.fillTexture(gl, tex, 5, 3, [0, 192, 128, 255]);
+
+var loc = gl.getUniformLocation(program, "tex");
+gl.uniform1i(loc, 0);
+
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+
+
+drawAndCheck();
+
+setTimeout(step2, 1000);
+
+function step2() {
+  drawAndCheck();
+  finishTest();
+}
+
+function drawAndCheck() {
+  glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors before drawing.");
+  wtu.clearAndDrawUnitQuad(gl);
+  glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from drawing.");
+  wtu.checkCanvas(
+      gl, [0, 192, 128, 255],
+      "draw should be 0, 192, 128, 255");
+}
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
+
diff --git a/conformance/misc/error-reporting.html b/conformance/misc/error-reporting.html
new file mode 100644
index 0000000..a105650
--- /dev/null
+++ b/conformance/misc/error-reporting.html
@@ -0,0 +1,98 @@
+<!--
+/*
+** 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.
+*/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+description("Tests generation of synthetic and real GL errors");
+
+var wtu = WebGLTestUtils;
+var context = wtu.create3DContext();
+var program = wtu.loadStandardProgram(context);
+
+// Other tests in this directory like getActiveTest and
+// incorrect-context-object-behaviour already test the raising of many
+// synthetic GL errors. This test verifies the raising of certain
+// known real GL errors, and contains a few regression tests for bugs
+// discovered in the synthetic error generation and in the WebGL
+// implementation itself.
+
+glErrorShouldBe(context, context.NO_ERROR);
+
+debug("Testing getActiveAttrib");
+// Synthetic OpenGL error
+shouldBeNull("context.getActiveAttrib(null, 2)");
+glErrorShouldBe(context, context.INVALID_VALUE);
+// Error state should be clear by this point
+glErrorShouldBe(context, context.NO_ERROR);
+// Real OpenGL error
+shouldBeNull("context.getActiveAttrib(program, 2)");
+glErrorShouldBe(context, context.INVALID_VALUE);
+// Error state should be clear by this point
+glErrorShouldBe(context, context.NO_ERROR);
+
+debug("Testing getActiveUniform");
+// Synthetic OpenGL error
+shouldBeNull("context.getActiveUniform(null, 0)");
+glErrorShouldBe(context, context.INVALID_VALUE);
+// Error state should be clear by this point
+glErrorShouldBe(context, context.NO_ERROR);
+// Real OpenGL error
+shouldBeNull("context.getActiveUniform(program, 50)");
+glErrorShouldBe(context, context.INVALID_VALUE);
+// Error state should be clear by this point
+glErrorShouldBe(context, context.NO_ERROR);
+
+debug("Testing attempts to manipulate the default framebuffer");
+shouldBeUndefined("context.bindFramebuffer(context.FRAMEBUFFER, null)");
+glErrorShouldBe(context, context.NO_ERROR);
+shouldBeUndefined("context.framebufferRenderbuffer(context.FRAMEBUFFER, context.DEPTH_ATTACHMENT, context.RENDERBUFFER, null)");
+// Synthetic OpenGL error
+glErrorShouldBe(context, context.INVALID_OPERATION);
+// Error state should be clear by this point
+glErrorShouldBe(context, context.NO_ERROR);
+shouldBeUndefined("context.framebufferTexture2D(context.FRAMEBUFFER, context.COLOR_ATTACHMENT0, context.TEXTURE_2D, null, 0)");
+// Synthetic OpenGL error
+glErrorShouldBe(context, context.INVALID_OPERATION);
+// Error state should be clear by this point
+glErrorShouldBe(context, context.NO_ERROR);
+
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/misc/functions-returning-strings.html b/conformance/misc/functions-returning-strings.html
new file mode 100644
index 0000000..7ed6a99
--- /dev/null
+++ b/conformance/misc/functions-returning-strings.html
@@ -0,0 +1,127 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../../resources/test-eval.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="2" height="2"> </canvas>
+<script>
+"use strict";
+description("Test that functions returning strings really do return strings (and not e.g. null)");
+debug("");
+
+var validVertexShaderString =
+  "attribute vec4 aVertex; attribute vec4 aColor; varying vec4 vColor; void main() { vColor = aColor; gl_Position = aVertex; }";
+var validFragmentShaderString =
+  "precision mediump float; varying vec4 vColor; void main() { gl_FragColor = vColor; }";
+
+function shouldReturnString(_a)
+{
+  var exception;
+  var _av;
+  try {
+     _av = TestEval(_a);
+  } catch (e) {
+     exception = e;
+  }
+
+  if (exception)
+    testFailed(_a + ' should return a string. Threw exception ' + exception);
+  else if (typeof _av == "string")
+    testPassed(_a + ' returns a string');
+  else
+    testFailed(_a + ' should return a string.  Returns: "' + _av + '"');
+}
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("canvas");
+if (!gl) {
+  testFailed("context does not exist");
+} else {
+  var vs = gl.createShader(gl.VERTEX_SHADER);
+  shouldReturnString("gl.getShaderSource(vs)");
+  shouldReturnString("gl.getShaderInfoLog(vs)");
+  gl.shaderSource(vs, validVertexShaderString);
+  gl.compileShader(vs);
+  shouldReturnString("gl.getShaderSource(vs)");
+  shouldReturnString("gl.getShaderInfoLog(vs)");
+
+  var fs = gl.createShader(gl.FRAGMENT_SHADER);
+  shouldReturnString("gl.getShaderSource(fs)");
+  shouldReturnString("gl.getShaderInfoLog(fs)");
+  gl.shaderSource(fs, validFragmentShaderString);
+  gl.compileShader(fs);
+  shouldReturnString("gl.getShaderSource(fs)");
+  shouldReturnString("gl.getShaderInfoLog(fs)");
+
+  var prog = gl.createProgram();
+  shouldReturnString("gl.getProgramInfoLog(prog)");
+  gl.attachShader(prog, vs);
+  gl.attachShader(prog, fs);
+  gl.linkProgram(prog);
+  shouldReturnString("gl.getProgramInfoLog(prog)");
+
+  // Make sure different numbers of extensions doesn't result in
+  // different test output.
+  var exts = gl.getSupportedExtensions();
+  var allPassed = true;
+  for (var ii = 0; ii < exts.length; ++ii) {
+    var s = exts[ii];
+    if (typeof s != "string") {
+      shouldReturnString("gl.getSupportedExtensions()[" + s + "]");
+      allPassed = false;
+    }
+  }
+  if (allPassed) {
+    testPassed('getSupportedExtensions() returns an array of strings');
+  }
+
+  shouldReturnString("gl.getParameter(gl.VENDOR)");
+  shouldReturnString("gl.getParameter(gl.RENDERER)");
+  shouldReturnString("gl.getParameter(gl.VERSION)");
+  shouldReturnString("gl.getParameter(gl.SHADING_LANGUAGE_VERSION)");
+}
+
+debug("");
+var successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/misc/instanceof-test.html b/conformance/misc/instanceof-test.html
new file mode 100644
index 0000000..92fbed7
--- /dev/null
+++ b/conformance/misc/instanceof-test.html
@@ -0,0 +1,112 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL instanceof test.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="canvas" width="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+varying vec2 texCoord;
+void main()
+{
+    gl_Position = vPosition;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+uniform vec4 color;
+void main()
+{
+    gl_FragColor = color;
+}
+</script>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description(document.title);
+debug("Tests that instanceof works on WebGL objects.");
+debug("");
+var gl = wtu.create3DContext("canvas");
+shouldBeTrue('gl instanceof WebGLRenderingContext');
+shouldBeTrue('gl.createBuffer() instanceof WebGLBuffer');
+shouldBeTrue('gl.createFramebuffer() instanceof WebGLFramebuffer');
+shouldBeTrue('gl.createProgram() instanceof WebGLProgram');
+shouldBeTrue('gl.createRenderbuffer() instanceof WebGLRenderbuffer');
+shouldBeTrue('gl.createShader(gl.VERTEX_SHADER) instanceof WebGLShader');
+shouldBeTrue('gl.createTexture() instanceof WebGLTexture');
+
+var program = wtu.setupProgram(gl, ['vshader', 'fshader'], ['vPosition'], [0]);
+
+shouldBeTrue('gl.getUniformLocation(program, "color") instanceof WebGLUniformLocation');
+shouldBeTrue('gl.getActiveAttrib(program, 0) instanceof WebGLActiveInfo');
+shouldBeTrue('gl.getActiveUniform(program, 0) instanceof WebGLActiveInfo');
+
+debug("");
+debug("Tests that those WebGL objects can not be constructed through new operator");
+debug("");
+
+function shouldThrowWithNew(objectType, objectName)
+{
+    try {
+        new objectType;
+        testFailed('new ' + objectName + ' did not throw');
+    } catch (e) {
+        testPassed('new ' + objectName + ' threw an error');
+    }
+}
+
+shouldThrowWithNew(window.WebGLRenderingContext, 'WebGLRenderingContext');
+shouldThrowWithNew(window.WebGLActiveInfo, 'WebGLActiveInfo');
+shouldThrowWithNew(window.WebGLBuffer, 'WebGLBuffer');
+shouldThrowWithNew(window.WebGLFramebuffer, 'WebGLFramebuffer');
+shouldThrowWithNew(window.WebGLProgram, 'WebGLProgram');
+shouldThrowWithNew(window.WebGLRenderbuffer, 'WebGLRenderbuffer');
+shouldThrowWithNew(window.WebGLShader, 'WebGLShader');
+shouldThrowWithNew(window.WebGLTexture, 'WebGLTexture');
+shouldThrowWithNew(window.WebGLUniformLocation, 'WebGLUniformLocation');
+shouldThrowWithNew(window.WebGLShaderPrecisionFormat, 'WebGLShaderPrecisionFormat');
+
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
+
+
diff --git a/conformance/misc/invalid-passed-params.html b/conformance/misc/invalid-passed-params.html
new file mode 100644
index 0000000..6d5b1f3
--- /dev/null
+++ b/conformance/misc/invalid-passed-params.html
@@ -0,0 +1,199 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../../resources/desktop-gl-constants.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+description("Test for invalid passed parameters");
+
+var wtu = WebGLTestUtils;
+var context = wtu.create3DContext();
+
+debug("");
+debug("Test createShader()");
+shouldGenerateGLError(context, context.NO_ERROR, "context.createShader(context.FRAGMENT_SHADER)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.createShader(context.VERTEX_SHADER)");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.createShader(0)");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.createShader(context.TRIANGLES)");
+
+debug("");
+debug("Test clear()");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.clear(desktopGL['ACCUM_BUFFER_BIT'])");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.clear(desktopGL['ACCUM_BUFFER_BIT'] | context.COLOR_BUFFER_BIT)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.clear(desktopGL['ACCUM_BUFFER_BIT'] | context.COLOR_BUFFER_BIT | context.DEPTH_BUFFER_BIT | context.STENCIL_BUFFER_BIT)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.clear(context.COLOR_BUFFER_BIT | context.DEPTH_BUFFER_BIT | context.STENCIL_BUFFER_BIT)");
+
+debug("");
+debug("Test bufferData()");
+var buffer = context.createBuffer();
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindBuffer(context.ARRAY_BUFFER, buffer)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bufferData(context.ARRAY_BUFFER, 16, context.STREAM_DRAW)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bufferData(context.ARRAY_BUFFER, 16, context.STATIC_DRAW)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bufferData(context.ARRAY_BUFFER, 16, context.DYNAMIC_DRAW)");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.bufferData(context.ARRAY_BUFFER, 16, desktopGL['STREAM_READ'])");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.bufferData(context.ARRAY_BUFFER, 16, desktopGL['STREAM_COPY'])");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.bufferData(context.ARRAY_BUFFER, 16, desktopGL['STATIC_READ'])");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.bufferData(context.ARRAY_BUFFER, 16, desktopGL['STATIC_COPY'])");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.bufferData(context.ARRAY_BUFFER, 16, desktopGL['DYNAMIC_READ'])");
+shouldGenerateGLError(context, context.INVALID_ENUM, "context.bufferData(context.ARRAY_BUFFER, 16, desktopGL['DYNAMIC_COPY'])");
+
+debug("");
+debug("Test {copy}Tex{Sub}Image2D with negative offset/width/height");
+var tex = context.createTexture();
+var pixels = new Uint8Array(2 * 2 * 4);
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindTexture(context.TEXTURE_2D, tex)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, -16, -16, 0, context.RGBA, context.UNSIGNED_BYTE, null)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, 16, 16, 0, context.RGBA, context.UNSIGNED_BYTE, null)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.texSubImage2D(context.TEXTURE_2D, 0, -1, -1, 2, 2, context.RGBA, context.UNSIGNED_BYTE, pixels)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.texSubImage2D(context.TEXTURE_2D, 0, 0, 0, -1, -1, context.RGBA, context.UNSIGNED_BYTE, pixels)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.texSubImage2D(context.TEXTURE_2D, 0, 0, 0, 2, 2, context.RGBA, context.UNSIGNED_BYTE, pixels)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.copyTexImage2D(context.TEXTURE_2D, 0, context.RGBA, 0, 0, -1, -1, 0)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.copyTexImage2D(context.TEXTURE_2D, 0, context.RGBA, 0, 0, 16, 16, 0)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.copyTexSubImage2D(context.TEXTURE_2D, 0, -1, -1, 0, 0, 2, 2)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.copyTexSubImage2D(context.TEXTURE_2D, 0, 0, 0, 0, 0, -1, -1)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.copyTexSubImage2D(context.TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2)");
+
+debug("");
+debug("Test renderbufferStorage() with negative width/height");
+var renderbuffer = context.createRenderbuffer();
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindRenderbuffer(context.RENDERBUFFER, renderbuffer)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.renderbufferStorage(context.RENDERBUFFER, context.RGBA4, -2, -2)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.renderbufferStorage(context.RENDERBUFFER, context.RGBA4, 16, 16)");
+
+debug("");
+debug("Test scissor() with negative width/height");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.scissor(0, 0, -2, -2)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.scissor(0, 0, 16, 16)");
+
+debug("");
+debug("Test viewport() with negative width/height");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.viewport(0, 0, -2, -2)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.viewport(0, 0, 16, 16)");
+
+debug("");
+debug("Set up a program to test invalid characters");
+var invalidSet = ['"', '$', '`', '@', '\\', "'"];
+var validUniformName = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_1234567890";
+var validAttribName = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+function generateShaderSource(opt_invalidIdentifierChar, opt_invalidCommentChar) {
+  var invalidIdentifierString = "";
+  var invalidCommentString = "";
+  if (opt_invalidIdentifierChar != undefined) {
+    invalidIdentifierString += opt_invalidIdentifierChar;
+  }
+  if (opt_invalidCommentChar != undefined) {
+    invalidCommentString += opt_invalidCommentChar;
+  }
+  return "uniform float " + validUniformName + invalidIdentifierString + ";\n"
+                          + "varying float " + validAttribName + ";\n"
+                          + "void main() {\n"
+                          + validAttribName  + " = " + validUniformName + ";\n"
+                          + "gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }\n";
+                          + "//.+-/*%<>[](){}^|&~=!:;,?# " + invalidCommentString;
+}
+var vShader = context.createShader(context.VERTEX_SHADER);
+context.shaderSource(vShader, generateShaderSource());
+context.compileShader(vShader);
+shouldBe("context.getError()", "context.NO_ERROR");
+var fShader = context.createShader(context.FRAGMENT_SHADER);
+context.shaderSource(fShader, "precision mediump float;\n"
+                              + "varying float " + validAttribName + ";\n"
+                              + "void main() {\n"
+                              + "gl_FragColor = vec4(" + validAttribName + ", 0.0, 0.0, 1.0); }");
+context.compileShader(fShader);
+shouldBe("context.getError()", "context.NO_ERROR");
+var program = context.createProgram();
+context.attachShader(program, vShader);
+context.attachShader(program, fShader);
+context.linkProgram(program);
+var linkStatus = context.getProgramParameter(program, context.LINK_STATUS);
+shouldBeTrue("linkStatus");
+if (!linkStatus)
+  debug(context.getProgramInfoLog(program));
+shouldBe("context.getError()", "context.NO_ERROR");
+context.bindAttribLocation(program, 1, validAttribName);
+shouldBe("context.getError()", "context.NO_ERROR");
+context.getAttribLocation(program, validAttribName);
+shouldBe("context.getError()", "context.NO_ERROR");
+context.getUniformLocation(program, validUniformName);
+shouldBe("context.getError()", "context.NO_ERROR");
+
+debug("");
+debug("Test shaderSource() with invalid characters");
+for (var i = 0; i < invalidSet.length; ++i) {
+  var validShaderSource = generateShaderSource(undefined, invalidSet[i]);
+  context.shaderSource(vShader, validShaderSource);
+  shouldBe("context.getError()", "context.NO_ERROR");
+  var invalidShaderSource = generateShaderSource(invalidSet[i], undefined);
+  context.shaderSource(vShader, invalidShaderSource);
+  shouldBe("context.getError()", "context.INVALID_VALUE");
+}
+
+debug("");
+debug("Test bindAttribLocation() with invalid characters");
+for (var i = 0; i < invalidSet.length; ++i) {
+  var invalidName = validAttribName + invalidSet[i];
+  context.bindAttribLocation(program, 1, invalidName);
+  shouldBe("context.getError()", "context.INVALID_VALUE");
+}
+
+debug("");
+debug("Test getAttribLocation() with invalid characters");
+for (var i = 0; i < invalidSet.length; ++i) {
+  var invalidName = validAttribName + invalidSet[i];
+  context.getAttribLocation(program, invalidName);
+  shouldBe("context.getError()", "context.INVALID_VALUE");
+}
+
+debug("");
+debug("Test getUniformLocation() with invalid characters");
+for (var i = 0; i < invalidSet.length; ++i) {
+  var invalidName = validUniformName + invalidSet[i];
+  context.getUniformLocation(program, invalidName);
+  shouldBe("context.getError()", "context.INVALID_VALUE");
+}
+
+debug("")
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/misc/is-object.html b/conformance/misc/is-object.html
new file mode 100644
index 0000000..fb023e5
--- /dev/null
+++ b/conformance/misc/is-object.html
@@ -0,0 +1,100 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas">
+<script>
+"use strict";
+var wtu;
+var canvas;
+var gl;
+var shouldGenerateGLError;
+
+var buffer;
+var framebuffer;
+var program;
+var renderbuffer;
+var shader;
+var texture;
+
+description("Tests 'is' calls against non-bound and deleted objects");
+
+wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("canvas");
+shouldGenerateGLError = wtu.shouldGenerateGLError;
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "buffer = gl.createBuffer()");
+shouldBeFalse("gl.isBuffer(buffer)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer)");
+shouldBeTrue("gl.isBuffer(buffer)");
+debug("");
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "framebuffer = gl.createFramebuffer()");
+shouldBeFalse("gl.isFramebuffer(framebuffer)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)");
+shouldBeTrue("gl.isFramebuffer(framebuffer)");
+debug("");
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "renderbuffer = gl.createRenderbuffer()");
+shouldBeFalse("gl.isRenderbuffer(renderbuffer)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer)");
+shouldBeTrue("gl.isRenderbuffer(renderbuffer)");
+debug("");
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "texture = gl.createTexture()");
+shouldBeFalse("gl.isTexture(texture)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, texture)");
+shouldBeTrue("gl.isTexture(texture)");
+debug("");
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "program = gl.createProgram()");
+shouldBeTrue("gl.isProgram(program)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteProgram(program)");
+shouldBeFalse("gl.isProgram(program)");
+debug("");
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "shader = gl.createShader(gl.VERTEX_SHADER)");
+shouldBeTrue("gl.isShader(shader)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteShader(shader)");
+shouldBeFalse("gl.isShader(shader)");
+debug("");
+
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/misc/null-object-behaviour.html b/conformance/misc/null-object-behaviour.html
new file mode 100644
index 0000000..830b057
--- /dev/null
+++ b/conformance/misc/null-object-behaviour.html
@@ -0,0 +1,111 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description("Tests calling WebGL APIs without providing the necessary objects");
+
+var context = wtu.create3DContext();
+var program = wtu.loadStandardProgram(context);
+var shader = wtu.loadStandardVertexShader(context);
+var shouldGenerateGLError = wtu.shouldGenerateGLError;
+
+assertMsg(program != null, "Program Compiled");
+assertMsg(shader != null, "Shader Compiled");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.compileShader(undefined)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.linkProgram(undefined)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.attachShader(undefined, undefined)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.attachShader(program, undefined)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.attachShader(undefined, shader)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.detachShader(program, undefined)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.detachShader(undefined, shader)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.shaderSource(undefined, undefined)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.shaderSource(undefined, 'foo')");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.bindAttribLocation(undefined, 0, 'foo')");
+shouldThrow("context.bindBuffer(context.ARRAY_BUFFER, 0)");
+shouldThrow("context.bindFramebuffer(context.FRAMEBUFFER, 0)");
+shouldThrow("context.bindRenderbuffer(context.RENDERBUFFER, 0)");
+shouldThrow("context.bindTexture(context.TEXTURE_2D, 0)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindBuffer(context.ARRAY_BUFFER, null)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindFramebuffer(context.FRAMEBUFFER, null)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindRenderbuffer(context.RENDERBUFFER, null)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindTexture(context.TEXTURE_2D, null)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindBuffer(context.ARRAY_BUFFER, undefined)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindFramebuffer(context.FRAMEBUFFER, undefined)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindRenderbuffer(context.RENDERBUFFER, undefined)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.bindTexture(context.TEXTURE_2D, undefined)");
+shouldGenerateGLError(context, context.INVALID_OPERATION, "context.framebufferRenderbuffer(context.FRAMEBUFFER, context.DEPTH_ATTACHMENT, context.RENDERBUFFER, null)");
+shouldGenerateGLError(context, context.INVALID_OPERATION, "context.framebufferTexture2D(context.FRAMEBUFFER, context.COLOR_ATTACHMENT0, context.TEXTURE_2D, null, 0)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.getProgramParameter(undefined, 0)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.getProgramInfoLog(undefined, 0)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.getShaderParameter(undefined, 0)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.getShaderInfoLog(undefined, 0)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.getShaderSource(undefined)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.getUniform(undefined, null)");
+shouldGenerateGLError(context, context.INVALID_VALUE, "context.getUniformLocation(undefined, 'foo')");
+
+debug("");
+debug("check with bindings");
+context.bindBuffer(context.ARRAY_BUFFER, context.createBuffer());
+context.bindTexture(context.TEXTURE_2D, context.createTexture());
+shouldGenerateGLError(context, context.NO_ERROR, "context.bufferData(context.ARRAY_BUFFER, 1, context.STATIC_DRAW)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.getBufferParameter(context.ARRAY_BUFFER, context.BUFFER_SIZE)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, 1, 1, 0, context.RGBA, context.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]))");
+shouldGenerateGLError(context, context.NO_ERROR, "context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST)");
+shouldGenerateGLError(context, context.NO_ERROR, "context.getTexParameter(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER)");
+
+debug("");
+debug("check without bindings");
+context.bindBuffer(context.ARRAY_BUFFER, null);
+context.bindTexture(context.TEXTURE_2D, null);
+shouldGenerateGLError(context, context.INVALID_OPERATION, "context.bufferData(context.ARRAY_BUFFER, 1, context.STATIC_DRAW)");
+shouldGenerateGLError(context, context.INVALID_OPERATION, "context.getBufferParameter(context.ARRAY_BUFFER, context.BUFFER_SIZE)");
+shouldGenerateGLError(context, context.INVALID_OPERATION, "context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, 1, 1, 0, context.RGBA, context.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]))");
+shouldGenerateGLError(context, context.INVALID_OPERATION, "context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST)");
+shouldGenerateGLError(context, context.INVALID_OPERATION, "context.getTexParameter(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER)");
+
+
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/misc/object-deletion-behaviour.html b/conformance/misc/object-deletion-behaviour.html
new file mode 100644
index 0000000..7bbf5de
--- /dev/null
+++ b/conformance/misc/object-deletion-behaviour.html
@@ -0,0 +1,448 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+description("Tests deletion behavior for texture, renderbuffer, shader, and program");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext();
+var shouldGenerateGLError = wtu.shouldGenerateGLError;
+
+debug("");
+debug("shader and program deletion");
+
+var vertexShader = wtu.loadStandardVertexShader(gl);
+assertMsg(vertexShader, "vertex shader loaded");
+var fragmentShader = wtu.loadStandardFragmentShader(gl);
+assertMsg(fragmentShader, "fragment shader loaded");
+
+var program = gl.createProgram();
+shouldBeNonNull("program");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.attachShader(program, vertexShader)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.attachShader(program, fragmentShader)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.linkProgram(program)");
+shouldBeTrue("gl.getProgramParameter(program, gl.LINK_STATUS)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.useProgram(program)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteShader(vertexShader)");
+shouldBeTrue("gl.isShader(vertexShader)");
+shouldBeTrue("gl.getShaderParameter(vertexShader, gl.DELETE_STATUS)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.detachShader(program, vertexShader)");
+shouldBeFalse("gl.isShader(vertexShader)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteShader(fragmentShader)");
+shouldBeTrue("gl.isShader(fragmentShader)");
+shouldBeTrue("gl.getShaderParameter(fragmentShader, gl.DELETE_STATUS)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteProgram(program)");
+shouldBeTrue("gl.isProgram(program)");
+shouldBeTrue("gl.getProgramParameter(program, gl.DELETE_STATUS)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.useProgram(null)");
+shouldBeFalse("gl.isProgram(program)");
+shouldBeFalse("gl.isShader(fragmentShader)");
+
+debug("");
+debug("texture deletion");
+
+var fbo = gl.createFramebuffer(), fbo2 = gl.createFramebuffer(), fbo3 = gl.createFramebuffer();
+shouldBeNonNull("fbo");
+shouldBeNonNull("fbo2");
+shouldBeNonNull("fbo3");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+
+var tex = gl.createTexture();
+shouldBeNonNull("tex");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
+shouldBe("gl.getParameter(gl.TEXTURE_BINDING_2D)", "tex");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
+shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "tex");
+shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.TEXTURE");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(tex)");
+// Deleting a texture bound to the currently-bound fbo is the same as
+// detaching the textue from fbo first, then delete the texture.
+shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
+shouldGenerateGLError(gl, gl.INVALID_ENUM, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)");
+shouldBeFalse("gl.isTexture(tex)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
+
+var texCubeMap = gl.createTexture();
+shouldBeNonNull("texCubeMap");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap)");
+shouldBe("gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP)", "texCubeMap");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(texCubeMap)");
+shouldBeFalse("gl.isTexture(texCubeMap)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP)");
+
+var t = gl.createTexture();
+shouldBeNonNull("t");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(t)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)");
+
+var t2 = gl.createTexture();
+shouldBeNonNull("t2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.activeTexture(gl.TEXTURE0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t2)");
+shouldBe("gl.getParameter(gl.TEXTURE_BINDING_2D)", "t2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.activeTexture(gl.TEXTURE1)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t2)");
+shouldBe("gl.getParameter(gl.TEXTURE_BINDING_2D)", "t2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(t2)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.activeTexture(gl.TEXTURE0)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
+
+debug("");
+debug("renderbuffer deletion");
+
+var rbo = gl.createRenderbuffer(), rbo2 = gl.createRenderbuffer(), rbo3 = gl.createRenderbuffer();
+shouldBeNonNull("rbo");
+shouldBeNonNull("rbo2");
+shouldBeNonNull("rbo3");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldBe("gl.getParameter(gl.RENDERBUFFER_BINDING)", "rbo");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "rbo");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
+// Deleting a renderbuffer bound to the currently-bound fbo is the same as
+// detaching the renderbuffer from fbo first, then delete the renderbuffer.
+shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
+shouldGenerateGLError(gl, gl.INVALID_ENUM, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)");
+shouldBeFalse("gl.isRenderbuffer(rbo)");
+shouldBeNull("gl.getParameter(gl.RENDERBUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldBeNull("gl.getParameter(gl.RENDERBUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo2)");
+shouldBe("gl.getParameter(gl.RENDERBUFFER_BINDING)", "rbo2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo3)");
+shouldBe("gl.getParameter(gl.RENDERBUFFER_BINDING)", "rbo2");
+
+debug("");
+debug("using deleted renderbuffer");
+rbo = gl.createRenderbuffer();
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  // make backbuffer red
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(1,0,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  // delete renderbuffer. It should still be attached to fbo though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+  // Use fbo that has deleted rbo.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,0,255,255], "fbo should be blue")');
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
+debug("renderbuffer attached twice to same framebuffer");
+rbo = gl.createRenderbuffer();
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  rbo2 = gl.createRenderbuffer();
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+  // attach rbo2 at two attachment points incompatible with it
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, rbo2)");
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "rbo2");
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "rbo2");
+  // fbo can't be complete as rbo2 is attached at incompatible attachment points
+  shouldNotBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  // now we delete rbo2, which detaches it from the two attachment points where it currently is attached
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo2)");
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
+  // we should now be in the same state as before with only rbo attached, so fbo should be complete again
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "rbo");
+}
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+
+
+
+debug("");
+debug("using deleted texture");
+tex = gl.createTexture();
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  // delete texture. It should still be attached to fbo though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(tex)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [255,0,0,255], "backbuffer should be red")');
+  // Use fbo that has deleted texture.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,0,255,255], "fbo should be blue")');
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
+debug("using deleted renderbuffer");
+rbo = gl.createRenderbuffer();
+shouldBeNonNull("rbo");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  // make backbuffer red
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(1,0,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // delete renderbuffer. It should still be attached to fbo2 though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
+  // fbo has no attachments
+  shouldNotBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  // Use fbo2 that has deleted rbo.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,0,255,255], "fbo should be blue")');
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "rbo");
+
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.INVALID_ENUM, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)");
+  shouldGenerateGLError(gl, gl.NONE, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)");
+  shouldNotBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
+debug("using deleted texture");
+tex = gl.createTexture();
+shouldBeNonNull("tex");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // delete texture. It should still be attached to fbo2 though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(tex)");
+  // fbo has no attachments
+  shouldNotBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  // Use fbo that has deleted texture.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,0,255,255], "fbo should be blue")');
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "tex");
+
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.INVALID_ENUM, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)");
+  shouldNotBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
+debug("buffer deletion");
+
+var buffer = gl.createBuffer();
+shouldBeNonNull("buffer");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer)");
+shouldBe("gl.getParameter(gl.ARRAY_BUFFER_BINDING)", "buffer");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(buffer)");
+shouldBeFalse("gl.isBuffer(buffer)");
+shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer)");
+shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
+
+var buffer2 = gl.createBuffer();
+shouldBeNonNull("buffer2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer2)");
+shouldBe("gl.getParameter(gl.ARRAY_BUFFER_BINDING)", "buffer2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, null)");
+shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(buffer2)");
+shouldBeFalse("gl.isBuffer(buffer2)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer2)");
+shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
+
+var bufferElement = gl.createBuffer();
+shouldBeNonNull("bufferElement");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferElement)");
+shouldBe("gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING)", "bufferElement");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(bufferElement)");
+shouldBeFalse("gl.isBuffer(bufferElement)");
+shouldBeNull("gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferElement)");
+shouldBeNull("gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING)");
+
+var b = gl.createBuffer();
+shouldBeNonNull("b");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bufferData(gl.ARRAY_BUFFER, 1, gl.STATIC_DRAW)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(b)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.bufferData(gl.ARRAY_BUFFER, 1, gl.STATIC_DRAW)");
+
+var b1 = gl.createBuffer();
+shouldBeNonNull("b1");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b1);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enableVertexAttribArray(1);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);");
+var b2 = gl.createBuffer();
+shouldBeNonNull("b2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b2);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enableVertexAttribArray(2);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enableVertexAttribArray(3);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.vertexAttribPointer(3, 4, gl.FLOAT, false, 0, 0);");
+shouldBe("gl.getVertexAttrib(1, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b1");
+shouldBe("gl.getVertexAttrib(2, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b2");
+shouldBe("gl.getVertexAttrib(3, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(b2);");
+shouldBe("gl.getVertexAttrib(1, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b1");
+shouldBeNull("gl.getVertexAttrib(2, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)");
+shouldBeNull("gl.getVertexAttrib(3, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(b1);");
+shouldBeNull("gl.getVertexAttrib(1, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)");
+
+debug("");
+debug("framebuffer deletion");
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldBe("gl.getParameter(gl.FRAMEBUFFER_BINDING)", "fbo");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteFramebuffer(fbo)");
+shouldBeFalse("gl.isFramebuffer(fbo)");
+shouldBeNull("gl.getParameter(gl.FRAMEBUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldBeNull("gl.getParameter(gl.FRAMEBUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+shouldBe("gl.getParameter(gl.FRAMEBUFFER_BINDING)", "fbo2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteFramebuffer(fbo3)");
+shouldBe("gl.getParameter(gl.FRAMEBUFFER_BINDING)", "fbo2");
+
+fbo = gl.createFramebuffer();
+rbo = gl.createRenderbuffer();
+shouldBeNonNull("fbo");
+shouldBeNonNull("rbo");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  // set backbuffer to red
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(1,0,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // set framebuffer to green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // check framebuffer
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 16, 16, 1, 1, [0,0,0,0], "outside fbo should be black")');
+  // delete framebuffer. because this was the bound fbo the backbuffer should be active now
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteFramebuffer(fbo)");
+  // check backbuffer
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 300, 150, [255,0,0,255], "backbuffer should be red")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 300, 0, 300, 300, [0,0,0,0], "outside backbuffer should be black")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 150, 300, 300, [0,0,0,0], "outside backbuffer should be black")');
+  // check drawing to backbuffer
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 300, 150, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  // check again because many buggy implementations will have bound to the true backbuffer on deleteFramebuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 300, 150, [0,255,0,255], "fbo should be green")');
+}
+
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/misc/shader-precision-format.html b/conformance/misc/shader-precision-format.html
new file mode 100644
index 0000000..87c6939
--- /dev/null
+++ b/conformance/misc/shader-precision-format.html
@@ -0,0 +1,159 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL shader precision format test.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="canvas" width="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description(document.title);
+debug("Tests that WebGLShaderPrecisionFormat class and getShaderPrecisionFormat work.");
+debug("");
+var gl = wtu.create3DContext("canvas");
+
+function verifyShaderPrecisionFormat(shadertype, precisiontype) {
+    shouldBeTrue('gl.getShaderPrecisionFormat(' + shadertype + ', ' +
+                 precisiontype + ') instanceof WebGLShaderPrecisionFormat');
+}
+
+debug("");
+debug("Test that getShaderPrecisionFormat returns a WebGLShaderPrecisionFormat object.");
+debug("");
+
+verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.LOW_FLOAT');
+verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.MEDIUM_FLOAT');
+verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.HIGH_FLOAT');
+verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.LOW_INT');
+verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.MEDIUM_INT');
+verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.HIGH_INT');
+verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.LOW_FLOAT');
+verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.MEDIUM_FLOAT');
+verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.HIGH_FLOAT');
+verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.LOW_INT');
+verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.MEDIUM_INT');
+verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.HIGH_INT');
+
+debug("");
+debug("Test that getShaderPrecisionFormat throws an error with invalid parameters.");
+debug("");
+
+shouldGenerateGLError(gl, gl.INVALID_ENUM, 'gl.getShaderPrecisionFormat(gl.HIGH_INT, gl.VERTEX_SHADER)');
+
+debug("");
+debug("Test that WebGLShaderPrecisionFormat values are sensible.");
+debug("");
+
+// The minimum values are from OpenGL ES Shading Language spec, section 4.5.
+
+var shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 1');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 1');
+shouldBeTrue('shaderPrecisionFormat.precision >= 8');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 14');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 14');
+shouldBeTrue('shaderPrecisionFormat.precision >= 10');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 62');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 62');
+shouldBeTrue('shaderPrecisionFormat.precision >= 16');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_INT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 8');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 8');
+shouldBeTrue('shaderPrecisionFormat.precision == 0');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_INT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 10');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 10');
+shouldBeTrue('shaderPrecisionFormat.precision == 0');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_INT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 16');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 16');
+shouldBeTrue('shaderPrecisionFormat.precision == 0');
+
+var shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_FLOAT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 1');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 1');
+shouldBeTrue('shaderPrecisionFormat.precision >= 8');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 14');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 14');
+shouldBeTrue('shaderPrecisionFormat.precision >= 10');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_INT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 8');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 8');
+shouldBeTrue('shaderPrecisionFormat.precision == 0');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_INT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin >= 10');
+shouldBeTrue('shaderPrecisionFormat.rangeMax >= 10');
+shouldBeTrue('shaderPrecisionFormat.precision == 0');
+
+debug("");
+debug("Test optional highp support in fragment shaders.");
+debug("");
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
+shouldBeTrue('(shaderPrecisionFormat.rangeMin == 0 && shaderPrecisionFormat.rangeMax == 0 && shaderPrecisionFormat.precision == 0) || (shaderPrecisionFormat.rangeMin >= 62 && shaderPrecisionFormat.rangeMax >= 62 && shaderPrecisionFormat.precision >= 16)');
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
+shouldBeTrue('(shaderPrecisionFormat.rangeMin == 0 && shaderPrecisionFormat.rangeMax == 0 && shaderPrecisionFormat.precision == 0) || (shaderPrecisionFormat.rangeMin >= 16 && shaderPrecisionFormat.rangeMax >= 16 && shaderPrecisionFormat.precision == 0)');
+
+debug("");
+debug("Test that getShaderPrecisionFormat returns the same thing every call.");
+debug("");
+
+shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT);
+var shaderPrecisionFormat2 = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT);
+shouldBeTrue('shaderPrecisionFormat.rangeMin == shaderPrecisionFormat2.rangeMin');
+shouldBeTrue('shaderPrecisionFormat.rangeMax == shaderPrecisionFormat2.rangeMax');
+shouldBeTrue('shaderPrecisionFormat.precision == shaderPrecisionFormat2.precision');
+
+finishTest();
+</script>
+
+</body>
+</html>
+
+
diff --git a/conformance/misc/type-conversion-test.html b/conformance/misc/type-conversion-test.html
new file mode 100644
index 0000000..f033728
--- /dev/null
+++ b/conformance/misc/type-conversion-test.html
@@ -0,0 +1,174 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description("Tests calling WebGL APIs with various types");
+
+var context = wtu.create3DContext();
+var program = wtu.loadStandardProgram(context);
+var shader = wtu.loadStandardVertexShader(context);
+var shouldGenerateGLError = wtu.shouldGenerateGLError;
+
+assertMsg(program != null, "Program Compiled");
+assertMsg(shader != null, "Shader Compiled");
+
+var loc = context.getUniformLocation(program, "u_modelViewProjMatrix");
+assertMsg(loc != null, "getUniformLocation succeeded");
+
+var buffer = context.createBuffer();
+context.bindBuffer(context.ARRAY_BUFFER, buffer);
+var texture = context.createTexture();
+context.bindTexture(context.TEXTURE_2D, texture);
+context.useProgram(program);
+
+var args = [
+  { type: "number", value: 0 },
+  { type: "number", value: 2 },
+  { type: "string that is NaN", value: "foo", },
+  { type: "string that is number", value: "2", },
+  { type: "null", value: null },
+  { type: "Empty Array", value: [] },
+  { type: "Object", value: {} },
+  { type: "Array of Number", value: [2] },
+  { type: "Array of String", value: ["foo"] },
+  { type: "Array of String that is number", value: ["0"] },
+  { type: "Array of String that is number", value: ["2"] },
+  { type: "TypedArray", value: new Float32Array(1) }
+];
+
+var argument;
+
+for (var i = 0; i < args.length; ++i) {
+  argument = args[i].value;
+  var func1 = shouldBeUndefined;
+  var func2 = shouldBeNonNull;
+  if (argument == 2) {
+    func2 = shouldBeNull;
+  }
+  var func3 = shouldBeNull;
+  debug("");
+  debug("testing type of " + args[i].type + " : value = " + argument);
+  func1("context.bindAttribLocation(program, argument, 'foo')");
+  func1("context.blendColor(argument, argument, argument, argument)");
+  func1("context.bufferData(context.ARRAY_BUFFER, argument, context.STATIC_DRAW)");
+  func1("context.bufferData(context.ARRAY_BUFFER, new Float32Array(10), context.STATIC_DRAW)");
+  func1("context.bufferSubData(context.ARRAY_BUFFER, argument, new Float32Array(2))");
+  func1("context.clear(argument)")
+  func1("context.clearColor(argument, 0, 0, 0)");
+  func1("context.clearColor(0, argument, 0, 0)");
+  func1("context.clearColor(0, 0, argument, 0)");
+  func1("context.clearColor(0, 0, 0, argument)");
+  func1("context.clearDepth(argument)");
+  func1("context.clearStencil(argument)");
+  func1("context.copyTexImage2D(context.TEXTURE_2D, argument, context.RGBA, 0, 0, 1, 1, 0)");
+  func1("context.copyTexImage2D(context.TEXTURE_2D, 0, context.RGBA, argument, 0, 1, 1, 0)");
+  func1("context.copyTexImage2D(context.TEXTURE_2D, 0, context.RGBA, 0, argument, 1, 1, 0)");
+  func1("context.copyTexImage2D(context.TEXTURE_2D, 0, context.RGBA, 0, 0, argument, 1, 0)");
+  func1("context.copyTexImage2D(context.TEXTURE_2D, 0, context.RGBA, 0, 0, 0, argument, 0)");
+  func1("context.copyTexSubImage2D(context.TEXTURE_2D, argument, 0, 0, 0, 0, 0, 0)");
+  func1("context.copyTexSubImage2D(context.TEXTURE_2D, 0, argument, 0, 0, 0, 0, 0)");
+  func1("context.copyTexSubImage2D(context.TEXTURE_2D, 0, 0, argument, 0, 0, 0, 0)");
+  func1("context.copyTexSubImage2D(context.TEXTURE_2D, 0, 0, 0, argument, 0, 0, 0)");
+  func1("context.copyTexSubImage2D(context.TEXTURE_2D, 0, 0, 0, 0, argument, 0, 0)");
+  func1("context.copyTexSubImage2D(context.TEXTURE_2D, 0, 0, 0, 0, 0, argument, 0)");
+  func1("context.copyTexSubImage2D(context.TEXTURE_2D, 0, 0, 0, 0, 0, 0, argument)");
+  func1("context.depthMask(argument)");
+  func1("context.depthRange(argument, 1)");
+  func1("context.depthRange(0, argument)");
+  func1("context.drawArrays(context.POINTS, argument, 1)");
+  func1("context.drawArrays(context.POINTS, 0, argument)");
+  //func1("context.drawElements(...)");
+  func1("context.enableVertexAttribArray(argument)");
+  func1("context.disableVertexAttribArray(argument)");
+  func2("context.getActiveAttrib(program, argument)");
+  func2("context.getActiveUniform(program, argument)");
+  func3("context.getParameter(argument)");
+  func1("context.lineWidth(argument)");
+  func1("context.polygonOffset(argument, 0)");
+  func1("context.polygonOffset(0, argument)");
+  //func1("context.readPixels(...)");
+  //func1("context.renderbufferStorage(...)");
+  func1("context.sampleCoverage(argument, 0)");
+  func1("context.sampleCoverage(0, argument)");
+  func1("context.scissor(argument, 0, 10, 10)");
+  func1("context.scissor(0, argument, 10, 10)");
+  func1("context.scissor(0, 0, argument, 10)");
+  func1("context.scissor(0, 0, 10, argument)");
+  func1("context.shaderSource(shader, argument)");
+  func1("context.stencilFunc(context.NEVER, argument, 255)");
+  func1("context.stencilFunc(context.NEVER, 0, argument)");
+  //func1("context.stencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask)");
+  func1("context.stencilMask(argument)");
+  //func1("context.stencilMaskSeparate(context.FRONT, argument);
+  //func1("context.texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView pixels)");
+  //func1("context.texParameterf(GLenum target, GLenum pname, GLfloat param)");
+  //func1("context.texParameteri(GLenum target, GLenum pname, GLint param)");
+  //func1("context.texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,GLsizei width, GLsizei height,GLenum format, GLenum type, ArrayBufferView pixels)");
+  func1("context.uniform1i(loc, argument)");
+  func1("context.uniform2i(loc, argument, 0)");
+  func1("context.uniform2i(loc, 0, argument)");
+  func1("context.uniform3i(loc, argument, 0, 0)");
+  func1("context.uniform3i(loc, 0, argument, 0)");
+  func1("context.uniform3i(loc, 0, 0, argument)");
+  func1("context.uniform4i(loc, argument, 0, 0, 0)");
+  func1("context.uniform4i(loc, 0, argument, 0, 0)");
+  func1("context.uniform4i(loc, 0, 0, argument, 0)");
+  func1("context.uniform4i(loc, 0, 0, 0, argument)");
+  func1("context.uniform1f(loc, argument)");
+  func1("context.uniform2f(loc, argument, 0)");
+  func1("context.uniform2f(loc, 0, argument)");
+  func1("context.uniform3f(loc, argument, 0, 0)");
+  func1("context.uniform3f(loc, 0, argument, 0)");
+  func1("context.uniform3f(loc, 0, 0, argument)");
+  func1("context.uniform4f(loc, argument, 0, 0, 0)");
+  func1("context.uniform4f(loc, 0, argument, 0, 0)");
+  func1("context.uniform4f(loc, 0, 0, argument, 0)");
+  func1("context.uniform4f(loc, 0, 0, 0, argument)");
+}
+
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
+
diff --git a/conformance/misc/uninitialized-test.html b/conformance/misc/uninitialized-test.html
new file mode 100644
index 0000000..f783935
--- /dev/null
+++ b/conformance/misc/uninitialized-test.html
@@ -0,0 +1,195 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Uninitialized GL Resources Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="2" height="2"> </canvas>
+<script>
+"use strict";
+description("Tests to check user code cannot access uninitialized data from GL resources.");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("canvas");
+if (!gl)
+  testFailed("Context created.");
+else
+  testPassed("Context created.");
+
+function setupTexture(texWidth, texHeight) {
+    var texture = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texWidth, texHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+    // this can be quite undeterministic so to improve odds of seeing uninitialized data write bits
+    // into tex then delete texture then re-create one with same characteristics (driver will likely reuse mem)
+    // with this trick on r59046 WebKit/OSX I get FAIL 100% of the time instead of ~15% of the time.
+
+    var badData = new Uint8Array(texWidth * texHeight * 4);
+    for (var i = 0; i < badData.length; ++i)
+        badData[i] = i % 255;
+
+    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, texWidth, texHeight, gl.RGBA, gl.UNSIGNED_BYTE, badData);
+    gl.finish(); // make sure it has been uploaded
+
+    gl.deleteTexture(texture);
+    gl.finish(); // make sure it has been deleted
+
+    var texture = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+    return texture;
+}
+
+function checkNonZeroPixels(texture, texWidth, texHeight, skipX, skipY, skipWidth, skipHeight, skipR, skipG, skipB, skipA) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    var fb = gl.createFramebuffer();
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+
+    var data = new Uint8Array(texWidth * texHeight * 4);
+    gl.readPixels(0, 0, texWidth, texHeight, gl.RGBA, gl.UNSIGNED_BYTE, data);
+
+    var k = 0;
+    for (var y = 0; y < texHeight; ++y) {
+        for (var x = 0; x < texWidth; ++x) {
+            var index = (y * texWidth + x) * 4;
+            if (x >= skipX && x < skipX + skipWidth && y >= skipY && y < skipY + skipHeight) {
+                if (data[index] != skipR || data[index + 1] != skipG || data[index + 2] != skipB || data[index + 3] != skipA) {
+                    testFailed("non-zero pixel values are wrong");
+                    return;
+                }
+            } else {
+                for (var i = 0; i < 4; ++i) {
+                    if (data[index + i] != 0)
+                        k++;
+                }
+            }
+        }
+    }
+    if (k) {
+        testFailed("Found " + k + " non-zero bytes");
+    } else {
+        testPassed("All data initialized");
+    }
+}
+
+var width = 512;
+var height = 512;
+
+debug("");
+debug("Reading an uninitialized texture (texImage2D) should succeed with all bytes set to 0.");
+
+var tex = setupTexture(width, height);
+gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+checkNonZeroPixels(tex, width, height, 0, 0, 0, 0, 0, 0, 0, 0);
+gl.deleteTexture(tex);
+gl.finish();
+glErrorShouldBe(gl, gl.NO_ERROR);
+
+debug("");
+debug("Reading an uninitialized portion of a texture (copyTexImage2D) should succeed with all bytes set to 0.");
+
+var tex = setupTexture(width, height);
+var fbo = gl.createFramebuffer();
+gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+var rbo = gl.createRenderbuffer();
+gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+var fboWidth = 16;
+var fboHeight = 16;
+gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, fboWidth, fboHeight);
+gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+gl.clearColor(1.0, 0.0, 0.0, 1.0);
+gl.clear(gl.COLOR_BUFFER_BIT);
+glErrorShouldBe(gl, gl.NO_ERROR);
+gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, width, height, 0);
+checkNonZeroPixels(tex, width, height, 0, 0, fboWidth, fboHeight, 255, 0, 0, 255);
+gl.deleteTexture(tex);
+gl.finish();
+glErrorShouldBe(gl, gl.NO_ERROR);
+
+debug("");
+debug("Reading an uninitialized portion of a texture (copyTexImage2D with negative x and y) should succeed with all bytes set to 0.");
+
+var tex = setupTexture(width, height);
+var fbo = gl.createFramebuffer();
+gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+var rbo = gl.createRenderbuffer();
+gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+var fboWidth = 16;
+var fboHeight = 16;
+gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, fboWidth, fboHeight);
+gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+gl.clearColor(1.0, 0.0, 0.0, 1.0);
+gl.clear(gl.COLOR_BUFFER_BIT);
+glErrorShouldBe(gl, gl.NO_ERROR);
+var x = -8;
+var y = -8;
+gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, x, y, width, height, 0);
+checkNonZeroPixels(tex, width, height, -x, -y, fboWidth, fboHeight, 255, 0, 0, 255);
+gl.deleteTexture(tex);
+gl.finish();
+glErrorShouldBe(gl, gl.NO_ERROR);
+
+debug("");
+debug("Reading an uninitialized portion of a texture (copyTexImage2D from WebGL internal fbo) should succeed with all bytes set to 0.");
+
+var tex = setupTexture(width, height);
+gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+gl.clearColor(0.0, 1.0, 0.0, 0.0);
+gl.clear(gl.COLOR_BUFFER_BIT);
+glErrorShouldBe(gl, gl.NO_ERROR);
+gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, width, height, 0);
+checkNonZeroPixels(tex, width, height, 0, 0, gl.canvas.width, gl.canvas.height, 0, 255, 0, 0);
+gl.deleteTexture(tex);
+gl.finish();
+glErrorShouldBe(gl, gl.NO_ERROR);
+
+//TODO: uninitialized vertex array buffer
+//TODO: uninitialized vertex elements buffer
+//TODO: uninitialized framebuffer? (implementations would need to do a GL clear at first binding?)
+//TODO: uninitialized renderbuffer? (implementations would need to do a GL clear at first binding?)
+//TODO: uninitialized uniform arrays?
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
+
diff --git a/conformance/misc/webgl-specific.html b/conformance/misc/webgl-specific.html
new file mode 100644
index 0000000..066e5d0
--- /dev/null
+++ b/conformance/misc/webgl-specific.html
@@ -0,0 +1,132 @@
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL GLES2 difference test.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description("Tests the a few differences between WebGL and GLES2");
+
+var gl = wtu.create3DContext();
+var program = wtu.loadStandardProgram(gl);
+gl.useProgram(program);
+var vertexObject = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+gl.enableVertexAttribArray(0);
+glErrorShouldBe(gl, gl.NO_ERROR, "Setup should succeed");
+
+debug("");
+debug("Verify that constant color and constant alpha cannot be used together as source and destination factors in the blend function");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.CONSTANT_COLOR, gl.CONSTANT_ALPHA)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.ONE_MINUS_CONSTANT_COLOR, gl.CONSTANT_ALPHA)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_ALPHA)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.ONE_MINUS_CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_ALPHA)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.CONSTANT_ALPHA, gl.CONSTANT_COLOR)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_COLOR)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.ONE_MINUS_CONSTANT_ALPHA, gl.CONSTANT_COLOR)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFunc(gl.ONE_MINUS_CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_COLOR)");
+
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.CONSTANT_COLOR, gl.CONSTANT_ALPHA, gl.ONE, gl.ZERO)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.ONE_MINUS_CONSTANT_COLOR, gl.CONSTANT_ALPHA, gl.ONE, gl.ZERO)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_ALPHA, gl.ONE, gl.ZERO)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.ONE_MINUS_CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_ALPHA, gl.ONE, gl.ZERO)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.CONSTANT_ALPHA, gl.CONSTANT_COLOR, gl.ONE, gl.ZERO)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_COLOR, gl.ONE, gl.ZERO)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.ONE_MINUS_CONSTANT_ALPHA, gl.CONSTANT_COLOR, gl.ONE, gl.ZERO)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.blendFuncSeparate(gl.ONE_MINUS_CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_COLOR, gl.ONE, gl.ZERO)");
+
+debug("");
+debug("Verify that in depthRange zNear <= zFar");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.depthRange(20, 10)");
+
+debug("");
+debug("Verify that front/back settings should be the same for stenclMask and stencilFunc");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilMask(255)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilMaskSeparate(gl.FRONT, 1)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilMaskSeparate(gl.BACK, 1)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFunc(gl.ALWAYS, 0, 255)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFuncSeparate(gl.BACK, gl.ALWAYS, 1, 255)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFuncSeparate(gl.FRONT, gl.ALWAYS, 1, 255)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFuncSeparate(gl.BACK, gl.ALWAYS, 1, 1)");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFuncSeparate(gl.FRONT, gl.ALWAYS, 1, 1)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
+
+debug("");
+debug("Verify that IMPLEMENTATION_COLOR_READ_FORMAT and IMPLEMENTATION_COLOR_READ_TYPE are undefined");
+shouldBeUndefined(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
+shouldBeUndefined(gl.IMPLEMENTATION_COLOR_READ_TYPE);
+
+debug("");
+debug("Verify that *LENGTH are undefined");
+shouldBeUndefined(gl.INFO_LOG_LENGTH);
+shouldBeUndefined(gl.SHADER_SOURCE_LENGTH);
+shouldBeUndefined(gl.ACTIVE_UNIFORM_MAX_LENGTH);
+shouldBeUndefined(gl.ACTIVE_ATTRIB_MAX_LENGTH);
+
+debug("");
+debug("Verify that UNPACK_COLORSPACE_CONVERSION_WEBGL is supported");
+shouldBe("gl.getParameter(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL)", "gl.BROWSER_DEFAULT_WEBGL");
+gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
+shouldBe("gl.getParameter(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL)", "gl.NONE");
+glErrorShouldBe(gl, gl.NO_ERROR, "set/get UNPACK_COLORSPACE_CONVERSION_WEBGL should generate no error");
+
+debug("");
+debug("Verify that drawingBufferWidth and drawingBufferHeights are implemented");
+shouldBeTrue("gl.drawingBufferWidth >= 0 && gl.drawingBufferHeight >= 0");
+
+debug("");
+debug("Verify that bindAttribLocation rejects names start with webgl_ or _webgl_");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.bindAttribLocation(program, 0, 'webgl_a')");
+shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.bindAttribLocation(program, 0, '_webgl_a')");
+
+var successfullyParsed = true;
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/more/00_test_list.txt b/conformance/more/00_test_list.txt
new file mode 100644
index 0000000..1a09da0
--- /dev/null
+++ b/conformance/more/00_test_list.txt
@@ -0,0 +1,58 @@
+conformance/constants.html
+conformance/getContext.html
+conformance/methods.html
+conformance/quickCheckAPI-A.html
+conformance/quickCheckAPI-B1.html
+conformance/quickCheckAPI-B2.html
+conformance/quickCheckAPI-B3.html
+conformance/quickCheckAPI-B4.html
+conformance/quickCheckAPI-C.html
+conformance/quickCheckAPI-D_G.html
+conformance/quickCheckAPI-G_I.html
+conformance/quickCheckAPI-L_S.html
+conformance/quickCheckAPI-S_V.html
+conformance/webGLArrays.html
+functions/bindBuffer.html
+functions/bindBufferBadArgs.html
+functions/bindFramebufferLeaveNonZero.html
+functions/bufferData.html
+functions/bufferDataBadArgs.html
+functions/bufferSubData.html
+functions/bufferSubDataBadArgs.html
+functions/copyTexImage2D.html
+functions/copyTexImage2DBadArgs.html
+functions/copyTexSubImage2D.html
+functions/copyTexSubImage2DBadArgs.html
+functions/deleteBufferBadArgs.html
+functions/drawArrays.html
+functions/drawArraysOutOfBounds.html
+functions/drawElements.html
+functions/drawElementsBadArgs.html
+functions/isTests.html
+--min-version 1.0.2 functions/isTestsBadArgs.html
+functions/readPixels.html
+functions/readPixelsBadArgs.html
+functions/texImage2D.html
+functions/texImage2DBadArgs.html
+functions/texImage2DHTML.html
+functions/texImage2DHTMLBadArgs.html
+functions/texSubImage2D.html
+functions/texSubImage2DBadArgs.html
+functions/texSubImage2DHTML.html
+functions/texSubImage2DHTMLBadArgs.html
+functions/uniformf.html
+functions/uniformfBadArgs.html
+functions/uniformfArrayLen1.html
+functions/uniformi.html
+functions/uniformiBadArgs.html
+functions/uniformMatrix.html
+functions/uniformMatrixBadArgs.html
+functions/vertexAttrib.html
+functions/vertexAttribBadArgs.html
+functions/vertexAttribPointer.html
+functions/vertexAttribPointerBadArgs.html
+glsl/arrayOutOfBounds.html
+#glsl/longLoops.html // No interactive tests.
+glsl/uniformOutOfBounds.html
+#glsl/unusedAttribsUniforms.html // No interactive tests.
+
diff --git a/conformance/more/README.md b/conformance/more/README.md
new file mode 100644
index 0000000..0193714
--- /dev/null
+++ b/conformance/more/README.md
@@ -0,0 +1,53 @@
+Tests for the WebGL canvas context
+==================================
+
+These tests are intended to serve the following purposes:
+
+  * Assert spec conformance
+  * Check the safety of the GL binding (bounds checking, same origin policy)
+  * Provide performance numbers for developers
+
+
+Running the tests
+-----------------
+
+  1. <a href="http://learningwebgl.com/blog/?p=11">Install a browser with WebGL support</a>
+  2. Run <code>ruby gen_tests.rb</code> if you have modified the tests.
+  3. Run <code>ruby test_server.rb</code> if you want to get test run output to test_server's stdout (especially useful for finding out which test crashed your browser.)
+  4. Open all_tests.html in your browser.
+
+
+Want to contribute?
+-------------------
+
+  1. Fork this repo
+  2. Run <code>gen_tests.rb</code>
+  3. Look into templates/ to see which functions lack tests (also see <a href="../raw/master/methods.txt">methods.txt</a> and <a href="http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/canvas/nsICanvasRenderingContextWebGL.idl">nsICanvasRenderingContextWebGL.idl</a>):
+    1. copy methodName.html to functions/methodName.html and write tests that test the results of valid inputs.
+    2. copy methodNameBadArgs.html to functions/methodNameBadArgs.html and write tests to assert that invalid inputs throw exceptions.
+    3. If your test causes a segfault, add the following to the top of the script tag: <code>Tests.autorun = false; Tests.message = "Caution: this may crash your browser";</code>
+  4. For each performance test:
+    1. Write a performance/myTestName.html and set <code>Tests.autorun = false;</code>
+  5. If you have a test that you would like to run over the whole API or want to generate tests programmatically, add them to gen_tests.rb or write your own script.
+  6. Create a commit for each file. (E.g. <code>for f in $(git status | grep -e "^#\\s*functions/\\S*$" | sed "s/^#\s*//"); do git add $f; git commit -m $f; done</code>)
+  7. Send me a pull request.
+  8. Congratulations, you're now a contributor!
+
+
+For more information on WebGL:
+
+  * <a href="http://planet-webgl.org">Planet WebGL</a>
+  * <a href="http://learningwebgl.com">Learning WebGL</a>
+  * <a href="http://www.khronos.org/message_boards/viewforum.php?f=34">WebGL on Khronos Message Boards</a>
+
+Developer links:
+
+  * <a href="https://bugzilla.mozilla.org/buglist.cgi?quicksearch=webgl">WebGL on Mozilla Bugzilla</a>
+  * <a href="https://bugzilla.webkit.org/buglist.cgi?quicksearch=webgl">WebGL on WebKit Bugzilla</a>
+  * <a href="http://code.google.com/p/chromium/issues/list?q=label:3D-WebGL">WebGL on Chromium Bugzilla</a>
+
+What's the stuff in apigen?
+
+  There are some Python scripts in the apigen/ directory that generate C++ based on the API definition files (gl2.h, api_modifications.txt, valid_args.txt.) The generated code is Mozilla XPCOM functions that check their args against the valid GLES 2.0 constants (as they were written on the man pages.) There's also some wackier stuff for checking copyTexImage2D and copyTexSubImage2D image dimensions against viewport dimensions.
+
+  If you can use it to generate code for your WebGL implementation, it might save you 1500 lines of typing and testing. The last time I used it was summer 2009 to generate a patch for Canvas 3D, so it's likely somewhat out of date.
diff --git a/conformance/more/all_tests.html b/conformance/more/all_tests.html
new file mode 100644
index 0000000..390e263
--- /dev/null
+++ b/conformance/more/all_tests.html
@@ -0,0 +1,399 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+  <title>OpenGL ES 2.0 &lt;canvas&gt; context tests</title>
+  <style type="text/css">
+    h2 { display: inline; font-size: 1em; margin-bottom: 0.2em; }
+    iframe { display: inline; border: 1px solid black; overflow: hidden;}
+  </style>
+  <script type="text/javascript">
+    function loadTest(id, url) {
+      document.getElementById(id).src = url;
+    }
+    function seqLoader() {
+      var iframes = document.getElementsByTagName('iframe');
+      for (var i=0; i<iframes.length; i++) {
+        iframes[i].addEventListener('load', (function(j) {
+          return function() {
+            var e = document.getElementById((j+1)+'_link');
+            if (e) loadTest(j+1, e.href);
+          }
+        })(i), false);
+      }
+      var e = document.getElementById('0_link');
+      if (e) loadTest(0, e.href);
+    }
+  </script>
+</head>
+<body onload="seqLoader()">
+
+    <div>
+      <iframe src="conformance/badArgsArityLessThanArgc.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/badArgsArityLessThanArgc.html">conformance/badArgsArityLessThanArgc.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="conformance/constants.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/constants.html">conformance/constants.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="conformance/fuzzTheAPI.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/fuzzTheAPI.html">conformance/fuzzTheAPI.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="conformance/getContext.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/getContext.html">conformance/getContext.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="conformance/methods.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/methods.html">conformance/methods.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="conformance/quickCheckAPI.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/quickCheckAPI.html">conformance/quickCheckAPI.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="conformance/quickCheckAPIBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/quickCheckAPIBadArgs.html">conformance/quickCheckAPIBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="conformance/webGLArrays.html" width="110" height="42"></iframe>
+      <h2><a href="conformance/webGLArrays.html">conformance/webGLArrays.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/bindBuffer.html" width="110" height="42"></iframe>
+      <h2><a href="functions/bindBuffer.html">functions/bindBuffer.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/bindBufferBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/bindBufferBadArgs.html">functions/bindBufferBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/bindFramebufferLeaveNonZero.html" width="110" height="42"></iframe>
+      <h2><a href="functions/bindFramebufferLeaveNonZero.html">functions/bindFramebufferLeaveNonZero.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/bufferData.html" width="110" height="42"></iframe>
+      <h2><a href="functions/bufferData.html">functions/bufferData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/bufferDataBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/bufferDataBadArgs.html">functions/bufferDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/bufferSubData.html" width="110" height="42"></iframe>
+      <h2><a href="functions/bufferSubData.html">functions/bufferSubData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/bufferSubDataBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/bufferSubDataBadArgs.html">functions/bufferSubDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/copyTexImage2D.html" width="110" height="42"></iframe>
+      <h2><a href="functions/copyTexImage2D.html">functions/copyTexImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/copyTexImage2DBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/copyTexImage2DBadArgs.html">functions/copyTexImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/copyTexSubImage2D.html" width="110" height="42"></iframe>
+      <h2><a href="functions/copyTexSubImage2D.html">functions/copyTexSubImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/copyTexSubImage2DBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/copyTexSubImage2DBadArgs.html">functions/copyTexSubImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/deleteBufferBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/deleteBufferBadArgs.html">functions/deleteBufferBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/drawArrays.html" width="110" height="42"></iframe>
+      <h2><a href="functions/drawArrays.html">functions/drawArrays.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/drawArraysOutOfBounds.html" width="110" height="42"></iframe>
+      <h2><a href="functions/drawArraysOutOfBounds.html">functions/drawArraysOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/drawElements.html" width="110" height="42"></iframe>
+      <h2><a href="functions/drawElements.html">functions/drawElements.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/drawElementsBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/drawElementsBadArgs.html">functions/drawElementsBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/getImageData.html" width="110" height="42"></iframe>
+      <h2><a href="functions/getImageData.html">functions/getImageData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/getImageDataBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/getImageDataBadArgs.html">functions/getImageDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/isTests.html" width="110" height="42"></iframe>
+      <h2><a href="functions/isTests.html">functions/isTests.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/linkProgramBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/linkProgramBadArgs.html">functions/linkProgramBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/readPixels.html" width="110" height="42"></iframe>
+      <h2><a href="functions/readPixels.html">functions/readPixels.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/readPixelsBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/readPixelsBadArgs.html">functions/readPixelsBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texImage2D.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texImage2D.html">functions/texImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texImage2DBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texImage2DBadArgs.html">functions/texImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texImage2DHTML.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texImage2DHTML.html">functions/texImage2DHTML.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texImage2DHTMLBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texImage2DHTMLBadArgs.html">functions/texImage2DHTMLBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texSubImage2D.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texSubImage2D.html">functions/texSubImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texSubImage2DBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texSubImage2DBadArgs.html">functions/texSubImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texSubImage2DHTML.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texSubImage2DHTML.html">functions/texSubImage2DHTML.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/texSubImage2DHTMLBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/texSubImage2DHTMLBadArgs.html">functions/texSubImage2DHTMLBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/uniformMatrix.html" width="110" height="42"></iframe>
+      <h2><a href="functions/uniformMatrix.html">functions/uniformMatrix.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/uniformMatrixBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/uniformMatrixBadArgs.html">functions/uniformMatrixBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/uniformf.html" width="110" height="42"></iframe>
+      <h2><a href="functions/uniformf.html">functions/uniformf.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/uniformfBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/uniformfBadArgs.html">functions/uniformfBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/uniformi.html" width="110" height="42"></iframe>
+      <h2><a href="functions/uniformi.html">functions/uniformi.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/uniformiBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/uniformiBadArgs.html">functions/uniformiBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/vertexAttrib.html" width="110" height="42"></iframe>
+      <h2><a href="functions/vertexAttrib.html">functions/vertexAttrib.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/vertexAttribBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/vertexAttribBadArgs.html">functions/vertexAttribBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/vertexAttribPointer.html" width="110" height="42"></iframe>
+      <h2><a href="functions/vertexAttribPointer.html">functions/vertexAttribPointer.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="functions/vertexAttribPointerBadArgs.html" width="110" height="42"></iframe>
+      <h2><a href="functions/vertexAttribPointerBadArgs.html">functions/vertexAttribPointerBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="performance/CPUvsGPU.html" width="110" height="42"></iframe>
+      <h2><a href="performance/CPUvsGPU.html">performance/CPUvsGPU.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="performance/bandwidth.html" width="110" height="42"></iframe>
+      <h2><a href="performance/bandwidth.html">performance/bandwidth.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="performance/jsGCPause.html" width="110" height="42"></iframe>
+      <h2><a href="performance/jsGCPause.html">performance/jsGCPause.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="performance/jsMatrixMult.html" width="110" height="42"></iframe>
+      <h2><a href="performance/jsMatrixMult.html">performance/jsMatrixMult.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="performance/jsToGLOverhead.html" width="110" height="42"></iframe>
+      <h2><a href="performance/jsToGLOverhead.html">performance/jsToGLOverhead.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="glsl/arrayOutOfBounds.html" width="110" height="42"></iframe>
+      <h2><a href="glsl/arrayOutOfBounds.html">glsl/arrayOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="glsl/longLoops.html" width="110" height="42"></iframe>
+      <h2><a href="glsl/longLoops.html">glsl/longLoops.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="glsl/uniformOutOfBounds.html" width="110" height="42"></iframe>
+      <h2><a href="glsl/uniformOutOfBounds.html">glsl/uniformOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe src="glsl/unusedAttribsUniforms.html" width="110" height="42"></iframe>
+      <h2><a href="glsl/unusedAttribsUniforms.html">glsl/unusedAttribsUniforms.html</a></h2>
+    </div>
+    
+</body>
+</html>
diff --git a/conformance/more/all_tests_linkonly.html b/conformance/more/all_tests_linkonly.html
new file mode 100644
index 0000000..7993393
--- /dev/null
+++ b/conformance/more/all_tests_linkonly.html
@@ -0,0 +1,399 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+  <title>OpenGL ES 2.0 &lt;canvas&gt; context tests</title>
+  <style type="text/css">
+    h2 { display: inline; font-size: 1em; margin-bottom: 0.2em; }
+    iframe { display: inline; border: 1px solid black; overflow: hidden;}
+  </style>
+  <script type="text/javascript">
+    function loadTest(id, url) {
+      document.getElementById(id).src = url;
+    }
+    function seqLoader() {
+      var iframes = document.getElementsByTagName('iframe');
+      for (var i=0; i<iframes.length; i++) {
+        iframes[i].addEventListener('load', (function(j) {
+          return function() {
+            var e = document.getElementById((j+1)+'_link');
+            if (e) loadTest(j+1, e.href);
+          }
+        })(i), false);
+      }
+      var e = document.getElementById('0_link');
+      if (e) loadTest(0, e.href);
+    }
+  </script>
+</head>
+<body onload="seqLoader()">
+
+    <div>
+      <iframe id="0" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(0, 'conformance/badArgsArityLessThanArgc.html');return false" href="conformance/badArgsArityLessThanArgc.html">conformance/badArgsArityLessThanArgc.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="1" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(1, 'conformance/constants.html');return false" href="conformance/constants.html">conformance/constants.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="2" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(2, 'conformance/fuzzTheAPI.html');return false" href="conformance/fuzzTheAPI.html">conformance/fuzzTheAPI.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="3" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(3, 'conformance/getContext.html');return false" href="conformance/getContext.html">conformance/getContext.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="4" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(4, 'conformance/methods.html');return false" href="conformance/methods.html">conformance/methods.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="5" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(5, 'conformance/quickCheckAPI.html');return false" href="conformance/quickCheckAPI.html">conformance/quickCheckAPI.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="6" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(6, 'conformance/quickCheckAPIBadArgs.html');return false" href="conformance/quickCheckAPIBadArgs.html">conformance/quickCheckAPIBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="7" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(7, 'conformance/webGLArrays.html');return false" href="conformance/webGLArrays.html">conformance/webGLArrays.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="8" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(8, 'functions/bindBuffer.html');return false" href="functions/bindBuffer.html">functions/bindBuffer.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="9" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(9, 'functions/bindBufferBadArgs.html');return false" href="functions/bindBufferBadArgs.html">functions/bindBufferBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="10" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(10, 'functions/bindFramebufferLeaveNonZero.html');return false" href="functions/bindFramebufferLeaveNonZero.html">functions/bindFramebufferLeaveNonZero.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="11" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(11, 'functions/bufferData.html');return false" href="functions/bufferData.html">functions/bufferData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="12" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(12, 'functions/bufferDataBadArgs.html');return false" href="functions/bufferDataBadArgs.html">functions/bufferDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="13" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(13, 'functions/bufferSubData.html');return false" href="functions/bufferSubData.html">functions/bufferSubData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="14" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(14, 'functions/bufferSubDataBadArgs.html');return false" href="functions/bufferSubDataBadArgs.html">functions/bufferSubDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="15" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(15, 'functions/copyTexImage2D.html');return false" href="functions/copyTexImage2D.html">functions/copyTexImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="16" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(16, 'functions/copyTexImage2DBadArgs.html');return false" href="functions/copyTexImage2DBadArgs.html">functions/copyTexImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="17" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(17, 'functions/copyTexSubImage2D.html');return false" href="functions/copyTexSubImage2D.html">functions/copyTexSubImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="18" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(18, 'functions/copyTexSubImage2DBadArgs.html');return false" href="functions/copyTexSubImage2DBadArgs.html">functions/copyTexSubImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="19" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(19, 'functions/deleteBufferBadArgs.html');return false" href="functions/deleteBufferBadArgs.html">functions/deleteBufferBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="20" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(20, 'functions/drawArrays.html');return false" href="functions/drawArrays.html">functions/drawArrays.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="21" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(21, 'functions/drawArraysOutOfBounds.html');return false" href="functions/drawArraysOutOfBounds.html">functions/drawArraysOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="22" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(22, 'functions/drawElements.html');return false" href="functions/drawElements.html">functions/drawElements.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="23" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(23, 'functions/drawElementsBadArgs.html');return false" href="functions/drawElementsBadArgs.html">functions/drawElementsBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="24" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(24, 'functions/getImageData.html');return false" href="functions/getImageData.html">functions/getImageData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="25" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(25, 'functions/getImageDataBadArgs.html');return false" href="functions/getImageDataBadArgs.html">functions/getImageDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="26" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(26, 'functions/isTests.html');return false" href="functions/isTests.html">functions/isTests.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="27" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(27, 'functions/linkProgramBadArgs.html');return false" href="functions/linkProgramBadArgs.html">functions/linkProgramBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="28" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(28, 'functions/readPixels.html');return false" href="functions/readPixels.html">functions/readPixels.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="29" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(29, 'functions/readPixelsBadArgs.html');return false" href="functions/readPixelsBadArgs.html">functions/readPixelsBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="30" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(30, 'functions/texImage2D.html');return false" href="functions/texImage2D.html">functions/texImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="31" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(31, 'functions/texImage2DBadArgs.html');return false" href="functions/texImage2DBadArgs.html">functions/texImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="32" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(32, 'functions/texImage2DHTML.html');return false" href="functions/texImage2DHTML.html">functions/texImage2DHTML.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="33" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(33, 'functions/texImage2DHTMLBadArgs.html');return false" href="functions/texImage2DHTMLBadArgs.html">functions/texImage2DHTMLBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="34" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(34, 'functions/texSubImage2D.html');return false" href="functions/texSubImage2D.html">functions/texSubImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="35" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(35, 'functions/texSubImage2DBadArgs.html');return false" href="functions/texSubImage2DBadArgs.html">functions/texSubImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="36" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(36, 'functions/texSubImage2DHTML.html');return false" href="functions/texSubImage2DHTML.html">functions/texSubImage2DHTML.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="37" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(37, 'functions/texSubImage2DHTMLBadArgs.html');return false" href="functions/texSubImage2DHTMLBadArgs.html">functions/texSubImage2DHTMLBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="38" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(38, 'functions/uniformMatrix.html');return false" href="functions/uniformMatrix.html">functions/uniformMatrix.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="39" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(39, 'functions/uniformMatrixBadArgs.html');return false" href="functions/uniformMatrixBadArgs.html">functions/uniformMatrixBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="40" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(40, 'functions/uniformf.html');return false" href="functions/uniformf.html">functions/uniformf.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="41" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(41, 'functions/uniformfBadArgs.html');return false" href="functions/uniformfBadArgs.html">functions/uniformfBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="42" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(42, 'functions/uniformi.html');return false" href="functions/uniformi.html">functions/uniformi.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="43" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(43, 'functions/uniformiBadArgs.html');return false" href="functions/uniformiBadArgs.html">functions/uniformiBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="44" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(44, 'functions/vertexAttrib.html');return false" href="functions/vertexAttrib.html">functions/vertexAttrib.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="45" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(45, 'functions/vertexAttribBadArgs.html');return false" href="functions/vertexAttribBadArgs.html">functions/vertexAttribBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="46" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(46, 'functions/vertexAttribPointer.html');return false" href="functions/vertexAttribPointer.html">functions/vertexAttribPointer.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="47" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(47, 'functions/vertexAttribPointerBadArgs.html');return false" href="functions/vertexAttribPointerBadArgs.html">functions/vertexAttribPointerBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="48" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(48, 'performance/CPUvsGPU.html');return false" href="performance/CPUvsGPU.html">performance/CPUvsGPU.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="49" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(49, 'performance/bandwidth.html');return false" href="performance/bandwidth.html">performance/bandwidth.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="50" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(50, 'performance/jsGCPause.html');return false" href="performance/jsGCPause.html">performance/jsGCPause.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="51" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(51, 'performance/jsMatrixMult.html');return false" href="performance/jsMatrixMult.html">performance/jsMatrixMult.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="52" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(52, 'performance/jsToGLOverhead.html');return false" href="performance/jsToGLOverhead.html">performance/jsToGLOverhead.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="53" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(53, 'glsl/arrayOutOfBounds.html');return false" href="glsl/arrayOutOfBounds.html">glsl/arrayOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="54" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(54, 'glsl/longLoops.html');return false" href="glsl/longLoops.html">glsl/longLoops.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="55" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(55, 'glsl/uniformOutOfBounds.html');return false" href="glsl/uniformOutOfBounds.html">glsl/uniformOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="56" width="110" height="42"></iframe>
+      <h2><a onclick="loadTest(56, 'glsl/unusedAttribsUniforms.html');return false" href="glsl/unusedAttribsUniforms.html">glsl/unusedAttribsUniforms.html</a></h2>
+    </div>
+    
+</body>
+</html>
diff --git a/conformance/more/all_tests_sequential.html b/conformance/more/all_tests_sequential.html
new file mode 100644
index 0000000..1174d96
--- /dev/null
+++ b/conformance/more/all_tests_sequential.html
@@ -0,0 +1,399 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+  <title>OpenGL ES 2.0 &lt;canvas&gt; context tests</title>
+  <style type="text/css">
+    h2 { display: inline; font-size: 1em; margin-bottom: 0.2em; }
+    iframe { display: inline; border: 1px solid black; overflow: hidden;}
+  </style>
+  <script type="text/javascript">
+    function loadTest(id, url) {
+      document.getElementById(id).src = url;
+    }
+    function seqLoader() {
+      var iframes = document.getElementsByTagName('iframe');
+      for (var i=0; i<iframes.length; i++) {
+        iframes[i].addEventListener('load', (function(j) {
+          return function() {
+            var e = document.getElementById((j+1)+'_link');
+            if (e) loadTest(j+1, e.href);
+          }
+        })(i), false);
+      }
+      var e = document.getElementById('0_link');
+      if (e) loadTest(0, e.href);
+    }
+  </script>
+</head>
+<body onload="seqLoader()">
+
+    <div>
+      <iframe id="0" width="110" height="42"></iframe>
+      <h2><a id="0_link" href="conformance/badArgsArityLessThanArgc.html">conformance/badArgsArityLessThanArgc.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="1" width="110" height="42"></iframe>
+      <h2><a id="1_link" href="conformance/constants.html">conformance/constants.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="2" width="110" height="42"></iframe>
+      <h2><a id="2_link" href="conformance/fuzzTheAPI.html">conformance/fuzzTheAPI.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="3" width="110" height="42"></iframe>
+      <h2><a id="3_link" href="conformance/getContext.html">conformance/getContext.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="4" width="110" height="42"></iframe>
+      <h2><a id="4_link" href="conformance/methods.html">conformance/methods.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="5" width="110" height="42"></iframe>
+      <h2><a id="5_link" href="conformance/quickCheckAPI.html">conformance/quickCheckAPI.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="6" width="110" height="42"></iframe>
+      <h2><a id="6_link" href="conformance/quickCheckAPIBadArgs.html">conformance/quickCheckAPIBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="7" width="110" height="42"></iframe>
+      <h2><a id="7_link" href="conformance/webGLArrays.html">conformance/webGLArrays.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="8" width="110" height="42"></iframe>
+      <h2><a id="8_link" href="functions/bindBuffer.html">functions/bindBuffer.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="9" width="110" height="42"></iframe>
+      <h2><a id="9_link" href="functions/bindBufferBadArgs.html">functions/bindBufferBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="10" width="110" height="42"></iframe>
+      <h2><a id="10_link" href="functions/bindFramebufferLeaveNonZero.html">functions/bindFramebufferLeaveNonZero.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="11" width="110" height="42"></iframe>
+      <h2><a id="11_link" href="functions/bufferData.html">functions/bufferData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="12" width="110" height="42"></iframe>
+      <h2><a id="12_link" href="functions/bufferDataBadArgs.html">functions/bufferDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="13" width="110" height="42"></iframe>
+      <h2><a id="13_link" href="functions/bufferSubData.html">functions/bufferSubData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="14" width="110" height="42"></iframe>
+      <h2><a id="14_link" href="functions/bufferSubDataBadArgs.html">functions/bufferSubDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="15" width="110" height="42"></iframe>
+      <h2><a id="15_link" href="functions/copyTexImage2D.html">functions/copyTexImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="16" width="110" height="42"></iframe>
+      <h2><a id="16_link" href="functions/copyTexImage2DBadArgs.html">functions/copyTexImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="17" width="110" height="42"></iframe>
+      <h2><a id="17_link" href="functions/copyTexSubImage2D.html">functions/copyTexSubImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="18" width="110" height="42"></iframe>
+      <h2><a id="18_link" href="functions/copyTexSubImage2DBadArgs.html">functions/copyTexSubImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="19" width="110" height="42"></iframe>
+      <h2><a id="19_link" href="functions/deleteBufferBadArgs.html">functions/deleteBufferBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="20" width="110" height="42"></iframe>
+      <h2><a id="20_link" href="functions/drawArrays.html">functions/drawArrays.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="21" width="110" height="42"></iframe>
+      <h2><a id="21_link" href="functions/drawArraysOutOfBounds.html">functions/drawArraysOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="22" width="110" height="42"></iframe>
+      <h2><a id="22_link" href="functions/drawElements.html">functions/drawElements.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="23" width="110" height="42"></iframe>
+      <h2><a id="23_link" href="functions/drawElementsBadArgs.html">functions/drawElementsBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="24" width="110" height="42"></iframe>
+      <h2><a id="24_link" href="functions/getImageData.html">functions/getImageData.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="25" width="110" height="42"></iframe>
+      <h2><a id="25_link" href="functions/getImageDataBadArgs.html">functions/getImageDataBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="26" width="110" height="42"></iframe>
+      <h2><a id="26_link" href="functions/isTests.html">functions/isTests.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="27" width="110" height="42"></iframe>
+      <h2><a id="27_link" href="functions/linkProgramBadArgs.html">functions/linkProgramBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="28" width="110" height="42"></iframe>
+      <h2><a id="28_link" href="functions/readPixels.html">functions/readPixels.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="29" width="110" height="42"></iframe>
+      <h2><a id="29_link" href="functions/readPixelsBadArgs.html">functions/readPixelsBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="30" width="110" height="42"></iframe>
+      <h2><a id="30_link" href="functions/texImage2D.html">functions/texImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="31" width="110" height="42"></iframe>
+      <h2><a id="31_link" href="functions/texImage2DBadArgs.html">functions/texImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="32" width="110" height="42"></iframe>
+      <h2><a id="32_link" href="functions/texImage2DHTML.html">functions/texImage2DHTML.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="33" width="110" height="42"></iframe>
+      <h2><a id="33_link" href="functions/texImage2DHTMLBadArgs.html">functions/texImage2DHTMLBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="34" width="110" height="42"></iframe>
+      <h2><a id="34_link" href="functions/texSubImage2D.html">functions/texSubImage2D.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="35" width="110" height="42"></iframe>
+      <h2><a id="35_link" href="functions/texSubImage2DBadArgs.html">functions/texSubImage2DBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="36" width="110" height="42"></iframe>
+      <h2><a id="36_link" href="functions/texSubImage2DHTML.html">functions/texSubImage2DHTML.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="37" width="110" height="42"></iframe>
+      <h2><a id="37_link" href="functions/texSubImage2DHTMLBadArgs.html">functions/texSubImage2DHTMLBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="38" width="110" height="42"></iframe>
+      <h2><a id="38_link" href="functions/uniformMatrix.html">functions/uniformMatrix.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="39" width="110" height="42"></iframe>
+      <h2><a id="39_link" href="functions/uniformMatrixBadArgs.html">functions/uniformMatrixBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="40" width="110" height="42"></iframe>
+      <h2><a id="40_link" href="functions/uniformf.html">functions/uniformf.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="41" width="110" height="42"></iframe>
+      <h2><a id="41_link" href="functions/uniformfBadArgs.html">functions/uniformfBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="42" width="110" height="42"></iframe>
+      <h2><a id="42_link" href="functions/uniformi.html">functions/uniformi.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="43" width="110" height="42"></iframe>
+      <h2><a id="43_link" href="functions/uniformiBadArgs.html">functions/uniformiBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="44" width="110" height="42"></iframe>
+      <h2><a id="44_link" href="functions/vertexAttrib.html">functions/vertexAttrib.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="45" width="110" height="42"></iframe>
+      <h2><a id="45_link" href="functions/vertexAttribBadArgs.html">functions/vertexAttribBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="46" width="110" height="42"></iframe>
+      <h2><a id="46_link" href="functions/vertexAttribPointer.html">functions/vertexAttribPointer.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="47" width="110" height="42"></iframe>
+      <h2><a id="47_link" href="functions/vertexAttribPointerBadArgs.html">functions/vertexAttribPointerBadArgs.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="48" width="110" height="42"></iframe>
+      <h2><a id="48_link" href="performance/CPUvsGPU.html">performance/CPUvsGPU.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="49" width="110" height="42"></iframe>
+      <h2><a id="49_link" href="performance/bandwidth.html">performance/bandwidth.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="50" width="110" height="42"></iframe>
+      <h2><a id="50_link" href="performance/jsGCPause.html">performance/jsGCPause.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="51" width="110" height="42"></iframe>
+      <h2><a id="51_link" href="performance/jsMatrixMult.html">performance/jsMatrixMult.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="52" width="110" height="42"></iframe>
+      <h2><a id="52_link" href="performance/jsToGLOverhead.html">performance/jsToGLOverhead.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="53" width="110" height="42"></iframe>
+      <h2><a id="53_link" href="glsl/arrayOutOfBounds.html">glsl/arrayOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="54" width="110" height="42"></iframe>
+      <h2><a id="54_link" href="glsl/longLoops.html">glsl/longLoops.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="55" width="110" height="42"></iframe>
+      <h2><a id="55_link" href="glsl/uniformOutOfBounds.html">glsl/uniformOutOfBounds.html</a></h2>
+    </div>
+    
+
+    <div>
+      <iframe id="56" width="110" height="42"></iframe>
+      <h2><a id="56_link" href="glsl/unusedAttribsUniforms.html">glsl/unusedAttribsUniforms.html</a></h2>
+    </div>
+    
+</body>
+</html>
diff --git a/conformance/more/conformance/argGenerators-A.js b/conformance/more/conformance/argGenerators-A.js
new file mode 100644
index 0000000..31039b5
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-A.js
@@ -0,0 +1,86 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// A
+
+  activeTexture : {
+    generate : function() { return [textureUnit.random()]; },
+    checkArgValidity : function(t) { return textureUnit.has(t); },
+    teardown : function() { GL.activeTexture(GL.TEXTURE0); }
+  },
+  attachShader : {
+    generate : function() {
+      var p = GL.createProgram();
+      var sh = GL.createShader(shaderType.random());
+      return [p, sh];
+    },
+    checkArgValidity : function(p, sh) {
+      return GL.isProgram(p) && GL.isShader(sh) && !GL.getAttachedShaders(p).has(sh);
+    },
+    cleanup : function(p, sh) {
+      try {GL.detachShader(p,sh);} catch(e) {}
+      try {GL.deleteProgram(p);} catch(e) {}
+      try {GL.deleteShader(sh);} catch(e) {}
+    }
+  }
+
+};
diff --git a/conformance/more/conformance/argGenerators-B1.js b/conformance/more/conformance/argGenerators-B1.js
new file mode 100644
index 0000000..e8abf39
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-B1.js
@@ -0,0 +1,78 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// B-1
+
+  bindAttribLocation : {
+    generate : function() {
+      var program = GL.createProgram();
+      return [program, randomVertexAttribute(), randomName()];
+    },
+    checkArgValidity : function(program, index, name) {
+      return GL.isProgram(program) && isVertexAttribute(index) && isValidName(name);
+    },
+    cleanup : function(program, index, name) {
+      try { GL.deleteProgram(program); } catch(e) {}
+    }
+  }
+
+};
diff --git a/conformance/more/conformance/argGenerators-B2.js b/conformance/more/conformance/argGenerators-B2.js
new file mode 100644
index 0000000..327f442
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-B2.js
@@ -0,0 +1,171 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// B-2
+
+  bindBuffer : {
+    generate : function(buf) {
+      return [bufferTarget.random(), GL.createBuffer()];
+    },
+    checkArgValidity : function(target, buf) {
+      if (!bufferTarget.has(target))
+        return false;
+      GL.bindBuffer(target, buf);
+      return GL.isBuffer(buf);
+    },
+    cleanup : function(t, buf, m) {
+      GL.deleteBuffer(buf);
+    }
+  },
+  bindFramebuffer : {
+    generate : function() {
+      return [GL.FRAMEBUFFER, Math.random() > 0.5 ? null : GL.createFramebuffer()];
+    },
+    checkArgValidity : function(target, fbo) {
+      if (target != GL.FRAMEBUFFER)
+        return false;
+      if (fbo != null)
+          GL.bindFramebuffer(target, fbo);
+      return (fbo == null || GL.isFramebuffer(fbo));
+    },
+    cleanup : function(target, fbo) {
+      GL.bindFramebuffer(target, null);
+      if (fbo)
+        GL.deleteFramebuffer(fbo);
+    }
+  },
+  bindRenderbuffer : {
+    generate : function() {
+      return [GL.RENDERBUFFER, Math.random() > 0.5 ? null : GL.createRenderbuffer()];
+    },
+    checkArgValidity : function(target, rbo) {
+      if (target != GL.RENDERBUFFER)
+        return false;
+      if (rbo != null)
+        GL.bindRenderbuffer(target, rbo);
+      return (rbo == null || GL.isRenderbuffer(rbo));
+    },
+    cleanup : function(target, rbo) {
+      GL.bindRenderbuffer(target, null);
+      if (rbo)
+        GL.deleteRenderbuffer(rbo);
+    }
+  },
+  bindTexture : {
+    generate : function() {
+      return [bindTextureTarget.random(), Math.random() > 0.5 ? null : GL.createTexture()];
+    },
+    checkArgValidity : function(target, o) {
+      if (!bindTextureTarget.has(target))
+        return false;
+      if (o != null)
+        GL.bindTexture(target, o);
+      return (o == null || GL.isTexture(o));
+    },
+    cleanup : function(target, o) {
+      GL.bindTexture(target, null);
+      if (o)
+        GL.deleteTexture(o);
+    }
+  },
+  blendColor : {
+    generate : function() { return randomColor(); },
+    teardown : function() { GL.blendColor(0,0,0,0); }
+  },
+  blendEquation : {
+    generate : function() { return [blendEquationMode.random()]; },
+    checkArgValidity : function(o) { return blendEquationMode.has(o); },
+    teardown : function() { GL.blendEquation(GL.FUNC_ADD); }
+  },
+  blendEquationSeparate : {
+    generate : function() {
+      return [blendEquationMode.random(), blendEquationMode.random()];
+    },
+    checkArgValidity : function(o,p) {
+      return blendEquationMode.has(o) && blendEquationMode.has(p);
+    },
+    teardown : function() { GL.blendEquationSeparate(GL.FUNC_ADD, GL.FUNC_ADD); }
+  },
+  blendFunc : {
+    generate : function() {
+      return [blendFuncSfactor.random(), blendFuncDfactor.random()];
+    },
+    checkArgValidity : function(s,d) {
+      return blendFuncSfactor.has(s) && blendFuncDfactor.has(d);
+    },
+    teardown : function() { GL.blendFunc(GL.ONE, GL.ZERO); }
+  },
+  blendFuncSeparate : {
+    generate : function() {
+      return [blendFuncSfactor.random(), blendFuncDfactor.random(),
+              blendFuncSfactor.random(), blendFuncDfactor.random()];
+    },
+    checkArgValidity : function(s,d,as,ad) {
+      return blendFuncSfactor.has(s) && blendFuncDfactor.has(d) &&
+              blendFuncSfactor.has(as) && blendFuncDfactor.has(ad) ;
+    },
+    teardown : function() {
+      GL.blendFuncSeparate(GL.ONE, GL.ZERO, GL.ONE, GL.ZERO);
+    }
+  }
+
+};
diff --git a/conformance/more/conformance/argGenerators-B3.js b/conformance/more/conformance/argGenerators-B3.js
new file mode 100644
index 0000000..7b7f46c
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-B3.js
@@ -0,0 +1,85 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// B-3
+
+  bufferData : {
+    setup : function() {
+      var buf = GL.createBuffer();
+      var ebuf = GL.createBuffer();
+      GL.bindBuffer(GL.ARRAY_BUFFER, buf);
+      GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, ebuf);
+      return [buf, ebuf];
+    },
+    generate : function(buf, ebuf) {
+      return [bufferTarget.random(), randomBufferData(), bufferMode.random()];
+    },
+    checkArgValidity : function(target, bufData, mode) {
+      return bufferTarget.has(target) && isBufferData(bufData) && bufferMode.has(mode);
+    },
+    teardown : function(buf, ebuf) {
+      GL.deleteBuffer(buf);
+      GL.deleteBuffer(ebuf);
+    },
+  }
+
+};
diff --git a/conformance/more/conformance/argGenerators-B4.js b/conformance/more/conformance/argGenerators-B4.js
new file mode 100644
index 0000000..6881c32
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-B4.js
@@ -0,0 +1,88 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// B-4
+
+  bufferSubData : {
+    setup : function() {
+      var buf = GL.createBuffer();
+      var ebuf = GL.createBuffer();
+      GL.bindBuffer(GL.ARRAY_BUFFER, buf);
+      GL.bufferData(GL.ARRAY_BUFFER, 256, GL.STATIC_DRAW);
+      GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, ebuf);
+      GL.bufferData(GL.ELEMENT_ARRAY_BUFFER, 256, GL.STATIC_DRAW);
+      return [buf, ebuf];
+    },
+    generate : function(buf, ebuf) {
+      var d = randomBufferSubData(256);
+      return [bufferTarget.random(), d.offset, d.data];
+    },
+    checkArgValidity : function(target, offset, data) {
+      return bufferTarget.has(target) && offset >= 0 && data.byteLength >= 0 && offset + data.byteLength <= 256;
+    },
+    teardown : function(buf, ebuf) {
+      GL.deleteBuffer(buf);
+      GL.deleteBuffer(ebuf);
+    },
+  }
+
+};
diff --git a/conformance/more/conformance/argGenerators-C.js b/conformance/more/conformance/argGenerators-C.js
new file mode 100644
index 0000000..974ff74
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-C.js
@@ -0,0 +1,136 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// C
+
+  checkFramebufferStatus : {
+    generate : function() {
+      return [Math.random() > 0.5 ? null : GL.createFramebuffer()];
+    },
+    checkArgValidity : function(fbo) {
+      if (fbo != null)
+        GL.bindFramebuffer(GL.FRAMEBUFFER, fbo);
+      return fbo == null || GL.isFramebuffer(fbo);
+    },
+    cleanup : function(fbo){
+      GL.bindFramebuffer(GL.FRAMEBUFFER, null);
+      if (fbo != null)
+        try{ GL.deleteFramebuffer(fbo); } catch(e) {}
+    }
+  },
+  clear : {
+    generate : function() { return [clearMask.random()]; },
+    checkArgValidity : function(mask) { return clearMask.has(mask); }
+  },
+  clearColor : {
+    generate : function() { return randomColor(); },
+    teardown : function() { GL.clearColor(0,0,0,0); }
+  },
+  clearDepth : {
+    generate : function() { return [Math.random()]; },
+    teardown : function() { GL.clearDepth(1); }
+  },
+  clearStencil : {
+    generate : function() { return [randomStencil()]; },
+    teardown : function() { GL.clearStencil(0); }
+  },
+  colorMask : {
+    generate : function() {
+      return [randomBool(), randomBool(), randomBool(), randomBool()];
+    },
+    teardown : function() { GL.colorMask(true, true, true, true); }
+  },
+  compileShader : {}, // FIXME
+  copyTexImage2D : {}, // FIXME
+  copyTexSubImage2D : {}, // FIXME
+  createBuffer : {
+    generate : function() { return []; },
+    returnValueCleanup : function(o) { GL.deleteBuffer(o); }
+  },
+  createFramebuffer : {
+    generate : function() { return []; },
+    returnValueCleanup : function(o) { GL.deleteFramebuffer(o); }
+  },
+  createProgram : {
+    generate : function() { return []; },
+    returnValueCleanup : function(o) { GL.deleteProgram(o); }
+  },
+  createRenderbuffer : {
+    generate : function() { return []; },
+    returnValueCleanup : function(o) { GL.deleteRenderbuffer(o); }
+  },
+  createShader : {
+    generate : function() { return [shaderType.random()]; },
+    checkArgValidity : function(t) { return shaderType.has(t); },
+    returnValueCleanup : function(o) { GL.deleteShader(o); }
+  },
+  createTexture : {
+    generate : function() { return []; },
+    returnValueCleanup : function(o) { GL.deleteTexture(o); }
+  },
+  cullFace : {
+    generate : function() { return [cullFace.random()]; },
+    checkArgValidity : function(f) { return cullFace.has(f); },
+    teardown : function() { GL.cullFace(GL.BACK); }
+  }
+
+};
diff --git a/conformance/more/conformance/argGenerators-D_G.js b/conformance/more/conformance/argGenerators-D_G.js
new file mode 100644
index 0000000..7d21851
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-D_G.js
@@ -0,0 +1,252 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// D
+
+  deleteBuffer : {
+    generate : function() { return [GL.createBuffer()]; },
+    checkArgValidity : function(o) {
+      GL.bindBuffer(GL.ARRAY_BUFFER, o);
+      return GL.isBuffer(o);
+    },
+    cleanup : function(o) {
+      GL.bindBuffer(GL.ARRAY_BUFFER, null);
+      try { GL.deleteBuffer(o); } catch(e) {}
+    }
+  },
+  deleteFramebuffer : {
+    generate : function() { return [GL.createFramebuffer()]; },
+    checkArgValidity : function(o) {
+      GL.bindFramebuffer(GL.FRAMEBUFFER, o);
+      return GL.isFramebuffer(o);
+    },
+    cleanup : function(o) {
+      GL.bindFramebuffer(GL.FRAMEBUFFER, null);
+      try { GL.deleteFramebuffer(o); } catch(e) {}
+    }
+  },
+  deleteProgram : {
+    generate : function() { return [GL.createProgram()]; },
+    checkArgValidity : function(o) { return GL.isProgram(o); },
+    cleanup : function(o) { try { GL.deleteProgram(o); } catch(e) {} }
+  },
+  deleteRenderbuffer : {
+    generate : function() { return [GL.createRenderbuffer()]; },
+    checkArgValidity : function(o) {
+      GL.bindRenderbuffer(GL.RENDERBUFFER, o);
+      return GL.isRenderbuffer(o);
+    },
+    cleanup : function(o) {
+      GL.bindRenderbuffer(GL.RENDERBUFFER, null);
+      try { GL.deleteRenderbuffer(o); } catch(e) {}
+    }
+  },
+  deleteShader : {
+    generate : function() { return [GL.createShader(shaderType.random())]; },
+    checkArgValidity : function(o) { return GL.isShader(o); },
+    cleanup : function(o) { try { GL.deleteShader(o); } catch(e) {} }
+  },
+  deleteTexture : {
+    generate : function() { return [GL.createTexture()]; },
+    checkArgValidity : function(o) {
+      GL.bindTexture(GL.TEXTURE_2D, o);
+      return GL.isTexture(o);
+    },
+    cleanup : function(o) {
+      GL.bindTexture(GL.TEXTURE_2D, null);
+      try { GL.deleteTexture(o); } catch(e) {}
+    }
+  },
+  depthFunc : {
+    generate : function() { return [depthFuncFunc.random()]; },
+    checkArgValidity : function(f) { return depthFuncFunc.has(f); },
+    teardown : function() { GL.depthFunc(GL.LESS); }
+  },
+  depthMask : {
+    generate : function() { return [randomBool()]; },
+    teardown : function() { GL.depthFunc(GL.TRUE); }
+  },
+  depthRange : {
+    generate : function() { return [Math.random(), Math.random()]; },
+    teardown : function() { GL.depthRange(0, 1); }
+  },
+  detachShader : {
+    generate : function() {
+      var p = GL.createProgram();
+      var sh = GL.createShader(shaderType.random());
+      GL.attachShader(p, sh);
+      return [p, sh];
+    },
+    checkArgValidity : function(p, sh) {
+      return GL.isProgram(p) && GL.isShader(sh) && GL.getAttachedShaders(p).has(sh);
+    },
+    cleanup : function(p, sh) {
+      try {GL.deleteProgram(p);} catch(e) {}
+      try {GL.deleteShader(sh);} catch(e) {}
+    }
+  },
+  disable : {
+    generate : function() { return [enableCap.random()]; },
+    checkArgValidity : function(c) { return enableCap.has(c); },
+    cleanup : function(c) { if (c == GL.DITHER) GL.enable(c); }
+  },
+  disableVertexAttribArray : {
+    generate : function() { return [randomVertexAttribute()]; },
+    checkArgValidity : function(v) { return isVertexAttribute(v); }
+  },
+  drawArrays : {}, // FIXME
+  drawElements : {}, // FIXME
+
+// E
+
+  enable : {
+    generate : function() { return [enableCap.random()]; },
+    checkArgValidity : function(c) { return enableCap.has(c); },
+    cleanup : function(c) { if (c != GL.DITHER) GL.disable(c); }
+  },
+  enableVertexAttribArray : {
+    generate : function() { return [randomVertexAttribute()]; },
+    checkArgValidity : function(v) { return isVertexAttribute(castToInt(v)); },
+    cleanup : function(v) { GL.disableVertexAttribArray(v); }
+  },
+
+// F
+
+  finish : {
+    generate : function() { return []; }
+  },
+  flush : {
+    generate : function() { return []; }
+  },
+  framebufferRenderbuffer : {}, // FIXME
+  framebufferTexture2D : {}, // FIXME
+  frontFace : {
+    generate : function() { return [frontFaceMode.random()]; },
+    checkArgValidity : function(c) { return frontFaceMode.has(c); },
+    cleanup : function(c) { GL.frontFace(GL.CCW); }
+  },
+
+// G-1
+
+  generateMipmap : {
+    setup : function() {
+      var tex = GL.createTexture();
+      var tex2 = GL.createTexture();
+      GL.bindTexture(GL.TEXTURE_2D, tex);
+      GL.bindTexture(GL.TEXTURE_CUBE_MAP, tex2);
+      var pix = new Uint8Array(16*16*4);
+      GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix);
+      GL.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix);
+      GL.texImage2D(GL.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix);
+      GL.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix);
+      GL.texImage2D(GL.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix);
+      GL.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix);
+      GL.texImage2D(GL.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL.RGBA, 16, 16, 0, GL.RGBA, GL.UNSIGNED_BYTE, pix);
+    },
+    generate : function() { return [bindTextureTarget.random()]; },
+    checkArgValidity : function(t) { return bindTextureTarget.has(t); },
+    teardown : function(tex, tex2) {
+      GL.bindTexture(GL.TEXTURE_2D, null);
+      GL.bindTexture(GL.TEXTURE_CUBE_MAP, null);
+      GL.deleteTexture(tex);
+      GL.deleteTexture(tex2);
+    }
+  },
+  getActiveAttrib : {
+  /* FIXME the queried attrib needs to be an active one
+    generate : function() {
+      var program = GL.createProgram();
+      return [program, randomVertexAttribute()];
+    },
+    checkArgValidity : function(program, index) {
+      return GL.isProgram(program) && isVertexAttribute(index);
+    },
+    cleanup : function(program, index) {
+      GL.deleteProgram(program);
+    }
+  */
+  },
+  getActiveUniform : {}, // FIXME
+  getAttachedShaders : {
+    setup : function() {
+      var program = GL.createProgram();
+      var s1 = GL.createShader(GL.VERTEX_SHADER);
+      var s2 = GL.createShader(GL.FRAGMENT_SHADER);
+      GL.attachShader(program, s1);
+      GL.attachShader(program, s2);
+      return [program, s1, s2];
+    },
+    generate : function(program, s1, s2) {
+      return [program]
+    },
+    checkArgValidity : function(program) {
+      return GL.isProgram(program);
+    },
+    teardown : function(program, s1, s2) {
+      GL.deleteProgram(program);
+      GL.deleteShader(s1);
+      GL.deleteShader(s2);
+    }
+  }
+
+};
+
diff --git a/conformance/more/conformance/argGenerators-G_I.js b/conformance/more/conformance/argGenerators-G_I.js
new file mode 100644
index 0000000..7af58b0
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-G_I.js
@@ -0,0 +1,141 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// G-2
+
+  getAttribLocation : {
+    generate : function() {
+      var program = GL.createProgram();
+      var name = randomName();
+      GL.bindAttribLocation(program, randomVertexAttribute(), name);
+      return [program, name];
+    },
+    checkArgValidity : function(program, name) {
+      return GL.isProgram(program) && isValidName(name);
+    },
+    cleanup : function(program, name) {
+      try { GL.deleteProgram(program); } catch(e) {}
+    }
+  },/*
+  getParameter : {
+    generate : function() { return [getParameterPname.random()]; },
+    checkArgValidity : function(p) { return getParameterPname.has(p); }
+  },
+  getBufferParameter : {}, // FIXME
+  getError : {
+    generate : function() { return []; }
+  },
+  getFramebufferAttachmentParameter : {}, // FIXME
+  getProgramParameter : {}, // FIXME
+  getProgramInfoLog : {}, // FIXME
+  getRenderbufferParameter : {}, // FIXME
+  getShaderParameter : {}, // FIXME
+  getShaderInfoLog : {}, // FIXME
+  getShaderSource : {}, // FIXME
+  getTexParameter : {}, // FIXME
+  getUniform : {}, // FIXME
+  getUniformLocation : {}, // FIXME
+  getVertexAttrib : {}, // FIXME
+  getVertexAttribOffset : {}, // FIXME
+
+// H
+
+  hint : {
+    generate : function() { return [GL.GENERATE_MIPMAP_HINT, mipmapHint.random()]; },
+    checkValidArgs : function(h, m) {
+      return h == GL.GENERATE_MIPMAP_HINT && mipmapHint.has(m);
+    },
+    teardown : function(){ GL.hint(GL.GENERATE_MIPMAP_HINT, GL.DONT_CARE); }
+  },
+
+// I
+
+  isBuffer : {
+    generate : function() { return [GL.createBuffer()]; },
+    cleanup : function(o) { try { GL.deleteBuffer(o); } catch(e) {} }
+  },
+  isEnabled : {
+    generate : function() { return [enableCap.random()]; },
+    checkArgValidity : function(c) { return enableCap.has(c); }
+  },
+  isFramebuffer : {
+    generate : function() { return [GL.createFramebuffer()]; },
+    cleanup : function(o) { try { GL.deleteFramebuffer(o); } catch(e) {} }
+  },
+  isProgram : {
+    generate : function() { return [GL.createProgram()]; },
+    cleanup : function(o) { try { GL.deleteProgram(o); } catch(e) {} }
+  },
+  isRenderbuffer : {
+    generate : function() { return [GL.createRenderbuffer()]; },
+    cleanup : function(o) { try { GL.deleteRenderbuffer(o); } catch(e) {} }
+  },
+  isShader : {
+    generate : function() { return [GL.createShader(shaderType.random())]; },
+    cleanup : function(o) { try { GL.deleteShader(o); } catch(e) {} }
+  },
+  isTexture : {
+    generate : function() { return [GL.createTexture()]; },
+    cleanup : function(o) { try { GL.deleteTexture(o); } catch(e) {} }
+  }*/
+
+};
diff --git a/conformance/more/conformance/argGenerators-L_S.js b/conformance/more/conformance/argGenerators-L_S.js
new file mode 100644
index 0000000..db7f38d
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-L_S.js
@@ -0,0 +1,139 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// L
+
+  lineWidth : {
+    generate : function() { return [randomLineWidth()]; },
+    teardown : function() { GL.lineWidth(1); }
+  },
+  linkProgram : {}, // FIXME
+
+// P
+  pixelStorei : {
+    generate : function() {
+      return [pixelStoreiPname.random(), pixelStoreiParam.random()];
+    },
+    checkArgValidity : function(pname, param) {
+      return pixelStoreiPname.has(pname) && pixelStoreiParam.has(param);
+    },
+    teardown : function() {
+      GL.pixelStorei(GL.PACK_ALIGNMENT, 4);
+      GL.pixelStorei(GL.UNPACK_ALIGNMENT, 4);
+    }
+  },
+  polygonOffset : {
+    generate : function() { return [randomFloat(), randomFloat()]; },
+    teardown : function() { GL.polygonOffset(0,0); }
+  },
+
+// R
+
+  readPixels : {}, // FIXME
+  renderbufferStorage : {}, // FIXME
+
+// S-1
+
+  sampleCoverage : {
+    generate : function() { return [randomFloatFromRange(0,1), randomBool()] },
+    teardown : function() { GL.sampleCoverage(1, false); }
+  },
+  scissor : {
+    generate : function() {
+      return [randomInt(3000)-1500, randomInt(3000)-1500, randomIntFromRange(0,3000), randomIntFromRange(0,3000)];
+    },
+    checkArgValidity : function(x,y,w,h) {
+      return castToInt(w) >= 0 && castToInt(h) >= 0;
+    },
+    teardown : function() {
+      GL.scissor(0,0,GL.canvas.width, GL.canvas.height);
+    }
+  },
+  shaderSource : {}, // FIXME
+  stencilFunc : {
+    generate : function(){
+      return [stencilFuncFunc.random(), randomInt(MaxStencilValue), randomInt(0xffffffff)];
+    },
+    checkArgValidity : function(func, ref, mask) {
+      return stencilFuncFunc.has(func) && castToInt(ref) >= 0 && castToInt(ref) < MaxStencilValue;
+    },
+    teardown : function() {
+      GL.stencilFunc(GL.ALWAYS, 0, 0xffffffff);
+    }
+  },
+  stencilFuncSeparate : {
+    generate : function(){
+      return [cullFace.random(), stencilFuncFunc.random(), randomInt(MaxStencilValue), randomInt(0xffffffff)];
+    },
+    checkArgValidity : function(face, func, ref, mask) {
+      return cullFace.has(face) && stencilFuncFunc.has(func) && castToInt(ref) >= 0 && castToInt(ref) < MaxStencilValue;
+    },
+    teardown : function() {
+      GL.stencilFunc(GL.ALWAYS, 0, 0xffffffff);
+    }
+  },
+  stencilMask : {
+    generate : function() { return [randomInt(0xffffffff)]; },
+    teardown : function() { GL.stencilMask(0xffffffff); }
+  }
+
+};
diff --git a/conformance/more/conformance/argGenerators-S_V.js b/conformance/more/conformance/argGenerators-S_V.js
new file mode 100644
index 0000000..726f6ef
--- /dev/null
+++ b/conformance/more/conformance/argGenerators-S_V.js
@@ -0,0 +1,229 @@
+/*
+** 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.
+*/
+
+// ArgGenerators contains argument generators for WebGL functions.
+// The argument generators are used for running random tests against the WebGL
+// functions.
+//
+// ArgGenerators is an object consisting of functionName : argGen -properties.
+//
+// functionName is a WebGL context function name and the argGen is an argument
+// generator object that encapsulates the requirements to run
+// randomly generated tests on the WebGL function.
+//
+// An argGen object has the following methods:
+//   - setup    -- set up state for testing the GL function, returns values
+//                 that need cleanup in teardown. Run once before entering a
+//                 test loop.
+//   - teardown -- do cleanup on setup's return values after testing is complete
+//   - generate -- generate a valid set of random arguments for the GL function
+//   - returnValueCleanup -- do cleanup on value returned by the tested GL function
+//   - cleanup  -- do cleanup on generated arguments from generate
+//   - checkArgValidity -- check if passed args are valid. Has a call signature
+//                         that matches generate's return value. Returns true
+//                         if args are valid, false if not.
+//
+//   Example test loop that demonstrates how the function args and return
+//   values flow together:
+//
+//   var setupArgs = argGen.setup();
+//   for (var i=0; i<numberOfTests; i++) {
+//     var generatedArgs = argGen.generate.apply(argGen, setupArgs);
+//     var validArgs = argGen.checkArgValidity.apply(argGen, generatedArgs);
+//     var rv = call the GL function with generatedArgs;
+//     argGen.returnValueCleanup(rv);
+//     argGen.cleanup.apply(argGen, generatedArgs);
+//   }
+//   argGen.teardown.apply(argGen, setupArgs);
+//
+ArgGenerators = {
+
+// GL functions in alphabetical order
+
+// S-2
+
+  stencilMaskSeparate : {
+    generate : function() { return [cullFace.random(), randomInt(0xffffffff)]; },
+    checkArgValidity : function(face, mask) {
+      return cullFace.has(face);
+    },
+    teardown : function() { GL.stencilMask(0xffffffff); }
+  },
+  stencilOp : {
+    generate : function() {
+      return [stencilOp.random(), stencilOp.random(), stencilOp.random()];
+    },
+    checkArgValidity : function(sfail, dpfail, dppass) {
+      return stencilOp.has(sfail) && stencilOp.has(dpfail) && stencilOp.has(dppass);
+    },
+    teardown : function() { GL.stencilOp(GL.KEEP, GL.KEEP, GL.KEEP); }
+  },
+  stencilOpSeparate : {
+    generate : function() {
+      return [cullFace.random(), stencilOp.random(), stencilOp.random(), stencilOp.random()];
+    },
+    checkArgValidity : function(face, sfail, dpfail, dppass) {
+      return cullFace.has(face) && stencilOp.has(sfail) &&
+              stencilOp.has(dpfail) && stencilOp.has(dppass);
+    },
+    teardown : function() { GL.stencilOp(GL.KEEP, GL.KEEP, GL.KEEP); }
+  },
+
+// T
+  texImage2D : {
+    noAlreadyTriedCheck : true, // Object.toSource is very slow here
+    setup : function() {
+      var tex = GL.createTexture();
+      var tex2 = GL.createTexture();
+      GL.bindTexture(GL.TEXTURE_2D, tex);
+      GL.bindTexture(GL.TEXTURE_CUBE_MAP, tex2);
+      return [tex, tex2];
+    },
+    generate : function() {
+      var format = texImageFormat.random();
+      if (Math.random() < 0.5) {
+        var img = randomImage(16,16);
+        var a = [ texImageTarget.random(), 0, format, format, GL.UNSIGNED_BYTE, img ];
+        return a;
+      } else {
+        var pix = null;
+        if (Math.random > 0.5) {
+          pix = new Uint8Array(16*16*4);
+        }
+        return [
+          texImageTarget.random(), 0,
+          format, 16, 16, 0,
+          format, GL.UNSIGNED_BYTE, pix
+        ];
+      }
+    },
+    checkArgValidity : function(target, level, internalformat, width, height, border, format, type, data) {
+               // or : function(target, level, internalformat, format, type, image)
+      if (!texImageTarget.has(target) || castToInt(level) < 0)
+        return false;
+      if (arguments.length <= 6) {
+        var xformat = width;
+        var xtype = height;
+        var ximage = border;
+        if ((ximage instanceof HTMLImageElement ||
+             ximage instanceof HTMLVideoElement ||
+             ximage instanceof HTMLCanvasElement ||
+             ximage instanceof ImageData) &&
+            texImageInternalFormat.has(internalformat) &&
+            texImageFormat.has(xformat) &&
+            texImageType.has(xtype) &&
+            internalformat == xformat)
+          return true;
+        return false;
+      }
+      var w = castToInt(width), h = castToInt(height), b = castToInt(border);
+      return texImageInternalFormat.has(internalformat) && w >= 0 && h >= 0 &&
+            b == 0 && (data == null || data.byteLength == w*h*4) &&
+            texImageFormat.has(format) && texImageType.has(type)
+            && internalformat == format;
+    },
+    teardown : function(tex, tex2) {
+      GL.bindTexture(GL.TEXTURE_2D, null);
+      GL.bindTexture(GL.TEXTURE_CUBE_MAP, null);
+      GL.deleteTexture(tex);
+      GL.deleteTexture(tex2);
+    }
+  },
+  texParameterf : {
+    generate : function() {
+      var pname = texParameterPname.random();
+      var param = texParameterParam[pname].random();
+      return [bindTextureTarget.random(), pname, param];
+    },
+    checkArgValidity : function(target, pname, param) {
+      if (!bindTextureTarget.has(target))
+        return false;
+      if (!texParameterPname.has(pname))
+        return false;
+      return texParameterParam[pname].has(param);
+    }
+  },
+  texParameteri : {
+    generate : function() {
+      var pname = texParameterPname.random();
+      var param = texParameterParam[pname].random();
+      return [bindTextureTarget.random(), pname, param];
+    },
+    checkArgValidity : function(target, pname, param) {
+      if (!bindTextureTarget.has(target))
+        return false;
+      if (!texParameterPname.has(pname))
+        return false;
+      return texParameterParam[pname].has(param);
+    }
+  },
+  texSubImage2D : {}, // FIXME
+
+// U
+
+  uniform1f : {}, // FIXME
+  uniform1fv : {}, // FIXME
+  uniform1i : {}, // FIXME
+  uniform1iv : {}, // FIXME
+  uniform2f : {}, // FIXME
+  uniform2fv : {}, // FIXME
+  uniform2i : {}, // FIXME
+  uniform2iv : {}, // FIXME
+  uniform3f : {}, // FIXME
+  uniform3fv : {}, // FIXME
+  uniform3i : {}, // FIXME
+  uniform3iv : {}, // FIXME
+  uniform4f : {}, // FIXME
+  uniform4fv : {}, // FIXME
+  uniform4i : {}, // FIXME
+  uniform4iv : {}, // FIXME
+  uniformMatrix2fv : {}, // FIXME
+  uniformMatrix3fv : {}, // FIXME
+  uniformMatrix4fv : {}, // FIXME
+  useProgram : {}, // FIXME
+
+// V
+
+  validateProgram : {}, // FIXME
+  vertexAttrib1f : {}, // FIXME
+  vertexAttrib1fv : {}, // FIXME
+  vertexAttrib2f : {}, // FIXME
+  vertexAttrib2fv : {}, // FIXME
+  vertexAttrib3f : {}, // FIXME
+  vertexAttrib3fv : {}, // FIXME
+  vertexAttrib4f : {}, // FIXME
+  vertexAttrib4fv : {}, // FIXME
+  vertexAttribPointer : {}, // FIXME
+  viewport : {
+    generate : function() {
+      return [randomInt(3000)-1500, randomInt(3000)-1500, randomIntFromRange(0,3000), randomIntFromRange(0,3000)];
+    },
+    checkArgValidity : function(x,y,w,h) {
+      return castToInt(w) >= 0 && castToInt(h) >= 0;
+    },
+    teardown : function() {
+      GL.viewport(0,0,GL.canvas.width, GL.canvas.height);
+    }
+  }
+
+};
diff --git a/conformance/more/conformance/badArgsArityLessThanArgc.html b/conformance/more/conformance/badArgsArityLessThanArgc.html
new file mode 100644
index 0000000..2eaf8f0
--- /dev/null
+++ b/conformance/more/conformance/badArgsArityLessThanArgc.html
@@ -0,0 +1,597 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+/*
+  The following tests are generated from
+  http://www.khronos.org/registry/gles/api/2.0/gl2.h
+  and api_modifications.txt
+*/
+Tests.test_activeTexture = function(gl) {
+  assertFail(function(){ gl.activeTexture(); });
+}
+Tests.test_attachShader = function(gl) {
+  assertFail(function(){ gl.attachShader(); });
+  assertFail(function(){ gl.attachShader(0); });
+}
+Tests.test_bindAttribLocation = function(gl) {
+  assertFail(function(){ gl.bindAttribLocation(); });
+  assertFail(function(){ gl.bindAttribLocation(0); });
+  assertFail(function(){ gl.bindAttribLocation(0,0); });
+}
+Tests.test_bindBuffer = function(gl) {
+  assertFail(function(){ gl.bindBuffer(); });
+  assertFail(function(){ gl.bindBuffer(0); });
+}
+Tests.test_bindFramebuffer = function(gl) {
+  assertFail(function(){ gl.bindFramebuffer(); });
+  assertFail(function(){ gl.bindFramebuffer(0); });
+}
+Tests.test_bindRenderbuffer = function(gl) {
+  assertFail(function(){ gl.bindRenderbuffer(); });
+  assertFail(function(){ gl.bindRenderbuffer(0); });
+}
+Tests.test_bindTexture = function(gl) {
+  assertFail(function(){ gl.bindTexture(); });
+  assertFail(function(){ gl.bindTexture(0); });
+}
+Tests.test_blendColor = function(gl) {
+  assertFail(function(){ gl.blendColor(); });
+  assertFail(function(){ gl.blendColor(0); });
+  assertFail(function(){ gl.blendColor(0,0); });
+  assertFail(function(){ gl.blendColor(0,0,0); });
+}
+Tests.test_blendEquation = function(gl) {
+  assertFail(function(){ gl.blendEquation(); });
+}
+Tests.test_blendEquationSeparate = function(gl) {
+  assertFail(function(){ gl.blendEquationSeparate(); });
+  assertFail(function(){ gl.blendEquationSeparate(0); });
+}
+Tests.test_blendFunc = function(gl) {
+  assertFail(function(){ gl.blendFunc(); });
+  assertFail(function(){ gl.blendFunc(0); });
+}
+Tests.test_blendFuncSeparate = function(gl) {
+  assertFail(function(){ gl.blendFuncSeparate(); });
+  assertFail(function(){ gl.blendFuncSeparate(0); });
+  assertFail(function(){ gl.blendFuncSeparate(0,0); });
+  assertFail(function(){ gl.blendFuncSeparate(0,0,0); });
+}
+Tests.test_bufferData = function(gl) {
+  assertFail(function(){ gl.bufferData(); });
+  assertFail(function(){ gl.bufferData(0); });
+  assertFail(function(){ gl.bufferData(0,0); });
+}
+Tests.test_bufferSubData = function(gl) {
+  assertFail(function(){ gl.bufferSubData(); });
+  assertFail(function(){ gl.bufferSubData(0); });
+  assertFail(function(){ gl.bufferSubData(0,0); });
+}
+Tests.test_checkFramebufferStatus = function(gl) {
+  assertFail(function(){ gl.checkFramebufferStatus(); });
+}
+Tests.test_clear = function(gl) {
+  assertFail(function(){ gl.clear(); });
+}
+Tests.test_clearColor = function(gl) {
+  assertFail(function(){ gl.clearColor(); });
+  assertFail(function(){ gl.clearColor(0); });
+  assertFail(function(){ gl.clearColor(0,0); });
+  assertFail(function(){ gl.clearColor(0,0,0); });
+}
+Tests.test_clearDepth = function(gl) {
+  assertFail(function(){ gl.clearDepth(); });
+}
+Tests.test_clearStencil = function(gl) {
+  assertFail(function(){ gl.clearStencil(); });
+}
+Tests.test_colorMask = function(gl) {
+  assertFail(function(){ gl.colorMask(); });
+  assertFail(function(){ gl.colorMask(0); });
+  assertFail(function(){ gl.colorMask(0,0); });
+  assertFail(function(){ gl.colorMask(0,0,0); });
+}
+Tests.test_compileShader = function(gl) {
+  assertFail(function(){ gl.compileShader(); });
+}
+Tests.test_copyTexImage2D = function(gl) {
+  assertFail(function(){ gl.copyTexImage2D(); });
+  assertFail(function(){ gl.copyTexImage2D(0); });
+  assertFail(function(){ gl.copyTexImage2D(0,0); });
+  assertFail(function(){ gl.copyTexImage2D(0,0,0); });
+  assertFail(function(){ gl.copyTexImage2D(0,0,0,0); });
+  assertFail(function(){ gl.copyTexImage2D(0,0,0,0,0); });
+  assertFail(function(){ gl.copyTexImage2D(0,0,0,0,0,0); });
+  assertFail(function(){ gl.copyTexImage2D(0,0,0,0,0,0,0); });
+}
+Tests.test_copyTexSubImage2D = function(gl) {
+  assertFail(function(){ gl.copyTexSubImage2D(); });
+  assertFail(function(){ gl.copyTexSubImage2D(0); });
+  assertFail(function(){ gl.copyTexSubImage2D(0,0); });
+  assertFail(function(){ gl.copyTexSubImage2D(0,0,0); });
+  assertFail(function(){ gl.copyTexSubImage2D(0,0,0,0); });
+  assertFail(function(){ gl.copyTexSubImage2D(0,0,0,0,0); });
+  assertFail(function(){ gl.copyTexSubImage2D(0,0,0,0,0,0); });
+  assertFail(function(){ gl.copyTexSubImage2D(0,0,0,0,0,0,0); });
+}
+Tests.test_createProgram = function(gl) {
+}
+Tests.test_createShader = function(gl) {
+  assertFail(function(){ gl.createShader(); });
+}
+Tests.test_cullFace = function(gl) {
+  assertFail(function(){ gl.cullFace(); });
+}
+Tests.test_deleteBuffer = function(gl) {
+  assertFail(function(){ gl.deleteBuffer(); });
+}
+Tests.test_deleteFramebuffer = function(gl) {
+  assertFail(function(){ gl.deleteFramebuffer(); });
+}
+Tests.test_deleteProgram = function(gl) {
+  assertFail(function(){ gl.deleteProgram(); });
+}
+Tests.test_deleteRenderbuffer = function(gl) {
+  assertFail(function(){ gl.deleteRenderbuffer(); });
+}
+Tests.test_deleteShader = function(gl) {
+  assertFail(function(){ gl.deleteShader(); });
+}
+Tests.test_deleteTexture = function(gl) {
+  assertFail(function(){ gl.deleteTexture(); });
+}
+Tests.test_depthFunc = function(gl) {
+  assertFail(function(){ gl.depthFunc(); });
+}
+Tests.test_depthMask = function(gl) {
+  assertFail(function(){ gl.depthMask(); });
+}
+Tests.test_depthRange = function(gl) {
+  assertFail(function(){ gl.depthRange(); });
+  assertFail(function(){ gl.depthRange(0); });
+}
+Tests.test_detachShader = function(gl) {
+  assertFail(function(){ gl.detachShader(); });
+  assertFail(function(){ gl.detachShader(0); });
+}
+Tests.test_disable = function(gl) {
+  assertFail(function(){ gl.disable(); });
+}
+Tests.test_disableVertexAttribArray = function(gl) {
+  assertFail(function(){ gl.disableVertexAttribArray(); });
+}
+Tests.test_drawArrays = function(gl) {
+  assertFail(function(){ gl.drawArrays(); });
+  assertFail(function(){ gl.drawArrays(0); });
+  assertFail(function(){ gl.drawArrays(0,0); });
+}
+Tests.test_drawElements = function(gl) {
+  assertFail(function(){ gl.drawElements(); });
+  assertFail(function(){ gl.drawElements(0); });
+  assertFail(function(){ gl.drawElements(0,0); });
+  assertFail(function(){ gl.drawElements(0,0,0); });
+}
+Tests.test_enable = function(gl) {
+  assertFail(function(){ gl.enable(); });
+}
+Tests.test_enableVertexAttribArray = function(gl) {
+  assertFail(function(){ gl.enableVertexAttribArray(); });
+}
+Tests.test_finish = function(gl) {
+}
+Tests.test_flush = function(gl) {
+}
+Tests.test_framebufferRenderbuffer = function(gl) {
+  assertFail(function(){ gl.framebufferRenderbuffer(); });
+  assertFail(function(){ gl.framebufferRenderbuffer(0); });
+  assertFail(function(){ gl.framebufferRenderbuffer(0,0); });
+  assertFail(function(){ gl.framebufferRenderbuffer(0,0,0); });
+}
+Tests.test_framebufferTexture2D = function(gl) {
+  assertFail(function(){ gl.framebufferTexture2D(); });
+  assertFail(function(){ gl.framebufferTexture2D(0); });
+  assertFail(function(){ gl.framebufferTexture2D(0,0); });
+  assertFail(function(){ gl.framebufferTexture2D(0,0,0); });
+  assertFail(function(){ gl.framebufferTexture2D(0,0,0,0); });
+}
+Tests.test_frontFace = function(gl) {
+  assertFail(function(){ gl.frontFace(); });
+}
+Tests.test_createBuffer = function(gl) {
+}
+Tests.test_generateMipmap = function(gl) {
+  assertFail(function(){ gl.generateMipmap(); });
+}
+Tests.test_createFramebuffer = function(gl) {
+}
+Tests.test_createRenderbuffer = function(gl) {
+}
+Tests.test_createTexture = function(gl) {
+}
+Tests.test_getActiveAttrib = function(gl) {
+  assertFail(function(){ gl.getActiveAttrib(); });
+  assertFail(function(){ gl.getActiveAttrib(0); });
+}
+Tests.test_getActiveUniform = function(gl) {
+  assertFail(function(){ gl.getActiveUniform(); });
+  assertFail(function(){ gl.getActiveUniform(0); });
+}
+Tests.test_getAttachedShaders = function(gl) {
+  assertFail(function(){ gl.getAttachedShaders(); });
+}
+Tests.test_getAttribLocation = function(gl) {
+  assertFail(function(){ gl.getAttribLocation(); });
+  assertFail(function(){ gl.getAttribLocation(0); });
+}
+Tests.test_getBufferParameteri = function(gl) {
+  assertFail(function(){ gl.getBufferParameteri(); });
+  assertFail(function(){ gl.getBufferParameteri(0); });
+}
+Tests.test_getError = function(gl) {
+}
+Tests.test_getFramebufferAttachmentParameteri = function(gl) {
+  assertFail(function(){ gl.getFramebufferAttachmentParameteri(); });
+  assertFail(function(){ gl.getFramebufferAttachmentParameteri(0); });
+  assertFail(function(){ gl.getFramebufferAttachmentParameteri(0,0); });
+}
+Tests.test_getProgramInfoLog = function(gl) {
+  assertFail(function(){ gl.getProgramInfoLog(); });
+}
+Tests.test_getRenderbufferParameteri = function(gl) {
+  assertFail(function(){ gl.getRenderbufferParameteri(); });
+  assertFail(function(){ gl.getRenderbufferParameteri(0); });
+}
+Tests.test_getShaderInfoLog = function(gl) {
+  assertFail(function(){ gl.getShaderInfoLog(); });
+}
+Tests.test_getShaderSource = function(gl) {
+  assertFail(function(){ gl.getShaderSource(); });
+}
+Tests.test_getUniformLocation = function(gl) {
+  assertFail(function(){ gl.getUniformLocation(); });
+  assertFail(function(){ gl.getUniformLocation(0); });
+}
+Tests.test_hint = function(gl) {
+  assertFail(function(){ gl.hint(); });
+  assertFail(function(){ gl.hint(0); });
+}
+Tests.test_isBuffer = function(gl) {
+  assertFail(function(){ gl.isBuffer(); });
+}
+Tests.test_isEnabled = function(gl) {
+  assertFail(function(){ gl.isEnabled(); });
+}
+Tests.test_isFramebuffer = function(gl) {
+  assertFail(function(){ gl.isFramebuffer(); });
+}
+Tests.test_isProgram = function(gl) {
+  assertFail(function(){ gl.isProgram(); });
+}
+Tests.test_isRenderbuffer = function(gl) {
+  assertFail(function(){ gl.isRenderbuffer(); });
+}
+Tests.test_isShader = function(gl) {
+  assertFail(function(){ gl.isShader(); });
+}
+Tests.test_isTexture = function(gl) {
+  assertFail(function(){ gl.isTexture(); });
+}
+Tests.test_lineWidth = function(gl) {
+  assertFail(function(){ gl.lineWidth(); });
+}
+Tests.test_linkProgram = function(gl) {
+  assertFail(function(){ gl.linkProgram(); });
+}
+Tests.test_polygonOffset = function(gl) {
+  assertFail(function(){ gl.polygonOffset(); });
+  assertFail(function(){ gl.polygonOffset(0); });
+}
+Tests.test_readPixels = function(gl) {
+  assertFail(function(){ gl.readPixels(); });
+  assertFail(function(){ gl.readPixels(0); });
+  assertFail(function(){ gl.readPixels(0,0); });
+  assertFail(function(){ gl.readPixels(0,0,0); });
+  assertFail(function(){ gl.readPixels(0,0,0,0); });
+  assertFail(function(){ gl.readPixels(0,0,0,0,0); });
+}
+Tests.test_renderbufferStorage = function(gl) {
+  assertFail(function(){ gl.renderbufferStorage(); });
+  assertFail(function(){ gl.renderbufferStorage(0); });
+  assertFail(function(){ gl.renderbufferStorage(0,0); });
+  assertFail(function(){ gl.renderbufferStorage(0,0,0); });
+}
+Tests.test_sampleCoverage = function(gl) {
+  assertFail(function(){ gl.sampleCoverage(); });
+  assertFail(function(){ gl.sampleCoverage(0); });
+}
+Tests.test_scissor = function(gl) {
+  assertFail(function(){ gl.scissor(); });
+  assertFail(function(){ gl.scissor(0); });
+  assertFail(function(){ gl.scissor(0,0); });
+  assertFail(function(){ gl.scissor(0,0,0); });
+}
+Tests.test_shaderSource = function(gl) {
+  assertFail(function(){ gl.shaderSource(); });
+  assertFail(function(){ gl.shaderSource(0); });
+}
+Tests.test_stencilFunc = function(gl) {
+  assertFail(function(){ gl.stencilFunc(); });
+  assertFail(function(){ gl.stencilFunc(0); });
+  assertFail(function(){ gl.stencilFunc(0,0); });
+}
+Tests.test_stencilFuncSeparate = function(gl) {
+  assertFail(function(){ gl.stencilFuncSeparate(); });
+  assertFail(function(){ gl.stencilFuncSeparate(0); });
+  assertFail(function(){ gl.stencilFuncSeparate(0,0); });
+  assertFail(function(){ gl.stencilFuncSeparate(0,0,0); });
+}
+Tests.test_stencilMask = function(gl) {
+  assertFail(function(){ gl.stencilMask(); });
+}
+Tests.test_stencilMaskSeparate = function(gl) {
+  assertFail(function(){ gl.stencilMaskSeparate(); });
+  assertFail(function(){ gl.stencilMaskSeparate(0); });
+}
+Tests.test_stencilOp = function(gl) {
+  assertFail(function(){ gl.stencilOp(); });
+  assertFail(function(){ gl.stencilOp(0); });
+  assertFail(function(){ gl.stencilOp(0,0); });
+}
+Tests.test_stencilOpSeparate = function(gl) {
+  assertFail(function(){ gl.stencilOpSeparate(); });
+  assertFail(function(){ gl.stencilOpSeparate(0); });
+  assertFail(function(){ gl.stencilOpSeparate(0,0); });
+  assertFail(function(){ gl.stencilOpSeparate(0,0,0); });
+}
+Tests.test_texImage2D = function(gl) {
+  assertFail(function(){ gl.texImage2D(); });
+  assertFail(function(){ gl.texImage2D(0); });
+  assertFail(function(){ gl.texImage2D(0,0); });
+}
+Tests.test_texParameteri = function(gl) {
+  assertFail(function(){ gl.texParameteri(); });
+  assertFail(function(){ gl.texParameteri(0); });
+  assertFail(function(){ gl.texParameteri(0,0); });
+}
+Tests.test_texSubImage2D = function(gl) {
+  assertFail(function(){ gl.texSubImage2D(); });
+  assertFail(function(){ gl.texSubImage2D(0); });
+  assertFail(function(){ gl.texSubImage2D(0,0); });
+  assertFail(function(){ gl.texSubImage2D(0,0,0); });
+  assertFail(function(){ gl.texSubImage2D(0,0,0,0); });
+  assertFail(function(){ gl.texSubImage2D(0,0,0,0,0); });
+  assertFail(function(){ gl.texSubImage2D(0,0,0,0,0,0); });
+  assertFail(function(){ gl.texSubImage2D(0,0,0,0,0,0,0); });
+  assertFail(function(){ gl.texSubImage2D(0,0,0,0,0,0,0,0); });
+}
+Tests.test_uniform1f = function(gl) {
+  assertFail(function(){ gl.uniform1f(); });
+  assertFail(function(){ gl.uniform1f(0); });
+}
+Tests.test_uniform1fv = function(gl) {
+  assertFail(function(){ gl.uniform1fv(); });
+  assertFail(function(){ gl.uniform1fv(0); });
+}
+Tests.test_uniform1i = function(gl) {
+  assertFail(function(){ gl.uniform1i(); });
+  assertFail(function(){ gl.uniform1i(0); });
+}
+Tests.test_uniform1iv = function(gl) {
+  assertFail(function(){ gl.uniform1iv(); });
+  assertFail(function(){ gl.uniform1iv(0); });
+}
+Tests.test_uniform2f = function(gl) {
+  assertFail(function(){ gl.uniform2f(); });
+  assertFail(function(){ gl.uniform2f(0); });
+  assertFail(function(){ gl.uniform2f(0,0); });
+}
+Tests.test_uniform2fv = function(gl) {
+  assertFail(function(){ gl.uniform2fv(); });
+  assertFail(function(){ gl.uniform2fv(0); });
+}
+Tests.test_uniform2i = function(gl) {
+  assertFail(function(){ gl.uniform2i(); });
+  assertFail(function(){ gl.uniform2i(0); });
+  assertFail(function(){ gl.uniform2i(0,0); });
+}
+Tests.test_uniform2iv = function(gl) {
+  assertFail(function(){ gl.uniform2iv(); });
+  assertFail(function(){ gl.uniform2iv(0); });
+}
+Tests.test_uniform3f = function(gl) {
+  assertFail(function(){ gl.uniform3f(); });
+  assertFail(function(){ gl.uniform3f(0); });
+  assertFail(function(){ gl.uniform3f(0,0); });
+  assertFail(function(){ gl.uniform3f(0,0,0); });
+}
+Tests.test_uniform3fv = function(gl) {
+  assertFail(function(){ gl.uniform3fv(); });
+  assertFail(function(){ gl.uniform3fv(0); });
+}
+Tests.test_uniform3i = function(gl) {
+  assertFail(function(){ gl.uniform3i(); });
+  assertFail(function(){ gl.uniform3i(0); });
+  assertFail(function(){ gl.uniform3i(0,0); });
+  assertFail(function(){ gl.uniform3i(0,0,0); });
+}
+Tests.test_uniform3iv = function(gl) {
+  assertFail(function(){ gl.uniform3iv(); });
+  assertFail(function(){ gl.uniform3iv(0); });
+}
+Tests.test_uniform4f = function(gl) {
+  assertFail(function(){ gl.uniform4f(); });
+  assertFail(function(){ gl.uniform4f(0); });
+  assertFail(function(){ gl.uniform4f(0,0); });
+  assertFail(function(){ gl.uniform4f(0,0,0); });
+  assertFail(function(){ gl.uniform4f(0,0,0,0); });
+}
+Tests.test_uniform4fv = function(gl) {
+  assertFail(function(){ gl.uniform4fv(); });
+  assertFail(function(){ gl.uniform4fv(0); });
+}
+Tests.test_uniform4i = function(gl) {
+  assertFail(function(){ gl.uniform4i(); });
+  assertFail(function(){ gl.uniform4i(0); });
+  assertFail(function(){ gl.uniform4i(0,0); });
+  assertFail(function(){ gl.uniform4i(0,0,0); });
+  assertFail(function(){ gl.uniform4i(0,0,0,0); });
+}
+Tests.test_uniform4iv = function(gl) {
+  assertFail(function(){ gl.uniform4iv(); });
+  assertFail(function(){ gl.uniform4iv(0); });
+}
+Tests.test_uniformMatrix2fv = function(gl) {
+  assertFail(function(){ gl.uniformMatrix2fv(); });
+  assertFail(function(){ gl.uniformMatrix2fv(0); });
+  assertFail(function(){ gl.uniformMatrix2fv(0,0); });
+}
+Tests.test_uniformMatrix3fv = function(gl) {
+  assertFail(function(){ gl.uniformMatrix3fv(); });
+  assertFail(function(){ gl.uniformMatrix3fv(0); });
+  assertFail(function(){ gl.uniformMatrix3fv(0,0); });
+}
+Tests.test_uniformMatrix4fv = function(gl) {
+  assertFail(function(){ gl.uniformMatrix4fv(); });
+  assertFail(function(){ gl.uniformMatrix4fv(0); });
+  assertFail(function(){ gl.uniformMatrix4fv(0,0); });
+}
+Tests.test_useProgram = function(gl) {
+  assertFail(function(){ gl.useProgram(); });
+}
+Tests.test_validateProgram = function(gl) {
+  assertFail(function(){ gl.validateProgram(); });
+}
+Tests.test_vertexAttrib1f = function(gl) {
+  assertFail(function(){ gl.vertexAttrib1f(); });
+  assertFail(function(){ gl.vertexAttrib1f(0); });
+}
+Tests.test_vertexAttrib1fv = function(gl) {
+  assertFail(function(){ gl.vertexAttrib1fv(); });
+  assertFail(function(){ gl.vertexAttrib1fv(0); });
+}
+Tests.test_vertexAttrib2f = function(gl) {
+  assertFail(function(){ gl.vertexAttrib2f(); });
+  assertFail(function(){ gl.vertexAttrib2f(0); });
+  assertFail(function(){ gl.vertexAttrib2f(0,0); });
+}
+Tests.test_vertexAttrib2fv = function(gl) {
+  assertFail(function(){ gl.vertexAttrib2fv(); });
+  assertFail(function(){ gl.vertexAttrib2fv(0); });
+}
+Tests.test_vertexAttrib3f = function(gl) {
+  assertFail(function(){ gl.vertexAttrib3f(); });
+  assertFail(function(){ gl.vertexAttrib3f(0); });
+  assertFail(function(){ gl.vertexAttrib3f(0,0); });
+  assertFail(function(){ gl.vertexAttrib3f(0,0,0); });
+}
+Tests.test_vertexAttrib3fv = function(gl) {
+  assertFail(function(){ gl.vertexAttrib3fv(); });
+  assertFail(function(){ gl.vertexAttrib3fv(0); });
+}
+Tests.test_vertexAttrib4f = function(gl) {
+  assertFail(function(){ gl.vertexAttrib4f(); });
+  assertFail(function(){ gl.vertexAttrib4f(0); });
+  assertFail(function(){ gl.vertexAttrib4f(0,0); });
+  assertFail(function(){ gl.vertexAttrib4f(0,0,0); });
+  assertFail(function(){ gl.vertexAttrib4f(0,0,0,0); });
+}
+Tests.test_vertexAttrib4fv = function(gl) {
+  assertFail(function(){ gl.vertexAttrib4fv(); });
+  assertFail(function(){ gl.vertexAttrib4fv(0); });
+}
+Tests.test_vertexAttribPointer = function(gl) {
+  assertFail(function(){ gl.vertexAttribPointer(); });
+  assertFail(function(){ gl.vertexAttribPointer(0); });
+  assertFail(function(){ gl.vertexAttribPointer(0,0); });
+  assertFail(function(){ gl.vertexAttribPointer(0,0,0); });
+  assertFail(function(){ gl.vertexAttribPointer(0,0,0,0); });
+  assertFail(function(){ gl.vertexAttribPointer(0,0,0,0,0); });
+}
+Tests.test_viewport = function(gl) {
+  assertFail(function(){ gl.viewport(); });
+  assertFail(function(){ gl.viewport(0); });
+  assertFail(function(){ gl.viewport(0,0); });
+  assertFail(function(){ gl.viewport(0,0,0); });
+}
+Tests.test_currentArrayBufferBinding = function(gl) {
+}
+Tests.test_currentElementArrayBufferBinding = function(gl) {
+}
+Tests.test_currentFramebufferBinding = function(gl) {
+}
+Tests.test_currentRenderbufferBinding = function(gl) {
+}
+Tests.test_currentTextureBinding2D = function(gl) {
+}
+Tests.test_currentTextureBindingCubeMap = function(gl) {
+}
+Tests.test_currentProgram = function(gl) {
+}
+Tests.test_getParameter = function(gl) {
+  assertFail(function(){ gl.getParameter(); });
+}
+Tests.test_getProgramParameter = function(gl) {
+  assertFail(function(){ gl.getProgramParameter(); });
+  assertFail(function(){ gl.getProgramParameter(0); });
+}
+Tests.test_getShaderParameter = function(gl) {
+  assertFail(function(){ gl.getShaderParameter(); });
+  assertFail(function(){ gl.getShaderParameter(0); });
+}
+Tests.test_getTexParameteri = function(gl) {
+  assertFail(function(){ gl.getTexParameteri(); });
+  assertFail(function(){ gl.getTexParameteri(0); });
+}
+Tests.test_getUniformi = function(gl) {
+  assertFail(function(){ gl.getUniformi(); });
+  assertFail(function(){ gl.getUniformi(0); });
+}
+Tests.test_getVertexAttribi = function(gl) {
+  assertFail(function(){ gl.getVertexAttribi(); });
+  assertFail(function(){ gl.getVertexAttribi(0); });
+}
+Tests.test_present = function(gl) {
+}
+Tests.startUnit = function() {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/conformance/constants.html b/conformance/more/conformance/constants.html
new file mode 100644
index 0000000..6933a92
--- /dev/null
+++ b/conformance/more/conformance/constants.html
@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+/*
+  The following tests are generated from
+  http://www.khronos.org/registry/gles/api/2.0/gl2.h
+  and api_modifications.txt
+*/
+var constants = {
+DEPTH_BUFFER_BIT : 0x00000100,
+STENCIL_BUFFER_BIT : 0x00000400,
+COLOR_BUFFER_BIT : 0x00004000,
+POINTS : 0x0000,
+LINES : 0x0001,
+LINE_LOOP : 0x0002,
+LINE_STRIP : 0x0003,
+TRIANGLES : 0x0004,
+TRIANGLE_STRIP : 0x0005,
+TRIANGLE_FAN : 0x0006,
+ZERO : 0,
+ONE : 1,
+SRC_COLOR : 0x0300,
+ONE_MINUS_SRC_COLOR : 0x0301,
+SRC_ALPHA : 0x0302,
+ONE_MINUS_SRC_ALPHA : 0x0303,
+DST_ALPHA : 0x0304,
+ONE_MINUS_DST_ALPHA : 0x0305,
+DST_COLOR : 0x0306,
+ONE_MINUS_DST_COLOR : 0x0307,
+SRC_ALPHA_SATURATE : 0x0308,
+FUNC_ADD : 0x8006,
+BLEND_EQUATION : 0x8009,
+BLEND_EQUATION_RGB : 0x8009,
+BLEND_EQUATION_ALPHA : 0x883D,
+FUNC_SUBTRACT : 0x800A,
+FUNC_REVERSE_SUBTRACT : 0x800B,
+BLEND_DST_RGB : 0x80C8,
+BLEND_SRC_RGB : 0x80C9,
+BLEND_DST_ALPHA : 0x80CA,
+BLEND_SRC_ALPHA : 0x80CB,
+CONSTANT_COLOR : 0x8001,
+ONE_MINUS_CONSTANT_COLOR : 0x8002,
+CONSTANT_ALPHA : 0x8003,
+ONE_MINUS_CONSTANT_ALPHA : 0x8004,
+BLEND_COLOR : 0x8005,
+ARRAY_BUFFER : 0x8892,
+ELEMENT_ARRAY_BUFFER : 0x8893,
+ARRAY_BUFFER_BINDING : 0x8894,
+ELEMENT_ARRAY_BUFFER_BINDING : 0x8895,
+STREAM_DRAW : 0x88E0,
+STATIC_DRAW : 0x88E4,
+DYNAMIC_DRAW : 0x88E8,
+BUFFER_SIZE : 0x8764,
+BUFFER_USAGE : 0x8765,
+CURRENT_VERTEX_ATTRIB : 0x8626,
+FRONT : 0x0404,
+BACK : 0x0405,
+FRONT_AND_BACK : 0x0408,
+TEXTURE_2D : 0x0DE1,
+CULL_FACE : 0x0B44,
+BLEND : 0x0BE2,
+DITHER : 0x0BD0,
+STENCIL_TEST : 0x0B90,
+DEPTH_TEST : 0x0B71,
+SCISSOR_TEST : 0x0C11,
+POLYGON_OFFSET_FILL : 0x8037,
+SAMPLE_ALPHA_TO_COVERAGE : 0x809E,
+SAMPLE_COVERAGE : 0x80A0,
+NO_ERROR : 0,
+INVALID_ENUM : 0x0500,
+INVALID_VALUE : 0x0501,
+INVALID_OPERATION : 0x0502,
+OUT_OF_MEMORY : 0x0505,
+CW : 0x0900,
+CCW : 0x0901,
+LINE_WIDTH : 0x0B21,
+ALIASED_POINT_SIZE_RANGE : 0x846D,
+ALIASED_LINE_WIDTH_RANGE : 0x846E,
+CULL_FACE_MODE : 0x0B45,
+FRONT_FACE : 0x0B46,
+DEPTH_RANGE : 0x0B70,
+DEPTH_WRITEMASK : 0x0B72,
+DEPTH_CLEAR_VALUE : 0x0B73,
+DEPTH_FUNC : 0x0B74,
+STENCIL_CLEAR_VALUE : 0x0B91,
+STENCIL_FUNC : 0x0B92,
+STENCIL_FAIL : 0x0B94,
+STENCIL_PASS_DEPTH_FAIL : 0x0B95,
+STENCIL_PASS_DEPTH_PASS : 0x0B96,
+STENCIL_REF : 0x0B97,
+STENCIL_VALUE_MASK : 0x0B93,
+STENCIL_WRITEMASK : 0x0B98,
+STENCIL_BACK_FUNC : 0x8800,
+STENCIL_BACK_FAIL : 0x8801,
+STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802,
+STENCIL_BACK_PASS_DEPTH_PASS : 0x8803,
+STENCIL_BACK_REF : 0x8CA3,
+STENCIL_BACK_VALUE_MASK : 0x8CA4,
+STENCIL_BACK_WRITEMASK : 0x8CA5,
+VIEWPORT : 0x0BA2,
+SCISSOR_BOX : 0x0C10,
+COLOR_CLEAR_VALUE : 0x0C22,
+COLOR_WRITEMASK : 0x0C23,
+UNPACK_ALIGNMENT : 0x0CF5,
+PACK_ALIGNMENT : 0x0D05,
+MAX_TEXTURE_SIZE : 0x0D33,
+MAX_VIEWPORT_DIMS : 0x0D3A,
+SUBPIXEL_BITS : 0x0D50,
+RED_BITS : 0x0D52,
+GREEN_BITS : 0x0D53,
+BLUE_BITS : 0x0D54,
+ALPHA_BITS : 0x0D55,
+DEPTH_BITS : 0x0D56,
+STENCIL_BITS : 0x0D57,
+POLYGON_OFFSET_UNITS : 0x2A00,
+POLYGON_OFFSET_FACTOR : 0x8038,
+TEXTURE_BINDING_2D : 0x8069,
+SAMPLE_BUFFERS : 0x80A8,
+SAMPLES : 0x80A9,
+SAMPLE_COVERAGE_VALUE : 0x80AA,
+SAMPLE_COVERAGE_INVERT : 0x80AB,
+COMPRESSED_TEXTURE_FORMATS : 0x86A3,
+DONT_CARE : 0x1100,
+FASTEST : 0x1101,
+NICEST : 0x1102,
+GENERATE_MIPMAP_HINT : 0x8192,
+BYTE : 0x1400,
+UNSIGNED_BYTE : 0x1401,
+SHORT : 0x1402,
+UNSIGNED_SHORT : 0x1403,
+INT : 0x1404,
+UNSIGNED_INT : 0x1405,
+FLOAT : 0x1406,
+//FIXED : 0x140C,
+DEPTH_COMPONENT : 0x1902,
+ALPHA : 0x1906,
+RGB : 0x1907,
+RGBA : 0x1908,
+LUMINANCE : 0x1909,
+LUMINANCE_ALPHA : 0x190A,
+UNSIGNED_SHORT_4_4_4_4 : 0x8033,
+UNSIGNED_SHORT_5_5_5_1 : 0x8034,
+UNSIGNED_SHORT_5_6_5 : 0x8363,
+FRAGMENT_SHADER : 0x8B30,
+VERTEX_SHADER : 0x8B31,
+MAX_VERTEX_ATTRIBS : 0x8869,
+MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB,
+MAX_VARYING_VECTORS : 0x8DFC,
+MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D,
+MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C,
+MAX_TEXTURE_IMAGE_UNITS : 0x8872,
+MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD,
+SHADER_TYPE : 0x8B4F,
+DELETE_STATUS : 0x8B80,
+LINK_STATUS : 0x8B82,
+VALIDATE_STATUS : 0x8B83,
+ATTACHED_SHADERS : 0x8B85,
+ACTIVE_UNIFORMS : 0x8B86,
+ACTIVE_ATTRIBUTES : 0x8B89,
+SHADING_LANGUAGE_VERSION : 0x8B8C,
+CURRENT_PROGRAM : 0x8B8D,
+NEVER : 0x0200,
+LESS : 0x0201,
+EQUAL : 0x0202,
+LEQUAL : 0x0203,
+GREATER : 0x0204,
+NOTEQUAL : 0x0205,
+GEQUAL : 0x0206,
+ALWAYS : 0x0207,
+KEEP : 0x1E00,
+REPLACE : 0x1E01,
+INCR : 0x1E02,
+DECR : 0x1E03,
+INVERT : 0x150A,
+INCR_WRAP : 0x8507,
+DECR_WRAP : 0x8508,
+VENDOR : 0x1F00,
+RENDERER : 0x1F01,
+VERSION : 0x1F02,
+//EXTENSIONS : 0x1F03,
+NEAREST : 0x2600,
+LINEAR : 0x2601,
+NEAREST_MIPMAP_NEAREST : 0x2700,
+LINEAR_MIPMAP_NEAREST : 0x2701,
+NEAREST_MIPMAP_LINEAR : 0x2702,
+LINEAR_MIPMAP_LINEAR : 0x2703,
+TEXTURE_MAG_FILTER : 0x2800,
+TEXTURE_MIN_FILTER : 0x2801,
+TEXTURE_WRAP_S : 0x2802,
+TEXTURE_WRAP_T : 0x2803,
+TEXTURE : 0x1702,
+TEXTURE_CUBE_MAP : 0x8513,
+TEXTURE_BINDING_CUBE_MAP : 0x8514,
+TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515,
+TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516,
+TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517,
+TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518,
+TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519,
+TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A,
+MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C,
+TEXTURE0 : 0x84C0,
+TEXTURE1 : 0x84C1,
+TEXTURE2 : 0x84C2,
+TEXTURE3 : 0x84C3,
+TEXTURE4 : 0x84C4,
+TEXTURE5 : 0x84C5,
+TEXTURE6 : 0x84C6,
+TEXTURE7 : 0x84C7,
+TEXTURE8 : 0x84C8,
+TEXTURE9 : 0x84C9,
+TEXTURE10 : 0x84CA,
+TEXTURE11 : 0x84CB,
+TEXTURE12 : 0x84CC,
+TEXTURE13 : 0x84CD,
+TEXTURE14 : 0x84CE,
+TEXTURE15 : 0x84CF,
+TEXTURE16 : 0x84D0,
+TEXTURE17 : 0x84D1,
+TEXTURE18 : 0x84D2,
+TEXTURE19 : 0x84D3,
+TEXTURE20 : 0x84D4,
+TEXTURE21 : 0x84D5,
+TEXTURE22 : 0x84D6,
+TEXTURE23 : 0x84D7,
+TEXTURE24 : 0x84D8,
+TEXTURE25 : 0x84D9,
+TEXTURE26 : 0x84DA,
+TEXTURE27 : 0x84DB,
+TEXTURE28 : 0x84DC,
+TEXTURE29 : 0x84DD,
+TEXTURE30 : 0x84DE,
+TEXTURE31 : 0x84DF,
+ACTIVE_TEXTURE : 0x84E0,
+REPEAT : 0x2901,
+CLAMP_TO_EDGE : 0x812F,
+MIRRORED_REPEAT : 0x8370,
+FLOAT_VEC2 : 0x8B50,
+FLOAT_VEC3 : 0x8B51,
+FLOAT_VEC4 : 0x8B52,
+INT_VEC2 : 0x8B53,
+INT_VEC3 : 0x8B54,
+INT_VEC4 : 0x8B55,
+BOOL : 0x8B56,
+BOOL_VEC2 : 0x8B57,
+BOOL_VEC3 : 0x8B58,
+BOOL_VEC4 : 0x8B59,
+FLOAT_MAT2 : 0x8B5A,
+FLOAT_MAT3 : 0x8B5B,
+FLOAT_MAT4 : 0x8B5C,
+SAMPLER_2D : 0x8B5E,
+SAMPLER_CUBE : 0x8B60,
+VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622,
+VERTEX_ATTRIB_ARRAY_SIZE : 0x8623,
+VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624,
+VERTEX_ATTRIB_ARRAY_TYPE : 0x8625,
+VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A,
+VERTEX_ATTRIB_ARRAY_POINTER : 0x8645,
+VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F,
+//IMPLEMENTATION_COLOR_READ_TYPE : 0x8B9A,
+//IMPLEMENTATION_COLOR_READ_FORMAT : 0x8B9B,
+COMPILE_STATUS : 0x8B81,
+//SHADER_COMPILER : 0x8DFA,
+LOW_FLOAT : 0x8DF0,
+MEDIUM_FLOAT : 0x8DF1,
+HIGH_FLOAT : 0x8DF2,
+LOW_INT : 0x8DF3,
+MEDIUM_INT : 0x8DF4,
+HIGH_INT : 0x8DF5,
+FRAMEBUFFER : 0x8D40,
+RENDERBUFFER : 0x8D41,
+RGBA4 : 0x8056,
+RGB5_A1 : 0x8057,
+RGB565 : 0x8D62,
+DEPTH_COMPONENT16 : 0x81A5,
+STENCIL_INDEX : 0x1901,
+STENCIL_INDEX8 : 0x8D48,
+DEPTH_STENCIL : 0x84F9,
+RENDERBUFFER_WIDTH : 0x8D42,
+RENDERBUFFER_HEIGHT : 0x8D43,
+RENDERBUFFER_INTERNAL_FORMAT : 0x8D44,
+RENDERBUFFER_RED_SIZE : 0x8D50,
+RENDERBUFFER_GREEN_SIZE : 0x8D51,
+RENDERBUFFER_BLUE_SIZE : 0x8D52,
+RENDERBUFFER_ALPHA_SIZE : 0x8D53,
+RENDERBUFFER_DEPTH_SIZE : 0x8D54,
+RENDERBUFFER_STENCIL_SIZE : 0x8D55,
+FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0,
+FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1,
+FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2,
+FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3,
+COLOR_ATTACHMENT0 : 0x8CE0,
+DEPTH_ATTACHMENT : 0x8D00,
+STENCIL_ATTACHMENT : 0x8D20,
+DEPTH_STENCIL_ATTACHMENT : 0x821A,
+NONE : 0,
+FRAMEBUFFER_COMPLETE : 0x8CD5,
+FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6,
+FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7,
+FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9,
+FRAMEBUFFER_UNSUPPORTED : 0x8CDD,
+FRAMEBUFFER_BINDING : 0x8CA6,
+RENDERBUFFER_BINDING : 0x8CA7,
+MAX_RENDERBUFFER_SIZE : 0x84E8,
+INVALID_FRAMEBUFFER_OPERATION : 0x0506,
+//FALSE : 0,
+//TRUE : 1,
+UNPACK_FLIP_Y_WEBGL : 0x9240,
+UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241
+}
+
+Tests.testOES20Constants = function(gl) {
+  for (var i in constants) {
+    assertProperty(gl, i) &&
+    assertEquals(i, gl[i], constants[i]);
+  }
+  var extended = false;
+  for (var i in gl) {
+    if (i.match(/^[A-Z_]+$/) && constants[i] == null) {
+      if (!extended) {
+        extended = true;
+        var h = document.createElement('h3');
+        h.textContent = "Also found the following extra constants";
+        __testLog__.appendChild(h);
+      }
+      log(i);
+    }
+  }
+}
+
+Tests.startUnit = function() {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/conformance/fuzzTheAPI.html b/conformance/more/conformance/fuzzTheAPI.html
new file mode 100644
index 0000000..342a1b7
--- /dev/null
+++ b/conformance/more/conformance/fuzzTheAPI.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+
+<script type="application/x-javascript">
+
+Tests.autorun = false;
+Tests.message = "This will fuzz the API with random inputs for a [long] while."
+
+
+function randomCall(testProgress, gl, m, arities) {
+    doTestNotify(m);
+    var argcs = {};
+    var foundArity = false;
+    if (arities == null) {
+        testProgress.textContent += "            500 random arity calls on " + m + "\n";
+        for (var i=0; i<50; i++) {
+            for (var j=0; j<10; j++) {
+                var args = generateRandomArgs(j);
+//                 doTestNotify(m + ": " + args.toSource());
+                try {
+                    gl[m].apply(gl, args);
+                    argcs[j] = true;
+                    foundArity = true;
+                } catch (e) {
+                }
+            }
+        }
+        for (var j in argcs) {
+            testProgress.textContent += "200 arity " + j + " calls on " + m + "\n";
+            for (var i=0; i<200; i++) {
+                var args = generateRandomArgs(j);
+//                 doTestNotify(m + ": " + args.toSource());
+                try {
+                    gl[m].apply(gl, args);
+                    argcs[j] = true;
+                } catch (e) {
+                }
+            }
+        }
+    } else {
+        for (var k=0; k<arities.length; k++) {
+            var j = arities[k];
+            testProgress.textContent += "500 arity " + j + " calls on " + m + "\n";
+            for (var i=0; i<500; i++) {
+                var args = generateRandomArgs(j);
+//                 doTestNotify(m + ": " + args.toSource());
+                try {
+                    gl[m].apply(gl, args);
+                } catch (e) {
+                }
+            }
+        }
+    }
+//     doTestNotify(m+"--OK");
+}
+
+Tests.testGetGLWeb20 = function() {
+    var testProgress = document.getElementById('testProgress');
+    var funcnames = document.getElementById('funcnames').value.split(",");
+    var canvas = document.getElementById('glweb20');
+    var gl;
+    assertOk(function(){gl = getGLContext(canvas);});
+    var funcs = [];
+    for (var i=0; i<funcnames.length; i++) {
+        var fn = funcnames[i];
+        fn = fn.replace(/^\s+|\s+$/g, '');
+        if (fn.length > 0)
+            funcs.push(fn);
+    }
+    if (funcs.length == 0) {
+        for (var m in gl)
+            try{if (typeof gl[m] == 'function')
+                funcs.push(m);}catch(e){}
+    }
+    var idx = 0;
+    var count = 0;
+    var limit = 1000;
+    var iv1;
+    var iv2;
+    iv1 = setInterval(function(){
+        if (idx >= funcs.length) {
+            iv2 = setInterval(function(){
+                if (count >= limit) {
+                    clearInterval(iv2);
+                    return false;
+                }
+                count++;
+                var f = Math.floor(Math.random() * funcs.length);
+                randomCall(testProgress, gl, funcs[f]);
+            }, 50);
+            clearInterval(iv1);
+            return false;
+        }
+        randomCall(testProgress, gl, funcs[idx]);
+        idx++;
+    }, 50);
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="glweb20" width="16" height="16"></canvas>
+  <p>Optional: Enter a comma-separated list of functions to fuzz (leave blank for full API)</p>
+  <input type="text" size="40" id="funcnames" value="">
+  <pre id="testProgress"></pre>
+</body></html>
diff --git a/conformance/more/conformance/getContext.html b/conformance/more/conformance/getContext.html
new file mode 100644
index 0000000..b513915
--- /dev/null
+++ b/conformance/more/conformance/getContext.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+
+<script type="application/x-javascript">
+
+Tests.testGetWebGL = function() {
+  var canvas = document.getElementById('webgl');
+  var gl;
+  assertOk(function(){gl = getGLContext(canvas)});
+  assertEquals("ctx.canvas === canvas", gl.canvas, canvas);
+  assertOk(function(){g2d = canvas.getContext('2d')});
+  assert("Should get a null 2d context after getting webgl context", g2d === null);
+}
+
+Tests.testGet2D = function() {
+  var canvas = document.getElementById('2d');
+  var g2d;
+  var gl;
+  assertOk(function(){g2d = canvas.getContext('2d')});
+  assertOk(function(){gl = getGLContext(canvas)});
+  assert("Should get a null WebGL context after getting 2D context", gl === null);
+}
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+  <canvas id="2d" width="1" height="1"></canvas>
+  <canvas id="webgl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/conformance/methods.html b/conformance/more/conformance/methods.html
new file mode 100644
index 0000000..d466fed
--- /dev/null
+++ b/conformance/more/conformance/methods.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+/*
+  The following tests are generated from
+  http://www.khronos.org/registry/gles/api/2.0/gl2.h
+  and api_modifications.txt
+*/
+var methods = ['canvas',
+"activeTexture",
+"attachShader",
+"bindAttribLocation",
+"bindBuffer",
+"bindFramebuffer",
+"bindRenderbuffer",
+"bindTexture",
+"blendColor",
+"blendEquation",
+"blendEquationSeparate",
+"blendFunc",
+"blendFuncSeparate",
+"bufferData",
+"bufferSubData",
+"checkFramebufferStatus",
+"clear",
+"clearColor",
+"clearDepth",
+"clearStencil",
+"colorMask",
+"compileShader",
+"copyTexImage2D",
+"copyTexSubImage2D",
+"createProgram",
+"createShader",
+"cullFace",
+"deleteBuffer",
+"deleteFramebuffer",
+"deleteProgram",
+"deleteRenderbuffer",
+"deleteShader",
+"deleteTexture",
+"depthFunc",
+"depthMask",
+"depthRange",
+"detachShader",
+"disable",
+"disableVertexAttribArray",
+"drawArrays",
+"drawElements",
+"enable",
+"enableVertexAttribArray",
+"finish",
+"flush",
+"framebufferRenderbuffer",
+"framebufferTexture2D",
+"frontFace",
+"createBuffer",
+"generateMipmap",
+"createFramebuffer",
+"createRenderbuffer",
+"createTexture",
+"getActiveAttrib",
+"getActiveUniform",
+"getAttachedShaders",
+"getAttribLocation",
+"getBufferParameter",
+"getError",
+"getExtension",
+"getSupportedExtensions",
+"getFramebufferAttachmentParameter",
+"getProgramInfoLog",
+"getRenderbufferParameter",
+"getShaderInfoLog",
+"getShaderSource",
+"getUniformLocation",
+"hint",
+"isBuffer",
+"isContextLost",
+"isEnabled",
+"isFramebuffer",
+"isProgram",
+"isRenderbuffer",
+"isShader",
+"isTexture",
+"lineWidth",
+"linkProgram",
+"polygonOffset",
+"readPixels",
+"renderbufferStorage",
+"sampleCoverage",
+"scissor",
+"shaderSource",
+"stencilFunc",
+"stencilFuncSeparate",
+"stencilMask",
+"stencilMaskSeparate",
+"stencilOp",
+"stencilOpSeparate",
+"texImage2D",
+"texParameteri",
+"texSubImage2D",
+"uniform1f",
+"uniform1fv",
+"uniform1i",
+"uniform1iv",
+"uniform2f",
+"uniform2fv",
+"uniform2i",
+"uniform2iv",
+"uniform3f",
+"uniform3fv",
+"uniform3i",
+"uniform3iv",
+"uniform4f",
+"uniform4fv",
+"uniform4i",
+"uniform4iv",
+"uniformMatrix2fv",
+"uniformMatrix3fv",
+"uniformMatrix4fv",
+"useProgram",
+"validateProgram",
+"vertexAttrib1f",
+"vertexAttrib1fv",
+"vertexAttrib2f",
+"vertexAttrib2fv",
+"vertexAttrib3f",
+"vertexAttrib3fv",
+"vertexAttrib4f",
+"vertexAttrib4fv",
+"vertexAttribPointer",
+"viewport",
+"getParameter",
+"getProgramParameter",
+"getShaderParameter",
+"getTexParameter",
+"getUniform",
+"getVertexAttrib"
+]
+
+Tests.testOES20Methods = function(gl) {
+  for (var i=0; i<methods.length; i++) {
+    assertProperty(gl, methods[i]);
+  }
+  var extended = false;
+  for (var i in gl) {
+    if (i.match(/^[a-z_]+$/) && methods.indexOf(i) == -1) {
+      if (!extended) {
+        extended = true;
+        var h = document.createElement('h3');
+        h.textContent = "Also found the following extra properties";
+        __testLog__.appendChild(h);
+      }
+      log(i);
+    }
+  }
+}
+
+Tests.startUnit = function() {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-A.html b/conformance/more/conformance/quickCheckAPI-A.html
new file mode 100644
index 0000000..184eec5
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-A.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-A.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-B1.html b/conformance/more/conformance/quickCheckAPI-B1.html
new file mode 100644
index 0000000..b7e4128
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-B1.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-B1.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-B2.html b/conformance/more/conformance/quickCheckAPI-B2.html
new file mode 100644
index 0000000..995e9b4
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-B2.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-B2.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-B3.html b/conformance/more/conformance/quickCheckAPI-B3.html
new file mode 100644
index 0000000..7b7e430
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-B3.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-B3.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-B4.html b/conformance/more/conformance/quickCheckAPI-B4.html
new file mode 100644
index 0000000..d7d08ad
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-B4.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-B4.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-C.html b/conformance/more/conformance/quickCheckAPI-C.html
new file mode 100644
index 0000000..5ce9b7d
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-C.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-C.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-D_G.html b/conformance/more/conformance/quickCheckAPI-D_G.html
new file mode 100644
index 0000000..2ec4d8f
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-D_G.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-D_G.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-G_I.html b/conformance/more/conformance/quickCheckAPI-G_I.html
new file mode 100644
index 0000000..53eae01
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-G_I.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-G_I.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-L_S.html b/conformance/more/conformance/quickCheckAPI-L_S.html
new file mode 100644
index 0000000..c785910
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-L_S.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-L_S.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI-S_V.html b/conformance/more/conformance/quickCheckAPI-S_V.html
new file mode 100644
index 0000000..36b7a6c
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI-S_V.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+<script type="application/x-javascript" src="argGenerators-S_V.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators work
+// when called with randomly generated valid arguments
+Tests.testValidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        if (!gen.noAlreadyTriedCheck) {
+          var src = Object.toSource(args);
+          if (alreadyTriedArgs[src])
+            return true;
+
+          alreadyTriedArgs[src] = true;
+        }
+        var ok = false;
+        // if we have an validity checker, assert that the generated args are valid
+        if (gen.checkArgValidity)
+          assert("Valid args: "+name+"("+argsToString(args)+")",
+                gen.checkArgValidity.apply(gen, args));
+        var rv;
+        // assert that GL function works when called with valid args
+        assertOk("This should work: "+name+"("+argsToString(args)+")",
+                function(){rv = GL[name].apply(GL, args); ok = true;});
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup)
+          assertOk("Cleaning up return value after "+name+"("+argsToString(args)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        return ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/quickCheckAPI.js b/conformance/more/conformance/quickCheckAPI.js
new file mode 100644
index 0000000..b39feca
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPI.js
@@ -0,0 +1,430 @@
+/*
+** 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.
+*/
+
+/*
+  QuickCheck tests for WebGL:
+
+    1. Write a valid arg generator for each function
+      1.1. Write valid arg predicates to use with random generator:
+            if value passes generator, accept it as valid.
+      1.2. Often needs initializing and cleanup:
+            setup - generate - cleanup
+            gl.createBuffer - test(bindBufferGenerator) - gl.deleteBuffer
+
+    2. Write an invalid arg generator
+      2.1. Take valid args, modify an arg until the args no longer pass
+            checkArgValidity.
+      2.2. Repeat for all args.
+
+    3. Test functions using the generators
+      3.1. Args generated with the valid arg generator should pass
+            assertOk(f(args))
+      3.2. Args generated with the invalid arg generator should pass
+            assertFail(f(args))
+*/
+var GLcanvas = document.createElement('canvas');
+var canvas2D = document.createElement('canvas');
+GLcanvas.width = GLcanvas.height = 256;
+GL = getGLContext(GLcanvas);
+Array.from = function(o) {
+  var a = [];
+  for (var i=0; i<o.length; i++)
+    a.push(o[i]);
+  return a;
+}
+Array.prototype.has = function(v) { return this.indexOf(v) != -1; }
+Array.prototype.random = function() { return this[randomInt(this.length)]; }
+
+castToInt = function(o) {
+  if (typeof o == 'number')
+    return isNaN(o) ? 0 : Math.floor(o);
+  if (o == true) return 1;
+  return 0;
+};
+
+// Creates a constant checker / generator from its arguments.
+//
+// E.g. if you want a constant checker for the constants 1, 2, and 3, you
+// would do the following:
+//
+//   var cc = constCheck(1,2,3);
+//   var randomConst = cc.random();
+//   if (cc.has(randomConst))
+//     console.log("randomConst is included in cc's constants");
+//
+constCheck = function() {
+  var a = Array.from(arguments);
+  a.has = function(v) { return this.indexOf(castToInt(v)) != -1; };
+  return a;
+}
+
+bindTextureTarget = constCheck(GL.TEXTURE_2D, GL.TEXTURE_CUBE_MAP);
+blendEquationMode = constCheck(GL.FUNC_ADD, GL.FUNC_SUBTRACT, GL.FUNC_REVERSE_SUBTRACT);
+blendFuncSfactor = constCheck(
+  GL.ZERO, GL.ONE, GL.SRC_COLOR, GL.ONE_MINUS_SRC_COLOR, GL.DST_COLOR,
+  GL.ONE_MINUS_DST_COLOR, GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.DST_ALPHA,
+  GL.ONE_MINUS_DST_ALPHA, GL.CONSTANT_COLOR, GL.ONE_MINUS_CONSTANT_COLOR,
+  GL.CONSTANT_ALPHA, GL.ONE_MINUS_CONSTANT_ALPHA, GL.SRC_ALPHA_SATURATE
+);
+blendFuncDfactor = constCheck(
+  GL.ZERO, GL.ONE, GL.SRC_COLOR, GL.ONE_MINUS_SRC_COLOR, GL.DST_COLOR,
+  GL.ONE_MINUS_DST_COLOR, GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.DST_ALPHA,
+  GL.ONE_MINUS_DST_ALPHA, GL.CONSTANT_COLOR, GL.ONE_MINUS_CONSTANT_COLOR,
+  GL.CONSTANT_ALPHA, GL.ONE_MINUS_CONSTANT_ALPHA
+);
+bufferTarget = constCheck(GL.ARRAY_BUFFER, GL.ELEMENT_ARRAY_BUFFER);
+bufferMode = constCheck(GL.STREAM_DRAW, GL.STATIC_DRAW, GL.DYNAMIC_DRAW);
+clearMask = constCheck(
+  GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT,
+  GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT,
+  GL.COLOR_BUFFER_BIT | GL.STENCIL_BUFFER_BIT,
+  GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT,
+  GL.COLOR_BUFFER_BIT, GL.DEPTH_BUFFER_BIT, GL.STENCIL_BUFFER_BIT, 0
+);
+cullFace = constCheck(GL.FRONT, GL.BACK, GL.FRONT_AND_BACK);
+depthFuncFunc = constCheck(
+  GL.NEVER, GL.LESS, GL.EQUAL, GL.LEQUAL, GL.GREATER, GL.NOTEQUAL,
+  GL.GEQUAL, GL.ALWAYS
+);
+stencilFuncFunc = depthFuncFunc;
+enableCap = constCheck(
+  GL.BLEND, GL.CULL_FACE, GL.DEPTH_TEST, GL.DITHER, GL.POLYGON_OFFSET_FILL,
+  GL.SAMPLE_ALPHA_TO_COVERAGE, GL.SAMPLE_COVERAGE, GL.SCISSOR_TEST,
+  GL.STENCIL_TEST
+);
+frontFaceMode = constCheck(GL.CCW, GL.CW);
+getParameterPname = constCheck(
+  GL.ACTIVE_TEXTURE || "GL.ACTIVE_TEXTURE",
+  GL.ALIASED_LINE_WIDTH_RANGE || "GL.ALIASED_LINE_WIDTH_RANGE",
+  GL.ALIASED_POINT_SIZE_RANGE || "GL.ALIASED_POINT_SIZE_RANGE",
+  GL.ALPHA_BITS || "GL.ALPHA_BITS",
+  GL.ARRAY_BUFFER_BINDING || "GL.ARRAY_BUFFER_BINDING",
+  GL.BLEND || "GL.BLEND",
+  GL.BLEND_COLOR || "GL.BLEND_COLOR",
+  GL.BLEND_DST_ALPHA || "GL.BLEND_DST_ALPHA",
+  GL.BLEND_DST_RGB || "GL.BLEND_DST_RGB",
+  GL.BLEND_EQUATION_ALPHA || "GL.BLEND_EQUATION_ALPHA",
+  GL.BLEND_EQUATION_RGB || "GL.BLEND_EQUATION_RGB",
+  GL.BLEND_SRC_ALPHA || "GL.BLEND_SRC_ALPHA",
+  GL.BLEND_SRC_RGB || "GL.BLEND_SRC_RGB",
+  GL.BLUE_BITS || "GL.BLUE_BITS",
+  GL.COLOR_CLEAR_VALUE || "GL.COLOR_CLEAR_VALUE",
+  GL.COLOR_WRITEMASK || "GL.COLOR_WRITEMASK",
+  GL.COMPRESSED_TEXTURE_FORMATS || "GL.COMPRESSED_TEXTURE_FORMATS",
+  GL.CULL_FACE || "GL.CULL_FACE",
+  GL.CULL_FACE_MODE || "GL.CULL_FACE_MODE",
+  GL.CURRENT_PROGRAM || "GL.CURRENT_PROGRAM",
+  GL.DEPTH_BITS || "GL.DEPTH_BITS",
+  GL.DEPTH_CLEAR_VALUE || "GL.DEPTH_CLEAR_VALUE",
+  GL.DEPTH_FUNC || "GL.DEPTH_FUNC",
+  GL.DEPTH_RANGE || "GL.DEPTH_RANGE",
+  GL.DEPTH_TEST || "GL.DEPTH_TEST",
+  GL.DEPTH_WRITEMASK || "GL.DEPTH_WRITEMASK",
+  GL.DITHER || "GL.DITHER",
+  GL.ELEMENT_ARRAY_BUFFER_BINDING || "GL.ELEMENT_ARRAY_BUFFER_BINDING",
+  GL.FRAMEBUFFER_BINDING || "GL.FRAMEBUFFER_BINDING",
+  GL.FRONT_FACE || "GL.FRONT_FACE",
+  GL.GENERATE_MIPMAP_HINT || "GL.GENERATE_MIPMAP_HINT",
+  GL.GREEN_BITS || "GL.GREEN_BITS",
+  GL.LINE_WIDTH || "GL.LINE_WIDTH",
+  GL.MAX_COMBINED_TEXTURE_IMAGE_UNITS || "GL.MAX_COMBINED_TEXTURE_IMAGE_UNITS",
+  GL.MAX_CUBE_MAP_TEXTURE_SIZE || "GL.MAX_CUBE_MAP_TEXTURE_SIZE",
+  GL.MAX_FRAGMENT_UNIFORM_VECTORS || "GL.MAX_FRAGMENT_UNIFORM_VECTORS",
+  GL.MAX_RENDERBUFFER_SIZE || "GL.MAX_RENDERBUFFER_SIZE",
+  GL.MAX_TEXTURE_IMAGE_UNITS || "GL.MAX_TEXTURE_IMAGE_UNITS",
+  GL.MAX_TEXTURE_SIZE || "GL.MAX_TEXTURE_SIZE",
+  GL.MAX_VARYING_VECTORS || "GL.MAX_VARYING_VECTORS",
+  GL.MAX_VERTEX_ATTRIBS || "GL.MAX_VERTEX_ATTRIBS",
+  GL.MAX_VERTEX_TEXTURE_IMAGE_UNITS || "GL.MAX_VERTEX_TEXTURE_IMAGE_UNITS",
+  GL.MAX_VERTEX_UNIFORM_VECTORS || "GL.MAX_VERTEX_UNIFORM_VECTORS",
+  GL.MAX_VIEWPORT_DIMS || "GL.MAX_VIEWPORT_DIMS",
+  GL.PACK_ALIGNMENT || "GL.PACK_ALIGNMENT",
+  GL.POLYGON_OFFSET_FACTOR || "GL.POLYGON_OFFSET_FACTOR",
+  GL.POLYGON_OFFSET_FILL || "GL.POLYGON_OFFSET_FILL",
+  GL.POLYGON_OFFSET_UNITS || "GL.POLYGON_OFFSET_UNITS",
+  GL.RED_BITS || "GL.RED_BITS",
+  GL.RENDERBUFFER_BINDING || "GL.RENDERBUFFER_BINDING",
+  GL.SAMPLE_BUFFERS || "GL.SAMPLE_BUFFERS",
+  GL.SAMPLE_COVERAGE_INVERT || "GL.SAMPLE_COVERAGE_INVERT",
+  GL.SAMPLE_COVERAGE_VALUE || "GL.SAMPLE_COVERAGE_VALUE",
+  GL.SAMPLES || "GL.SAMPLES",
+  GL.SCISSOR_BOX || "GL.SCISSOR_BOX",
+  GL.SCISSOR_TEST || "GL.SCISSOR_TEST",
+  GL.STENCIL_BACK_FAIL || "GL.STENCIL_BACK_FAIL",
+  GL.STENCIL_BACK_FUNC || "GL.STENCIL_BACK_FUNC",
+  GL.STENCIL_BACK_PASS_DEPTH_FAIL || "GL.STENCIL_BACK_PASS_DEPTH_FAIL",
+  GL.STENCIL_BACK_PASS_DEPTH_PASS || "GL.STENCIL_BACK_PASS_DEPTH_PASS",
+  GL.STENCIL_BACK_REF || "GL.STENCIL_BACK_REF",
+  GL.STENCIL_BACK_VALUE_MASK || "GL.STENCIL_BACK_VALUE_MASK",
+  GL.STENCIL_BACK_WRITEMASK || "GL.STENCIL_BACK_WRITEMASK",
+  GL.STENCIL_BITS || "GL.STENCIL_BITS",
+  GL.STENCIL_CLEAR_VALUE || "GL.STENCIL_CLEAR_VALUE",
+  GL.STENCIL_FAIL || "GL.STENCIL_FAIL",
+  GL.STENCIL_FUNC || "GL.STENCIL_FUNC",
+  GL.STENCIL_PASS_DEPTH_FAIL || "GL.STENCIL_PASS_DEPTH_FAIL",
+  GL.STENCIL_PASS_DEPTH_PASS || "GL.STENCIL_PASS_DEPTH_PASS",
+  GL.STENCIL_REF || "GL.STENCIL_REF",
+  GL.STENCIL_TEST || "GL.STENCIL_TEST",
+  GL.STENCIL_VALUE_MASK || "GL.STENCIL_VALUE_MASK",
+  GL.STENCIL_WRITEMASK || "GL.STENCIL_WRITEMASK",
+  GL.SUBPIXEL_BITS || "GL.SUBPIXEL_BITS",
+  GL.TEXTURE_BINDING_2D || "GL.TEXTURE_BINDING_2D",
+  GL.TEXTURE_BINDING_CUBE_MAP || "GL.TEXTURE_BINDING_CUBE_MAP",
+  GL.UNPACK_ALIGNMENT || "GL.UNPACK_ALIGNMENT",
+  GL.VIEWPORT || "GL.VIEWPORT"
+);
+mipmapHint = constCheck(GL.FASTEST, GL.NICEST, GL.DONT_CARE);
+pixelStoreiPname = constCheck(GL.PACK_ALIGNMENT, GL.UNPACK_ALIGNMENT);
+pixelStoreiParam = constCheck(1,2,4,8);
+shaderType = constCheck(GL.VERTEX_SHADER, GL.FRAGMENT_SHADER);
+stencilOp = constCheck(GL.KEEP, GL.ZERO, GL.REPLACE, GL.INCR, GL.INCR_WRAP,
+                        GL.DECR, GL.DECR_WRAP, GL.INVERT);
+texImageTarget = constCheck(
+  GL.TEXTURE_2D,
+  GL.TEXTURE_CUBE_MAP_POSITIVE_X,
+  GL.TEXTURE_CUBE_MAP_NEGATIVE_X,
+  GL.TEXTURE_CUBE_MAP_POSITIVE_Y,
+  GL.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+  GL.TEXTURE_CUBE_MAP_POSITIVE_Z,
+  GL.TEXTURE_CUBE_MAP_NEGATIVE_Z
+);
+texImageInternalFormat = constCheck(
+  GL.ALPHA,
+  GL.LUMINANCE,
+  GL.LUMINANCE_ALPHA,
+  GL.RGB,
+  GL.RGBA
+);
+texImageFormat = constCheck(
+  GL.ALPHA,
+  GL.LUMINANCE,
+  GL.LUMINANCE_ALPHA,
+  GL.RGB,
+  GL.RGBA
+);
+texImageType = constCheck(GL.UNSIGNED_BYTE);
+texParameterPname = constCheck(
+  GL.TEXTURE_MIN_FILTER, GL.TEXTURE_MAG_FILTER,
+  GL.TEXTURE_WRAP_S, GL.TEXTURE_WRAP_T);
+texParameterParam = {};
+texParameterParam[GL.TEXTURE_MIN_FILTER] = constCheck(
+  GL.NEAREST, GL.LINEAR, GL.NEAREST_MIPMAP_NEAREST, GL.LINEAR_MIPMAP_NEAREST,
+  GL.NEAREST_MIPMAP_LINEAR, GL.LINEAR_MIPMAP_LINEAR);
+texParameterParam[GL.TEXTURE_MAG_FILTER] = constCheck(GL.NEAREST, GL.LINEAR);
+texParameterParam[GL.TEXTURE_WRAP_S] = constCheck(
+  GL.CLAMP_TO_EDGE, GL.MIRRORED_REPEAT, GL.REPEAT);
+texParameterParam[GL.TEXTURE_WRAP_T] = texParameterParam[GL.TEXTURE_WRAP_S];
+textureUnit = constCheck.apply(this, (function(){
+  var textureUnits = [];
+  var texUnits = GL.getParameter(GL.MAX_TEXTURE_IMAGE_UNITS);
+  for (var i=0; i<texUnits; i++) textureUnits.push(GL['TEXTURE'+i]);
+  return textureUnits;
+})());
+
+var StencilBits = GL.getParameter(GL.STENCIL_BITS);
+var MaxStencilValue = 1 << StencilBits;
+
+var MaxVertexAttribs = GL.getParameter(GL.MAX_VERTEX_ATTRIBS);
+var LineWidthRange = GL.getParameter(GL.ALIASED_LINE_WIDTH_RANGE);
+
+// Returns true if bufData can be passed to GL.bufferData
+isBufferData = function(bufData) {
+  if (typeof bufData == 'number')
+    return bufData >= 0;
+  if (bufData instanceof ArrayBuffer)
+    return true;
+  return WebGLArrayTypes.some(function(t) {
+    return bufData instanceof t;
+  });
+};
+
+isVertexAttribute = function(idx) {
+  if (typeof idx != 'number') return false;
+  return idx >= 0 && idx < MaxVertexAttribs;
+};
+
+isValidName = function(name) {
+  if (typeof name != 'string') return false;
+  for (var i=0; i<name.length; i++) {
+    var c = name.charCodeAt(i);
+    if (c & 0x00FF == 0 || c & 0xFF00 == 0) {
+      return false;
+    }
+  }
+  return true;
+};
+
+WebGLArrayTypes = [
+  Float32Array,
+  Int32Array,
+  Int16Array,
+  Int8Array,
+  Uint32Array,
+  Uint16Array,
+  Uint8Array
+];
+webGLArrayContentGenerators = [randomLength, randomSmallIntArray];
+randomWebGLArray = function() {
+  var t = WebGLArrayTypes.random();
+  return new t(webGLArrayContentGenerators.random()());
+};
+
+randomArrayBuffer = function(buflen) {
+  if (buflen == null) buflen = 256;
+  var len = randomInt(buflen)+1;
+  var rv;
+  try {
+    rv = new ArrayBuffer(len);
+  } catch(e) {
+    log("Error creating ArrayBuffer with length " + len);
+    throw(e);
+  }
+  return rv;
+};
+
+bufferDataGenerators = [randomLength, randomWebGLArray, randomArrayBuffer];
+randomBufferData = function() {
+  return bufferDataGenerators.random()();
+};
+
+randomSmallWebGLArray = function(buflen) {
+  var t = WebGLArrayTypes.random();
+  return new t(randomInt(buflen/4)+1);
+};
+
+bufferSubDataGenerators = [randomSmallWebGLArray, randomArrayBuffer];
+randomBufferSubData = function(buflen) {
+  var data = bufferSubDataGenerators.random()(buflen);
+  var offset = randomInt(buflen - data.byteLength);
+  return {data:data, offset:offset};
+};
+
+randomColor = function() {
+  return [Math.random(), Math.random(), Math.random(), Math.random()];
+};
+
+randomName = function() {
+  var arr = [];
+  var len = randomLength()+1;
+  for (var i=0; i<len; i++) {
+    var l = randomInt(255)+1;
+    var h = randomInt(255)+1;
+    var c = (h << 8) | l;
+    arr.push(String.fromCharCode(c));
+  }
+  return arr.join('');
+};
+randomVertexAttribute = function() {
+  return randomInt(MaxVertexAttribs);
+};
+
+randomBool = function() { return Math.random() > 0.5; };
+
+randomStencil = function() {
+  return randomInt(MaxStencilValue);
+};
+
+randomLineWidth = function() {
+  var lo = LineWidthRange[0],
+      hi = LineWidthRange[1];
+  return randomFloatFromRange(lo, hi);
+};
+
+randomImage = function(w,h) {
+  var img;
+  var r = Math.random();
+  if (r < 0.25) {
+    img = document.createElement('canvas');
+    img.width = w; img.height = h;
+    img.getContext('2d').fillRect(0,0,w,h);
+  } else if (r < 0.5) {
+    img = document.createElement('video');
+    img.width = w; img.height = h;
+  } else if (r < 0.75) {
+    img = document.createElement('img');
+    img.width = w; img.height = h;
+  } else {
+    img = canvas2D.getContext('2d').createImageData(w,h);
+  }
+  return img
+};
+
+mutateArgs = function(args) {
+  var mutateCount = randomIntFromRange(1, args.length);
+  var newArgs = Array.from(args);
+  for (var i=0; i<mutateCount; i++) {
+    var idx = randomInt(args.length);
+    newArgs[idx] = generateRandomArg(idx, args.length);
+  }
+  return newArgs;
+};
+
+// Calls testFunction numberOfTests times with arguments generated by
+// argGen.generate() (or empty arguments if no generate present).
+//
+// The arguments testFunction is called with are the generated args,
+// the argGen, and what argGen.setup() returned or [] if argGen has not setup
+// method. I.e. testFunction(generatedArgs, argGen, setupVars).
+//
+argGeneratorTestRunner = function(argGen, testFunction, numberOfTests) {
+  // do argument generator setup if needed
+  var setupVars = argGen.setup ? argGen.setup() : [];
+  var error;
+  for (var i=0; i<numberOfTests; i++) {
+    var failed = false;
+    // generate arguments if argGen has a generate method
+    var generatedArgs = argGen.generate ? argGen.generate.apply(argGen, setupVars) : [];
+    try {
+      // call testFunction with the generated args
+      testFunction.call(this, generatedArgs, argGen, setupVars);
+    } catch (e) {
+      failed = true;
+      error = e;
+    }
+    // if argGen needs cleanup for generated args, do it here
+    if (argGen.cleanup)
+      argGen.cleanup.apply(argGen, generatedArgs);
+    if (failed) break;
+  }
+  // if argGen needs to do a final cleanup for setupVars, do it here
+  if (argGen.teardown)
+    argGen.teardown.apply(argGen, setupVars);
+  if (error) throw(error);
+};
+
+// TODO: Remove this
+// WebKit or at least Chrome is really slow at laying out strings with
+// unprintable characters. Without this tests can take 30-90 seconds.
+// With this they're instant.
+sanitize = function(str) {
+  var newStr = [];
+  for (var ii = 0; ii < str.length; ++ii) {
+    var c = str.charCodeAt(ii);
+    newStr.push((c > 31 && c < 128) ? str[ii] : "?");
+  }
+  return newStr.join('');
+};
+
+argsToString = function(args) {
+  return sanitize(args.map(function(a){return Object.toSource(a)}).join(","));
+};
diff --git a/conformance/more/conformance/quickCheckAPIBadArgs.html b/conformance/more/conformance/quickCheckAPIBadArgs.html
new file mode 100644
index 0000000..785a982
--- /dev/null
+++ b/conformance/more/conformance/quickCheckAPIBadArgs.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript" src="quickCheckAPI.js"></script>
+
+<script type="application/x-javascript">
+
+// Test that all GL functions specified in ArgGenerators fail
+// when called with randomly generated invalid arguments
+// Works only on tests with checkArgValidity defined
+Tests.testInvalidArgs = function() {
+  var randomTestCount = 100;
+  for (var name in ArgGenerators) {
+    try {
+      if (!GL[name])
+        throw (new Error(name + " is missing from the WebGL context"));
+      var argGen = ArgGenerators[name];
+      var alreadyTriedArgs = {};
+      if (!argGen.generate || !argGen.checkArgValidity) continue;
+      // test each GL function with randomTestCount randomly generated valid args
+      argGeneratorTestRunner(argGen, function(args, gen, setupVars) {
+        var mutatedArgs;
+        var foundArgs = false;
+        for (var j=0; j<100; j++) {
+          mutatedArgs = mutateArgs(args);
+          var validArgs = false;
+          try {
+            validArgs = gen.checkArgValidity.apply(gen, mutatedArgs);
+          } catch(e) {}
+          if (!validArgs) {
+            if (gen.noAlreadyTriedCheck) {
+              foundArgs = true;
+              break; // found new invalid args
+            }
+            var src = Object.toSource(mutatedArgs);
+            if (!alreadyTriedArgs[src]) {
+              alreadyTriedArgs[src] = true;
+              foundArgs = true;
+              break; // found new invalid args
+            }
+          }
+        }
+        if (!foundArgs)
+          return true;
+        var ok = false;
+        var rv;
+        // assert that GL function fails when called with invalid args
+        assertFail("This should fail: "+name+"("+argsToString(mutatedArgs)+")",
+          function(){
+            GL.getError(); // clear off existing error
+            rv = GL[name].apply(GL, mutatedArgs);
+            throwError(GL, name);
+            ok = true;
+          });
+        // if we need to cleanup the return value, do it here
+        // e.g. calling gl.deleteBuffer(rv) after testing gl.createBuffer() above
+        if (gen.returnValueCleanup && rv != null) {
+          assertOk("Cleaning up return value after "+name+"("+argsToString(mutatedArgs)+")",
+                   function() { gen.returnValueCleanup(rv); });
+        }
+        GL.getError();
+        return !ok;
+      }, argGen.testCount || randomTestCount);
+    } catch(e) {
+      testFailed(name, e.name, formatError(e));
+    }
+  }
+}
+
+</script>
+<style>canvas{position:absolute;}</style>
+</head><body>
+</body></html>
diff --git a/conformance/more/conformance/webGLArrays.html b/conformance/more/conformance/webGLArrays.html
new file mode 100644
index 0000000..26a68e4
--- /dev/null
+++ b/conformance/more/conformance/webGLArrays.html
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+
+<script type="application/x-javascript">
+
+function assertIdxs(name, arr, length) {
+//   assertOk(name+": Read with negative idx should work", function(){ return arr[-1] });
+//   assertOk(name+": Read with too large idx should work", function(){ return arr[length] });
+//   assertOk(name+": Write with negative idx should work", function(){ arr[-1] = 0 });
+//   assertOk(name+": Write with too large idx should work", function(){ arr[length] = 0 });
+//   arr[0] = 2;
+//   assertEquals(name+": Test that write worked", 2, arr[0]);
+//   assertOk(name+": Write with bad value should work", function(){ arr[0] = {x:"foo"} });
+//   assertEquals(name+": Test that bad write didn't work", 2, arr[0]);
+  assertOk(name+": Read and writes with OK idxs should work", function(){
+    for (var i=0; i<length; i++) arr[i] = i + 1;
+    for (var i=0; i<length; i++) arr[i] = arr[i] + 1;
+    for (var i=0; i<length; i++) assertEquals(name+": Test that reads and writes work", i+2, arr[i]);
+  });
+}
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  prog.uniform4f('c', 255, 0, 0, 255);
+  va = prog.attrib('Vertex');
+  buffer = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+  return [gl];
+}
+
+Tests.endUnit = function() {
+  prog.destroy();
+}
+
+Tests.testCreateFromArray = function() {
+  var a = new Float32Array([1,2,3,4,5,6]);
+  assertIdxs('Float', a, 6);
+  var a = new Int32Array([1,2,3,4,5,6]);
+  assertIdxs('Int', a, 6);
+  var a = new Int16Array([1,2,3,4,5,6]);
+  assertIdxs('Short', a, 6);
+  var a = new Int8Array([1,2,3,4,5,6]);
+  assertIdxs('Byte', a, 6);
+  var a = new Uint32Array([1,2,3,4,5,6]);
+  assertIdxs('UInt', a, 6);
+  var a = new Uint16Array([1,2,3,4,5,6]);
+  assertIdxs('UShort', a, 6);
+  var a = new Uint8Array([1,2,3,4,5,6]);
+  assertIdxs('UByte', a, 6);
+}
+Tests.testCreateFromCount = function() {
+  var a = new Float32Array(6);
+  assertIdxs('Float', a, 6);
+  var a = new Int32Array(6);
+  assertIdxs('Int', a, 6);
+  var a = new Int16Array(6);
+  assertIdxs('Short', a, 6);
+  var a = new Int8Array(6);
+  assertIdxs('Byte', a, 6);
+  var a = new Uint32Array(6);
+  assertIdxs('UInt', a, 6);
+  var a = new Uint16Array(6);
+  assertIdxs('UShort', a, 6);
+  var a = new Uint8Array(6);
+  assertIdxs('UByte', a, 6);
+}
+Tests.testCreateFromBuffer = function() {
+  var sz = 24;
+  var b = new ArrayBuffer(sz);
+  var a = new Float32Array(b);
+  assertIdxs('Float', a, sz/4);
+  var a = new Int32Array(b);
+  assertIdxs('Int', a, sz/4);
+  var a = new Int16Array(b);
+  assertIdxs('Short', a, sz/2);
+  var a = new Int8Array(b);
+  assertIdxs('Byte', a, sz/1);
+  var a = new Uint32Array(b);
+  assertIdxs('UInt', a, sz/4);
+  var a = new Uint16Array(b);
+  assertIdxs('UShort', a, sz/2);
+  var a = new Uint8Array(b);
+  assertIdxs('UByte', a, sz/1);
+}
+
+Tests.testThatWritesChangeDrawing = function(gl) {
+  var verts = [
+    0,0,
+    1,0,
+    1,1,
+
+    0,0,
+    1,1,
+    0,1
+  ];
+  var a = new Float32Array(verts);
+  var arr = [];
+  for (var i=0; i<12; i++)
+    arr[i] = a[i];
+  assertEquals("Test that reads work from an array-initialized Float32Array", arr, verts);
+  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+  gl.bufferData(gl.ARRAY_BUFFER, a, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(va, 2, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(va);
+
+  var id = new Uint8Array(4);
+  gl.readPixels(8,8,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);
+  assertEquals([0, 0, 0, 0], [id[0], id[1], id[2], id[3]]);
+
+  gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+  gl.readPixels(8,8,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);
+  assertEquals([255, 0, 0, 255], [id[0], id[1], id[2], id[3]]);
+  gl.readPixels(0,8,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);
+  assertEquals([0, 0, 0, 0], [id[0], id[1], id[2], id[3]]);
+
+  a[0] = a[6] = a[10] = -1;
+  gl.bufferData(gl.ARRAY_BUFFER, a, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(va, 2, gl.FLOAT, false, 0, 0);
+
+  gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+  gl.readPixels(8,8,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);
+  assertEquals([255, 0, 0, 255], [id[0], id[1], id[2], id[3]]);
+  gl.readPixels(0,8,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);
+  assertEquals("Test that Float32Array#[]= worked and drawArrays drew a full-width rectangle",
+               [255, 0, 0, 255], [id[0], id[1], id[2], id[3]]);
+  gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);
+  assertEquals([0, 0, 0, 0], [id[0], id[1], id[2], id[3]]);
+}
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec2 Vertex;
+  void main()
+  {
+    gl_Position = vec4(Vertex, 0.0, 1.0);
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  uniform vec4 c;
+  void main()
+  {
+    gl_FragColor = c;
+  }
+</script>
+<style>canvas{border: 1px solid black}</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/demos/opengl_web.html b/conformance/more/demos/opengl_web.html
new file mode 100644
index 0000000..2fdbe3a
--- /dev/null
+++ b/conformance/more/demos/opengl_web.html
@@ -0,0 +1,607 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<title>OpenGL for the web</title>
+
+<script type="application/x-javascript" src="../util.js"></script>
+
+    <script type="application/x-javascript">
+
+function log(msg) {
+  document.getElementById('note').textContent += "\n"+msg;
+}
+
+
+
+
+function init(ev) {
+    var canvas = document.getElementById('canvas');
+    var gl = getGLContext(canvas);
+
+    var shader = new Shader(gl, "ppix-vert", "ppix-frag");
+    shader.compile();
+    var fbo = new FBO(gl, canvas.width, canvas.height);
+    var fbo2 = new FBO(gl, canvas.width, canvas.height);
+    var fbo3 = new FBO(gl, canvas.width, canvas.height);
+    var depth = new Shader(gl, "depth-vert", "depth-frag");
+    var identity = new Filter(gl, "identity-vert", "identity-frag");
+    var unpremult = new Filter(gl, "identity-vert", "unpremult-frag");
+    var hblur = new Filter(gl, "identity-vert", "hblur-frag");
+    var vblur = new Filter(gl, "identity-vert", "vblur-frag");
+    var hdof = new Filter(gl, "identity-vert", "hdof-frag");
+    var vdof = new Filter(gl, "identity-vert", "vdof-frag");
+
+    redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult, hblur, vblur, hdof, vdof);
+
+    setInterval(function(){
+        redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult, hblur, vblur, hdof, vdof);
+    }, 33);
+}
+
+function drawCube (gl, shader, angle, axis, x,y,z, s, va, na, ta) {
+    Matrix.copyMatrix(look, vmat);
+    Matrix.translate3InPlace(x,y,z,vmat);
+    Matrix.scale1InPlace(s,vmat);
+    Matrix.rotateInPlace(angle, axis, vmat);
+
+    // Note: we could just use mat3(MVMatrix) as the normal matrix
+    // as MVMatrix has only rotations, translations and uniform scaling
+    // <=> MVMatrix is a scaled orthonormal matrix
+    // hence normalize(mat3(MVMatrix)*v) == normalize(mat3(transpose(inverse(MVMatrix))*v)
+    //
+    // But let's do it the hard way to see if Matrix.inverse3x3 works...
+    Matrix.inverseTo3x3InPlace(vmat, nmat);
+    Matrix.transpose3x3InPlace(nmat);
+
+    shader.uniformMatrix4fv("MVMatrix", vmat);
+    shader.uniformMatrix3fv("NMatrix", nmat);
+
+    var cube = Cube.getCachedVBO(gl);
+    cube.draw(va, na, ta);
+}
+
+var carr = [];
+for (var i=0; i<25; i++) {
+    carr.push([Math.random(), Math.random(), Math.random()]);
+}
+
+function drawScene (gl, shader, va, na, ta) {
+    var ot = new Date().getTime();
+    var t = ot;
+
+    shader.uniformMatrix4fv("PMatrix", pmat);
+    for (var i=0; i<carr.length; i++){
+        var c = carr[i];
+        var f = c[1] < 0.5 ? 1 : -1;
+        var t = ot;
+        drawCube(gl, shader,
+                (t/(f*400*(c[0]+0.5))) % (2*Math.PI), c,
+
+                0.45+0.8*c[2],
+                -0.4+Math.cos((i/carr.length*Math.PI*2)+t/1000),
+                0.8+Math.sin((i/carr.length*Math.PI*2)+t/1000)*3.2,
+
+                0.05 + Math.pow((c[0]+c[1]+c[2])*0.33, 2)*0.3,
+                va, na, ta);
+    }
+}
+
+var nmat = Matrix.newIdentity3x3();
+var vmat = Matrix.newIdentity();
+var vmat2 = Matrix.newIdentity();
+var pmat = null;
+var look = Matrix.lookAt([4,-1,8], [-0.2,0,0], [0,1,0]);
+var useDoF = false;
+
+var firstFrame = true;
+
+function redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult, hblur, vblur, hdof, vdof) {
+
+    var doDoF = useDoF;
+    gl.viewport(0, 0, canvas.width, canvas.height);
+    gl.clearColor(0.0, 0.0, 0.0, 0.0);
+    gl.enable(gl.DEPTH_TEST);
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+    fbo.use();
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+    shader.use();
+
+    var va = shader.attrib("Vertex");
+    var na = shader.attrib("Normal");
+    var ta = shader.attrib("Tex");
+
+    if (pmat == null)
+        pmat = Matrix.perspective(30, canvas.width/canvas.height, 1, 100);
+
+    shader.uniform4f("MaterialSpecular", 0.95, 0.9, 0.6, 1);
+    shader.uniform4f("MaterialDiffuse", 0.50, 0.35, 0.35, 1);
+    shader.uniform4f("MaterialAmbient", 0.0, 0.1, 0.2, 1);
+    shader.uniform1f("MaterialShininess", 1.5);
+
+    shader.uniform4f("GlobalAmbient", 0.1, 0.1, 0.1, 1);
+
+    shader.uniform4f("LightPos", 1, 5, 3, 1.0);
+
+    shader.uniform4f("LightSpecular", 0.9, 0.9, 0.9, 1);
+    shader.uniform4f("LightDiffuse", 0.8, 0.8, 0.8, 1);
+    shader.uniform4f("LightAmbient", 0.0, 0.06, 0.2, 1);
+    shader.uniform1f("LightConstantAtt", 0.0);
+    shader.uniform1f("LightLinearAtt", 0.1);
+    shader.uniform1f("LightQuadraticAtt", 0.0);
+
+    drawScene(gl, shader, va, na);
+
+    if (doDoF || firstFrame) {
+
+        fbo3.use();
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        depth.use();
+        var dva = depth.attrib("Vertex");
+
+        drawScene(gl, depth, dva);
+
+        gl.disable(gl.DEPTH_TEST);
+        gl.activeTexture(gl.TEXTURE1);
+        gl.bindTexture(gl.TEXTURE_2D, fbo3.texture);
+        gl.activeTexture(gl.TEXTURE0);
+
+
+        for (var i=0; i<3; i++) {
+            fbo2.use();
+            gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
+
+            hdof.apply(function(f){
+                f.uniform1i("Texture", 0);
+                f.uniform1i("Depth", 1);
+                f.uniform1f("iter", i);
+                f.uniform1f("step", 1.0/canvas.width);
+            });
+
+            fbo.use();
+            gl.bindTexture(gl.TEXTURE_2D, fbo2.texture);
+
+            vdof.apply(function(f){
+                f.uniform1i("Texture", 0);
+                f.uniform1i("Depth", 1);
+                f.uniform1f("iter", i);
+                f.uniform1f("step", 1.0/canvas.width);
+            });
+        }
+
+    }
+    firstFrame = false;
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+
+    gl.activeTexture(gl.TEXTURE1);
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.activeTexture(gl.TEXTURE0);
+    gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
+
+    // The DoF blur blurs the color from the transparent black background with
+    // the cubes. To get rid of the border, we can treat it as premultiplied alpha.
+    // To see the problem, try replacing unpremult with identity.
+    unpremult.apply(function(f){
+      f.uniform1i("Texture", 0);
+    });
+
+}
+
+window.addEventListener("load", init, false);
+    </script>
+
+    <script id="ppix-vert" type="x-shader/x-vertex">
+      attribute vec3 Vertex;
+      attribute vec3 Normal;
+      attribute vec2 Tex;
+
+      uniform mat4 PMatrix;
+      uniform mat4 MVMatrix;
+      uniform mat3 NMatrix;
+
+      uniform vec4 MaterialAmbient;
+      uniform vec4 MaterialDiffuse;
+
+      uniform vec4 LightAmbient;
+      uniform vec4 LightDiffuse;
+
+      uniform vec4 GlobalAmbient;
+
+      uniform vec4 LightPos;
+
+      varying vec4 diffuse, ambientGlobal, ambient;
+      varying vec3 normal, lightDir, halfVector;
+      varying float dist;
+
+      void main()
+      {
+        vec4 worldPos;
+        vec3 lightVector;
+        vec4 v = vec4(Vertex, 1.0);
+
+        /* transform vertex normal into world space and normalize */
+        normal = normalize(NMatrix * Normal);
+
+        /* transform vertex into world space and compute the vector
+          from it to the light */
+        worldPos = MVMatrix * v;
+        lightVector = vec3(LightPos - worldPos);
+        lightDir = normalize(lightVector);
+        dist = length(lightVector);
+
+        /* Half-vector used in Blinn-Phong shading due to computational efficiency */
+        halfVector = normalize(lightVector - vec3(worldPos));
+
+        diffuse = MaterialDiffuse * LightDiffuse;
+
+        /* The ambient terms have been separated since one of them */
+        /* suffers attenuation */
+        ambient = MaterialAmbient * LightAmbient;
+        ambientGlobal = GlobalAmbient * MaterialAmbient;
+
+        gl_Position = PMatrix * worldPos;
+      }
+    </script>
+
+    <script id="ppix-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform vec4 LightSpecular;
+      uniform vec4 MaterialSpecular;
+      uniform float MaterialShininess;
+
+      uniform float LightConstantAtt;
+      uniform float LightLinearAtt;
+      uniform float LightQuadraticAtt;
+
+      varying vec4 diffuse,ambientGlobal, ambient;
+      varying vec3 normal, lightDir, halfVector;
+      varying float dist;
+
+      void main()
+      {
+        vec3 n, halfV, viewV, ldir;
+        float NdotL, NdotHV;
+        vec4 color = ambientGlobal;
+        float att;
+
+        n = normalize(normal);
+
+        NdotL = max(dot(n, normalize(lightDir)), 0.0);
+
+        if (NdotL > 0.0) {
+
+          att = 1.0 / (LightConstantAtt + LightLinearAtt * dist + LightQuadraticAtt * dist * dist);
+
+          color += att * (diffuse * NdotL + ambient);
+
+          halfV = normalize(halfVector);
+          NdotHV = max( dot(normal, halfV), 0.0 );
+
+          color += att * MaterialSpecular * LightSpecular * pow(NdotHV, MaterialShininess);
+        }
+
+        gl_FragColor = color;
+      }
+    </script>
+    <script id="depth-vert" type="x-shader/x-vertex">
+      attribute vec3 Vertex;
+      uniform mat4 PMatrix;
+      uniform mat4 MVMatrix;
+      varying float depth;
+      void main()
+      {
+        gl_Position = PMatrix * (MVMatrix * vec4(Vertex, 1.0));
+        depth = 1.0-(gl_Position.z / gl_Position.w);
+      }
+    </script>
+    <script id="depth-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      varying float depth;
+      void main()
+      {
+        vec4 c = vec4(depth, 0.0, 0.0, 1.0);
+        gl_FragColor = c;
+      }
+    </script>
+
+    <script id="identity-vert" type="x-shader/x-vertex">
+      attribute vec3 Vertex;
+      attribute vec2 Tex;
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        texCoord0 = vec4(Tex,0.0,0.0);
+        gl_Position = vec4(Vertex, 1.0);
+      }
+    </script>
+    <script id="identity-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform sampler2D Texture;
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        vec4 c = texture2D(Texture, texCoord0.st);
+        gl_FragColor = c;
+      }
+    </script>
+    <script id="premult-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform sampler2D Texture;
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        vec4 c = texture2D(Texture, texCoord0.st);
+        float a = c.a;
+        c *= a;
+        c.a = a;
+        gl_FragColor = c;
+      }
+    </script>
+    <script id="unpremult-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform sampler2D Texture;
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        vec4 c = texture2D(Texture, texCoord0.st);
+        float a = c.a;
+        c /= a;
+        c.a = a;
+        gl_FragColor = c;
+      }
+    </script>
+
+    <script id="hblur-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform sampler2D Texture;
+      uniform float step;
+      float kernel[7] = float[](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046);
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        int i=0;
+        if (texture2D(Texture, texCoord0.st).a > 0.0) {
+          vec4 sum = vec4(0.0);
+          for (i=0; i<7; i++) {
+            vec4 tmp = texture2D(Texture, texCoord0.st + vec2(i*step,0));
+            sum += tmp * kernel[i];
+          }
+          gl_FragColor = sum;
+        } else {
+          gl_FragColor = texture2D(Texture, texCoord0.st);
+        }
+      }
+    </script>
+    <script id="vblur-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform sampler2D Texture;
+      uniform float step;
+      float kernel[7] = float[](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046);
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        int i=0;
+        if (texture2D(Texture, texCoord0.st).a > 0.0) {
+          vec4 sum = vec4(0.0);
+          for (i=0; i<7; i++) {
+            vec4 tmp = texture2D(Texture, texCoord0.st + vec2(0,i*step));
+            sum += tmp * kernel[i];
+          }
+          gl_FragColor = sum;
+        } else {
+          gl_FragColor = texture2D(Texture, texCoord0.st);
+        }
+      }
+    </script>
+    <script id="hdof-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform sampler2D Texture;
+      uniform sampler2D Depth;
+      uniform float step;
+      uniform float iter;
+      float kernel[7] = { 0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046 };
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        vec4 tmp;
+        vec4 sum = vec4(0.0);
+        bool b = (iter < -26.0+36.0*(1.0-texture2D(Depth, texCoord0.st).r) && texture2D(Texture, texCoord0.st).a > 0.0);
+        tmp = texture2D(Texture, texCoord0.st + vec2(float(0-3)*step,0));
+        sum += tmp * kernel[0];
+        tmp = texture2D(Texture, texCoord0.st + vec2(float(1-3)*step,0));
+        sum += tmp * kernel[1];
+        tmp = texture2D(Texture, texCoord0.st + vec2(float(2-3)*step,0));
+        sum += tmp * kernel[2];
+        tmp = texture2D(Texture, texCoord0.st + vec2(float(3-3)*step,0));
+        sum += tmp * kernel[3];
+        tmp = texture2D(Texture, texCoord0.st + vec2(float(4-3)*step,0));
+        sum += tmp * kernel[4];
+        tmp = texture2D(Texture, texCoord0.st + vec2(float(5-3)*step,0));
+        sum += tmp * kernel[5];
+        tmp = texture2D(Texture, texCoord0.st + vec2(float(6-3)*step,0));
+        sum += tmp * kernel[6];
+        gl_FragColor = mix(texture2D(Texture, texCoord0.st), sum, b ? 1.0 : 0.0);
+      }
+    </script>
+    <script id="vdof-frag" type="x-shader/x-fragment">
+      precision mediump float;
+
+      uniform sampler2D Texture;
+      uniform sampler2D Depth;
+      uniform float step;
+      uniform float iter;
+      float kernel[7] = float[7](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046);
+
+      varying vec4 texCoord0;
+      void main()
+      {
+        vec4 tmp;
+        vec4 sum = vec4(0.0);
+        bool b = (iter < -26.0+36.0*(1.0-texture2D(Depth, texCoord0.st).r) && texture2D(Texture, texCoord0.st).a > 0.0);
+        tmp = texture2D(Texture, texCoord0.st + vec2(0,float(0-3)*step));
+        sum += tmp * kernel[0];
+        tmp = texture2D(Texture, texCoord0.st + vec2(0,float(1-3)*step));
+        sum += tmp * kernel[1];
+        tmp = texture2D(Texture, texCoord0.st + vec2(0,float(2-3)*step));
+        sum += tmp * kernel[2];
+        tmp = texture2D(Texture, texCoord0.st + vec2(0,float(3-3)*step));
+        sum += tmp * kernel[3];
+        tmp = texture2D(Texture, texCoord0.st + vec2(0,float(4-3)*step));
+        sum += tmp * kernel[4];
+        tmp = texture2D(Texture, texCoord0.st + vec2(0,float(5-3)*step));
+        sum += tmp * kernel[5];
+        tmp = texture2D(Texture, texCoord0.st + vec2(0,float(6-3)*step));
+        sum += tmp * kernel[6];
+        gl_FragColor = mix(texture2D(Texture, texCoord0.st), sum, b ? 1.0 : 0.0);
+      }
+    </script>
+
+    <style>
+      * { margin: 0px; padding: 0px; }
+      html {
+        background-color: #707888;
+        color: #222222;
+      }
+      #canvas {
+        position: absolute;
+        cursor: pointer;
+        top: 115px; left: 550px;
+      }
+      #note {
+        position: absolute;
+        top: 4px;
+        left: 4px;
+      }
+      #content {
+        margin-left: 99px;
+        padding-left: 8px;
+        padding-right: 8px;
+        padding-bottom: 16px;
+        width: 600px;
+        background-color: rgba(255,255,255,1.0);
+        text-align: center;
+        border-left: 1px solid rgba(0,0,0,0.5);
+        border-right: 2px solid rgba(0,0,0,0.75);
+      }
+      h1 {
+        padding-top: 24px;
+        padding-bottom: 16px;
+        margin-bottom: 24px;
+        border-bottom: 1px solid black;
+        font-family: Times New Roman, Serif;
+        font-weight: bold;
+        font-size: 40px;
+      }
+      #content p {
+        text-indent: 24px;
+        margin-left: 24px;
+        margin-right: 32px;
+        text-align: justify;
+        font-family: Serif;
+        padding-bottom: 16px;
+      }
+      #above {
+        position: absolute;
+        top: 300px;
+        left: 716px;
+        padding: 10px 20px;
+        background-color: rgba(0,225,0,0.5);
+        border-left: 2px solid rgba(0,64,0,0.75);
+        color: white;
+        font-size: small;
+        font-family: sans-serif;
+      }
+      #above p {
+        text-align: center;
+      }
+    </style>
+
+</head><body>
+    <canvas id="canvas" width="400" height="400" title="Click to toggle DOF shader" onclick="useDoF = !useDoF"></canvas>
+    <pre id="note"></pre>
+
+    <div id="content">
+    <h1>OpenGL for the web</h1>
+    <p>
+The WebGL specification gives web developers access to an
+OpenGL ES 2.0 drawing context for the canvas tag. What that means is
+that you can finally harness the power of the GPU for awesome visuals
+and heavy-duty number crunching in your web apps. </p><p> OpenGL ES 2.0 is a subset of OpenGL 2.0 aimed at embedded
+devices and game consoles. As such, it's a very minimalistic low-level
+API, even more so than desktop OpenGL. In fact, if you took desktop
+OpenGL and stripped out everything but shaders, vertex arrays and
+textures, you'd get something quite like OpenGL ES 2.0. </p>
+    <p>
+      As there is no fixed-function pipeline, you need to write GLSL shaders to draw <i>anything</i>.
+And you need to do your own transformation math, including keeping
+track of the transformation matrix stack. So the raw API is really not
+for the faint of heart; you do need to know your 3D math and shading
+equations. </p>
+    <p> For example, to draw the spinning cubes on the
+right - around 200 lines of application code, 250 lines of shaders and
+800 lines of library code - I had to scrounge the internet for <a href="http://www.lighthouse3d.com/opengl/glsl/index.php?pointlight">GLSL shaders</a>
+to do the transformation and lighting, write a small matrix math
+library in JavaScript and a DOF blur shader. While highly educational,
+it was also a rather steep hill to climb. </p>
+    <p> So, the intended audience of the raw context
+interface are not really end-users, but library developers who can
+write easy-to-use interfaces to the functionality, and 3D developers
+who require a high level of control over the rendering pipeline. </p>
+    <p> The really cool thing about the OpenGL Canvas is
+that it doesn't make policy decisions. There's no single set-in-stone
+use case for it: In addition to 3D graphics, you can also use it for
+filtering images, visualizing fluid dynamics, doing real-time video
+effects, or just crunching a whole lot of FP math. If you can do it on
+the GPU, you're in luck! </p>
+    </div>
+    <div id="above">
+      <p>You can also place content above the canvas</p>
+    </div>
+  </body></html>
diff --git a/conformance/more/demos/video.html b/conformance/more/demos/video.html
new file mode 100644
index 0000000..c5a8da1
--- /dev/null
+++ b/conformance/more/demos/video.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<script type="application/x-javascript" src="../util.js"></script>
+<script>
+"use strict";
+var processor = {
+  lastTime : new Date,
+  timerCallback: function() {
+    if (this.video.paused || this.video.ended) {
+      return;
+    }
+    this.computeFrame();
+    var t = new Date;
+    var elapsed = t - this.lastTime;
+    this.lastTime = t;
+    var self = this;
+    setTimeout(function () {
+        self.timerCallback();
+      }, Math.max(0, 33-elapsed));
+  },
+
+  init: function() {
+    if (this.initDone) return;
+    this.c2 = document.getElementById("c2");
+    this.ctx2 = getGLContext(this.c2);
+    this.greenScreen = new Filter(this.ctx2, 'identity-flip-vert', 'greenScreen');
+    this.ctx2.activeTexture(this.ctx2.TEXTURE0);
+    this.tex = loadTexture(this.ctx2, this.c1, false);
+    this.ctx2.activeTexture(this.ctx2.TEXTURE1);
+    this.tex2 = loadTexture(this.ctx2, document.getElementById('i'), false);
+    this.ctx2.activeTexture(this.ctx2.TEXTURE0);
+    this.initDone = true;
+  },
+
+  doLoad: function() {
+    this.video = document.getElementById("video");
+    this.c1 = document.getElementById("c1");
+    this.ctx1 = this.c1.getContext("2d");
+    this.ctx1.globalCompositeOperation = 'copy';
+    this.ctx1.fillRect(0,0,this.c1.width, this.c1.height);
+    var self = this;
+    this.video.addEventListener("play", function() {
+        self.init();
+        self.width = self.video.videoWidth;
+        self.height = self.video.videoHeight;
+        self.lastTime = new Date;
+        self.timerCallback();
+      }, false);
+  },
+
+  computeFrame: function() {
+//     this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
+    this.ctx2.texImage2D(this.ctx2.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.video);
+    this.greenScreen.apply(function(s) {
+        s.uniform1i('Texture', 0);
+        s.uniform1i('Texture2', 1);
+    });
+
+    return;
+  }
+};
+
+</script>
+<script id="identity-flip-vert" type="x-shader/x-vertex">
+    attribute vec3 Vertex;
+    attribute vec2 Tex;
+
+    varying vec4 texCoord0;
+    void main()
+    {
+        texCoord0 = vec4(Tex.s,1.0-Tex.t,0.0,0.0);
+        gl_Position = vec4(Vertex, 1.0);
+    }
+</script>
+<script id="greenScreen" type="x-shader/x-fragment">
+    precision mediump float;
+
+    uniform sampler2D Texture;
+    uniform sampler2D Texture2;
+
+    varying vec4 texCoord0;
+    void main()
+    {
+        vec4 c = texture2D(Texture, texCoord0.st);
+        float r = c.r * 0.5;
+        float a = c.g * 6.28;
+        float x = cos(a) * r;
+        float y = sin(a) * r;
+        vec4 c2 = texture2D(Texture2, vec2(0.7+x, 0.5+y));
+        // Shaders with branches are not allowed when dealing with non-SOP content
+        // so instead of "if (b) c2.a = 0;", we use mix()
+        bool b = (c.g > 120.0/255.0 && c.r > 120.0/255.0 && c.b < 50.0/255.0);
+        c2.a = mix(c2.a, 0.0, float(b));
+        gl_FragColor = c2;
+    }
+</script>
+    <style>
+      body {
+        background: black;
+        color:#CCCCCC;
+      }
+      a {
+          color: white;
+      }
+      #c2 {
+        background-image: url(http://www.mozbox.org/pub/green/foo.png);
+        background-repeat: repeat;
+      }
+      div {
+        float: left;
+        border :1px solid #444444;
+        padding:10px;
+        margin: 10px;
+        background:#3B3B3B;
+      }
+      img { display: none; }
+    </style>
+ </head>
+  <body onload="processor.doLoad()">
+    <div>
+        This is a port of Paul Rouget's <a href="http://blog.mozbox.org/post/2009/02/25/video-canvas%3A-special-effects">Canvas+Video green screen demo</a>, plus a texture lookup from the Firefox logo based on the values of the green and red color channels.
+    </div>
+    <div>
+      <video id="video" controls="true">
+        <source src="http://www.mozbox.org/pub/green/video.ogv"></source>
+        <source src="http://cs.helsinki.fi/u/ilmarihe/c3d/demos/video.mp4"></source>
+      </video>
+      <canvas id="c1" width="320" height="192"></canvas>
+    </div>
+    <div>
+      <canvas id="c2" width="640" height="384"></canvas>
+    </div>
+    <img id="i" src="http://www.mozbox.org/pub/green/foo.png">
+  </body>
+</html>
diff --git a/conformance/more/functions/bindBuffer.html b/conformance/more/functions/bindBuffer.html
new file mode 100644
index 0000000..75ec715
--- /dev/null
+++ b/conformance/more/functions/bindBuffer.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testBindBufferARRAY_BUFFER = function(gl) {
+  var b = gl.createBuffer();
+  assert(gl.NO_ERROR == checkError(gl, "genBuffers"));
+  gl.bindBuffer(gl.ARRAY_BUFFER, b);
+  assert(gl.NO_ERROR == checkError(gl, "bindBuffer"));
+  gl.bindBuffer(gl.ARRAY_BUFFER, null);
+  assert(gl.NO_ERROR == checkError(gl, "bindBuffer 0"));
+  gl.deleteBuffer(b);
+  assert(gl.NO_ERROR == checkError(gl, "deleteBuffers"));
+}
+
+Tests.testBindBufferELEMENT_ARRAY_BUFFER = function(gl) {
+  var b = gl.createBuffer();
+  assert(gl.NO_ERROR == checkError(gl, "genBuffers"));
+  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, b);
+  assert(gl.NO_ERROR == checkError(gl, "bindBuffer"));
+  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+  assert(gl.NO_ERROR == checkError(gl, "bindBuffer 0"));
+  gl.deleteBuffer(b);
+  assert(gl.NO_ERROR == checkError(gl, "deleteBuffers"));
+}
+
+</script>
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/bindBufferBadArgs.html b/conformance/more/functions/bindBufferBadArgs.html
new file mode 100644
index 0000000..f1286e9
--- /dev/null
+++ b/conformance/more/functions/bindBufferBadArgs.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testBindBufferARRAY_BUFFER = function(gl) {
+  var b = gl.createBuffer();
+  assertOk("bind buffer", function(){gl.bindBuffer(gl.ARRAY_BUFFER, b)});
+  assertFail("bufferData to null buffer", function() {
+    gl.bindBuffer(gl.ARRAY_BUFFER, null);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([10]), gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([10]));
+  });
+  assertFail("bind to number", function(){
+    gl.bindBuffer(gl.ARRAY_BUFFER, 1000000000);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([10]), gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([10]));
+  });
+  assertOk("bind to null", function(){gl.bindBuffer(gl.ARRAY_BUFFER, null)});
+  gl.deleteBuffer(b);
+}
+
+Tests.testBindBufferELEMENT_ARRAY_BUFFER = function(gl) {
+  var b = gl.createBuffer();
+  assertOk("bind buffer", function(){gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, b)});
+  assertFail("bufferData to null buffer", function() {
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Float32Array([10]), gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([10]));
+  });
+  assertFail("bind to number", function(){
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 1000000000);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Float32Array([10]), gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([10]));
+  });
+  assertOk("bind to null",
+      function(){gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)});
+  gl.deleteBuffer(b);
+}
+Tests.testBindBuffer = function(gl) {
+    assertFail("bind ARRAY_BUFFER to number",
+        function(){gl.bindBuffer(gl.ARRAY_BUFFER, 1);});
+    assertFail("bind ELEMENT_ARRAY_BUFFER to number",
+        function(){gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 1);});
+    assertFail("bind bad target",
+        function(){gl.bindBuffer(gl.FLOAT, 0);});
+    assertFail("bind ARRAY_BUFFER to string",
+        function(){gl.bindBuffer(gl.ARRAY_BUFFER, "foo");});
+    assertFail("bind ELEMENT_ARRAY_BUFFER to string",
+        function(){gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, "foo");});
+}
+
+</script>
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/bindFramebufferLeaveNonZero.html b/conformance/more/functions/bindFramebufferLeaveNonZero.html
new file mode 100644
index 0000000..06722a1
--- /dev/null
+++ b/conformance/more/functions/bindFramebufferLeaveNonZero.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<title>OpenGL for the web</title>
+
+<script type="application/x-javascript" src="../util.js"></script>
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+
+    <script type="application/x-javascript">
+Tests.message = "This was segfaulting when the GL context got GC'd (in glXDestroyContext)";
+Tests.testSeg = function () {
+    var canvas = document.getElementById('canvas');
+    var gl = getGLContext(canvas);
+
+    var fbo = gl.createFramebuffer();
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+}
+</script>
+
+</head><body>
+    <canvas id="canvas" width="400" height="400"></canvas>
+</body></html>
diff --git a/conformance/more/functions/bufferData.html b/conformance/more/functions/bufferData.html
new file mode 100644
index 0000000..f3ca6db
--- /dev/null
+++ b/conformance/more/functions/bufferData.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var buf = gl.createBuffer();
+  var ebuf = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuf);
+  return [gl, buf, ebuf];
+}
+
+Tests.testBufferData = function(gl, buf, ebuf) {
+    var data = [0,0,0,0, 1,0,0,0, 0,1,0,0];
+    gl.bufferData(gl.ARRAY_BUFFER, 12, gl.STATIC_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([]), gl.STATIC_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STATIC_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STREAM_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STREAM_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.DYNAMIC_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.DYNAMIC_DRAW);
+
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(data), gl.STATIC_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(data), gl.STREAM_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(data), gl.DYNAMIC_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*2);
+    throwError(gl, "array bufferSubData");
+}
+Tests.testBufferDataElement = function(gl, buf, ebuf) {
+    var data = [0,0,0,0, 1,0,0,0, 0,1,0,0];
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.STATIC_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STATIC_DRAW);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.STREAM_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STREAM_DRAW);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.DYNAMIC_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_USAGE), gl.DYNAMIC_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_SIZE), 12*2);
+    throwError(gl, "element array bufferSubData");
+}
+
+Tests.endUnit = function(gl, buf, ebuf) {
+    gl.deleteBuffer(buf);
+    gl.deleteBuffer(ebuf);
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/bufferDataBadArgs.html b/conformance/more/functions/bufferDataBadArgs.html
new file mode 100644
index 0000000..6676e77
--- /dev/null
+++ b/conformance/more/functions/bufferDataBadArgs.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var buf = gl.createBuffer();
+  var ebuf = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuf);
+  return [gl, buf, ebuf];
+}
+
+Tests.testBufferData = function(gl) {
+    assertOk("zero size data",
+        function(){gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(0), gl.STATIC_DRAW);});
+    assertFail("bad target",
+        function(){gl.bufferData(gl.TEXTURE_2D, new Float32Array([1,2,3]), gl.STATIC_DRAW);});
+//    assertFail("array for data",
+//        function(){gl.bufferData(gl.ARRAY_BUFFER, [1,2,3], gl.STATIC_DRAW);});
+    assertFail("bad usage",
+        function(){gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1,2,3]), gl.TEXTURE_2D);});
+    assertFail("null data",
+        function(){gl.bufferData(gl.ARRAY_BUFFER, null, gl.STATIC_DRAW);});
+    assertFail("undefined data",
+        function(){gl.bufferData(gl.ARRAY_BUFFER, undefined, gl.STATIC_DRAW);});
+    assertOk(function(){gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Float32Array([1,2,3]), gl.STATIC_DRAW);});
+    throwError(gl, 'bufferData');
+    gl.bindBuffer(gl.ARRAY_BUFFER, null);
+    assertFail(function(){gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1,2,3]), gl.STATIC_DRAW);});
+    throwError(gl, 'bufferData');
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+    assertFail(function(){gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([1,2,3]), gl.STATIC_DRAW);});
+    throwError(gl, 'bufferData');
+}
+
+Tests.endUnit = function(gl, buf, ebuf) {
+    gl.deleteBuffer(buf);
+    gl.deleteBuffer(ebuf);
+}
+
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/bufferSubData.html b/conformance/more/functions/bufferSubData.html
new file mode 100644
index 0000000..fe31da0
--- /dev/null
+++ b/conformance/more/functions/bufferSubData.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = getGLContext(canvas);
+    var buf = gl.createBuffer();
+    var ebuf = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuf);
+    return [gl, buf, ebuf];
+}
+
+Tests.testBufferSubData = function(gl, buf, ebuf) {
+    var data = [0,0,0,0, 1,0,0,0, 0,1,0,0];
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([1,2,3,4]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Float32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 32, new Float32Array([1,1,1,1]));
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STATIC_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STREAM_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Float32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([1,2,3,4]));
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STREAM_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.DYNAMIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Float32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([1,2,3,4]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Int32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Int16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Int8Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint8Array([1,1,1,1]));
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.DYNAMIC_DRAW);
+
+    gl.bufferData(gl.ARRAY_BUFFER, 12, gl.STATIC_DRAW);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STATIC_DRAW);
+
+    gl.bufferData(gl.ARRAY_BUFFER, 12*4, gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([1,2,3,4]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Float32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 32, new Float32Array([1,1,1,1]));
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STATIC_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, 12*4, gl.STREAM_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Float32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([1,2,3,4]));
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.STREAM_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, 12*4, gl.DYNAMIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Float32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([1,2,3,4]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Int32Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Int16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Int8Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint8Array([1,1,1,1]));
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*4);
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_USAGE), gl.DYNAMIC_DRAW);
+
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(data), gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Uint16Array([1,2,3,4]));
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(data), gl.STREAM_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Uint16Array([1,2,3,4]));
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(data), gl.DYNAMIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Uint16Array([1,2,3,4]));
+    assertEquals(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), 12*2);
+    throwError(gl, "array bufferSubData");
+}
+Tests.testBufferSubDataElement = function(gl, buf, ebuf) {
+    var data = [0,0,0,0, 1,0,0,0, 0,1,0,0];
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array([1,2,3,4]));
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.STREAM_DRAW);
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array([1,2,3,4]));
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.DYNAMIC_DRAW);
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 16, new Uint16Array([1,1,1,1]));
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array([1,2,3,4]));
+    assertEquals(gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_SIZE), 12*2);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array(data), gl.DYNAMIC_DRAW);
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 4, new Uint8Array([1,1,1,1]));
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint8Array([1,2,3,4]));
+    assertEquals(gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_SIZE), 12);
+    throwError(gl, "element array bufferSubData");
+}
+
+Tests.endUnit = function(gl, buf, ebuf) {
+    gl.deleteBuffer(buf);
+    gl.deleteBuffer(ebuf);
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/bufferSubDataBadArgs.html b/conformance/more/functions/bufferSubDataBadArgs.html
new file mode 100644
index 0000000..1961e2f
--- /dev/null
+++ b/conformance/more/functions/bufferSubDataBadArgs.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = wrapGLContext(getGLContext(canvas));
+    var buf = gl.createBuffer();
+    var ebuf = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuf);
+    return [gl, buf, ebuf];
+}
+
+Tests.testBufferData = function(gl) {
+    var data = [0,0,0,0, 1,0,0,0, 0,1,0,0];
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STREAM_DRAW);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), gl.STREAM_DRAW);
+    assertOk("zero length data",
+        function(){gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(0));});
+    assertFail("number for data",
+        function(){gl.bufferSubData(gl.ARRAY_BUFFER, 0, 12);});
+    assertFail("bad target",
+        function(){gl.bufferSubData(gl.TEXTURE_2D, 0, new Float32Array([1,2,3]));});
+    assertFail("array for data",
+        function(){gl.bufferSubData(gl.ARRAY_BUFFER, 0, [1,2,3]);});
+    assertOk("floats in element buffer",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Float32Array([1,2,3]));});
+    assertFail("negative offset",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, -1, new Uint16Array([1,2,3]));});
+    assertFail("offset out of range",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 24, new Uint16Array([1,2,3]));});
+    assertFail("offset out of range",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 2400000, new Uint16Array([1,2,3]));});
+    assertFail("offset out of range",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 19, new Uint16Array([1,2,3]));});
+    assertFail("data too large",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array(data.concat([1])));});
+    assertOk("offset + data too large",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 18, new Uint16Array([1,2,3]));});
+    assertOk("good args",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array([1,2,3]));});
+    throwError(gl, 'bufferData0');
+    gl.bindBuffer(gl.ARRAY_BUFFER, null);
+    assertFail("setting buffer 0",
+      function(){gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([1,2,3]));});
+    throwError(gl, 'bufferData1');
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+    assertFail("setting buffer 0",
+        function(){gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array([1,2,3]));});
+    throwError(gl, 'bufferData2');
+}
+
+Tests.endUnit = function(gl, buf, ebuf) {
+    gl.deleteBuffer(buf);
+    gl.deleteBuffer(ebuf);
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/copyTexImage2D.html b/conformance/more/functions/copyTexImage2D.html
new file mode 100644
index 0000000..e5a4f2f
--- /dev/null
+++ b/conformance/more/functions/copyTexImage2D.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.setup = function(gl) {
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  var texCubeMap = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap);
+  return [gl]
+}
+
+Tests.teardown = function(gl,tex, texCubeMap) {
+  gl.bindTexture(gl.TEXTURE_2D, null);
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+  gl.deleteTexture(tex);
+  gl.deleteTexture(texCubeMap);
+}
+
+Tests.testTexImage2D = function(gl) {
+  gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0,1,1,0);
+  gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0,2,1,0);
+  gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0,1,2,0);
+  gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0,16,16,0);
+  gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 15,15,1,1,0);
+  var valid_targets = [
+    gl.TEXTURE_2D,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_X,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+  ];
+  valid_targets.forEach(function(t) {
+    assertOk(function(){gl.copyTexImage2D(t, 0, gl.RGBA, 0,0,1,1,0);});
+  });
+}
+Tests.testRoundtrip = function(gl) {
+    var sh = new Filter(gl, 'identity-flip-vert', 'identity-frag');
+    gl.clearColor(1.0, 0.0, 0.0, 1.0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    var buf = new Uint8Array(4);
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, buf);
+    assertEquals([255,0,0,255], [buf[0], buf[1], buf[2], buf[3]]);
+    // red texture
+    gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0,16,16,0);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+    gl.clearColor(0.0, 0.0, 1.0, 1.0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    // blue framebuffer
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, buf);
+    assertEquals([0,0,255,255], [buf[0], buf[1], buf[2], buf[3]]);
+    sh.apply(); // paint it with texture
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, buf);
+    assertEquals([255,0,0,255], [buf[0], buf[1], buf[2], buf[3]]);
+    sh.destroy();
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="identity-flip-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="identity-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform sampler2D Texture;
+
+varying vec4 texCoord0;
+void main()
+{
+    vec4 c = texture2D(Texture, texCoord0.st);
+    gl_FragColor = c;
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/copyTexImage2DBadArgs.html b/conformance/more/functions/copyTexImage2DBadArgs.html
new file mode 100644
index 0000000..3b4dcc2
--- /dev/null
+++ b/conformance/more/functions/copyTexImage2DBadArgs.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = wrapGLContext(getGLContext(canvas));
+    return [gl];
+}
+
+Tests.setup = function(gl) {
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  return [gl]
+}
+
+Tests.teardown = function(gl,tex) {
+  gl.bindTexture(gl.TEXTURE_2D, null);
+  gl.deleteTexture(tex);
+}
+
+Tests.testTexImage2D = function(gl) {
+    assertOk("height > backHeight", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, 17,1,0);
+    });
+    assertOk("width > backWidth", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, 1,17,0);
+    });
+    assertOk("x + width > backWidth", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 16,0, 1,1,0);
+    });
+    assertOk("y + height > backHeight", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,16, 1,1,0);
+    });
+    assertOk("Negative X", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, -1,0, 1,1,0);
+    });
+    assertOk("Negative Y", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,-1, 1,1,0);
+    });
+    assertFail("Negative height", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, -1,1,0);
+    });
+    assertFail("Negative width", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, 1,-1,0);
+    });
+    assertFail("Non 0 border", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, 1,1,1);
+    });
+    assertFail("Negative border",function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, 1,1,-1);
+    });
+    assertOk("Good Args", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, 16,16,0);
+    });
+    assertFail("NPOT texture to > level 0", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 1, gl.RGBA, 0,0, 15,16,0);
+    });
+    assertFail("Bad target", function(){
+        gl.copyTexImage2D(gl.FLOAT, 0, gl.RGBA, 0,0, 16,16,0);
+    });
+    assertFail("Bad internal format", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.FLOAT, 0,0, 16,16,0);
+    });
+    assertFail("Negative level", function(){
+        gl.copyTexImage2D(gl.TEXTURE_2D, -1, gl.RGBA, 0,0, 16,16,0);
+    });
+}
+
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/copyTexSubImage2D.html b/conformance/more/functions/copyTexSubImage2D.html
new file mode 100644
index 0000000..ed51b3e
--- /dev/null
+++ b/conformance/more/functions/copyTexSubImage2D.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.setup = function(gl) {
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  var texCubeMap = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap);
+  return [gl]
+}
+
+Tests.teardown = function(gl,tex,texCubeMap) {
+  gl.bindTexture(gl.TEXTURE_2D, null);
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+  gl.deleteTexture(tex);
+  gl.deleteTexture(texCubeMap);
+}
+
+
+Tests.testTexImage2D = function(gl) {
+  gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0,16,16,0);
+  gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0,1,1);
+  gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0,2,1);
+  gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0,1,2);
+  gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0,16,16);
+  gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 15,15,1,1);
+  gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 1,1, 0,0,15,15);
+  gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 15,15, 0,0,1,1);
+  var valid_targets = [
+    gl.TEXTURE_2D,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_X,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+  ];
+  valid_targets.forEach(function(t) {
+    assertOk(function(){
+        gl.copyTexImage2D(t, 0, gl.RGBA, 0,0,1,1,0);
+        gl.copyTexSubImage2D(t, 0, 0,0,0,0,1,1);
+    });
+  });
+}
+Tests.testRoundtrip = function(gl) {
+    var sh = new Filter(gl, 'identity-flip-vert', 'identity-frag');
+    gl.clearColor(1.0, 0.0, 0.0, 1.0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    var buf = new Uint8Array(4);
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, buf);
+    assertEquals([255,0,0,255], [buf[0], buf[1], buf[2], buf[3]]);
+    // red texture
+    gl.clearColor(0.0, 0.0, 0.0, 0.0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, document.getElementById('gl'));
+    gl.clearColor(1.0, 0.0, 0.0, 1.0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0,0,0,16,16);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+    gl.clearColor(0.0, 0.0, 1.0, 1.0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    // blue framebuffer
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, buf);
+    assertEquals([0,0,255,255], [buf[0], buf[1], buf[2], buf[3]]);
+    sh.apply(); // paint with texture
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, buf);
+    assertEquals([255,0,0,255], [buf[0], buf[1], buf[2], buf[3]]);
+    sh.destroy();
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="identity-flip-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="identity-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform sampler2D Texture;
+
+varying vec4 texCoord0;
+void main()
+{
+    vec4 c = texture2D(Texture, texCoord0.st);
+    gl_FragColor = c;
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/copyTexSubImage2DBadArgs.html b/conformance/more/functions/copyTexSubImage2DBadArgs.html
new file mode 100644
index 0000000..42c474c
--- /dev/null
+++ b/conformance/more/functions/copyTexSubImage2DBadArgs.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = wrapGLContext(getGLContext(canvas));
+    return [gl];
+}
+
+Tests.setup = function(gl) {
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    return [gl]
+}
+
+Tests.teardown = function(gl,tex) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.deleteTexture(tex);
+}
+
+Tests.testTexImage2D = function(gl) {
+    gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0, 16,16, 0);
+    assertGLError(gl, gl.INVALID_VALUE, "width > dst tex width", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0, 17,1);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "height > dst tex height", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0, 1,17);
+    });
+    // The spec says the source image dimensions can be out of range.
+    assertOk("x > dst tex width", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 16,0, 1,1);
+    });
+    assertOk("y > dst tex width", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,16, 1,1);
+    });
+    assertOk("x < 0", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, -1,0, 1,1);
+    });
+    assertOk("y < 0", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,-1, 1,1);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "width < 0", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0, -1,1);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "height < 0", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0, 1,-1);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "xoffset < 0", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, -1,0, 0,0, 16,16);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "yoffset < 0", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,-1, 0,0, 16,16);
+    });
+    assertOk("x < 0 full width", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, -1,0, 16,16);
+    });
+    assertOk("y < 0 full height", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,-1, 16,16);
+    });
+    assertOk(function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0,0, 0,0, 16,16);
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad target", function(){
+        gl.copyTexSubImage2D(gl.FLOAT, 0, 0,0, 0,0, 16,16);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "", function(){
+        gl.copyTexSubImage2D(gl.TEXTURE_2D, -1, 0,0, 0,0, 16,16);
+    });
+}
+
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/deleteBufferBadArgs.html b/conformance/more/functions/deleteBufferBadArgs.html
new file mode 100644
index 0000000..ee0cbc0
--- /dev/null
+++ b/conformance/more/functions/deleteBufferBadArgs.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = getGLContext(canvas);
+  return [gl];
+}
+
+Tests.testDeleteBuffer = function(gl) {
+  assertThrowNoGLError(gl, "number ", function(){ gl.deleteBuffer(1); });
+  assertGLError(gl, gl.NO_ERROR, "null", function(){ gl.deleteBuffer(null); });
+  assertThrowNoGLError(gl, "0", function(){ gl.deleteBuffer(0); });
+  assertThrowNoGLError(gl, "false", function(){ gl.deleteBuffer(false); });
+  assertThrowNoGLError(gl, "true", function(){ gl.deleteBuffer(true); });
+  assertThrowNoGLError(gl, "{}", function(){ gl.deleteBuffer({}); });
+  var tex = gl.createTexture();
+  assertThrowNoGLError(gl, "tex as buf", function(){ gl.deleteBuffer(tex); });
+  var buf = gl.createBuffer();
+  assertOk(function(){ gl.deleteBuffer(buf); });
+  assertOk(function(){ gl.deleteBuffer(buf); });
+  assertOk(function(){ gl.deleteBuffer(buf); });
+  assertOk(function(){ gl.deleteTexture(tex); });
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/drawArrays.html b/conformance/more/functions/drawArrays.html
new file mode 100644
index 0000000..fa5c1d8
--- /dev/null
+++ b/conformance/more/functions/drawArrays.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var sh = prog.shader.program;
+//   log(gl.getShaderInfoLog(prog.shaders[1]));
+  var v = gl.getAttribLocation(sh, 'Vertex');
+  var n = gl.getAttribLocation(sh, 'Normal');
+  var t = gl.getAttribLocation(sh, 'Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  assert(0 == gl.getError());
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+Tests.testDrawArraysVBO = function(gl, prog, v,n,t) {
+  var vbo = new VBO(gl, {size:3, data:Quad.vertices});
+  vbo.draw(v);
+  assert(0 == checkError(gl, "vbo.draw"));
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 5, 1);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 2);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 6);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 1, 5);});
+  vbo.destroy();
+  assert(0 == checkError(gl, "vbo.destroy"));
+}
+
+Tests.testDrawArraysVBOMulti = function(gl, prog, v,n,t) {
+  // creates VBOs for the quad arrays, binds them with
+  // vertexAttribPointer and calls drawArrays
+  var vbo = new VBO(gl,
+    {size:3, data:Quad.vertices},
+    {size:3, data:Quad.normals},
+    {size:2, data:Quad.texcoords});
+  vbo.draw(v, n, t);
+  assert(0 == checkError(gl, "vbo.draw"));
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 5, 1);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 2);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 6);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 1, 5);});
+  assertFail(function(){gl.drawArrays(gl.TRIANGLES, 1, 6);});
+  assertFail(function(){gl.drawArrays(gl.TRIANGLES, 6, 1);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo.vbos[1]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 5, 1);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 2);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 6);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 1, 5);});
+  assertFail(function(){gl.drawArrays(gl.TRIANGLES, 1, 6);});
+  assertFail(function(){gl.drawArrays(gl.TRIANGLES, 6, 1);});
+  vbo.destroy();
+  assert(0 == checkError(gl, "vbo.destroy"));
+}
+
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    vec4 c = texCoord0;
+    gl_FragColor = c;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/drawArraysOutOfBounds.html b/conformance/more/functions/drawArraysOutOfBounds.html
new file mode 100644
index 0000000..543ee85
--- /dev/null
+++ b/conformance/more/functions/drawArraysOutOfBounds.html
@@ -0,0 +1,305 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+
+var vertsA = new Float32Array(verts);
+var normalsA = new Float32Array(normals);
+var texcoordsA = new Float32Array(texcoords);
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var v = prog.attrib('Vertex');
+  var n = prog.attrib('Normal');
+  var t = prog.attrib('Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+Tests.testDrawArraysEmpty = function(gl, prog, v,n,t) {
+  var b = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, b);
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([]), gl.STATIC_DRAW);
+  assertOk(function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);})
+  gl.enableVertexAttribArray(v);
+  assertGLError(gl, gl.INVALID_OPERATION, "zero size array",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 1);});
+  gl.deleteBuffer(b);
+}
+
+Tests.testDrawArraysOutOfBounds = function(gl, prog, v,n,t) {
+  var b = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, b);
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([]), gl.STATIC_DRAW);
+  assertOk(function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);});
+  gl.enableVertexAttribArray(v);
+  assertGLError(gl, gl.INVALID_OPERATION, "zero size array",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 1);});
+  assertGLError(gl, gl.INVALID_OPERATION, "zero size array 10000",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 10000);});
+  assertGLError(gl, gl.INVALID_OPERATION, "zero size array 10000000000000",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 10000000000000);});
+  assertGLError(gl, gl.INVALID_OPERATION, "zero size array fraction",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 1.6);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative offset",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, -1);});
+  assertGLError(gl, gl.INVALID_OPERATION, "count out of range",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 1);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative count",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, -1);});
+  assertGLError(gl, gl.INVALID_VALUE, "positive count, negative offset",
+      function(){gl.drawArrays(gl.TRIANGLES, -1, 1);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative count, positive offset",
+      function(){gl.drawArrays(gl.TRIANGLES, 1, -1);});
+  gl.deleteBuffer(b);
+}
+
+
+Tests.testDrawArraysWithDataOutOfBounds = function(gl, prog, v,n,t) {
+  var b = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, b);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsA, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(v);
+  assertGLError(gl, gl.INVALID_OPERATION, "3 element array",
+      function(){gl.drawArrays(gl.TRIANGLES, 3, 2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "3 element array 10000",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 10000);});
+  assertGLError(gl, gl.INVALID_OPERATION, "3 element array 10000000000000",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 10000000000000);});
+  assertGLError(gl, gl.INVALID_OPERATION, "fractional count",
+      function(){gl.drawArrays(gl.TRIANGLES, 3, 1.6);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative offset",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, -1);});
+  assertGLError(gl, gl.INVALID_OPERATION, "count out of range",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 4);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative count",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, -1);});
+  assertGLError(gl, gl.INVALID_VALUE, "positive count, negative offset",
+      function(){gl.drawArrays(gl.TRIANGLES, -1, 2);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative count, positive offset",
+      function(){gl.drawArrays(gl.TRIANGLES, 1, -1);});
+  gl.deleteBuffer(b);
+}
+
+Tests.testDrawArraysMultiOutOfBounds = function(gl, prog, v,n,t) {
+  var bs = [];
+  bs.push(gl.createBuffer());
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[bs.length-1]);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsA, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  bs.push(gl.createBuffer());
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[bs.length-1]);
+  gl.bufferData(gl.ARRAY_BUFFER, normalsA, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  bs.push(gl.createBuffer());
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[bs.length-1]);
+  gl.bufferData(gl.ARRAY_BUFFER, texcoordsA, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(t, 2, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(v);
+  gl.enableVertexAttribArray(n);
+  gl.enableVertexAttribArray(t);
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 1",
+      function(){gl.drawArrays(gl.TRIANGLES, 3, 2);});
+  bs.push(gl.createBuffer());
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[bs.length-1]);
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts.concat(verts)), gl.STATIC_DRAW);
+  bs.push(gl.createBuffer());
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[bs.length-1]);
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords.concat(texcoords)), gl.STATIC_DRAW);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[1]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[2]);
+  gl.vertexAttribPointer(t, 2, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(v);
+  gl.enableVertexAttribArray(n);
+  gl.enableVertexAttribArray(t);
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 2",
+      function(){gl.drawArrays(gl.TRIANGLES, 3, 2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 3",
+      function(){gl.drawArrays(gl.TRIANGLES, 4, 2);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[0]);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[3]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[2]);
+  gl.vertexAttribPointer(t, 2, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(v);
+  gl.enableVertexAttribArray(n);
+  gl.enableVertexAttribArray(t);
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 4",
+      function(){gl.drawArrays(gl.TRIANGLES, 3, 2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 5",
+      function(){gl.drawArrays(gl.TRIANGLES, 4, 2);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[0]);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[1]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[4]);
+  gl.vertexAttribPointer(t, 2, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(v);
+  gl.enableVertexAttribArray(n);
+  gl.enableVertexAttribArray(t);
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 6",
+      function(){gl.drawArrays(gl.TRIANGLES, 3, 2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 7",
+      function(){gl.drawArrays(gl.TRIANGLES, 3, 2);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[3]);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[3]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, bs[2]);
+  gl.vertexAttribPointer(t, 2, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(v);
+  gl.enableVertexAttribArray(n);
+  gl.enableVertexAttribArray(t);
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 8",
+      function(){gl.drawArrays(gl.TRIANGLES, 4, 1);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative count",
+      function(){gl.drawArrays(gl.TRIANGLES, -1, 2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "zero count???",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 4);});
+  assertGLError(gl, gl.INVALID_OPERATION, "multi array 9",
+      function(){gl.drawArrays(gl.TRIANGLES, 1, 4);});
+  bs.forEach(function(b){ gl.deleteBuffer(b) });
+}
+
+
+Tests.testDrawArraysVBOOutOfBounds = function(gl, prog, v,n,t) {
+  var vbo = new VBO(gl, {size:3, data:Quad.vertices});
+  vbo.use(v);
+  assertGLError(gl, gl.INVALID_OPERATION, "1",
+      function(){gl.drawArrays(gl.TRIANGLES, 6, 1);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative count",
+      function(){gl.drawArrays(gl.TRIANGLES, 2, -1);});
+  assertGLError(gl, gl.INVALID_OPERATION, "3",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 7);});
+  assertGLError(gl, gl.INVALID_OPERATION, "4",
+      function(){gl.drawArrays(gl.TRIANGLES, 1, 6);});
+  vbo.destroy();
+}
+
+Tests.testDrawArraysVBOMultiOutOfBounds = function(gl, prog, v,n,t) {
+  // creates VBOs for the quad arrays, binds them with
+  // vertexAttribPointer and calls drawArrays
+  var vbo = new VBO(gl,
+    {size:3, data:Quad.vertices},
+    {size:3, data:Quad.normals},
+    {size:2, data:Quad.texcoords});
+  vbo.use(v, n, t);
+  assertGLError(gl, gl.INVALID_OPERATION, "1",
+      function(){gl.drawArrays(gl.TRIANGLES, 6, 1);});
+  assertGLError(gl, gl.INVALID_VALUE, "negative count",
+      function(){gl.drawArrays(gl.TRIANGLES, 2, -1);});
+  assertGLError(gl, gl.INVALID_OPERATION, "2",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 7);});
+  assertGLError(gl, gl.INVALID_OPERATION, "3",
+      function(){gl.drawArrays(gl.TRIANGLES, 1, 6);});
+  vbo.destroy();
+}
+
+Tests.testDrawArraysOOBShaderJuggle = function(gl, prog, v,n,t) {
+  var vbo = new VBO(gl,
+    {size:3, data:[0,0,0]},
+    {size:3, data:[0,0,0,0,0,0]});
+  vbo.init();
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo.vbos[0]);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(v);
+  assertGLError(gl, gl.INVALID_OPERATION, "offset too big",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 2);});
+  var sh2 = new Shader(gl, 'vert', 'frag');
+  sh2.use();
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo.vbos[1]);
+  gl.vertexAttribPointer(sh2.attrib('Vertex'), 3, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(sh2.attrib('Vertex'));
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "offset too big 2",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 3);});
+  prog.use();
+  gl.vertexAttribPointer(prog.attrib('Vertex'), 3, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(prog.attrib('Vertex'));
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "offset too big 3",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 3);});
+  sh2.destroy();
+}
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_FragColor = texCoord0;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/drawElements.html b/conformance/more/functions/drawElements.html
new file mode 100644
index 0000000..801685d
--- /dev/null
+++ b/conformance/more/functions/drawElements.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+var indices = [0,1,2]
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var sh = prog.shader.program;
+  var v = gl.getAttribLocation(sh, 'Vertex');
+  var n = gl.getAttribLocation(sh, 'Normal');
+  var t = gl.getAttribLocation(sh, 'Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  assert(0 == gl.getError());
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+Tests.testDrawElementsVBO = function(gl, prog, v,n,t) {
+    var vbo = new VBO(gl,
+                       {size:3, data:Quad.vertices},
+                       {elements:true, data:Quad.indices});
+  vbo.draw(v);
+  assert(gl.NO_ERROR == checkError(gl, "vbo.draw"));
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_SHORT, 1*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 0, gl.UNSIGNED_SHORT, 2*1);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 5*2);});
+  vbo.destroy();
+  assert(gl.NO_ERROR == checkError(gl, "vbo.destroy"));
+}
+
+Tests.testDrawElementsVBOMulti = function(gl, prog, v,n,t) {
+  // creates VBOs for the quad arrays, binds them with
+  // vertexAttribPointer and calls drawElements
+  var vbo = new VBO(gl,
+                    {size:3, data:Quad.vertices},
+                    {size:3, data:Quad.normals},
+                    {size:2, data:Quad.texcoords},
+                    {elements:true, data:Quad.indices});
+  vbo.draw(v, n, t);
+  assert(gl.NO_ERROR == checkError(gl, "vbo.draw"));
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_SHORT, 1*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 0, gl.UNSIGNED_SHORT, 2*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 5*2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "count + offset out of range",
+      function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 6*2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "count + offset out of range 2",
+      function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 1*2);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, null);
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo.vbos[1]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_SHORT, 1*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 0, gl.UNSIGNED_SHORT, 2*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0*2);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 5*2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "count + offset out of range 3",
+      function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 6*2);});
+  assertGLError(gl, gl.INVALID_OPERATION, "count + offset out of range 4",
+      function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 1*2);});
+  vbo.destroy();
+  assert(gl.NO_ERROR == checkError(gl, "vbo.destroy"));
+}
+
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    vec4 c = texCoord0;
+    gl_FragColor = c;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/drawElementsBadArgs.html b/conformance/more/functions/drawElementsBadArgs.html
new file mode 100644
index 0000000..1902b6f
--- /dev/null
+++ b/conformance/more/functions/drawElementsBadArgs.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+// Tests.autorun = false;
+// Tests.message = "Caution: May crash your browser";
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+var indices = [60000000,1,2]
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var sh = prog.shader.program;
+  var v = gl.getAttribLocation(sh, 'Vertex');
+  var n = gl.getAttribLocation(sh, 'Normal');
+  var t = gl.getAttribLocation(sh, 'Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  assert(0 == gl.getError());
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+
+Tests.testDrawElementsVBO = function(gl, prog, v,n,t) {
+    var vbo = new VBO(gl,
+                       {size:3, data:Quad.vertices},
+                       {elements:true, data:[0,1,2,2000, 40802, 5887992]});
+  assertFail(function(){vbo.draw(v);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 5*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_SHORT, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_SHORT, 1*2);});
+  vbo.destroy();
+  assert(gl.NO_ERROR == checkError(gl, "vbo.destroy"));
+}
+
+Tests.testDrawElementsVBOByte = function(gl, prog, v,n,t) {
+    var vbo = new VBO(gl,
+                       {size:3, data:Quad.vertices},
+                       {elements:true, type:gl.UNSIGNED_BYTE, data:[0,1,2,2000, 40802, 5887992]});
+  assertFail(function(){vbo.draw(v);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_BYTE, 5);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_BYTE, 1);});
+  vbo.destroy();
+  assert(gl.NO_ERROR == checkError(gl, "vbo.destroy"));
+}
+
+Tests.testDrawElementsVBOMulti = function(gl, prog, v,n,t) {
+  // creates VBOs for the quad arrays, binds them with
+  // vertexAttribPointer and calls drawElements
+  // The quad has 6 vertices
+  var vbo = new VBO(gl,
+                    {size:3, data:Quad.vertices},
+                    {size:3, data:Quad.normals},
+                    {size:2, data:Quad.texcoords},
+                    {elements:true, data:[0,1,2,6,2000, 256]});
+  assertFail(function(){vbo.draw(v, n, t);});
+  gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 5*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 2, gl.UNSIGNED_SHORT, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 4, gl.UNSIGNED_SHORT, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 1*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 2, gl.UNSIGNED_SHORT, 2*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 3*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_SHORT, 1*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 1*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 6*2);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo.vbos[1]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 5*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_SHORT, 1*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 1*2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 6*2);});
+  vbo.destroy();
+  assert(gl.NO_ERROR == checkError(gl, "vbo.destroy"));
+}
+
+Tests.testDrawElementsVBOMultiByte = function(gl, prog, v,n,t) {
+  // creates VBOs for the quad arrays, binds them with
+  // vertexAttribPointer and calls drawElements
+  // The quad has 6 vertices
+  var vbo = new VBO(gl,
+                    {size:3, data:Quad.vertices},
+                    {size:3, data:Quad.normals},
+                    {size:2, data:Quad.texcoords},
+                    {elements:true, type:gl.UNSIGNED_BYTE, data:[0,1,2,6,2000, 256]});
+  assertFail(function(){vbo.draw(v, n, t);});
+  gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0);
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 4, gl.UNSIGNED_BYTE, -1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, -1, gl.UNSIGNED_BYTE, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_BYTE, 5);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 2, gl.UNSIGNED_BYTE, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 4, gl.UNSIGNED_BYTE, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 2, gl.UNSIGNED_BYTE, 2);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_BYTE, 3);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_BYTE, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_BYTE, 6);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo.vbos[1]);
+  gl.vertexAttribPointer(n, 3, gl.FLOAT, false, 0, 0);
+  gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0);
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_BYTE, 5);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);});
+  assertOk(function(){gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 5, gl.UNSIGNED_BYTE, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 1);});
+  assertFail(function(){gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_BYTE, 6);});
+  vbo.destroy();
+  assert(gl.NO_ERROR == checkError(gl, "vbo.destroy"));
+}
+
+Tests.testSharedBuffers = function(gl, prog, v,n,t) {
+  var vbo = gl.createBuffer();
+  var vertsArr = new Uint16Array([0,1,3,3,4,5,6,7,8]);
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsArr, gl.STATIC_DRAW);
+  assertFail(function(){gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vbo)});
+  gl.deleteBuffer(vbo);
+}
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    vec4 c = texCoord0;
+    gl_FragColor = c;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/isTests.html b/conformance/more/functions/isTests.html
new file mode 100644
index 0000000..a5cd722
--- /dev/null
+++ b/conformance/more/functions/isTests.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testIs = function(gl) {
+    var tex = loadTexture(gl, document.getElementById('2d'));
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    var fbo = new FBO(gl, 1, 1);
+    fbo.use();
+    var prog = gl.createProgram();
+    var sh1 = gl.createShader(gl.VERTEX_SHADER);
+    var sh2 = gl.createShader(gl.FRAGMENT_SHADER);
+    var buf = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+    assert("tex", gl.isTexture(tex));
+    assert("fbo", gl.isFramebuffer(fbo.fbo));
+    assert("rbo", gl.isRenderbuffer(fbo.rbo));
+    assert("prog", gl.isProgram(prog));
+    assert("sh1", gl.isShader(sh1));
+    assert("sh2", gl.isShader(sh2));
+    assert("buf", gl.isBuffer(buf));
+    gl.deleteTexture(tex);
+    gl.deleteFramebuffer(fbo.fbo);
+    gl.deleteRenderbuffer(fbo.rbo);
+    gl.deleteProgram(prog);
+    gl.deleteShader(sh1);
+    gl.deleteShader(sh2);
+    gl.deleteBuffer(buf);
+    // NOTE: we purposely do not unbind things.
+    assert("tex", !gl.isTexture(tex));
+    assert("fbo", !gl.isFramebuffer(fbo.fbo));
+    assert("rbo", !gl.isRenderbuffer(fbo.rbo));
+    assert("prog", !gl.isProgram(prog));
+    assert("sh1", !gl.isShader(sh1));
+    assert("sh2", !gl.isShader(sh2));
+    assert("buf", !gl.isBuffer(buf));
+}
+
+</script>
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+  <canvas id="2d" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/isTestsBadArgs.html b/conformance/more/functions/isTestsBadArgs.html
new file mode 100644
index 0000000..93e8d81
--- /dev/null
+++ b/conformance/more/functions/isTestsBadArgs.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" href="../../../resources/js-test-style.css"/>
+<script src="../../../resources/js-test-pre.js"></script>
+<script src="../../../conformance/resources/webgl-test-utils.js"></script>
+
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas_element" width="1" height="1"></canvas>
+<script type="application/x-javascript">
+
+function runTest()
+{
+    shouldThrow("gl.isBuffer(gl.createFramebuffer())");
+    shouldThrow("gl.isBuffer(gl.createProgram())");
+    shouldThrow("gl.isBuffer(gl.createRenderbuffer())");
+    shouldThrow("gl.isBuffer(gl.createShader(gl.VERTEX_SHADER))");
+    shouldThrow("gl.isBuffer(gl.createTexture())");
+
+    shouldThrow("gl.isFramebuffer(gl.createBuffer())");
+    shouldThrow("gl.isFramebuffer(gl.createProgram())");
+    shouldThrow("gl.isFramebuffer(gl.createRenderbuffer())");
+    shouldThrow("gl.isFramebuffer(gl.createShader(gl.VERTEX_SHADER))");
+    shouldThrow("gl.isFramebuffer(gl.createTexture())");
+
+    shouldThrow("gl.isProgram(gl.createBuffer())");
+    shouldThrow("gl.isProgram(gl.createFramebuffer())");
+    shouldThrow("gl.isProgram(gl.createRenderbuffer())");
+    shouldThrow("gl.isProgram(gl.createShader(gl.VERTEX_SHADER))");
+    shouldThrow("gl.isProgram(gl.createTexture())");
+
+    shouldThrow("gl.isRenderbuffer(gl.createBuffer())");
+    shouldThrow("gl.isRenderbuffer(gl.createFramebuffer())");
+    shouldThrow("gl.isRenderbuffer(gl.createProgram())");
+    shouldThrow("gl.isRenderbuffer(gl.createShader(gl.VERTEX_SHADER))");
+    shouldThrow("gl.isRenderbuffer(gl.createTexture())");
+
+    shouldThrow("gl.isShader(gl.createBuffer())");
+    shouldThrow("gl.isShader(gl.createFramebuffer())");
+    shouldThrow("gl.isShader(gl.createProgram())");
+    shouldThrow("gl.isShader(gl.createRenderbuffer())");
+    shouldThrow("gl.isShader(gl.createTexture())");
+
+    shouldThrow("gl.isTexture(gl.createBuffer())");
+    shouldThrow("gl.isTexture(gl.createFramebuffer())");
+    shouldThrow("gl.isTexture(gl.createProgram())");
+    shouldThrow("gl.isTexture(gl.createRenderbuffer())");
+    shouldThrow("gl.isTexture(gl.createShader(gl.VERTEX_SHADER))");
+
+    shouldBe("gl.isBuffer(null)",            "false");
+    shouldBe("gl.isBuffer(undefined)",       "false");
+
+    shouldBe("gl.isFramebuffer(null)",       "false");
+    shouldBe("gl.isFramebuffer(undefined)",  "false");
+
+    shouldBe("gl.isProgram(null)",           "false");
+    shouldBe("gl.isProgram(undefined)",      "false");
+
+    shouldBe("gl.isRenderbuffer(null)",      "false");
+    shouldBe("gl.isRenderbuffer(undefined)", "false");
+
+    shouldBe("gl.isShader(null)",            "false");
+    shouldBe("gl.isShader(undefined)",       "false");
+
+    shouldBe("gl.isTexture(null)",           "false");
+    shouldBe("gl.isTexture(undefined)",      "false");
+}
+
+description("Tests type checking for isX() functions");
+var gl = WebGLTestUtils.create3DContext(document.getElementById("canvas_element"));
+runTest();
+var successfullyParsed = true;
+</script>
+
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
+
diff --git a/conformance/more/functions/readPixels.html b/conformance/more/functions/readPixels.html
new file mode 100644
index 0000000..be205cc
--- /dev/null
+++ b/conformance/more/functions/readPixels.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testReadPixels = function(gl) {
+  var id = new Uint8Array(16 * 16 * 4);
+  assertOk(function(){gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);});
+  assertOk(function(){gl.readPixels(0,0,16,16,gl.RGBA, gl.UNSIGNED_BYTE, id);});
+  assertOk(function(){gl.readPixels(15,15,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);});
+}
+Tests.testReadPixelsRGBA = function(gl) {
+  gl.clearColor(1, 0, 1, 0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+  var id = new Uint8Array(4);
+  gl.readPixels(1,2,1,1,gl.RGBA, gl.UNSIGNED_BYTE, id);
+  assertArrayEquals([255, 0, 255, 0], id);
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/readPixelsBadArgs.html b/conformance/more/functions/readPixelsBadArgs.html
new file mode 100644
index 0000000..3e69613
--- /dev/null
+++ b/conformance/more/functions/readPixelsBadArgs.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testReadPixels = function(gl) {
+  // we can't know if this is going to fail because of negative width
+  // or because the buffer size doesn't match the dimensions.
+  assertSomeGLError(gl, "negative width",
+      function(){gl.readPixels(0,0,-1,1, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(4));});
+  assertSomeGLError(gl,  "negative height",
+      function(){gl.readPixels(0,0,1,-1, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(4));});
+  assertOk("negative x",
+      function(){gl.readPixels(-1,0,1,1, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(4));});
+  assertOk("negative y",
+      function(){gl.readPixels(0,-1,1,1, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(4));});
+  assertOk("height > backbuffer height",
+      function(){gl.readPixels(0,0,16,17, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(16*17*4));});
+  assertOk("width > backbuffer width",
+      function(){gl.readPixels(0,0,17,16, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(17*16*4));});
+  assertOk("width, height = 0",
+      function(){gl.readPixels(0,0,0,0, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(0));});
+  // we can't know if this is going to fail because of negative width
+  // or because the buffer size doesn't match the dimensions.
+  assertSomeGLError(gl, "bad format",
+      function(){gl.readPixels(0,0,1,1, gl.FLOAT, gl.UNSIGNED_BYTE,
+          new Uint8Array(4*4));});
+  // we can't know if this is going to fail because of negative width
+  // or because the buffer size doesn't match the dimensions.
+  assertSomeGLError(gl, "bad type",
+      function(){gl.readPixels(0,0,1,1, gl.ALPHA, gl.FLOAT,
+          new Uint8Array(1*4));});
+}
+
+Tests.testReadPixelsSOPIMG = function(gl) {
+    var img = document.getElementById("i");
+    while (!img.complete) {}
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    // SOP failure
+    assertThrowNoGLError(gl, "throw because img is from another domain",
+      function(){gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);});
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    assertOk("canvas still origin-clean",
+      function(){gl.readPixels(0,0,1,1, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(4));});
+    gl.deleteTexture(tex);
+}
+Tests.testReadPixelsSOPCanvas = function(gl) {
+    var img = document.getElementById("i");
+    while (!img.complete) {}
+    var c = document.getElementById("c");
+    c.getContext("2d").drawImage(img, 0, 0);
+    assertFail("canvas throws because not origin clean",
+      function(){c.getContext("2d").getImageData(0,0,1,1);});
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    // SOP failure
+    assertThrowNoGLError(gl, "throw because canvas is not origin clean",
+      function(){gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);});
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    assertOk("canvas still origin-clean",
+      function(){gl.readPixels(0,0,1,1, gl.RGBA, gl.UNSIGNED_BYTE,
+          new Uint8Array(4));});
+    gl.deleteTexture(tex);
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+<canvas id="c" width="128" height="128"></canvas>
+<img id="i" src="http://www.opengl.org/img/opengl_logo.jpg">
+</body></html>
diff --git a/conformance/more/functions/texImage2D.html b/conformance/more/functions/texImage2D.html
new file mode 100644
index 0000000..c72ac41
--- /dev/null
+++ b/conformance/more/functions/texImage2D.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.setup = function(gl) {
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  var texCubeMap = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap);
+  return [gl]
+}
+
+Tests.teardown = function(gl,tex,texCubeMap) {
+  gl.bindTexture(gl.TEXTURE_2D, null);
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+  gl.deleteTexture(tex);
+  gl.deleteTexture(texCubeMap);
+}
+
+Tests.testTexImage2D = function(gl) {
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2,1,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0]));
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,2,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0]));
+  var valid_targets = [
+    gl.TEXTURE_2D,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_X,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+  ];
+  valid_targets.forEach(function(t) {
+    assertOk(function(){gl.texImage2D(t, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));});
+  });
+}
+
+Tests.testTexImage2DNull = function(gl) {
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, null);
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/texImage2DBadArgs.html b/conformance/more/functions/texImage2DBadArgs.html
new file mode 100644
index 0000000..2772487
--- /dev/null
+++ b/conformance/more/functions/texImage2DBadArgs.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = wrapGLContext(getGLContext(canvas));
+    return [gl];
+}
+
+Tests.setup = function(gl) {
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    return [gl]
+}
+
+Tests.teardown = function(gl,tex) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.deleteTexture(tex);
+}
+
+Tests.testTexImage2D = function(gl) {
+    var data = new Uint8Array(4);
+    assertGLError(gl, gl.INVALID_OPERATION, "not enough data", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2,1,0,gl.RGBA,gl.UNSIGNED_BYTE, data);
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "not enough data", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,2,0,gl.RGBA,gl.UNSIGNED_BYTE, data);
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad target", function(){
+        gl.texImage2D(gl.FLOAT, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, null);
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad internal format/format", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.FLOAT, 1,1,0,gl.FLOAT,gl.UNSIGNED_BYTE, null);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "border > 0", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,1,48,gl.RGBA,gl.UNSIGNED_BYTE, null);
+    });
+    // The spec says zero size is OK. If you disagree please list the section
+    // in the spec that details this issue.
+    assertOk("zero size", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0,0,0,gl.RGBA,gl.UNSIGNED_BYTE, null);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "negative width", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, -1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, null);
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "negative height", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,-1,0,gl.RGBA,gl.UNSIGNED_BYTE, null);
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad format", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,1,0,gl.FLOAT,gl.UNSIGNED_BYTE, null);
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad type", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.TEXTURE_2D, null);
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "not enough data", function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array(3));
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "format and type incompatible",function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_SHORT_5_6_5, null);
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "format and type incompatible",function(){
+        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1,1,0,gl.RGB,gl.UNSIGNED_SHORT_4_4_4_4, null);
+    });
+}
+
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/texImage2DHTML.html b/conformance/more/functions/texImage2DHTML.html
new file mode 100644
index 0000000..e772f6b
--- /dev/null
+++ b/conformance/more/functions/texImage2DHTML.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = wrapGLContext(getGLContext(canvas));
+    gl.viewport(0,0,canvas.width,canvas.height);
+    gl.clearColor(0,0,1,1);
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+    gl.disable(gl.DEPTH_TEST);
+    return [gl];
+}
+
+Tests.setup = function(gl) {
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    var texCubeMap = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap);
+    return [gl]
+}
+
+Tests.teardown = function(gl, tex, texCubeMap) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.deleteTexture(tex);
+    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+    gl.deleteTexture(texCubeMap);
+}
+
+Tests.testTexImage2D = function(gl) {
+    gl.enable(gl.BLEND);
+    var img = document.getElementById('i');
+    var c = document.getElementById('c');
+    var ctx = c.getContext('2d');
+    ctx.drawImage(img,0,0);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+    var f = new Filter(gl, 'identity-flip-vert', 'identity-frag');
+    gl.blendFunc(gl.ONE, gl.ZERO);
+    f.apply();
+    f.destroy();
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
+    f = new Filter(gl, 'identity-hflip-vert', 'identity-frag');
+    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
+    f.apply();
+    f.destroy();
+    var valid_targets = [
+        gl.TEXTURE_2D,
+        gl.TEXTURE_CUBE_MAP_POSITIVE_X,
+        gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
+        gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
+        gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+        gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+        gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+    ];
+    valid_targets.forEach(function(t) {
+        assertOk(function(){gl.texImage2D(t, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);});
+        assertOk(function(){gl.texImage2D(t, 1, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);});
+    });
+}
+
+Tests.testTexImage2DNonSOP = function(gl) {
+    var img = document.getElementById('i2');
+    while (!img.complete) {}
+    var c = document.getElementById('c');
+    var ctx = c.getContext('2d');
+    ctx.drawImage(img,0,0);
+    assertThrowNoGLError(gl, "texImage2D with cross-origin image should throw exception.",
+      function(){gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);});
+    assertThrowNoGLError(gl, "texImage2D with dirty origin canvas should throw exception.",
+      function(){gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);});
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="identity-flip-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="identity-hflip-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(1.0-Tex.s, Tex.t, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="identity-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform sampler2D Texture;
+
+varying vec4 texCoord0;
+void main()
+{
+    vec4 c = texture2D(Texture, texCoord0.st);
+    gl_FragColor = c;
+}
+</script>
+</head><body>
+<canvas id="gl" width="256" height="256"></canvas>
+<canvas id="c" width="256" height="256"></canvas>
+<img id="i" width="256" height="256"  src="data:image/png;base64,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">
+<img id="i2" src="http://mashable.com/wp-content/uploads/2008/08/thunderbird-logo-64x64.png">
+
+</body></html>
diff --git a/conformance/more/functions/texImage2DHTMLBadArgs.html b/conformance/more/functions/texImage2DHTMLBadArgs.html
new file mode 100644
index 0000000..75517ba
--- /dev/null
+++ b/conformance/more/functions/texImage2DHTMLBadArgs.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<meta charset="utf-8">
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  return [gl, tex];
+}
+
+Tests.testTexImage2D = function(gl) {
+    var b = document.createElement('b');
+    var div = document.createElement('div');
+    var c = document.getElementById('c');
+    assertFail("bad element b",
+        function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, b); });
+    assertFail("bad element div",
+        function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, div); });
+    assertFail("no element",
+        function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, 0); });
+    assertFail("string as data",
+        function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, "foo"); });
+    assertOk("canvas as data",
+         function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertFail("bad target",
+        function() {gl.texImage2D(gl.FLOAT, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+}
+
+Tests.endUnit = function(gl, tex) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.deleteTexture(tex);
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+<canvas id="c" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/texSubImage2D.html b/conformance/more/functions/texSubImage2D.html
new file mode 100644
index 0000000..cab9ccf
--- /dev/null
+++ b/conformance/more/functions/texSubImage2D.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.setup = function(gl) {
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  var texCubeMap = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap);
+  return [gl]
+}
+
+Tests.teardown = function(gl,tex,texCubeMap) {
+  gl.bindTexture(gl.TEXTURE_2D, null);
+  gl.deleteTexture(tex);
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+  gl.deleteTexture(texCubeMap);
+}
+
+Tests.testTexSubImage2D = function(gl) {
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2,2,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]));
+  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 2,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0]));
+  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 1,2,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0]));
+  gl.texSubImage2D(gl.TEXTURE_2D, 0, 1,0, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,1, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+  gl.texSubImage2D(gl.TEXTURE_2D, 0, 1,1, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+  gl.texImage2D(gl.TEXTURE_2D, 1,gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+  gl.texSubImage2D(gl.TEXTURE_2D, 1, 0,0, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+  var valid_targets = [
+    gl.TEXTURE_2D,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_X,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+    gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+    gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+  ];
+  valid_targets.forEach(function(t) {
+    assertOk(function(){
+        gl.texImage2D(t, 0, gl.RGBA, 1,1,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+        gl.texSubImage2D(t, 0, 0,0, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+  });
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/texSubImage2DBadArgs.html b/conformance/more/functions/texSubImage2DBadArgs.html
new file mode 100644
index 0000000..96f5361
--- /dev/null
+++ b/conformance/more/functions/texSubImage2DBadArgs.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = wrapGLContext(getGLContext(canvas));
+    return [gl];
+}
+
+Tests.setup = function(gl) {
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    return [gl]
+}
+
+Tests.teardown = function(gl,tex) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.deleteTexture(tex);
+}
+
+Tests.testTexImage2D = function(gl) {
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2,2,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]));
+    assertGLError(gl, gl.INVALID_OPERATION, "not enough data", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, 2,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "not enough data", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, 1,2,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad target", function(){
+        gl.texSubImage2D(gl.FLOAT, 0, 0,0, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "width out of range", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, 3,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "height out of range", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, 1,3,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]));
+    });
+    assertOk("zero size", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, 0,0,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertSomeGLError(gl, "negative width", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, -1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertSomeGLError(gl, "negative height", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, 1,-1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "negative x", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,-1,1,1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_VALUE, "negative y", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0,1,-1,1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad format", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 1,1,gl.FLOAT,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_ENUM, "bad type", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 1,1,gl.RGBA,gl.TEXTURE_2D, new Uint8Array([0,0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "not enough data", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 1,1,gl.RGBA,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "format does not match internal format", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 1,1,gl.RGB,gl.UNSIGNED_BYTE, new Uint8Array([0,0,0]));
+    });
+    assertGLError(gl, gl.INVALID_OPERATION, "type does not match original", function(){
+        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, 1,1,gl.RGBA,gl.UNSIGNED_SHORT_4_4_4_4, new Uint16Array([0]));
+    });
+}
+
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/texSubImage2DHTML.html b/conformance/more/functions/texSubImage2DHTML.html
new file mode 100644
index 0000000..d284fd8
--- /dev/null
+++ b/conformance/more/functions/texSubImage2DHTML.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+    var canvas = document.getElementById('gl');
+    var gl = wrapGLContext(getGLContext(canvas));
+    gl.viewport(0,0,canvas.width,canvas.height);
+    gl.clearColor(0,0,1,1);
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+    gl.disable(gl.DEPTH_TEST);
+    return [gl];
+}
+
+Tests.setup = function(gl) {
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    var texCubeMap = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap);
+    return [gl]
+}
+
+Tests.teardown = function(gl, tex, texCubeMap) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.deleteTexture(tex);
+    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+    gl.deleteTexture(texCubeMap);
+}
+
+Tests.testTexImage2D = function(gl) {
+    gl.enable(gl.BLEND);
+    var img = document.getElementById('i');
+    var c = document.getElementById('c');
+    var ctx = c.getContext('2d');
+    ctx.drawImage(img,0,0);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
+    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, img);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+    var f = new Filter(gl, 'identity-flip-vert', 'identity-frag');
+    gl.blendFunc(gl.ONE, gl.ZERO);
+    f.apply();
+    f.destroy();
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
+    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, c);
+    f = new Filter(gl, 'identity-hflip-vert', 'identity-frag');
+    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
+    f.apply();
+    f.destroy();
+    var valid_targets = [
+        gl.TEXTURE_2D,
+        gl.TEXTURE_CUBE_MAP_POSITIVE_X,
+        gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
+        gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
+        gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+        gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+        gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+    ];
+    valid_targets.forEach(function(t) {
+        assertOk(function(){
+                gl.texImage2D(t, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
+                gl.texSubImage2D(t, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, c);
+        });
+        assertOk(function(){
+                gl.texImage2D(t, 1, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
+                gl.texSubImage2D(t, 1, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, c);
+        });
+    });
+}
+
+Tests.testTexImage2DNonSOP = function(gl) {
+    var img = document.getElementById('i2');
+    while (!img.complete) {}
+    var c = document.getElementById('c');
+    var ctx = c.getContext('2d');
+    ctx.drawImage(img,0,0);
+    assertThrowNoGLError(gl, "texImage2D with cross-origin image should throw exception.",
+      function(){gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);});
+    assertThrowNoGLError(gl, "texSubImage2D with cross-origin image should throw exception.",
+      function(){gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, img);});
+    assertThrowNoGLError(gl, "texImage2D with dirty origin canvas should throw exception.",
+      function(){gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);});
+    assertThrowNoGLError(gl, "texSubImage2D with dirty origin canvas should throw exception.",
+      function(){gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, c);});
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="identity-flip-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="identity-hflip-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(1.0-Tex.s, Tex.t, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="identity-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform sampler2D Texture;
+
+varying vec4 texCoord0;
+void main()
+{
+    vec4 c = texture2D(Texture, texCoord0.st);
+    gl_FragColor = c;
+}
+</script>
+</head><body>
+<canvas id="gl" width="256" height="256"></canvas>
+<canvas id="c" width="256" height="256"></canvas>
+<img id="i" width="256" height="256"  src="data:image/png;base64,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">
+<img id="i2" src="http://mashable.com/wp-content/uploads/2008/08/thunderbird-logo-64x64.png">
+
+</body></html>
diff --git a/conformance/more/functions/texSubImage2DHTMLBadArgs.html b/conformance/more/functions/texSubImage2DHTMLBadArgs.html
new file mode 100644
index 0000000..aa441f6
--- /dev/null
+++ b/conformance/more/functions/texSubImage2DHTMLBadArgs.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  return [gl, tex];
+}
+
+Tests.testTexImage2D = function(gl) {
+    var b = document.createElement('b');
+    var div = document.createElement('div');
+    var c = document.getElementById('c');
+    assertOk("make texture",
+         function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_VALUE, "y + height > texture height",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 1, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_VALUE, "x + width > texture width",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_VALUE, "negative x",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, -1, 0, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_VALUE, "negative y",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, -1, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_VALUE, "negative level",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, -1, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertThrowNoGLError(gl, "bad element b",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, b); });
+    assertThrowNoGLError(gl, "bad element div",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, div); });
+    assertThrowNoGLError(gl, "no element",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, 0); });
+    assertThrowNoGLError(gl, "string as data",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0,0, 0, gl.RGBA, gl.UNSIGNED_BYTE, "foo"); });
+    assertGLError(gl, gl.INVALID_ENUM, "bad target",
+        function() {gl.texSubImage2D(gl.FLOAT, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertOk("good args",
+         function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_OPERATION, "format not same as original",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGB, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_OPERATION, "type not same as original",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_SHORT_4_4_4_4, c); });
+    assertOk("make texture RGB",
+         function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, c); });
+    assertOk("format same as original RGB",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGB, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_OPERATION, "format not same as original RGB",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_OPERATION, "type not same as original RGB",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, c); });
+    assertOk("make texture RGBA 4_4_4_4",
+         function() {gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_SHORT_4_4_4_4, c); });
+    assertOk("format same as original RGBA 4_4_4_4",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_SHORT_4_4_4_4, c); });
+    assertGLError(gl, gl.INVALID_OPERATION, "format not same as original RGBA 4_4_4_4",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGB, gl.UNSIGNED_BYTE, c); });
+    assertGLError(gl, gl.INVALID_OPERATION, "type not same as original RGBA 4_4_4_4",
+        function() {gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0, gl.RGBA, gl.UNSIGNED_BYTE, c); });
+}
+
+Tests.endUnit = function(gl, tex) {
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.deleteTexture(tex);
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+<canvas id="c" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/uniformMatrix.html b/conformance/more/functions/uniformMatrix.html
new file mode 100644
index 0000000..f47f6f5
--- /dev/null
+++ b/conformance/more/functions/uniformMatrix.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testUniformf = function(gl) {
+  var sh = new Filter(gl, 'foobar-vert', 'foobar-frag');
+  sh.apply(function(f){
+    var foo = f.uniform('foo');
+    var bar = f.uniform('bar');
+    var bar3 = f.uniform('bar3');
+    gl.uniformMatrix4fv(foo, false, [1,0,0,0, 0,2,0,0, 0,0,3,0, 0,0,0,4]);
+    gl.uniformMatrix2fv(bar, false, [2,1, 2,2]);
+    gl.uniformMatrix3fv(bar3, false, [2,2,2, 2,2,2, 2,1,2]);
+  });
+  var d = new Uint8Array(4);
+  gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+  assertEquals([1,2,3,8], [d[0], d[1], d[2], d[3]]);
+  sh.destroy();
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="foobar-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+uniform mat2 bar;
+uniform mat3 bar3;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, bar[0][1]+bar3[2][1], 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="foobar-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform mat4 foo;
+
+varying vec4 texCoord0;
+void main()
+{
+    gl_FragColor = vec4(foo[0][0]/256.0, foo[1][1]/256.0, foo[2][2]/256.0, foo[3][3]*texCoord0.z/256.0);
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/uniformMatrixBadArgs.html b/conformance/more/functions/uniformMatrixBadArgs.html
new file mode 100644
index 0000000..305f634
--- /dev/null
+++ b/conformance/more/functions/uniformMatrixBadArgs.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var unwrappedGL = getGLContext(canvas);
+  var gl = wrapGLContext(unwrappedGL);
+  return [gl, unwrappedGL];
+}
+
+Tests.testUniformf = function(gl, unwrappedGL) {
+  var sh = new Filter(gl, 'foobar-vert', 'foobar-frag');
+  sh.apply(function(f){
+    var fm4 = f.uniform('fm4');
+    var fm2 = f.uniform('fm2');
+    var fm3 = f.uniform('fm3');
+    assertGLError(gl, gl.INVALID_VALUE, "bad transpose 4fv",
+        function(){gl.uniformMatrix4fv(fm4, true, [1,0,0,0, 0,2,0,0, 0,0,3,0, 0,0,0,4]);});
+    assertGLError(gl, gl.INVALID_VALUE, "bad transpose 3fv",
+        function(){gl.uniformMatrix3fv(fm3, true, [1,0,0, 0,2,0, 0,0,3]);});
+    assertGLError(gl, gl.INVALID_VALUE, "bad transpose 2fv",
+        function(){gl.uniformMatrix2fv(fm2, true, [1,0, 0,2]);});
+    assertThrowNoGLError(gl, "bad location",
+        function(){gl.uniformMatrix4fv(588939, false, [1,0,0,0, 0,2,0,0, 0,0,3,0, 0,0,0,4]);});
+    assertThrowNoGLError(gl, "bad location (negative)",
+        function(){gl.uniformMatrix4fv(-588939, false, [1,0,0,0, 0,2,0,0, 0,0,3,0, 0,0,0,4]);});
+    assertGLError(gl, gl.INVALID_VALUE, "17 values to 4fv",
+        function(){gl.uniformMatrix4fv(fm4, false, [0,1,0,0,0, 0,2,0,0, 0,0,3,0, 0,0,0,4]);});
+    assertGLError(gl, gl.INVALID_VALUE, "5 values to 2fv",
+        function(){gl.uniformMatrix2fv(fm2, false, [0,2,1, 2,2]);});
+    assertGLError(gl, gl.INVALID_VALUE, "10 values to 3fv",
+        function(){gl.uniformMatrix3fv(fm3, false, [0,2,2,2, 2,2,2, 2,1,2]);});
+    assertGLError(gl, gl.INVALID_VALUE, "too few values to 4fv",
+        function(){gl.uniformMatrix4fv(fm4, false, [0,0,0, 0,2,0,0, 0,0,3,0, 0,0,0,4]);});
+    assertGLError(gl, gl.INVALID_VALUE, "too few values to 2fv",
+        function(){gl.uniformMatrix2fv(fm2, false, [1, 2,2]);});
+    assertGLError(gl, gl.INVALID_VALUE, "too few values to 2fv",
+        function(){gl.uniformMatrix2fv(fm2, false, []);});
+    assertThrowNoGLError(gl, "string for data",
+        function(){gl.uniformMatrix2fv(fm2, false, "fm4");});
+    assertGLError(gl, gl.INVALID_VALUE, "too few values to 3fv",
+        function(){gl.uniformMatrix3fv(fm3, false, [2,2, 2,2,2, 2,1,2]);});
+    gl.uniformMatrix4fv(fm4, false, [1,0,0,0, 0,2,0,0, 0,0,3,0, 0,0,0,4]);
+    gl.uniformMatrix2fv(fm2, false, [2,1, 2,2]);
+    gl.uniformMatrix3fv(fm3, false, [2,2,2, 2,2,2, 2,1,2]);
+    assertGLError(gl, gl.INVALID_OPERATION, "3fv on mat4",
+        function(){gl.uniformMatrix3fv(fm4, false, [1,0,0, 0,2,0, 0,0,3]);});
+    assertGLError(gl, gl.INVALID_OPERATION, "3fv on mat2",
+        function(){gl.uniformMatrix3fv(fm2, false, [0,2,1, 0,2,2, 0,0,0]);});
+    assertGLError(gl, gl.INVALID_OPERATION, "2fv om mat3",
+        function(){gl.uniformMatrix2fv(fm3, false, [2,2, 2,2]);});
+    for (var ii = 2; ii <= 4; ++ii) {
+      var all = [];
+      var mats = [[],[]];
+      for (var jj = 0; jj < 2; ++jj) {
+        for (var kk = 0; kk < ii * ii; ++kk) {
+          mats[jj].push(jj + 1);
+          all.push(jj + 1);
+        }
+      }
+      var loc0Name = 'am' + ii + '[0]';
+      var loc1Name = 'am' + ii + '[1]';
+      var loc0 = f.uniform(loc0Name);
+      var loc1 = f.uniform(loc1Name);
+      var fname = "uniformMatrix" + ii + "fv";
+      assert(loc0Name, loc0 != null);
+      assert(loc1Name, loc1 != null);
+      assertOk("set array of 2 matrices " + ii + "fv",
+          function(){gl[fname].call(gl,loc0, false, all);});
+      var actual = unwrappedGL.getUniform(sh.shader.program, loc0);
+      assert("got value for loc0",
+             gl.NO_ERROR == checkError(gl, "getUniform loc0"));
+      assertArrayEquals(mats[0], actual);
+      var actual = unwrappedGL.getUniform(sh.shader.program, loc1);
+      assert("got value for loc1",
+             gl.NO_ERROR == checkError(gl, "getUniform loc1"));
+      assertArrayEquals(mats[1], actual);
+      assertOk("set array of second array of 2 matrixes",
+          function(){gl[fname].call(gl, loc1, false, mats[0]);});
+      var actual = unwrappedGL.getUniform(sh.shader.program, loc1);
+      assert("got value for loc1",
+             gl.NO_ERROR == checkError(gl, "getUniform loc1"));
+      assertArrayEquals(mats[0], actual);
+      var big = mats[1].concat([3]);
+      assertGLError(gl, gl.INVALID_VALUE, "set array of first array of 2 matrixes plus 1 value",
+          function(){gl[fname].call(gl, loc0, false, big);});
+    }
+  });
+  var d = new Uint8Array(4);
+  gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+  assertArrayEquals([1,2,3,8], d);
+  sh.destroy();
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="foobar-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+uniform mat2 fm2;
+uniform mat3 fm3;
+uniform mat2 am2[2];
+uniform mat3 am3[2];
+uniform mat4 am4[2];
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, fm2[0][1]+fm3[2][1], 0.0);
+    float d = am2[0][1][1] + am3[0][2][2] + am4[0][3][3] +
+              am2[1][1][1] + am3[1][2][2] + am4[1][3][3];
+    gl_Position = vec4(Vertex, 1.0 + d * 0.0001);
+}
+</script>
+<script id="foobar-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform mat4 fm4;
+
+varying vec4 texCoord0;
+void main()
+{
+    gl_FragColor = vec4(fm4[0][0]/256.0, fm4[1][1]/256.0, fm4[2][2]/256.0, fm4[3][3]*texCoord0.z/256.0);
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/uniformf.html b/conformance/more/functions/uniformf.html
new file mode 100644
index 0000000..17fb141
--- /dev/null
+++ b/conformance/more/functions/uniformf.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testUniformf = function(gl) {
+  var sh = new Filter(gl, 'foobar-vert', 'foobar-frag');
+  sh.apply(function(f){
+    var foo = f.uniform('foo');
+    var bar = f.uniform('bar');
+    gl.uniform4fv(foo, [1,2,3,4]);
+    gl.uniform1fv(bar, [2]);
+  });
+  var d = new Uint8Array(4);
+  gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+  assertEquals([1,2,3,8], [d[0], d[1], d[2], d[3]]);
+  sh.apply(function(f){
+    var foo = f.uniform('foo');
+    var bar = f.uniform('bar');
+    gl.uniform4f(foo, 2,2,3,4);
+    gl.uniform1f(bar, 3);
+  });
+  gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+  assertEquals([2,2,3,12], [d[0], d[1], d[2], d[3]]);
+  sh.destroy();
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="foobar-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+uniform float bar;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, bar, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="foobar-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform vec4 foo;
+
+varying vec4 texCoord0;
+void main()
+{
+    gl_FragColor = vec4(foo.r/255.0, foo.g/255.0, foo.b/255.0, foo.a*texCoord0.z/255.0);
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/uniformfArrayLen1.html b/conformance/more/functions/uniformfArrayLen1.html
new file mode 100644
index 0000000..6146be4
--- /dev/null
+++ b/conformance/more/functions/uniformfArrayLen1.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testUniformArray = function(gl) {
+    var sh = new Filter(gl, 'foobar-vert', 'foobar-frag');
+    sh.apply(function(f){
+        var uniV4 = f.uniform('uniV4');
+        var uniFloat = f.uniform('uniFloat');
+        assertOk("1fv on 1fv",
+            function(){gl.uniform1fv(uniFloat, [2]);});
+        assertOk("5 values on 1fv",
+            function(){gl.uniform1fv(uniFloat, [2,3,4,5,6]);});
+        assertOk("4fv on 4fv",
+            function(){gl.uniform4fv(uniV4, [1, 2, 3, 4]);});
+        assertOk("8 values on 4fv",
+            function(){gl.uniform4fv(uniV4, [1, 2, 3, 4, 5, 6, 7, 8]);});
+
+    var uniformsFound = 0;
+    var numUniforms = gl.getProgramParameter(f.shader.program, gl.ACTIVE_UNIFORMS);
+    for (var i = 0; i < numUniforms; ++i) {
+        var uniformName = gl.getActiveUniform(f.shader.program, i).name;
+        if (uniformName.indexOf('uniV4') == 0 || uniformName.indexOf('uniFloat') == 0) {
+            assert("Uniform array of length 1 ends with [0]", uniformName.indexOf("[0]") != -1);
+            ++uniformsFound;
+        }
+    }
+    assert("Both uniforms found", uniformsFound == 2);
+
+        uniV4 = f.uniform('uniV4[0]');
+        uniFloat = f.uniform('uniFloat[0]');
+        assertOk("1fv on 1fv",
+            function(){gl.uniform1fv(uniFloat, [2]);});
+        assertOk("5 values on 1fv",
+            function(){gl.uniform1fv(uniFloat, [2,3,4,5,6]);});
+        assertOk("4fv on 4fv",
+            function(){gl.uniform4fv(uniV4, [1, 2, 3, 4]);});
+        assertOk("8 values on 4fv",
+            function(){gl.uniform4fv(uniV4, [1, 2, 3, 4, 5, 6, 7, 8]);});
+
+    });
+    var d = new Uint8Array(4);
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+    assertArrayEquals([1,2,3,8], d);
+    sh.destroy();
+    throwError(gl);
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="foobar-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+uniform float uniFloat[1];
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, uniFloat[0], 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="foobar-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform vec4 uniV4[1];
+
+varying vec4 texCoord0;
+void main()
+{
+    gl_FragColor = vec4(
+        uniV4[0].r/255.0,
+        uniV4[0].g/255.0,
+        uniV4[0].b/255.0,
+        uniV4[0].a*texCoord0.z/255.0);
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/uniformfBadArgs.html b/conformance/more/functions/uniformfBadArgs.html
new file mode 100644
index 0000000..47b6dbe
--- /dev/null
+++ b/conformance/more/functions/uniformfBadArgs.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testUniformf = function(gl) {
+    var sh = new Filter(gl, 'foobar-vert', 'foobar-frag');
+    sh.apply(function(f){
+        var uniV4 = f.uniform('uniV4');
+        var uniFloat = f.uniform('uniFloat');
+        assertThrowNoGLError(gl, "number for location",
+            function(){gl.uniform4fv(58882929, [1,2,3,4]);});
+        assertThrowNoGLError(gl, "negative number for location",
+            function(){gl.uniform4fv(-58882929, [1,2,3,4]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "1fv on 4fv",
+            function(){gl.uniform1fv(uniV4, [1,2,3,4]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "more than enough values 1fv",
+            function(){gl.uniform1fv(uniFloat, [2,3,4,5,6]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "4fv on float",
+            function(){gl.uniform4fv(uniFloat, [2,3,4,5]);});
+        assertOk("4fv on 4fv",
+            function(){gl.uniform4fv(uniV4, [1, 2, 3, 4]);});
+        assertGLError(gl, gl.INVALID_VALUE, "5 values on 4fv",
+            function(){gl.uniform4fv(uniV4, [1, 2, 3, 4, 5]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "8 values on 4fv",
+            function(){gl.uniform4fv(uniV4, [1, 2, 3, 4, 5, 6, 7, 8]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "3fv on float",
+            function(){gl.uniform3fv(uniFloat, [2,3,4]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "2fv on float",
+            function(){gl.uniform2fv(uniFloat, [2,3]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "3fv on 4fv",
+            function(){gl.uniform3fv(uniV4, [4,5,6]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "2fv on 4fv",
+            function(){gl.uniform2fv(uniV4, [5,6]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "1fv on 4fv",
+            function(){gl.uniform1fv(uniV4, [6]);});
+        assertOk("1fv on 1fv",
+            function(){gl.uniform1fv(uniFloat, [2]);});
+        assertGLError(gl, gl.INVALID_VALUE, "not enough values on 1fv",
+            function(){gl.uniform1fv(uniFloat, []);});
+        assertGLError(gl, gl.INVALID_VALUE, "not enough values on 4fv",
+            function(){gl.uniform4fv(uniV4, [3,3,4]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "4iv on 4fv",
+            function(){gl.uniform4iv(uniV4, [1, 2, 3, 4]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "1iv on 1fv",
+            function(){gl.uniform1iv(uniFloat, [2]);});
+    });
+    var d = new Uint8Array(4);
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+    assertArrayEquals([1,2,3,8], d);
+    sh.destroy();
+    throwError(gl);
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="foobar-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+uniform float uniFloat;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, uniFloat, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="foobar-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform vec4 uniV4;
+
+varying vec4 texCoord0;
+void main()
+{
+    gl_FragColor = vec4(
+        uniV4.r/255.0,
+        uniV4.g/255.0,
+        uniV4.b/255.0,
+        uniV4.a*texCoord0.z/255.0);
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/uniformi.html b/conformance/more/functions/uniformi.html
new file mode 100644
index 0000000..7765639
--- /dev/null
+++ b/conformance/more/functions/uniformi.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testUniformf = function(gl) {
+  var sh = new Filter(gl, 'foobar-vert', 'foobar-frag');
+  sh.apply(function(f){
+    var foo = f.uniform('foo');
+    var bar = f.uniform('bar');
+    gl.uniform4iv(foo, [1,2,3,4]);
+    gl.uniform1iv(bar, [2]);
+  });
+  var d = new Uint8Array(4);
+  gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+  assertEquals([1,2,3,8], [d[0], d[1], d[2], d[3]]);
+  sh.apply(function(f){
+    var foo = f.uniform('foo');
+    var bar = f.uniform('bar');
+    gl.uniform4i(foo, 2,2,3,4);
+    gl.uniform1i(bar, 3);
+  });
+  gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+  assertEquals([2,2,3,12], [d[0], d[1], d[2], d[3]]);
+  sh.destroy();
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="foobar-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+uniform int bar;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, float(bar), 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="foobar-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform ivec4 foo;
+
+varying vec4 texCoord0;
+void main()
+{
+    gl_FragColor = vec4(float(foo.r)/256.0, float(foo.g)/256.0, float(foo.b)/256.0, float(foo.a)*texCoord0.z/256.0);
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/uniformiBadArgs.html b/conformance/more/functions/uniformiBadArgs.html
new file mode 100644
index 0000000..9bfc4e4
--- /dev/null
+++ b/conformance/more/functions/uniformiBadArgs.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  return [gl];
+}
+
+Tests.testUniformf = function(gl) {
+    var sh = new Filter(gl, 'foobar-vert', 'foobar-frag');
+    sh.apply(function(f){
+        var uniIV4 = f.uniform('uniIV4');
+        var uniInt = f.uniform('uniInt');
+        assertThrowNoGLError(gl, "number as location",
+            function(){gl.uniform4iv(58882929, [1,2,3,4]);});
+        assertThrowNoGLError(gl, "negative number as location",
+            function(){gl.uniform4iv(-58882929, [1,2,3,4]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "more than 1 value to 1iv",
+            function(){gl.uniform1iv(uniInt, [2,3,4,5,6]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "4iv on int",
+            function(){gl.uniform4iv(uniInt, [2,3,4,5]);});
+        assertOk("4iv on 4iv",
+            function(){gl.uniform4iv(uniIV4, [1, 2, 3, 4]);});
+        assertGLError(gl, gl.INVALID_VALUE, "5 values on 4iv",
+            function(){gl.uniform4iv(uniIV4, [1, 2, 3, 4, 5]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "8 values on 4iv",
+            function(){gl.uniform4iv(uniIV4, [1, 2, 3, 4, 5, 6, 7, 8]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "3iv on int",
+            function(){gl.uniform3iv(uniInt, [2,3,4]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "2iv on int",
+            function(){gl.uniform2iv(uniInt, [2,3]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "3iv on 4iv",
+            function(){gl.uniform3iv(uniIV4, [4,5,6]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "2iv on 4iv",
+            function(){gl.uniform2iv(uniIV4, [5,6]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "1iv on 4iv",
+            function(){gl.uniform1iv(uniIV4, [6]);});
+        assertGLError(gl, gl.INVALID_VALUE, "not enough values",
+            function(){gl.uniform1iv(uniInt, []);});
+        assertGLError(gl, gl.INVALID_OPERATION, "1fv on int",
+            function(){gl.uniform1fv(uniInt, [2]);});
+        assertGLError(gl, gl.INVALID_OPERATION, "4fv on ivec4",
+            function(){gl.uniform4fv(uniIV4, [2,3,4,5]);});
+        gl.uniform1iv(uniInt, [2]);
+        gl.uniform4iv(uniIV4, [1, 2, 3, 4]);
+    });
+    var d = new Uint8Array(4);
+    gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, d);
+    assertArrayEquals([1,2,3,8], d);
+    sh.destroy();
+    throwError(gl);
+}
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="foobar-vert" type="x-shader/x-vertex">
+attribute vec3 Vertex;
+attribute vec2 Tex;
+
+uniform int uniInt;
+
+varying vec4 texCoord0;
+void main()
+{
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, float(uniInt), 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+}
+</script>
+<script id="foobar-frag" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform ivec4 uniIV4;
+
+varying vec4 texCoord0;
+void main()
+{
+    gl_FragColor = vec4(
+        float(uniIV4.x)/256.0,
+        float(uniIV4.y)/256.0,
+        float(uniIV4.z)/256.0,
+        float(uniIV4.a)*texCoord0.z/256.0);
+}
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/functions/vertexAttrib.html b/conformance/more/functions/vertexAttrib.html
new file mode 100644
index 0000000..6ba021a
--- /dev/null
+++ b/conformance/more/functions/vertexAttrib.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var sh = prog.shader.program;
+//   log(gl.getShaderInfoLog(prog.shaders[1]));
+  var v = gl.getAttribLocation(sh, 'Vertex');
+  var n = gl.getAttribLocation(sh, 'Normal');
+  var t = gl.getAttribLocation(sh, 'Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  assert(0 == gl.getError());
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+Tests.testVertexAttrib = function(gl, prog, v,n,t) {
+  var vbo = gl.createBuffer();
+  var vertsArr = new Float32Array(verts);
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsArr, gl.STATIC_DRAW);
+  gl.enableVertexAttribArray(v);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 3);});
+  gl.vertexAttrib1fv(v, [1]);
+  gl.vertexAttrib2fv(v, [1,2]);
+  gl.vertexAttrib3fv(v, [1,2,3]);
+  gl.vertexAttrib4fv(v, [1,2,3,4]);
+  gl.vertexAttrib1f(v, 1);
+  gl.vertexAttrib2f(v, 1,2);
+  gl.vertexAttrib3f(v, 1,2,3);
+  gl.vertexAttrib4f(v, 1,2,3,4);
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 3);});
+  throwError(gl);
+  gl.bindBuffer(gl.ARRAY_BUFFER, null);
+  gl.deleteBuffer(vbo);
+  throwError(gl);
+}
+Tests.testVertexAttribVBO = function(gl, prog, v,n,t) {
+  var vbo = gl.createBuffer();
+  var vertsArr = new Float32Array(verts);
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsArr, gl.STATIC_DRAW);
+  gl.enableVertexAttribArray(v);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.vertexAttrib1fv(v, [1]);
+  gl.vertexAttrib2fv(v, [1,2]);
+  gl.vertexAttrib3fv(v, [1,2,3]);
+  gl.vertexAttrib4fv(v, [1,2,3,4]);
+  gl.vertexAttrib1f(v, 1);
+  gl.vertexAttrib2f(v, 1,2);
+  gl.vertexAttrib3f(v, 1,2,3);
+  gl.vertexAttrib4f(v, 1,2,3,4);
+  assertOk(function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);});
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 3);});
+  gl.vertexAttrib4fv(v, [1,2,3,4]);
+  assertOk(function(){gl.drawArrays(gl.TRIANGLES, 0, 3);});
+  throwError(gl);
+  gl.bindBuffer(gl.ARRAY_BUFFER, null);
+  gl.deleteBuffer(vbo);
+  throwError(gl);
+}
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    vec4 c = texCoord0;
+    gl_FragColor = c;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/vertexAttribBadArgs.html b/conformance/more/functions/vertexAttribBadArgs.html
new file mode 100644
index 0000000..06b366f
--- /dev/null
+++ b/conformance/more/functions/vertexAttribBadArgs.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var sh = prog.shader.program;
+//   log(gl.getShaderInfoLog(prog.shaders[1]));
+  var v = gl.getAttribLocation(sh, 'Vertex');
+  var n = gl.getAttribLocation(sh, 'Normal');
+  var t = gl.getAttribLocation(sh, 'Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  assert(0 == gl.getError());
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+
+Tests.testVertexAttrib = function(gl, prog, v,n,t) {
+  var vbo = gl.createBuffer();
+  var vertsArr = new Float32Array(verts);
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsArr, gl.STATIC_DRAW);
+  gl.enableVertexAttribArray(v);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  assertFail("bad index",
+      function(){gl.vertexAttrib1f(-1, 1);});
+  assertFail("bad index (big negative)",
+      function(){gl.vertexAttrib1f(-69092342, 1);});
+  assertFail("bad index (big positive)",
+      function(){gl.vertexAttrib1f(58928938, 1);});
+  assertOk("array too large",
+      function(){gl.vertexAttrib1fv(v, [1,2,3,4,5]);});
+  assertFail("array too small",
+      function(){gl.vertexAttrib1fv(v, []);});
+  assertOk("draw",
+      function(){gl.drawArrays(gl.TRIANGLES, 0, 3);});
+  throwError(gl);
+}
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    vec4 c = texCoord0;
+    gl_FragColor = c;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/vertexAttribPointer.html b/conformance/more/functions/vertexAttribPointer.html
new file mode 100644
index 0000000..e53a02c
--- /dev/null
+++ b/conformance/more/functions/vertexAttribPointer.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var sh = prog.shader.program;
+//   log(gl.getShaderInfoLog(prog.shaders[1]));
+  var v = gl.getAttribLocation(sh, 'Vertex');
+  var n = gl.getAttribLocation(sh, 'Normal');
+  var t = gl.getAttribLocation(sh, 'Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  assert(0 == gl.getError());
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+Tests.testVertexAttribPointerVBO = function(gl, prog, v,n,t) {
+  var vbo = gl.createBuffer();
+  var vertsArr = new Float32Array(verts);
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsArr, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 4);
+  gl.bindBuffer(gl.ARRAY_BUFFER, null);
+  gl.deleteBuffer(vbo);
+}
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    vec4 c = texCoord0;
+    gl_FragColor = c;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/functions/vertexAttribPointerBadArgs.html b/conformance/more/functions/vertexAttribPointerBadArgs.html
new file mode 100644
index 0000000..2ab45f1
--- /dev/null
+++ b/conformance/more/functions/vertexAttribPointerBadArgs.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+var verts = [0.0, 0.0, 0.0,   1.0, 0.0, 0.0,   0.0, 1.0, 0.0];
+var normals = [0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0];
+var texcoords = [0.0,0.0,  1.0,0.0,  0.0,1.0];
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = wrapGLContext(getGLContext(canvas));
+  var prog = new Shader(gl, 'vert', 'frag');
+  prog.use();
+  var sh = prog.shader.program;
+//   log(gl.getShaderInfoLog(prog.shaders[1]));
+  var v = gl.getAttribLocation(sh, 'Vertex');
+  var n = gl.getAttribLocation(sh, 'Normal');
+  var t = gl.getAttribLocation(sh, 'Tex');
+  return [gl,prog,v,n,t];
+}
+
+Tests.setup = function(gl, prog, v,n,t) {
+  assert(0 == gl.getError());
+  return [gl, prog, v,n,t];
+}
+Tests.teardown = function(gl, prog, v,n,t) {
+  gl.disableVertexAttribArray(v);
+  gl.disableVertexAttribArray(n);
+  gl.disableVertexAttribArray(t);
+}
+
+Tests.endUnit = function(gl, prog, v,n,t) {
+  prog.destroy();
+}
+
+Tests.testVertexAttribPointerVBO = function(gl, prog, v,n,t) {
+  var vbo = gl.createBuffer();
+  var vertsArr = new Float32Array(verts);
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+  gl.bufferData(gl.ARRAY_BUFFER, vertsArr, gl.STATIC_DRAW);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  assertFail("negative offset",
+      function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, -4);});
+  assertOk("out of range offset (OK because we can change the buffer later)",
+      function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 1200);});
+  assertFail("Offset that is incompatible with type",
+      function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 3);});
+  assertFail("negative stride",
+      function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, -1, 0);});
+  assertFail("bad size",
+      function(){gl.vertexAttribPointer(v, 5, gl.FLOAT, false, 0, 0);});
+  assertFail("stride that doesn't match type",
+      function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 1, 0);});
+  assertFail("bad type",
+      function(){gl.vertexAttribPointer(v, 3, gl.TEXTURE_2D, false, 0, 0);});
+  assertFail("bad index",
+      function(){gl.vertexAttribPointer(-1, 3, gl.FLOAT, false, 0, 0);});
+  assertFail("bad index (big negative)",
+      function(){gl.vertexAttribPointer(-8693948, 3, gl.FLOAT, false, 0, 0);});
+  assertFail("bad index (big positive)",
+      function(){gl.vertexAttribPointer(8693948, 3, gl.FLOAT, false, 0, 0);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, null);
+  assertFail("binding to null buffer",
+      function(){gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);});
+  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+  gl.vertexAttribPointer(v, 3, gl.FLOAT, false, 0, 0);
+  gl.bindBuffer(gl.ARRAY_BUFFER, null);
+  gl.deleteBuffer(vbo);
+  throwError(gl);
+}
+
+</script>
+<script id="vert" type="x-shader/x-vertex">
+  attribute vec3 Vertex;
+  attribute vec3 Normal;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex * Normal, 1.0);
+    texCoord0 = vec4(Tex,0.0,0.0) + gl_Position;
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+  precision mediump float;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    vec4 c = texCoord0;
+    gl_FragColor = c;
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/glsl/arrayOutOfBounds.html b/conformance/more/glsl/arrayOutOfBounds.html
new file mode 100644
index 0000000..94d03de
--- /dev/null
+++ b/conformance/more/glsl/arrayOutOfBounds.html
@@ -0,0 +1,279 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = getGLContext(canvas);
+  return [gl];
+}
+
+Tests.testOk = function(gl) {
+    var sh = new Filter(gl, 'okvert', 'frag');
+    assertOk(function(){sh.apply();});
+    sh.destroy();
+
+    var sh = new Filter(gl, 'vert', 'okfrag');
+    assertOk(function(){sh.apply();});
+    sh.destroy();
+
+    var sh = new Filter(gl, 'vert', 'frag');
+    assertOk(function(){sh.apply();});
+    sh.destroy();
+}
+
+var arr = ['cr', 'cw', 'vr', 'vw'];
+arr.forEach(function(e){
+    if (e == 'cr' || e == 'cw') {
+        Tests['test'+e+'vert'] = function(gl) {
+            var sh = new Filter(gl, e+'vert', 'frag');
+            assertFail(function(){sh.apply();});
+            sh.destroy();
+        }
+    }
+    Tests['test'+e+'frag'] = function(gl) {
+        var sh = new Filter(gl, 'vert', e+'frag');
+        assertFail(function(){sh.apply();});
+        sh.destroy();
+    }
+});
+
+
+</script>
+<script id="okvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Tex;
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+    gl_Position = vec4(Vertex, x[2]);
+  }
+</script>
+<script id="crvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Tex;
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+    gl_Position = vec4(Vertex, x[4]);
+  }
+</script>
+<script id="cwvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Tex;
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+    x[4] = Vertex.z;
+    gl_Position = vec4(Vertex, x[4]);
+  }
+</script>
+<!-- This one can't be required to fail compilation, because vertex shaders must support arbitrary array indexing -->
+<script id="vrvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Tex;
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+    int idx = 4 * int(max(1.0, Vertex.x*20.0));
+    gl_Position = vec4(Vertex, x[idx]);
+  }
+</script>
+<!-- This one can't be required to fail compilation, because vertex shaders must support arbitrary array indexing -->
+<script id="vwvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Tex;
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+    int idx = 4 * int(max(1.0, Vertex.x*20.0));
+    x[idx] = Vertex.z;
+    gl_Position = vec4(Vertex, x[idx]);
+  }
+</script>
+<script id="vert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Tex;
+    gl_Position = vec4(Vertex, 0.0);
+  }
+</script>
+
+<script id="okfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  varying vec2 TexCoord;
+
+  void main()
+  {
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+    gl_FragColor = vec4(1.0, 0.0, TexCoord.s, x[2]);
+  }
+</script>
+<script id="crfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  varying vec2 TexCoord;
+
+  void main()
+  {
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+    gl_FragColor = vec4(1.0, 0.0, TexCoord.s, x[4]);
+  }
+</script>
+<script id="cwfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  varying vec2 TexCoord;
+
+  void main()
+  {
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+
+    x[4] = 6.0;
+    gl_FragColor = vec4(1.0, 0.0, TexCoord.s, x[4]);
+  }
+</script>
+<!-- This one actually fails because of WebGL's restrictions on indexing expressions in fragment shaders -->
+<script id="vrfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  varying vec2 TexCoord;
+
+  void main()
+  {
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+
+    int idx = 4 * int(max(1.0, TexCoord.x*20.0));
+    gl_FragColor = vec4(1.0, 0.0, TexCoord.s, x[idx]);
+  }
+</script>
+<!-- This one actually fails because of WebGL's restrictions on indexing expressions in fragment shaders -->
+<script id="vwfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  varying vec2 TexCoord;
+
+  void main()
+  {
+    float x[3];
+    x[0] = 1.0;
+    x[1] = 2.0;
+    x[2] = 3.0;
+
+    int idx = 4 * int(max(1.0, TexCoord.x*20.0));
+    x[idx] = 6.0;
+    gl_FragColor = vec4(1.0, 0.0, TexCoord.s, x[idx]);
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  varying vec2 TexCoord;
+
+  void main()
+  {
+    gl_FragColor = vec4(1.0, 0.0, TexCoord.s, 1.0);
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="16" height="16"></canvas>
+</body></html>
diff --git a/conformance/more/glsl/longLoops.html b/conformance/more/glsl/longLoops.html
new file mode 100644
index 0000000..6a11fdf
--- /dev/null
+++ b/conformance/more/glsl/longLoops.html
@@ -0,0 +1,253 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = getGLContext(canvas);
+  return [gl];
+}
+
+Tests.autorun = false;
+Tests.message = "Caution: might hang your GPU"
+
+var arr = ['whiletrue', 'loop100M', 'loopComp', 'variable'];
+arr.forEach(function(e){
+    Tests['test'+e+'vert'] = function(gl) {
+        var sh = new Filter(gl, e+'vert', 'frag');
+        assertOk(function(){
+            sh.apply();
+        });
+        sh.destroy();
+    }
+    Tests['test'+e+'frag'] = function(gl) {
+        var sh = new Filter(gl, 'vert', e+'frag');
+        assertOk(function(){
+            sh.apply();
+        });
+        sh.destroy();
+    }
+});
+
+Tests.testMandelbrot = function(gl) {
+    gl.disable(gl.DEPTH_TEST);
+    var sh = new Filter(gl, 'identity-vert', 'mandelbrot-frag');
+    sh.apply(function(s){
+        s.uniform1f('z', 0.15);
+        s.uniform1f('x', -1.25);
+    });
+    for (var i=0; i<256; i++) {
+        sh.apply();
+    }
+    sh.destroy();
+}
+
+</script>
+<script id="identity-vert" type="x-shader/x-vertex">
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+
+  varying vec2 texCoord0;
+  void main()
+  {
+    texCoord0 = vec2(Tex.s, Tex.t);
+    gl_Position = vec4(Vertex, 1.0);
+  }
+</script>
+<script id="mandelbrot-frag" type="x-shader/x-fragment">
+    precision mediump float;
+
+    uniform float x,y,z;
+    varying vec2 texCoord0;
+    vec4 iter_z(float cr, float ci) {
+        int i;
+        float nzr, nzi, zr = 0.0, zi = 0.0;
+        vec4 color = vec4(0.0);
+        for (i=0; i<2500; i++) {
+            nzr = zr * zr - zi * zi + cr;
+            nzi = 2.0 * zr * zi + ci;
+            zr = nzr;
+            zi = nzi;
+        }
+        color = vec4(zi);
+        color.a = 1.0;
+        return color;
+    }
+
+    void main()
+    {
+        gl_FragColor = iter_z(x+z*(2.0*texCoord0.s-1.5), y+z*(2.0*texCoord0.t-1.0));
+    }
+</script>
+<script id="whiletruevert" type="x-shader/x-vertex">
+
+
+    attribute vec3 Vertex; attribute vec2 Tex;
+    varying vec2 TexCoord;
+    void main()
+    {
+        float z = 1.0;
+        while(true) { z += 0.1; z *= 0.995; }
+        TexCoord = Tex.st;
+        gl_Position = vec4(Vertex, z);
+    }
+</script>
+<script id="loop100Mvert" type="x-shader/x-vertex">
+
+
+    attribute vec3 Vertex; attribute vec2 Tex;
+    varying vec2 TexCoord;
+    void main()
+    {
+        int i;
+        float z = 1.0;
+        for (i = 0; i<1000000000; i++) {
+            z += 0.1; z *= 0.995;
+        }
+        TexCoord = Tex.st;
+        gl_Position = vec4(Vertex, z);
+    }
+</script>
+<script id="loopCompvert" type="x-shader/x-vertex">
+
+
+    attribute vec3 Vertex; attribute vec2 Tex;
+    varying vec2 TexCoord;
+    void main()
+    {
+        float z = 1.0;
+        while(z > 0.0) { z += 0.1; z *= 0.995; }
+        TexCoord = Tex.st;
+        gl_Position = vec4(Vertex, z);
+    }
+</script>
+<script id="variablevert" type="x-shader/x-vertex">
+
+
+    attribute vec3 Vertex; attribute vec2 Tex;
+    varying vec2 TexCoord;
+
+    void main()
+    {
+        float z = 1.0;
+        while(z > Vertex.z) { z += 0.1; z *= 0.995; }
+        TexCoord = Tex.st;
+        gl_Position = vec4(Vertex, z);
+    }
+</script>
+<script id="vert" type="x-shader/x-vertex">
+
+
+    attribute vec3 Vertex; attribute vec2 Tex;
+    varying vec2 TexCoord;
+    void main()
+    {
+        TexCoord = Tex.st;
+        gl_Position = vec4(Vertex, 0.0);
+    }
+</script>
+
+<script id="whiletruefrag" type="x-shader/x-fragment">
+
+
+    precision mediump float;
+
+    varying vec2 TexCoord;
+    void main()
+    {
+        float z = 1.0;
+        while(true) { z += 0.1; z *= 0.995; }
+        gl_FragColor = vec4(1.0, TexCoord.s, TexCoord.t, z);
+    }
+</script>
+<script id="loop100Mfrag" type="x-shader/x-fragment">
+
+
+    precision mediump float;
+
+    varying vec2 TexCoord;
+    void main()
+    {
+        int i;
+        float z = 1.0;
+        for (i = 0; i<1000000000; i++) {
+            z += 0.1; z *= 0.995;
+        }
+        gl_FragColor = vec4(1.0, TexCoord.s, TexCoord.t, z);
+    }
+</script>
+<script id="loopCompfrag" type="x-shader/x-fragment">
+
+
+    precision mediump float;
+
+    varying vec2 TexCoord;
+    void main()
+    {
+        float z = TexCoord.s;
+        while(z > 0.0) { z += 0.1; z *= 0.995; }
+        gl_FragColor = vec4(1.0, TexCoord.s, TexCoord.t, z);
+    }
+</script>
+<script id="variablefrag" type="x-shader/x-fragment">
+
+
+    precision mediump float;
+
+    varying vec2 TexCoord;
+    void main()
+    {
+        float z = 1.0;
+        while(z > TexCoord.s) { z += 0.1; z *= 0.995; }
+        gl_FragColor = vec4(1.0, TexCoord.s, TexCoord.t, z);
+    }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  varying vec2 TexCoord;
+  void main()
+  {
+    gl_FragColor = vec4(1.0, TexCoord.s, TexCoord.t, 1.0);
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="512" height="512"></canvas>
+</body></html>
diff --git a/conformance/more/glsl/uniformOutOfBounds.html b/conformance/more/glsl/uniformOutOfBounds.html
new file mode 100644
index 0000000..1016cb7
--- /dev/null
+++ b/conformance/more/glsl/uniformOutOfBounds.html
@@ -0,0 +1,217 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = getGLContext(canvas);
+  return [gl];
+}
+
+var arr = ['cr', 'cw', 'vr', 'vw', 'tvw'];
+arr.forEach(function(e){
+    Tests['test'+e+'vert'] = function(gl) {
+        var sh = new Filter(gl, e+'vert', 'frag');
+        assertFail(function(){sh.apply(function(f){
+            f.uniform3fv('x', [0.0, 1.0, 2.0]);
+            throwError(e+"vert");
+        });});
+        sh.destroy();
+    }
+    Tests['test'+e+'frag'] = function(gl) {
+        var sh = new Filter(gl, 'vert', e+'frag');
+        assertFail(function(){sh.apply(function(f){
+            f.uniform3fv('x', [0.0, 1.0, 2.0]);
+            throwError(e+"frag");
+        });});
+        sh.destroy();
+    }
+});
+
+</script>
+<script id="crvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex; attribute vec2 Tex;
+  uniform float x[3];
+  void main()
+  {
+      gl_Position = vec4(Vertex.st, Tex.s, x[4]);
+  }
+</script>
+<script id="cwvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex; attribute vec2 Tex;
+  uniform float x[3];
+  void main()
+  {
+    x[4] = Vertex.z;
+    gl_Position = vec4(Vertex.st, Tex.s, x[4]);
+  }
+</script>
+<script id="vrvert" type="x-shader/x-vertex">
+
+
+  uniform float x[3];
+  attribute vec3 Vertex; attribute vec2 Tex;
+  void main()
+  {
+    float idx = 40.0 * max(1.0, Vertex.x*20.0);
+    gl_Position = vec4(Vertex, x[2] + Tex.s + x[int(idx)]);
+  }
+</script>
+<script id="vwvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex; attribute vec2 Tex;
+  uniform float x[3];
+  void main()
+  {
+    int idx = 4 * int(max(1.0, Vertex.x*20.0));
+    x[idx] = Vertex.z;
+    gl_Position = vec4(Vertex.st, Tex.s, x[idx]);
+  }
+</script>
+<script id="tvwvert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex; attribute vec2 Tex;
+  uniform float x[3];
+  void main()
+  {
+    int idx = 4 * int(max(1.0, Vertex.x*20.0));
+    x[2] = Vertex[idx];
+    gl_Position = vec4(Vertex.st, Tex.s, x[2]);
+  }
+</script>
+<script id="vert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex; attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Vertex.st;
+    gl_Position = vec4(Vertex, Tex.s);
+  }
+</script>
+
+<script id="crfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  uniform float x[3];
+
+  varying vec2 TexCoord;
+  void main()
+  {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, x[4]);
+  }
+</script>
+<script id="cwfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  uniform float x[3];
+
+  varying vec2 TexCoord;
+  void main()
+  {
+    x[4] = 6.0;
+    gl_FragColor = vec4(1.0, 0.0, 0.0, x[4]);
+  }
+</script>
+<script id="vrfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  uniform float x[3];
+
+  varying vec2 TexCoord;
+  void main()
+  {
+    int idx = 4 * int(max(1.0, TexCoord.s*20.0));
+    gl_FragColor = vec4(1.0, 0.0, 0.0, x[idx]);
+  }
+</script>
+<script id="vwfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  uniform float x[3];
+
+  varying vec2 TexCoord;
+  void main()
+  {
+    int idx = 4 * int(max(1.0, TexCoord.s*20.0));
+    x[idx] = 6.0;
+    gl_FragColor = vec4(1.0, 0.0, 0.0, x[idx]);
+  }
+</script>
+<script id="tvwfrag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  uniform float x[3];
+
+  varying vec2 TexCoord;
+  void main()
+  {
+    int idx = 4 * int(max(1.0, TexCoord.s*20.0));
+    x[2] = TexCoord[idx];
+    gl_FragColor = vec4(1.0, 0.0, 0.0, x[2]);
+  }
+</script>
+<script id="frag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  void main()
+  {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/glsl/unusedAttribsUniforms.html b/conformance/more/glsl/unusedAttribsUniforms.html
new file mode 100644
index 0000000..177b776
--- /dev/null
+++ b/conformance/more/glsl/unusedAttribsUniforms.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.autorun = false;
+Tests.message = "Caution: may crash the browser";
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = getGLContext(canvas);
+  return [gl];
+}
+
+Tests.testPassingTooManyVBOs = function(gl) {
+    var sh = new Filter(gl, 'vert', 'frag');
+    assertFail(function(){sh.apply();});
+    sh.destroy();
+}
+Tests.testPassingTooManyUniforms = function(gl) {
+    var sh = new Filter(gl, 'vert2', 'frag2');
+    sh.apply(function(f){
+        assertFail(function(){f.uniform1f('foo', 3.0);throwError("foo")});
+        f.uniform1f('bar', 1.0);
+        assertFail(function(){f.uniform1f('baz', 1.0);throwError("baz")});
+    });
+    sh.destroy();
+}
+
+</script>
+
+<script id="vert" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex; attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Vertex.st;
+    gl_Position = vec4(Vertex, 0.0);
+  }
+</script>
+
+<script id="frag" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  void main()
+  {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+  }
+</script>
+<script id="vert2" type="x-shader/x-vertex">
+
+
+  uniform float foo, bar;
+  attribute vec3 Vertex; attribute vec2 Tex;
+  varying vec2 TexCoord;
+  void main()
+  {
+    TexCoord = Tex.st;
+    gl_Position = vec4(Vertex, bar);
+  }
+</script>
+
+<script id="frag2" type="x-shader/x-fragment">
+
+
+  precision mediump float;
+
+  uniform float baz;
+
+  varying vec2 TexCoord;
+  void main()
+  {
+    gl_FragColor = vec4(1.0, TexCoord.s, 0.0, 1.0);
+  }
+</script>
+
+
+<style>canvas{ position:absolute; }</style>
+</head><body>
+  <canvas id="gl" width="1" height="1"></canvas>
+</body></html>
diff --git a/conformance/more/index.html b/conformance/more/index.html
new file mode 100644
index 0000000..82cb95a
--- /dev/null
+++ b/conformance/more/index.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+  <head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+    <title>WebGL tests</title>
+  </head>
+  <body>
+    
+    <h1>Tests for the WebGL canvas context</h1>
+    
+    <h2>These tests are intended to serve the following purposes:</h2>
+    <ul>
+      <li>Assert spec conformance (so that WebGL pages work the same across browsers and hardware)</li>
+      <li>Check the safety of the GL binding (bounds checking, same origin policy)</li>
+      <li>Provide performance numbers for developers</li>
+    </ul>
+
+    <h2>Test runners</h2>
+    <ul>
+      <li><a href="all_tests_linkonly.html">Run tests manually</a></li>
+      <li><a href="all_tests.html">Run all tests in parallel</a></li>
+      <li><a href="all_tests_sequential.html">Run all tests sequentially</a></li>
+    </ul>
+
+
+    <h2>Demos</h2>
+    <ul>
+      <li><a href="demos/opengl_web.html">OpenGL for the web</a> (requires working FBOs and null textures or using canvases as textures)</li>
+      <li><a href="demos/video.html">Green screen video demo with a color remapping filter</a> (requires loading &lt;video> to texture with texImage2D)</li>
+    </ul>
+
+    <h2>Running the tests</h2>
+
+    <ol>
+      <li><a href="http://learningwebgl.com/blog/?p=11">Install a browser with WebGL support</a></li>
+      <li>Open one of the test runners linked above in your browser.</li>
+      <li>For more control over the tests, go the <a href="http://github.com/kig/canvas3d-tests">GitHub page</a>, see the readme, and clone the repo.</li>
+    </ol>
+
+    <h2>Want to contribute?</h2>
+
+    <p>See the <a href="README.md">README</a>.</p>
+    <p>See the <a href="http://github.com/kig/canvas3d-tests">GitHub page</a>.</p>
+    <p>Mail me at <a href="mailto:ilmari.heikkinen@gmail.com">ilmari.heikkinen@gmail.com</a></p>
+
+
+    <h2>For more information on WebGL</h2>
+
+    <ul>
+      <li><a href="http://planet-webgl.org">Planet WebGL</a></li>
+      <li><a href="http://learningwebgl.com">Learning WebGL</a></li>
+      <li><a href="http://www.khronos.org/message_boards/viewforum.php?f=34">WebGL on Khronos Message Boards</a></li>
+    </ul>
+
+    <h2>Developer links</h2>
+    <ul>
+      <li><a href="https://bugzilla.mozilla.org/buglist.cgi?quicksearch=webgl">WebGL on Mozilla Bugzilla</a></li>
+      <li><a href="https://bugzilla.webkit.org/buglist.cgi?quicksearch=webgl">WebGL on WebKit Bugzilla</a></li>
+      <li><a href="http://code.google.com/p/chromium/issues/list?q=label:3D-WebGL">WebGL on Chromium Bugzilla</a></li>
+    </ul>
+
+
+    <h2>License</h2>
+
+    <p>
+      These tests are released under the BSD license. The images and videos used in the tests are the respective property of their authors.
+    </p>
+
+  </body>
+</html>
diff --git a/conformance/more/performance/CPUvsGPU.html b/conformance/more/performance/CPUvsGPU.html
new file mode 100644
index 0000000..8d1f91c
--- /dev/null
+++ b/conformance/more/performance/CPUvsGPU.html
@@ -0,0 +1,360 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.autorun = false;
+Tests.message = "This might take a few seconds to run"
+
+Tests.startUnit = function() {
+  var gl = document.getElementById('gl').getContext(GL_CONTEXT_ID);
+  var ctx = document.getElementById('2d').getContext('2d');
+  return [gl, ctx];
+}
+
+var kernel = [0.006, 0.061, 0.242, 0.383, 0.242, 0.061, 0.006];
+
+Tests.testGPU = function(gl, ctx) {
+  Tests.gpuGaussianBlur(gl);
+}
+
+Tests.testCPU = function(gl, ctx) {
+  Tests.cpuGaussianBlur(ctx);
+}
+
+function hblur(ctx,idata) {
+  var d = idata.data;
+  var res = ctx.createImageData(256,256);
+  var rd = res.data;
+  var sumR=0.0,sumG=0.0,sumB=0.0,sumA=0.0, kv=0.0;
+  var col_offset = 0, row_offset = 0, k4 = 0;
+  for (var y=0; y<idata.height; ++y) {
+    col_offset = y * idata.width * 4;
+    for (var x=3; x<idata.width-3; ++x) {
+      row_offset = col_offset+x*4;
+      sumR=sumG=sumB=sumA=0.0;
+      for (var k=-3; k<4; ++k) {
+        k4 = k * 4;
+        kv = kernel[k+3];
+        sumR += d[row_offset+k4+0] * kv;
+        sumG += d[row_offset+k4+1] * kv;
+        sumB += d[row_offset+k4+2] * kv;
+        sumA += d[row_offset+k4+3] * kv;
+      }
+      rd[row_offset+0] = Math.floor(sumR);
+      rd[row_offset+1] = Math.floor(sumG);
+      rd[row_offset+2] = Math.floor(sumB);
+      rd[row_offset+3] = Math.floor(sumA);
+    }
+  }
+  var xr = 3;
+  for (var y=0; y<idata.height; ++y) {
+    col_offset = y * idata.width * 4;
+    for (var x=0; x<xr; ++x) {
+      row_offset = col_offset+x*4;
+      sumR=sumG=sumB=sumA=0.0;
+      for (var k=-3; k<4; ++k) {
+        if (k+x < 0)
+          k4 = 0;
+        else
+          k4 = k * 4;
+        kv = kernel[k+3];
+        sumR += d[row_offset+k4+0] * kv;
+        sumG += d[row_offset+k4+1] * kv;
+        sumB += d[row_offset+k4+2] * kv;
+        sumA += d[row_offset+k4+3] * kv;
+      }
+      rd[row_offset+0] = Math.floor(sumR);
+      rd[row_offset+1] = Math.floor(sumG);
+      rd[row_offset+2] = Math.floor(sumB);
+      rd[row_offset+3] = Math.floor(sumA);
+    }
+  }
+  var xr = idata.width-3;
+  for (var y=0; y<idata.height; ++y) {
+    col_offset = y * idata.width * 4;
+    for (var x=xr; x<idata.width; ++x) {
+      row_offset = col_offset+x*4;
+      sumR=sumG=sumB=sumA=0.0;
+      for (var k=-3; k<4; ++k) {
+        if (k+x >= idata.width)
+          k4 = (idata.width-x-1)*4;
+        else
+          k4 = k * 4;
+        kv = kernel[k+3];
+        sumR += d[row_offset+k4+0] * kv;
+        sumG += d[row_offset+k4+1] * kv;
+        sumB += d[row_offset+k4+2] * kv;
+        sumA += d[row_offset+k4+3] * kv;
+      }
+      rd[row_offset+0] = Math.floor(sumR);
+      rd[row_offset+1] = Math.floor(sumG);
+      rd[row_offset+2] = Math.floor(sumB);
+      rd[row_offset+3] = Math.floor(sumA);
+    }
+  }
+  return res;
+}
+
+function vblur(ctx,idata) {
+  var d = idata.data;
+  var res = ctx.createImageData(256,256);
+  var rd = res.data;
+  var sumR=0.0,sumG=0.0,sumB=0.0,sumA=0.0, kv=0.0;
+  var col_offset = 0, row_offset = 0, kfac = idata.width*4;
+  for (var y=3; y<idata.height-3; ++y) {
+    col_offset = y * idata.width * 4;
+    for (var x=0; x<idata.width; ++x) {
+      row_offset = col_offset+x*4;
+      sumR=sumG=sumB=sumA=0.0;
+      for (var k=-3; k<4; ++k) {
+        k4 = k * kfac;
+        kv = kernel[k+3];
+        sumR += d[row_offset+k4+0] * kv;
+        sumG += d[row_offset+k4+1] * kv;
+        sumB += d[row_offset+k4+2] * kv;
+        sumA += d[row_offset+k4+3] * kv;
+      }
+      rd[row_offset+0] = Math.floor(sumR);
+      rd[row_offset+1] = Math.floor(sumG);
+      rd[row_offset+2] = Math.floor(sumB);
+      rd[row_offset+3] = Math.floor(sumA);
+    }
+  }
+  var yr = 3;
+  for (var y=0; y<yr; ++y) {
+    col_offset = y * idata.width * 4;
+    for (var x=0; x<idata.width; ++x) {
+      row_offset = col_offset+x*4;
+      sumR=sumG=sumB=sumA=0.0;
+      for (var k=-3; k<4; ++k) {
+        if (k+y < 0)
+          k4 = 0;
+        else
+          k4 = k * kfac;
+        kv = kernel[k+3];
+        sumR += d[row_offset+k4+0] * kv;
+        sumG += d[row_offset+k4+1] * kv;
+        sumB += d[row_offset+k4+2] * kv;
+        sumA += d[row_offset+k4+3] * kv;
+      }
+      rd[row_offset+0] = Math.floor(sumR);
+      rd[row_offset+1] = Math.floor(sumG);
+      rd[row_offset+2] = Math.floor(sumB);
+      rd[row_offset+3] = Math.floor(sumA);
+    }
+  }
+  var yr = idata.height-3;
+  for (var y=yr; y<idata.height; ++y) {
+    col_offset = y * idata.width * 4;
+    for (var x=0; x<idata.width; ++x) {
+      row_offset = col_offset+x*4;
+      sumR=sumG=sumB=sumA=0.0;
+      for (var k=-3; k<4; ++k) {
+        if (k+y >= idata.height)
+          k4 = (idata.height-y-1)*kfac;
+        else
+          k4 = k * kfac;
+        kv = kernel[k+3];
+        sumR += d[row_offset+k4+0] * kv;
+        sumG += d[row_offset+k4+1] * kv;
+        sumB += d[row_offset+k4+2] * kv;
+        sumA += d[row_offset+k4+3] * kv;
+      }
+      rd[row_offset+0] = Math.floor(sumR);
+      rd[row_offset+1] = Math.floor(sumG);
+      rd[row_offset+2] = Math.floor(sumB);
+      rd[row_offset+3] = Math.floor(sumA);
+    }
+  }
+  return res;
+}
+
+Tests.cpuGaussianBlur = function(ctx) {
+  var s = document.getElementById('cpustat');
+  var t0 = new Date().getTime();
+  ctx.drawImage(document.getElementById('logo'),0,0);
+  var idata = ctx.getImageData(0,0,256,256);
+  for (var i=0; i<1; i++){
+    idata = hblur(ctx,idata);
+    idata = vblur(ctx,idata);
+  }
+  ctx.putImageData(idata, 0, 0);
+  var t1 = new Date().getTime();
+  s.textContent = 'Done! Time: '+(t1-t0)+'ms';
+}
+
+Tests.gpuGaussianBlur = function(gl) {
+  var s = document.getElementById('gpustat');
+  var t0 = new Date().getTime();
+
+  var fbo1 = new FBO(gl, 256, 256);
+  var fbo2 = new FBO(gl, 256, 256);
+  var hblur = new Filter(gl, 'identity-vert', 'hblur-frag');
+  var vblur = new Filter(gl, 'identity-vert', 'vblur-frag');
+  var identity = new Filter(gl, 'identity-vert', 'identity-frag');
+  var identityFlip = new Filter(gl, 'identity-flip-vert', 'identity-frag');
+
+  gl.viewport(0,0,256,256);
+  gl.clearColor(0,0,1,1);
+  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+  gl.disable(gl.DEPTH_TEST);
+  gl.activeTexture(gl.TEXTURE0);
+
+  fbo1.use();
+  var tex = loadTexture(gl, document.getElementById('logo'));
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  identityFlip.apply(); // draw image
+
+  // gaussian blur
+  for (var i=0; i<1000; i++) {
+    fbo2.use();
+    gl.bindTexture(gl.TEXTURE_2D, fbo1.texture);
+    hblur.apply(function(f){
+      f.uniform1f('width', 256.0);
+      f.uniform1i('Texture', 0);
+    });
+    fbo1.use();
+    gl.bindTexture(gl.TEXTURE_2D, fbo2.texture);
+    vblur.apply(function(f){
+      f.uniform1f('height', 256.0);
+      f.uniform1i('Texture', 0);
+    });
+  }
+
+  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+  gl.bindTexture(gl.TEXTURE_2D, fbo1.texture);
+  identity.apply(); // draw blurred image on screen
+
+  fbo1.destroy();
+  fbo2.destroy();
+  hblur.destroy();
+  vblur.destroy();
+  identity.destroy();
+  identityFlip.destroy();
+  gl.deleteTexture(tex);
+  checkError(gl, "end");
+  var t1 = new Date().getTime();
+  s.textContent = 'Done! Time: '+(t1-t0)+'ms';
+}
+
+
+</script>
+<script id="identity-vert" type="x-shader/x-vertex">
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    texCoord0 = vec4(Tex, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+  }
+</script>
+<script id="identity-flip-vert" type="x-shader/x-vertex">
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    texCoord0 = vec4(Tex.s, 1.0-Tex.t, 0.0, 0.0);
+    gl_Position = vec4(Vertex, 1.0);
+  }
+</script>
+<script id="identity-frag" type="x-shader/x-fragment">
+
+  precision mediump float;
+
+  uniform sampler2D Texture;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    gl_FragColor = texture2D(Texture, texCoord0.st);
+  }
+</script>
+<script id="hblur-frag" type="x-shader/x-fragment">
+
+  precision mediump float;
+
+  uniform sampler2D Texture;
+  uniform float width;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    float kernel[7] = float[7](0.006, 0.061, 0.242, 0.383, 0.242, 0.061, 0.006);
+    int i;
+    float step = 1.0 / width;
+    vec4 sum = vec4(0.0);
+    for (i=-3; i<=3; i++) {
+      vec4 tmp = texture2D(Texture, texCoord0.st + vec2(float(i)*step, 0.0));
+      sum = (tmp * kernel[i+3]) + sum;
+    }
+    gl_FragColor = sum;
+  }
+</script>
+<script id="vblur-frag" type="x-shader/x-fragment">
+
+  precision mediump float;
+
+  uniform sampler2D Texture;
+  uniform float height;
+
+  varying vec4 texCoord0;
+  void main()
+  {
+    float kernel[7] = float[7](0.006, 0.061, 0.242, 0.383, 0.242, 0.061, 0.006);
+    int i;
+    float step = 1.0 / height;
+    vec4 sum = vec4(0.0);
+    for (i=-3; i<=3; i++) {
+      vec4 tmp = texture2D(Texture, texCoord0.st + vec2(0.0, float(i)*step));
+      sum = (tmp * kernel[i+3]) + sum;
+    }
+    gl_FragColor = sum;
+  }
+</script>
+</head><body>
+<img id="logo" src="data:image/png;base64,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" width="256" height="256"><br>
+<div id="cpu">CPU 1x Gaussian blur</div>
+<div id="cpustat"></div>
+<canvas id="2d" width="256" height="256"></canvas>
+<div id="gpu">GPU 1000x Gaussian blur</div>
+<div id="gpustat"></div>
+<canvas id="gl" width="256" height="256"></canvas><br>
+</body></html>
diff --git a/conformance/more/performance/bandwidth.html b/conformance/more/performance/bandwidth.html
new file mode 100644
index 0000000..c2c524d
--- /dev/null
+++ b/conformance/more/performance/bandwidth.html
@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.autorun = false;
+Tests.message = "This might take a second or two. Take the upload numbers with a dose of salt, as there's no drawing code using the data.";
+
+Tests.startUnit = function () {
+  var canvas = document.getElementById('gl');
+  var gl = getGLContext(canvas);
+  return [gl];
+}
+
+Tests.testTexImage2D = function(gl) {
+    var tex = gl.createTexture();
+    var texArr = new Array(256*256*4);
+    var bufData = new Array(256*256*4);
+    for (var i=0; i<texArr.length; i++) texArr[i] = 0;
+    for (var i=0; i<bufData.length; i++) bufData[i] = 0.5;
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    time("texImage2D", function() {
+        for (var i=0; i<100; i++)
+            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.UNSIGNED_BYTE, texArr);
+    });
+    time("texImage2D", function() {
+        for (var i=0; i<100; i++)
+            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.UNSIGNED_BYTE, texArr);
+    });
+    time("texSubImage2D", function() {
+        for (var i=0; i<100; i++)
+            gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 256, 256, gl.RGBA, gl.UNSIGNED_BYTE, texArr);
+    });
+    var img = document.getElementById('logo');
+    time("texImage2DHTML", function() {
+        for (var i=0; i<100; i++)
+            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
+    });
+    time("texSubImage2DHTML", function() {
+        for (var i=0; i<100; i++)
+            gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, img);
+    });
+    var bufs = [gl.createBuffer(), gl.createBuffer()];
+    var buf = bufs[0], buf2 = bufs[1];
+    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+    var bufArr = new Float32Array(bufData);
+    time("bufferDataNoChange", function() {
+        for (var i=0; i<100; i++)
+            gl.bufferData(gl.ARRAY_BUFFER, bufArr, gl.STATIC_DRAW);
+    });
+    time("bufferSubDataNoChange", function() {
+        for (var i=0; i<100; i++)
+            gl.bufferSubData(gl.ARRAY_BUFFER, 0, bufArr);
+    });
+    time("bufferData", function() {
+        var bufArr = new Float32Array(bufData);
+        for (var i=0; i<25; i++)
+            gl.bufferData(gl.ARRAY_BUFFER, bufArr, gl.STATIC_DRAW);
+    });
+    time("bufferSubData", function() {
+        var bufArr = new Float32Array(bufData);
+        for (var i=0; i<25; i++)
+            gl.bufferSubData(gl.ARRAY_BUFFER, 0, bufArr);
+    });
+    var sh = new Shader(gl, 'vert-v', 'frag-v');
+    gl.disable(gl.DEPTH_TEST);
+    sh.use();
+    var v = sh.attrib('Vertex');
+    for (var i=0; i<16; i++)
+        gl.disableVertexAttribArray(i);
+    gl.enableVertexAttribArray(v);
+    gl.vertexAttribPointer(v, 4, gl.FLOAT, false, 0, 0);
+    time("verticeDraw", function() {
+        for (var i=0; i<100; i++)
+            gl.drawArrays(gl.TRIANGLES, 0, 256*256);
+        gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE);
+    });
+    gl.bindBuffer(gl.ARRAY_BUFFER, buf2);
+    gl.bufferData(gl.ARRAY_BUFFER, bufArr, gl.STATIC_DRAW);
+    time("verticeDrawC", function() {
+        for (var i=0; i<100; i++) {
+            gl.bindBuffer(gl.ARRAY_BUFFER, (i % 2 == 0) ? buf : buf2);
+            gl.drawArrays(gl.TRIANGLES, 0, 256*256);
+        }
+        gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE);
+    });
+    // Drawing arrays with vertexAttribPointer seems to have been removed from WebGL.
+/*    gl.bindBuffer(gl.ARRAY_BUFFER, null);
+    gl.vertexAttribPointer(v, 4, gl.FLOAT, false, 0, bufArr);
+    time("verticeDrawVA", function() {
+        for (var i=0; i<100; i++)
+            gl.drawArrays(gl.TRIANGLES, 0, 256*256);
+        gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE);
+    });
+    time("verticeDrawVAC", function() {
+        for (var i=0; i<100; i++) {
+            gl.vertexAttribPointer(v, 4, gl.FLOAT, false, 0, bufArr);
+            gl.drawArrays(gl.TRIANGLES, 0, 256*256);
+        }
+        gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE);
+    });*/
+    sh.destroy();
+    sh = new Filter(gl, 'vert-t', 'frag-t');
+    sh.apply();
+    time("textureDraw", function() {
+        for (var i=0; i<1000; i++)
+            gl.drawArrays(gl.TRIANGLES, 0, 6);
+        gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE);
+    });
+    sh.destroy();
+    time("readPixels", function() {
+        for (var i=0; i<100; i++)
+            gl.readPixels(0, 0, 256, 256, gl.RGBA, gl.UNSIGNED_BYTE);
+    });
+    time("getImageData", function() {
+        for (var i=0; i<100; i++)
+            gl.getImageData(0, 0, 256, 256);
+    });
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    gl.bindBuffer(gl.ARRAY_BUFFER, null);
+    bufs.forEach(function(buf){ gl.deleteBuffer(buf) });
+    gl.deleteTexture(tex);
+}
+
+
+Tests.endUnit = function(gl) {
+}
+
+</script>
+<script id="vert-v" type="x-shader/x-vertex">
+
+  attribute vec4 Vertex;
+  void main()
+  {
+    gl_Position = Vertex;
+  }
+</script>
+<script id="frag-v" type="x-shader/x-fragment">
+
+  precision mediump float;
+
+  void main()
+  {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+  }
+</script>
+<script id="vert-t" type="x-shader/x-vertex">
+
+
+  attribute vec3 Vertex;
+  attribute vec2 Tex;
+  varying vec2 texCoord0;
+  void main()
+  {
+    gl_Position = vec4(Vertex, 1.0);
+    texCoord0 = Tex;
+  }
+</script>
+<script id="frag-t" type="x-shader/x-fragment">
+
+  precision mediump float;
+
+  uniform sampler2D Texture;
+
+  varying vec2 texCoord0;
+  void main()
+  {
+    gl_FragColor = texture2D(Texture, texCoord0);
+  }
+</script>
+
+<style>canvas{ position:absolute; }
+img{ display:none; }</style>
+</head><body>
+<h3>100x 256x256x4 texture upload with texImage2D (26.2MB total)</h3>
+<p id="texImage2D"></p>
+<h3>100x 256x256x4 texture upload with texSubImage2D (26.2MB total)</h3>
+<p id="texSubImage2D"></p>
+<h3>100x 256x256x4 texture upload with texImage2DHTML (26.2MB total)</h3>
+<p id="texImage2DHTML"></p>
+<h3>100x 256x256x4 texture upload with texSubImage2DHTML (26.2MB total)</h3>
+<p id="texSubImage2DHTML"></p>
+<h3>100x 256x256x4 readPixels (26.2MB total)</h3>
+<p id="readPixels"></p>
+<h3>100x 256x256x4 getImageData (26.2MB total)</h3>
+<p id="getImageData"></p>
+<h3>25x 256x256x4 float bufferData (6.6MB total)</h3>
+<p id="bufferData"></p>
+<h3>25x 256x256x4 float bufferSubData (6.6MB total)</h3>
+<p id="bufferSubData"></p>
+<h3>100x 256x256x4 float bufferData, reuse Float32Array (26.2MB total)</h3>
+<p id="bufferDataNoChange"></p>
+<h3>100x 256x256x4 float bufferSubData, reuse Float32Array (26.2MB total)</h3>
+<p id="bufferSubDataNoChange"></p>
+<h3>100x 256x256 vert VBO draw</h3>
+<p id="verticeDraw"></p>
+<h3>100x 256x256 vert VBO draw, change VBO after each draw</h3>
+<p id="verticeDrawC"></p>
+<!--<h3>100x 256x256 vert vertex array draw</h3>
+<p id="verticeDrawVA"></p>
+<h3>100x 256x256 vert vertex array draw, change array after each draw</h3>
+<p id="verticeDrawVAC"></p>-->
+<h3>1000x 256x256 texture draw</h3>
+<p id="textureDraw"></p>
+<canvas id="gl" width="256" height="256"></canvas>
+<img id="logo" src="data:image/png;base64,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" width="256" height="256">
+</body></html>
diff --git a/conformance/more/performance/jsGCPause.html b/conformance/more/performance/jsGCPause.html
new file mode 100644
index 0000000..9ff0cf3
--- /dev/null
+++ b/conformance/more/performance/jsGCPause.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.autorun = false;
+
+Tests.testInterval = function() {
+    document.getElementById('dtMax').textContent = "Running, please wait...";
+    setTimeout(function() {
+        var t0 = new Date().getTime();
+        var t = t0;
+        var dtMax = 0;
+        var frames = [];
+        var iv;
+        iv = setInterval(function() {
+            if (t > t0 + 10000) {
+                var hist = [];
+                var dtMed = 0, maxi = [0, 0];
+                for (var i=0; i<dtMax; i++) hist[i] = 0;
+                for (var i=0; i<frames.length; i++)
+                    hist[frames[i]]++;
+                for (var i=0; i<hist.length; i++)
+                    if (hist[i] > maxi[1]) maxi = [i, hist[i]];
+                dtMed = maxi[0];
+                var dtAvg = frames.reduce(function(s,i){return s + i;})/frames.length;
+                document.getElementById('dtMax').textContent = "Longest frame: " + dtMax + " ms. Average frame: " + Math.floor(dtAvg*100)*0.01 + " ms. Median frame: " + dtMed + " ms.";
+                clearInterval(iv);
+                var c = document.getElementById('dtCanvas');
+                c.width = frames.length;
+                c.height = dtMax;
+                var ctx = c.getContext('2d');
+                for (var i=0; i<frames.length; i++)
+                    ctx.fillRect(i,0,1,frames[i]);
+            }
+            var t1 = new Date().getTime();
+            if (t1-t > dtMax) dtMax = t1-t;
+            frames.push(t1-t);
+            t = t1;
+            var rot = Matrix.rotate((t/400) % (2*Math.PI), [0, 1+(t%1000), 1]);
+            var trans = Matrix.translate3(0, Math.cos(t/1000)*1, Math.sin(t/1000)*3);
+            for (var i=0; i<200; i++)
+                var mat = Matrix.mul4x4(rot, trans);
+        }, 16);
+    }, 0);
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<h3>10 seconds of 60fps 200x mul4x4, frame time statistics</h3>
+<p id="dtMax"></p>
+<canvas id="dtCanvas"></canvas>
+</body></html>
+
diff --git a/conformance/more/performance/jsMatrixMult.html b/conformance/more/performance/jsMatrixMult.html
new file mode 100644
index 0000000..610fe4f
--- /dev/null
+++ b/conformance/more/performance/jsMatrixMult.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.autorun = false;
+Tests.message = "This might take a second or two (or ten)";
+
+Tests.testMatrixMultiply = function() {
+    time("testMatrixMultiply", function() {
+        var mat = Matrix.identity;
+        for (var i=0; i<1000000; i++)
+            Matrix.mul4x4(mat, mat);
+    });
+}
+Tests.testTransformStack = function() {
+    time("testTransformStack", function() {
+        var j = 0;
+        for (var i=0; i<1000; i++) {
+            var t = new Date().getTime();
+            var pmat = Matrix.perspective(30, 600/400, 1, 100+(t%1000));
+            var look = Matrix.lookAt([4,-1,8], [-0.2,0+(t%1000),0], [0,1,0]);
+            var rot = Matrix.rotate((t/400) % (2*Math.PI), [0, 1+(t%1000), 1]);
+            var trans = Matrix.translate3(0, Math.cos(t/1000)*1, Math.sin(t/1000)*3);
+            var sca = Matrix.scale1(0.6+(t%1000));
+            var vmat = Matrix.identity;
+            vmat = Matrix.mul4x4(vmat, pmat);
+            vmat = Matrix.mul4x4(vmat, look);
+            vmat = Matrix.mul4x4(vmat, trans);
+            vmat = Matrix.mul4x4(vmat, sca);
+            vmat = Matrix.mul4x4(vmat, rot);
+            j += vmat.length;
+        }
+        Tests.j = j;
+    });
+}
+
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<h3>1000000x JavaScript Matrix.mul4x4</h3>
+<p id="testMatrixMultiply"></p>
+<h3>1000x JavaScript transform stack</h3>
+<p id="testTransformStack"></p>
+</body></html>
+
diff --git a/conformance/more/performance/jsToGLOverhead.html b/conformance/more/performance/jsToGLOverhead.html
new file mode 100644
index 0000000..f555ea7
--- /dev/null
+++ b/conformance/more/performance/jsToGLOverhead.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<!--
+
+/*
+** 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.
+*/
+
+-->
+<link rel="stylesheet" type="text/css" href="../unit.css" />
+<script type="application/x-javascript" src="../unit.js"></script>
+<script type="application/x-javascript" src="../util.js"></script>
+<script type="application/x-javascript">
+
+Tests.autorun = false;
+Tests.message = "This might take a second or two";
+
+Tests.testGLOverhead = function() {
+    var gl = document.getElementById("gl").getContext(GL_CONTEXT_ID);
+    var fakeGl = {getError: function(){ return 0; }};
+    time("testGLOverhead", function() {
+        for (var i=0; i<1000000; i++)
+            gl.getError();
+    });
+    time("testJSOverhead", function() {
+        for (var i=0; i<1000000; i++)
+            fakeGl.getError();
+    });
+    time("testLoopOverhead", function() {
+        for (var i=0; i<1000000;)
+            i++;
+    });
+}
+
+</script>
+<style>canvas{ position:absolute; }</style>
+</head><body>
+<canvas id="gl" width="16" height="16"></canvas>
+<h3>1000000x gl.getError() (measuring JS->GL call overhead)</h3>
+<p id="testGLOverhead"></p>
+<h3>1000000x fakeGl.getError() (measuring JS->JS call overhead)</h3>
+<p id="testJSOverhead"></p>
+<h3>1000000x i++ (measuring loop overhead)</h3>
+<p id="testLoopOverhead"></p>
+</body></html>
+
diff --git a/conformance/more/unit.css b/conformance/more/unit.css
new file mode 100644
index 0000000..0758b43
--- /dev/null
+++ b/conformance/more/unit.css
@@ -0,0 +1,66 @@
+/*
+Tests for the OpenGL ES 2.0 HTML Canvas context
+
+Copyright (C) 2009  Ilmari Heikkinen <ilmari.heikkinen@gmail.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is 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 Software.
+
+THE SOFTWARE IS 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 SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+.ok {
+  color: green;
+}
+.fail {
+  color: red;
+}
+canvas {
+  display: none;
+}
+#test-status {
+  font-size: large;
+}
+
+#test-log {
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+  background: white;
+  color: black;
+}
+#test-log > div {
+  padding-bottom: 0.5em;
+}
+#test-log h2 {
+  font-size: 1em;
+  margin-bottom: 0em;
+  padding-top: 0.5em;
+}
+#test-log h3 {
+  font-size: small;
+  margin-left: 1.5em;
+  margin-bottom: 0em;
+  margin-top: 0.5em;
+}
+#test-log p {
+  margin-left: 4em;
+  font-size: small;
+  margin-top: 0em;
+  margin-bottom: 0.2em;
+}
diff --git a/conformance/more/unit.js b/conformance/more/unit.js
new file mode 100644
index 0000000..ca9faac
--- /dev/null
+++ b/conformance/more/unit.js
@@ -0,0 +1,911 @@
+/*
+Unit testing library for the OpenGL ES 2.0 HTML Canvas context
+*/
+
+/*
+** 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.
+*/
+
+/* -- plaform specific code -- */
+
+// WebKit
+if (window.testRunner && !window.layoutTestController) {
+  window.layoutTestController = window.testRunner;
+}
+
+if (window.layoutTestController) {
+  layoutTestController.overridePreference("WebKitWebGLEnabled", "1");
+  layoutTestController.dumpAsText();
+  layoutTestController.waitUntilDone();
+
+  // The WebKit testing system compares console output.
+  // Because the output of the WebGL Tests is GPU dependent
+  // we turn off console messages.
+  window.console.log = function() { };
+  window.console.error = function() { };
+
+  // RAF doesn't work in LayoutTests. Disable it so the tests will
+  // use setTimeout instead.
+  window.requestAnimationFrame = undefined;
+  window.webkitRequestAnimationFrame = undefined;
+}
+
+if (window.internals) {
+  window.internals.settings.setWebGLErrorsToConsoleEnabled(false);
+}
+
+/* -- end platform specific code --*/
+Tests = {
+  autorun : true,
+  message : null,
+  delay : 0,
+
+  startUnit : function(){ return []; },
+  setup : function() { return arguments; },
+  teardown : function() {},
+  endUnit : function() {}
+}
+
+var __testSuccess__ = true;
+var __testFailCount__ = 0;
+var __testLog__;
+var __backlog__ = [];
+
+Object.toSource = function(a, seen){
+  if (a == null) return "null";
+  if (typeof a == 'boolean') return a ? "true" : "false";
+  if (typeof a == 'string') return '"' + a.replace(/"/g, '\\"') + '"';
+  if (a instanceof HTMLElement) return a.toString();
+  if (a.width && a.height && a.data) return "[ImageData]";
+  if (a instanceof Array) {
+    if (!seen) seen = [];
+    var idx = seen.indexOf(a);
+    if (idx != -1) return '#'+(idx+1)+'#';
+    seen.unshift(a);
+    var srcs = a.map(function(o){ return Object.toSource(o,seen) });
+    var prefix = '';
+    idx = seen.indexOf(a);
+    if (idx != -1) prefix = '#'+(idx+1)+'=';
+    return prefix + '[' + srcs.join(", ") + ']';
+  }
+  if (typeof a == 'object') {
+    if (!seen) seen = [];
+    var idx = seen.indexOf(a);
+    if (idx != -1) return '#'+(idx+1)+'#';
+    seen.unshift(a);
+    var members = [];
+    var name;
+    try {
+      for (var i in a) {
+        if (i.search(/^[a-zA-Z0-9]+$/) != -1)
+          name = i;
+        else
+          name = '"' + i.replace(/"/g, '\\"') + '"';
+        var ai;
+        try { ai = a[i]; }
+        catch(e) { ai = 'null /*ERROR_ACCESSING*/'; }
+        var s = name + ':' + Object.toSource(ai, seen);
+        members.push(s);
+      }
+    } catch (e) {}
+    var prefix = '';
+    idx = seen.indexOf(a);
+    if (idx != -1) prefix = '#'+(idx+1)+'=';
+    return prefix + '{' + members.join(", ") + '}'
+  }
+  if (typeof a == 'function')
+    return '('+a.toString().replace(/\n/g, " ").replace(/\s+/g, " ")+')';
+  return a.toString();
+}
+
+function formatError(e) {
+  if (window.console) console.log(e);
+  var pathSegs = location.href.toString().split("/");
+  var currentDoc = e.lineNumber != null ? pathSegs[pathSegs.length - 1] : null;
+  var trace = (e.filename || currentDoc) + ":" + e.lineNumber + (e.trace ? "\n"+e.trace : "");
+  return e.message + "\n" + trace;
+}
+
+function runTests() {
+  var h = document.getElementById('test-status');
+  if (h == null) {
+    h = document.createElement('h1');
+    h.id = 'test-status';
+    document.body.appendChild(h);
+  }
+  h.textContent = "";
+  var log = document.getElementById('test-log');
+  if (log == null) {
+    log = document.createElement('div');
+    log.id = 'test-log';
+    document.body.appendChild(log);
+  }
+  while (log.childNodes.length > 0)
+    log.removeChild(log.firstChild);
+
+  var setup_args = [];
+
+  if (Tests.startUnit != null) {
+    __testLog__ = document.createElement('div');
+    try {
+      setup_args = Tests.startUnit();
+      if (__testLog__.childNodes.length > 0)
+        log.appendChild(__testLog__);
+    } catch(e) {
+      testFailed("startUnit", formatError(e));
+      log.appendChild(__testLog__);
+      printTestStatus();
+      return;
+    }
+  }
+
+  var testsRun = false;
+  var allTestsSuccessful = true;
+
+  for (var i in Tests) {
+    if (i.substring(0,4) != "test") continue;
+    __testLog__ = document.createElement('div');
+    __testSuccess__ = true;
+    try {
+      doTestNotify (i);
+      var args = setup_args;
+      if (Tests.setup != null)
+        args = Tests.setup.apply(Tests, setup_args);
+      Tests[i].apply(Tests, args);
+      if (Tests.teardown != null)
+        Tests.teardown.apply(Tests, args);
+    }
+    catch (e) {
+      testFailed(i, e.name, formatError(e));
+    }
+    if (__testSuccess__ == false) {
+      ++__testFailCount__;
+    }
+    var h = document.createElement('h2');
+    h.textContent = i;
+    __testLog__.insertBefore(h, __testLog__.firstChild);
+    log.appendChild(__testLog__);
+    allTestsSuccessful = allTestsSuccessful && __testSuccess__ == true;
+    reportTestResultsToHarness(__testSuccess__, i);
+    doTestNotify (i+"--"+(__testSuccess__?"OK":"FAIL"));
+    testsRun = true;
+  }
+
+  printTestStatus(testsRun);
+  if (Tests.endUnit != null) {
+    __testLog__ = document.createElement('div');
+    try {
+      Tests.endUnit.apply(Tests, setup_args);
+      if (__testLog__.childNodes.length > 0)
+        log.appendChild(__testLog__);
+    } catch(e) {
+      testFailed("endUnit", e.name, formatError(e));
+      log.appendChild(__testLog__);
+    }
+  }
+  notifyFinishedToHarness(allTestsSuccessful, "finished tests");
+}
+
+function doTestNotify(name) {
+  //try {
+  //  var xhr = new XMLHttpRequest();
+  //  xhr.open("GET", "http://localhost:8888/"+name, true);
+  //  xhr.send(null);
+  //} catch(e) {}
+}
+
+function testFailed(assertName, name) {
+  var d = document.createElement('div');
+  var h = document.createElement('h3');
+  var d1 = document.createElement("span");
+  h.appendChild(d1);
+  d1.appendChild(document.createTextNode("FAIL: "));
+  d1.style.color = "red";
+  h.appendChild(document.createTextNode(
+      name==null ? assertName : name + " (in " + assertName + ")"));
+  d.appendChild(h);
+  var args = []
+  for (var i=2; i<arguments.length; i++) {
+    var a = arguments[i];
+    var p = document.createElement('p');
+    p.style.whiteSpace = 'pre';
+    p.textContent = (a == null) ? "null" :
+                    (typeof a == 'boolean' || typeof a == 'string') ? a : Object.toSource(a);
+    args.push(p.textContent);
+    d.appendChild(p);
+  }
+  __testLog__.appendChild(d);
+  __testSuccess__ = false;
+  doTestNotify([assertName, name].concat(args).join("--"));
+}
+
+function testPassed(assertName, name) {
+  var d = document.createElement('div');
+  var h = document.createElement('h3');
+  var d1 = document.createElement("span");
+  h.appendChild(d1);
+  d1.appendChild(document.createTextNode("PASS: "));
+  d1.style.color = "green";
+  h.appendChild(document.createTextNode(
+      name==null ? assertName : name + " (in " + assertName + ")"));
+  d.appendChild(h);
+  var args = []
+  for (var i=2; i<arguments.length; i++) {
+    var a = arguments[i];
+    var p = document.createElement('p');
+    p.style.whiteSpace = 'pre';
+    p.textContent = (a == null) ? "null" :
+                    (typeof a == 'boolean' || typeof a == 'string') ? a : Object.toSource(a);
+    args.push(p.textContent);
+    d.appendChild(p);
+  }
+  __testLog__.appendChild(d);
+  doTestNotify([assertName, name].concat(args).join("--"));
+}
+
+function checkTestSuccess() {
+  return __testFailCount__ == 0;
+}
+
+window.addEventListener('load', function(){
+  for (var i=0; i<__backlog__.length; i++)
+    log(__backlog__[i]);
+}, false);
+
+function log(msg) {
+  var p = document.createElement('p');
+  var a = [];
+  for (var i=0; i<arguments.length; i++)
+    a.push(arguments[i]);
+  p.textContent = a.join(", ");
+  if (!__testLog__) {
+    if (document.body)
+      document.body.appendChild(p);
+    else
+      __backlog__.push(msg);
+  } else {
+    __testLog__.appendChild(p);
+  }
+}
+
+function printTestStatus(testsRun) {
+  var status = document.getElementById('test-status');
+  if (testsRun) {
+    status.className = checkTestSuccess() ? 'ok' : 'fail';
+    status.textContent = checkTestSuccess() ? "PASS" : "FAIL";
+  } else {
+    status.className = 'fail';
+    status.textContent = "NO TESTS FOUND";
+  }
+}
+
+function assertFail(name, f) {
+  if (f == null) { f = name; name = null; }
+  var r = false;
+  try { f(); } catch(e) { r=true; }
+  if (!r) {
+    testFailed("assertFail", name, f);
+    return false;
+  } else {
+    testPassed("assertFail", name, f);
+    return true;
+  }
+}
+
+function assertOk(name, f) {
+  if (f == null) { f = name; name = null; }
+  var r = false;
+  var err;
+  try { f(); r=true; } catch(e) { err = e; }
+  if (!r) {
+    testFailed("assertOk", name, f, err.toString());
+    return false;
+  } else {
+    testPassed("assertOk", name, f);
+    return true;
+  }
+}
+
+function assert(name, v) {
+  if (v == null) { v = name; name = null; }
+  if (!v) {
+    testFailed("assert", name, v);
+    return false;
+  } else {
+    testPassed("assert", name, v);
+    return true;
+  }
+}
+
+function assertProperty(name, v, p) {
+  if (p == null) { p = v; v = name; name = p; }
+  if (v[p] == null) {
+    testFailed("assertProperty", name);
+    return false;
+  } else {
+    testPassed("assertProperty", name);
+    return true;
+  }
+}
+
+function compare(a,b) {
+  if (typeof a == 'number' && typeof b == 'number') {
+    return a == b;
+  } else {
+    return Object.toSource(a) == Object.toSource(b);
+  }
+}
+
+function assertEquals(name, v, p) {
+  if (p == null) { p = v; v = name; name = null; }
+  if (!compare(v, p)) {
+    testFailed("assertEquals", name, v, p);
+    return false;
+  } else {
+    testPassed("assertEquals", name, v, p);
+    return true;
+  }
+}
+
+function assertArrayEquals(name, v, p) {
+  if (p == null) { p = v; v = name; name = null; }
+  if (!v) {
+    testFailed("assertArrayEquals: first array undefined", name, v, p);
+    return false;
+  }
+  if (!p) {
+    testFailed("assertArrayEquals: second array undefined", name, v, p);
+    return false;
+  }
+  if (v.length != p.length) {
+    testFailed("assertArrayEquals", name, v, p);
+    return false;
+  }
+  for (var ii = 0; ii < v.length; ++ii) {
+    if (v[ii] != p[ii]) {
+      testFailed("assertArrayEquals", name, v, p);
+      return false;
+    }
+  }
+  testPassed("assertArrayEquals", name, v, p);
+  return true;
+}
+
+function assertNotEquals(name, v, p) {
+  if (p == null) { p = v; v = name; name = null; }
+  if (compare(v, p)) {
+    testFailed("assertNotEquals", name, v, p)
+    return false;
+  } else {
+    testPassed("assertNotEquals", name, v, p)
+    return true;
+  }
+}
+
+function time(elementId, f) {
+    var s = document.getElementById(elementId);
+    var t0 = new Date().getTime();
+    f();
+    var t1 = new Date().getTime();
+    s.textContent = 'Elapsed: '+(t1-t0)+' ms';
+}
+
+function randomFloat () {
+    // note that in fuzz-testing, this can used as the size of a buffer to allocate.
+    // so it shouldn't return astronomic values. The maximum value 10000000 is already quite big.
+    var fac = 1.0;
+    var r = Math.random();
+    if (r < 0.25)
+        fac = 10;
+    else if (r < 0.4)
+        fac = 100;
+    else if (r < 0.5)
+        fac = 1000;
+    else if (r < 0.6)
+        fac = 100000;
+    else if (r < 0.7)
+        fac = 10000000;
+    else if (r < 0.8)
+        fac = NaN;
+    return -0.5*fac + Math.random() * fac;
+}
+function randomFloatFromRange(lo, hi) {
+  var r = Math.random();
+  if (r < 0.05)
+    return lo;
+  else if (r > 0.95)
+    return hi;
+  else
+    return lo + Math.random()*(hi-lo);
+}
+function randomInt (sz) {
+  if (sz != null)
+    return Math.floor(Math.random()*sz);
+  else
+    return Math.floor(randomFloat());
+}
+function randomIntFromRange(lo, hi) {
+  return Math.floor(randomFloatFromRange(lo, hi));
+}
+function randomLength () {
+    var l = Math.floor(Math.random() * 256);
+    if (Math.random < 0.5) l = l / 10;
+    if (Math.random < 0.3) l = l / 10;
+    return l;
+}
+function randomSmallIntArray () {
+    var l = randomLength();
+    var s = new Array(l);
+    for (var i=0; i<l; i++)
+        s[i] = Math.floor(Math.random() * 256)-1;
+    return s;
+}
+function randomFloatArray () {
+    var l = randomLength();
+    var s = new Array(l);
+    for (var i=0; i<l; i++)
+        s[i] = randomFloat();
+    return s;
+}
+function randomIntArray () {
+    var l = randomLength();
+    var s = new Array(l);
+    for (var i=0; i<l; i++)
+        s[i] = randomFloat();
+    return s;
+}
+function randomMixedArray () {
+    var l = randomLength();
+    var s = new Array(l);
+    for (var i=0; i<l; i++)
+        s[i] = randomNonArray();
+    return s;
+}
+function randomArray () {
+    var r = Math.random();
+    if (r < 0.3)
+        return randomFloatArray();
+    else if (r < 0.6)
+        return randomIntArray();
+    else if (r < 0.8)
+        return randomSmallIntArray();
+    else
+        return randomMixedArray();
+}
+function randomString () {
+    return String.fromCharCode.apply(String, randomSmallIntArray());
+}
+function randomGLConstant () {
+    return GLConstants[Math.floor(Math.random() * GLConstants.length)];
+}
+
+function randomNonArray() {
+    var r = Math.random();
+    if (r < 0.25) {
+        return randomFloat();
+    } else if (r < 0.6) {
+        return randomInt();
+    } else if (r < 0.7) {
+        return (r < 0.65);
+    } else if (r < 0.87) {
+        return randomString();
+    } else if (r < 0.98) {
+        return randomGLConstant();
+    } else {
+        return null;
+    }
+}
+
+function generateRandomArg(pos, count) {
+    if (pos == 0 && Math.random() < 0.5)
+        return randomGLConstant();
+    if (pos == count-1 && Math.random() < 0.25)
+        if (Math.random() < 0.5)
+            return randomString();
+        else
+            return randomArray();
+    var r = Math.random();
+    if (r < 0.25) {
+        return randomFloat();
+    } else if (r < 0.6) {
+        return randomInt();
+    } else if (r < 0.7) {
+        return (r < 0.65);
+    } else if (r < 0.77) {
+        return randomString();
+    } else if (r < 0.84) {
+        return randomArray();
+    } else if (r < 0.98) {
+        return randomGLConstant();
+    } else {
+        return null;
+    }
+}
+
+
+function generateRandomArgs(count) {
+    var arr = new Array(count);
+    for (var i=0; i<count; i++)
+        arr[i] = generateRandomArg(i, count);
+    return arr;
+}
+
+// qc (arg1gen, arg2gen, ..., predicate)
+// qc (randomString, randomInt, randomInt, function(s,i,j){ s.substring(i,j) })
+function qc() {
+}
+
+GLConstants = [
+1,
+0x00000100,
+0x00000400,
+0x00004000,
+0x0000,
+0x0001,
+0x0002,
+0x0003,
+0x0004,
+0x0005,
+0x0006,
+0,
+1,
+0x0300,
+0x0301,
+0x0302,
+0x0303,
+0x0304,
+0x0305,
+0x0306,
+0x0307,
+0x0308,
+0x8006,
+0x8009,
+0x8009,
+0x883D,
+0x800A,
+0x800B,
+0x80C8,
+0x80C9,
+0x80CA,
+0x80CB,
+0x8001,
+0x8002,
+0x8003,
+0x8004,
+0x8005,
+0x8892,
+0x8893,
+0x8894,
+0x8895,
+0x88E0,
+0x88E4,
+0x88E8,
+0x8764,
+0x8765,
+0x8626,
+0x0404,
+0x0405,
+0x0408,
+0x0DE1,
+0x0B44,
+0x0BE2,
+0x0BD0,
+0x0B90,
+0x0B71,
+0x0C11,
+0x8037,
+0x809E,
+0x80A0,
+0,
+0x0500,
+0x0501,
+0x0502,
+0x0505,
+0x0900,
+0x0901,
+0x0B21,
+0x846D,
+0x846E,
+0x0B45,
+0x0B46,
+0x0B70,
+0x0B72,
+0x0B73,
+0x0B74,
+0x0B91,
+0x0B92,
+0x0B94,
+0x0B95,
+0x0B96,
+0x0B97,
+0x0B93,
+0x0B98,
+0x8800,
+0x8801,
+0x8802,
+0x8803,
+0x8CA3,
+0x8CA4,
+0x8CA5,
+0x0BA2,
+0x0C10,
+0x0C22,
+0x0C23,
+0x0CF5,
+0x0D05,
+0x0D33,
+0x0D3A,
+0x0D50,
+0x0D52,
+0x0D53,
+0x0D54,
+0x0D55,
+0x0D56,
+0x0D57,
+0x2A00,
+0x8038,
+0x8069,
+0x80A8,
+0x80A9,
+0x80AA,
+0x80AB,
+0x86A2,
+0x86A3,
+0x1100,
+0x1101,
+0x1102,
+0x8192,
+0x1400,
+0x1401,
+0x1402,
+0x1403,
+0x1404,
+0x1405,
+0x1406,
+0x140C,
+0x1902,
+0x1906,
+0x1907,
+0x1908,
+0x1909,
+0x190A,
+0x8033,
+0x8034,
+0x8363,
+0x8B30,
+0x8B31,
+0x8869,
+0x8DFB,
+0x8DFC,
+0x8B4D,
+0x8B4C,
+0x8872,
+0x8DFD,
+0x8B4F,
+0x8B80,
+0x8B82,
+0x8B83,
+0x8B85,
+0x8B86,
+0x8B87,
+0x8B89,
+0x8B8A,
+0x8B8C,
+0x8B8D,
+0x0200,
+0x0201,
+0x0202,
+0x0203,
+0x0204,
+0x0205,
+0x0206,
+0x0207,
+0x1E00,
+0x1E01,
+0x1E02,
+0x1E03,
+0x150A,
+0x8507,
+0x8508,
+0x1F00,
+0x1F01,
+0x1F02,
+0x1F03,
+0x2600,
+0x2601,
+0x2700,
+0x2701,
+0x2702,
+0x2703,
+0x2800,
+0x2801,
+0x2802,
+0x2803,
+0x1702,
+0x8513,
+0x8514,
+0x8515,
+0x8516,
+0x8517,
+0x8518,
+0x8519,
+0x851A,
+0x851C,
+0x84C0,
+0x84C1,
+0x84C2,
+0x84C3,
+0x84C4,
+0x84C5,
+0x84C6,
+0x84C7,
+0x84C8,
+0x84C9,
+0x84CA,
+0x84CB,
+0x84CC,
+0x84CD,
+0x84CE,
+0x84CF,
+0x84D0,
+0x84D1,
+0x84D2,
+0x84D3,
+0x84D4,
+0x84D5,
+0x84D6,
+0x84D7,
+0x84D8,
+0x84D9,
+0x84DA,
+0x84DB,
+0x84DC,
+0x84DD,
+0x84DE,
+0x84DF,
+0x84E0,
+0x2901,
+0x812F,
+0x8370,
+0x8B50,
+0x8B51,
+0x8B52,
+0x8B53,
+0x8B54,
+0x8B55,
+0x8B56,
+0x8B57,
+0x8B58,
+0x8B59,
+0x8B5A,
+0x8B5B,
+0x8B5C,
+0x8B5E,
+0x8B60,
+0x8622,
+0x8623,
+0x8624,
+0x8625,
+0x886A,
+0x8645,
+0x889F,
+0x8B9A,
+0x8B9B,
+0x8B81,
+0x8B84,
+0x8B88,
+0x8DFA,
+0x8DF8,
+0x8DF9,
+0x8DF0,
+0x8DF1,
+0x8DF2,
+0x8DF3,
+0x8DF4,
+0x8DF5,
+0x8D40,
+0x8D41,
+0x8056,
+0x8057,
+0x8D62,
+0x81A5,
+0x1901,
+0x8D48,
+0x8D42,
+0x8D43,
+0x8D44,
+0x8D50,
+0x8D51,
+0x8D52,
+0x8D53,
+0x8D54,
+0x8D55,
+0x8CD0,
+0x8CD1,
+0x8CD2,
+0x8CD3,
+0x8CE0,
+0x8D00,
+0x8D20,
+0,
+0x8CD5,
+0x8CD6,
+0x8CD7,
+0x8CD9,
+0x8CDD,
+0x8CA6,
+0x8CA7,
+0x84E8,
+0x0506,
+0x809D
+];
+
+function reportTestResultsToHarness(success, msg) {
+  if (window.parent.webglTestHarness) {
+    window.parent.webglTestHarness.reportResults(window.location.pathname, success, msg);
+  }
+}
+
+function notifyFinishedToHarness() {
+  if (window.parent.webglTestHarness) {
+    window.parent.webglTestHarness.notifyFinished(window.location.pathname);
+  }
+}
+
+function initTests() {
+  if (Tests.message != null) {
+    var h = document.getElementById('test-message');
+    if (h == null) {
+      h = document.createElement('p');
+      h.id = 'test-message';
+      document.body.insertBefore(h, document.body.firstChild);
+    }
+    h.textContent = Tests.message;
+  }
+  if (Tests.autorun) {
+    runTests();
+  } else {
+    var h = document.getElementById('test-run');
+    if (h == null) {
+      h = document.createElement('input');
+      h.type = 'submit';
+      h.value = "Run tests";
+      h.addEventListener('click', function(ev){
+        runTests();
+        ev.preventDefault();
+      }, false);
+      h.id = 'test-run';
+      document.body.insertBefore(h, document.body.firstChild);
+    }
+    h.textContent = Tests.message;
+  }
+
+}
+
+window.addEventListener('load', function(){
+  // let the browser hopefully finish updating the gl canvas surfaces if we are given a delay
+  if (Tests.delay)
+    setTimeout(initTests, Tests.delay);
+  else
+    initTests()
+}, false);
+
diff --git a/conformance/more/util.js b/conformance/more/util.js
new file mode 100644
index 0000000..2366caf
--- /dev/null
+++ b/conformance/more/util.js
@@ -0,0 +1,1252 @@
+/*
+Utilities for the OpenGL ES 2.0 HTML Canvas context
+*/
+
+/*
+** 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.
+*/
+
+function loadTexture(gl, elem, mipmaps) {
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, elem);
+  if (mipmaps != false)
+    gl.generateMipmap(gl.TEXTURE_2D);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+  if (mipmaps)
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
+  else
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+  return tex;
+}
+
+function getShader(gl, id) {
+  var shaderScript = document.getElementById(id);
+  if (!shaderScript) {
+    throw(new Error("No shader element with id: "+id));
+  }
+
+  var str = "";
+  var k = shaderScript.firstChild;
+  while (k) {
+    if (k.nodeType == 3)
+      str += k.textContent;
+    k = k.nextSibling;
+  }
+
+  var shader;
+  if (shaderScript.type == "x-shader/x-fragment") {
+    shader = gl.createShader(gl.FRAGMENT_SHADER);
+  } else if (shaderScript.type == "x-shader/x-vertex") {
+    shader = gl.createShader(gl.VERTEX_SHADER);
+  } else {
+    throw(new Error("Unknown shader type "+shaderScript.type));
+  }
+
+  gl.shaderSource(shader, str);
+  gl.compileShader(shader);
+
+  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) != 1) {
+    var ilog = gl.getShaderInfoLog(shader);
+    gl.deleteShader(shader);
+    throw(new Error("Failed to compile shader "+shaderScript.id + ", Shader info log: " + ilog));
+  }
+  return shader;
+}
+
+function loadShaderArray(gl, shaders) {
+  var id = gl.createProgram();
+  var shaderObjs = [];
+  for (var i=0; i<shaders.length; ++i) {
+    try {
+      var sh = getShader(gl, shaders[i]);
+      shaderObjs.push(sh);
+      gl.attachShader(id, sh);
+    } catch (e) {
+      var pr = {program: id, shaders: shaderObjs};
+      deleteShader(gl, pr);
+      throw (e);
+    }
+  }
+  var prog = {program: id, shaders: shaderObjs};
+  gl.linkProgram(id);
+  gl.validateProgram(id);
+  if (gl.getProgramParameter(id, gl.LINK_STATUS) != 1) {
+    deleteShader(gl,prog);
+    throw(new Error("Failed to link shader"));
+  }
+  if (gl.getProgramParameter(id, gl.VALIDATE_STATUS) != 1) {
+    deleteShader(gl,prog);
+    throw(new Error("Failed to validate shader"));
+  }
+  return prog;
+}
+function loadShader(gl) {
+  var sh = [];
+  for (var i=1; i<arguments.length; ++i)
+    sh.push(arguments[i]);
+  return loadShaderArray(gl, sh);
+}
+
+function deleteShader(gl, sh) {
+  gl.useProgram(null);
+  sh.shaders.forEach(function(s){
+    gl.detachShader(sh.program, s);
+    gl.deleteShader(s);
+  });
+  gl.deleteProgram(sh.program);
+}
+
+function getGLErrorAsString(ctx, err) {
+  if (err === ctx.NO_ERROR) {
+    return "NO_ERROR";
+  }
+  for (var name in ctx) {
+    if (ctx[name] === err) {
+      return name;
+    }
+  }
+  return err.toString();
+}
+
+function checkError(gl, msg) {
+  var e = gl.getError();
+  if (e != gl.NO_ERROR) {
+    log("Error " + getGLErrorAsString(gl, e) + " at " + msg);
+  }
+  return e;
+}
+
+function throwError(gl, msg) {
+  var e = gl.getError();
+  if (e != 0) {
+    throw(new Error("Error " + getGLErrorAsString(gl, e) + " at " + msg));
+  }
+}
+
+Math.cot = function(z) { return 1.0 / Math.tan(z); }
+
+/*
+  Matrix utilities, using the OpenGL element order where
+  the last 4 elements are the translation column.
+
+  Uses flat arrays as matrices for performance.
+
+  Most operations have in-place variants to avoid allocating temporary matrices.
+
+  Naming logic:
+    Matrix.method operates on a 4x4 Matrix and returns a new Matrix.
+    Matrix.method3x3 operates on a 3x3 Matrix and returns a new Matrix. Not all operations have a 3x3 version (as 3x3 is usually only used for the normal matrix: Matrix.transpose3x3(Matrix.inverseTo3x3(mat4x4)))
+    Matrix.method[3x3]InPlace(args, target) stores its result in the target matrix.
+
+    Matrix.scale([sx, sy, sz]) -- non-uniform scale by vector
+    Matrix.scale1(s)           -- uniform scale by scalar
+    Matrix.scale3(sx, sy, sz)  -- non-uniform scale by scalars
+
+    Ditto for translate.
+*/
+Matrix = {
+  identity : [
+    1.0, 0.0, 0.0, 0.0,
+    0.0, 1.0, 0.0, 0.0,
+    0.0, 0.0, 1.0, 0.0,
+    0.0, 0.0, 0.0, 1.0
+  ],
+
+  newIdentity : function() {
+    return [
+      1.0, 0.0, 0.0, 0.0,
+      0.0, 1.0, 0.0, 0.0,
+      0.0, 0.0, 1.0, 0.0,
+      0.0, 0.0, 0.0, 1.0
+    ];
+  },
+
+  newIdentity3x3 : function() {
+    return [
+      1.0, 0.0, 0.0,
+      0.0, 1.0, 0.0,
+      0.0, 0.0, 1.0
+    ];
+  },
+
+  copyMatrix : function(src, dst) {
+    for (var i=0; i<16; i++) dst[i] = src[i];
+    return dst;
+  },
+
+  to3x3 : function(m) {
+    return [
+      m[0], m[1], m[2],
+      m[4], m[5], m[6],
+      m[8], m[9], m[10]
+    ];
+  },
+
+  // orthonormal matrix inverse
+  inverseON : function(m) {
+    var n = this.transpose4x4(m);
+    var t = [m[12], m[13], m[14]];
+    n[3] = n[7] = n[11] = 0;
+    n[12] = -Vec3.dot([n[0], n[4], n[8]], t);
+    n[13] = -Vec3.dot([n[1], n[5], n[9]], t);
+    n[14] = -Vec3.dot([n[2], n[6], n[10]], t);
+    return n;
+  },
+
+  inverseTo3x3 : function(m) {
+    return this.inverse4x4to3x3InPlace(m, this.newIdentity3x3());
+  },
+
+  inverseTo3x3InPlace : function(m,n) {
+    var a11 = m[10]*m[5]-m[6]*m[9],
+        a21 = -m[10]*m[1]+m[2]*m[9],
+        a31 = m[6]*m[1]-m[2]*m[5],
+        a12 = -m[10]*m[4]+m[6]*m[8],
+        a22 = m[10]*m[0]-m[2]*m[8],
+        a32 = -m[6]*m[0]+m[2]*m[4],
+        a13 = m[9]*m[4]-m[5]*m[8],
+        a23 = -m[9]*m[0]+m[1]*m[8],
+        a33 = m[5]*m[0]-m[1]*m[4];
+    var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13);
+    if (det == 0) // no inverse
+      return [1,0,0,0,1,0,0,0,1];
+    var idet = 1 / det;
+    n[0] = idet*a11;
+    n[1] = idet*a21;
+    n[2] = idet*a31;
+    n[3] = idet*a12;
+    n[4] = idet*a22;
+    n[5] = idet*a32;
+    n[6] = idet*a13;
+    n[7] = idet*a23;
+    n[8] = idet*a33;
+    return n;
+  },
+
+  inverse3x3 : function(m) {
+    return this.inverse3x3InPlace(m, this.newIdentity3x3());
+  },
+
+  inverse3x3InPlace : function(m,n) {
+    var a11 = m[8]*m[4]-m[5]*m[7],
+        a21 = -m[8]*m[1]+m[2]*m[7],
+        a31 = m[5]*m[1]-m[2]*m[4],
+        a12 = -m[8]*m[3]+m[5]*m[6],
+        a22 = m[8]*m[0]-m[2]*m[6],
+        a32 = -m[5]*m[0]+m[2]*m[3],
+        a13 = m[7]*m[4]-m[4]*m[8],
+        a23 = -m[7]*m[0]+m[1]*m[6],
+        a33 = m[4]*m[0]-m[1]*m[3];
+    var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13);
+    if (det == 0) // no inverse
+      return [1,0,0,0,1,0,0,0,1];
+    var idet = 1 / det;
+    n[0] = idet*a11;
+    n[1] = idet*a21;
+    n[2] = idet*a31;
+    n[3] = idet*a12;
+    n[4] = idet*a22;
+    n[5] = idet*a32;
+    n[6] = idet*a13;
+    n[7] = idet*a23;
+    n[8] = idet*a33;
+    return n;
+  },
+
+  frustum : function (left, right, bottom, top, znear, zfar) {
+    var X = 2*znear/(right-left);
+    var Y = 2*znear/(top-bottom);
+    var A = (right+left)/(right-left);
+    var B = (top+bottom)/(top-bottom);
+    var C = -(zfar+znear)/(zfar-znear);
+    var D = -2*zfar*znear/(zfar-znear);
+
+    return [
+      X, 0, 0, 0,
+      0, Y, 0, 0,
+      A, B, C, -1,
+      0, 0, D, 0
+    ];
+ },
+
+  perspective : function (fovy, aspect, znear, zfar) {
+    var ymax = znear * Math.tan(fovy * Math.PI / 360.0);
+    var ymin = -ymax;
+    var xmin = ymin * aspect;
+    var xmax = ymax * aspect;
+
+    return this.frustum(xmin, xmax, ymin, ymax, znear, zfar);
+  },
+
+  mul4x4 : function (a,b) {
+    return this.mul4x4InPlace(a,b,this.newIdentity());
+  },
+
+  mul4x4InPlace : function (a, b, c) {
+        c[0] =   b[0] * a[0] +
+                 b[0+1] * a[4] +
+                 b[0+2] * a[8] +
+                 b[0+3] * a[12];
+        c[0+1] = b[0] * a[1] +
+                 b[0+1] * a[5] +
+                 b[0+2] * a[9] +
+                 b[0+3] * a[13];
+        c[0+2] = b[0] * a[2] +
+                 b[0+1] * a[6] +
+                 b[0+2] * a[10] +
+                 b[0+3] * a[14];
+        c[0+3] = b[0] * a[3] +
+                 b[0+1] * a[7] +
+                 b[0+2] * a[11] +
+                 b[0+3] * a[15];
+        c[4] =   b[4] * a[0] +
+                 b[4+1] * a[4] +
+                 b[4+2] * a[8] +
+                 b[4+3] * a[12];
+        c[4+1] = b[4] * a[1] +
+                 b[4+1] * a[5] +
+                 b[4+2] * a[9] +
+                 b[4+3] * a[13];
+        c[4+2] = b[4] * a[2] +
+                 b[4+1] * a[6] +
+                 b[4+2] * a[10] +
+                 b[4+3] * a[14];
+        c[4+3] = b[4] * a[3] +
+                 b[4+1] * a[7] +
+                 b[4+2] * a[11] +
+                 b[4+3] * a[15];
+        c[8] =   b[8] * a[0] +
+                 b[8+1] * a[4] +
+                 b[8+2] * a[8] +
+                 b[8+3] * a[12];
+        c[8+1] = b[8] * a[1] +
+                 b[8+1] * a[5] +
+                 b[8+2] * a[9] +
+                 b[8+3] * a[13];
+        c[8+2] = b[8] * a[2] +
+                 b[8+1] * a[6] +
+                 b[8+2] * a[10] +
+                 b[8+3] * a[14];
+        c[8+3] = b[8] * a[3] +
+                 b[8+1] * a[7] +
+                 b[8+2] * a[11] +
+                 b[8+3] * a[15];
+        c[12] =   b[12] * a[0] +
+                 b[12+1] * a[4] +
+                 b[12+2] * a[8] +
+                 b[12+3] * a[12];
+        c[12+1] = b[12] * a[1] +
+                 b[12+1] * a[5] +
+                 b[12+2] * a[9] +
+                 b[12+3] * a[13];
+        c[12+2] = b[12] * a[2] +
+                 b[12+1] * a[6] +
+                 b[12+2] * a[10] +
+                 b[12+3] * a[14];
+        c[12+3] = b[12] * a[3] +
+                 b[12+1] * a[7] +
+                 b[12+2] * a[11] +
+                 b[12+3] * a[15];
+    return c;
+  },
+
+  mulv4 : function (a, v) {
+    c = new Array(4);
+    for (var i=0; i<4; ++i) {
+      var x = 0;
+      for (var k=0; k<4; ++k)
+        x += v[k] * a[k*4+i];
+      c[i] = x;
+    }
+    return c;
+  },
+
+  rotate : function (angle, axis) {
+    axis = Vec3.normalize(axis);
+    var x=axis[0], y=axis[1], z=axis[2];
+    var c = Math.cos(angle);
+    var c1 = 1-c;
+    var s = Math.sin(angle);
+    return [
+      x*x*c1+c, y*x*c1+z*s, z*x*c1-y*s, 0,
+      x*y*c1-z*s, y*y*c1+c, y*z*c1+x*s, 0,
+      x*z*c1+y*s, y*z*c1-x*s, z*z*c1+c, 0,
+      0,0,0,1
+    ];
+  },
+  rotateInPlace : function(angle, axis, m) {
+    axis = Vec3.normalize(axis);
+    var x=axis[0], y=axis[1], z=axis[2];
+    var c = Math.cos(angle);
+    var c1 = 1-c;
+    var s = Math.sin(angle);
+    var tmpMatrix = this.tmpMatrix;
+    var tmpMatrix2 = this.tmpMatrix2;
+    tmpMatrix[0] = x*x*c1+c; tmpMatrix[1] = y*x*c1+z*s; tmpMatrix[2] = z*x*c1-y*s; tmpMatrix[3] = 0;
+    tmpMatrix[4] = x*y*c1-z*s; tmpMatrix[5] = y*y*c1+c; tmpMatrix[6] = y*z*c1+x*s; tmpMatrix[7] = 0;
+    tmpMatrix[8] = x*z*c1+y*s; tmpMatrix[9] = y*z*c1-x*s; tmpMatrix[10] = z*z*c1+c; tmpMatrix[11] = 0;
+    tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1;
+    this.copyMatrix(m, tmpMatrix2);
+    return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m);
+  },
+
+  scale : function(v) {
+    return [
+      v[0], 0, 0, 0,
+      0, v[1], 0, 0,
+      0, 0, v[2], 0,
+      0, 0, 0, 1
+    ];
+  },
+  scale3 : function(x,y,z) {
+    return [
+      x, 0, 0, 0,
+      0, y, 0, 0,
+      0, 0, z, 0,
+      0, 0, 0, 1
+    ];
+  },
+  scale1 : function(s) {
+    return [
+      s, 0, 0, 0,
+      0, s, 0, 0,
+      0, 0, s, 0,
+      0, 0, 0, 1
+    ];
+  },
+  scale3InPlace : function(x, y, z, m) {
+    var tmpMatrix = this.tmpMatrix;
+    var tmpMatrix2 = this.tmpMatrix2;
+    tmpMatrix[0] = x; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0;
+    tmpMatrix[4] = 0; tmpMatrix[5] = y; tmpMatrix[6] = 0; tmpMatrix[7] = 0;
+    tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = z; tmpMatrix[11] = 0;
+    tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1;
+    this.copyMatrix(m, tmpMatrix2);
+    return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m);
+  },
+  scale1InPlace : function(s, m) { return this.scale3InPlace(s, s, s, m); },
+  scaleInPlace : function(s, m) { return this.scale3InPlace(s[0],s[1],s[2],m); },
+
+  translate3 : function(x,y,z) {
+    return [
+      1, 0, 0, 0,
+      0, 1, 0, 0,
+      0, 0, 1, 0,
+      x, y, z, 1
+    ];
+  },
+
+  translate : function(v) {
+    return this.translate3(v[0], v[1], v[2]);
+  },
+  tmpMatrix : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0],
+  tmpMatrix2 : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0],
+  translate3InPlace : function(x,y,z,m) {
+    var tmpMatrix = this.tmpMatrix;
+    var tmpMatrix2 = this.tmpMatrix2;
+    tmpMatrix[0] = 1; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0;
+    tmpMatrix[4] = 0; tmpMatrix[5] = 1; tmpMatrix[6] = 0; tmpMatrix[7] = 0;
+    tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = 1; tmpMatrix[11] = 0;
+    tmpMatrix[12] = x; tmpMatrix[13] = y; tmpMatrix[14] = z; tmpMatrix[15] = 1;
+    this.copyMatrix(m, tmpMatrix2);
+    return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m);
+  },
+  translateInPlace : function(v,m){ return this.translate3InPlace(v[0], v[1], v[2], m); },
+
+  lookAt : function (eye, center, up) {
+    var z = Vec3.direction(eye, center);
+    var x = Vec3.normalizeInPlace(Vec3.cross(up, z));
+    var y = Vec3.normalizeInPlace(Vec3.cross(z, x));
+
+    var m = [
+      x[0], y[0], z[0], 0,
+      x[1], y[1], z[1], 0,
+      x[2], y[2], z[2], 0,
+      0, 0, 0, 1
+    ];
+
+    var t = [
+      1, 0, 0, 0,
+      0, 1, 0, 0,
+      0, 0, 1, 0,
+      -eye[0], -eye[1], -eye[2], 1
+    ];
+
+    return this.mul4x4(m,t);
+  },
+
+  transpose4x4 : function(m) {
+    return [
+      m[0], m[4], m[8], m[12],
+      m[1], m[5], m[9], m[13],
+      m[2], m[6], m[10], m[14],
+      m[3], m[7], m[11], m[15]
+    ];
+  },
+
+  transpose4x4InPlace : function(m) {
+    var tmp = 0.0;
+    tmp = m[1]; m[1] = m[4]; m[4] = tmp;
+    tmp = m[2]; m[2] = m[8]; m[8] = tmp;
+    tmp = m[3]; m[3] = m[12]; m[12] = tmp;
+    tmp = m[6]; m[6] = m[9]; m[9] = tmp;
+    tmp = m[7]; m[7] = m[13]; m[13] = tmp;
+    tmp = m[11]; m[11] = m[14]; m[14] = tmp;
+    return m;
+  },
+
+  transpose3x3 : function(m) {
+    return [
+      m[0], m[3], m[6],
+      m[1], m[4], m[7],
+      m[2], m[5], m[8]
+    ];
+  },
+
+  transpose3x3InPlace : function(m) {
+    var tmp = 0.0;
+    tmp = m[1]; m[1] = m[3]; m[3] = tmp;
+    tmp = m[2]; m[2] = m[6]; m[6] = tmp;
+    tmp = m[5]; m[5] = m[7]; m[7] = tmp;
+    return m;
+  },
+}
+
+Vec3 = {
+  make : function() { return [0,0,0]; },
+  copy : function(v) { return [v[0],v[1],v[2]]; },
+
+  add : function (u,v) {
+    return [u[0]+v[0], u[1]+v[1], u[2]+v[2]];
+  },
+
+  sub : function (u,v) {
+    return [u[0]-v[0], u[1]-v[1], u[2]-v[2]];
+  },
+
+  negate : function (u) {
+    return [-u[0], -u[1], -u[2]];
+  },
+
+  direction : function (u,v) {
+    return this.normalizeInPlace(this.sub(u,v));
+  },
+
+  normalizeInPlace : function(v) {
+    var imag = 1.0 / Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
+    v[0] *= imag; v[1] *= imag; v[2] *= imag;
+    return v;
+  },
+
+  normalize : function(v) {
+    return this.normalizeInPlace(this.copy(v));
+  },
+
+  scale : function(f, v) {
+    return [f*v[0], f*v[1], f*v[2]];
+  },
+
+  dot : function(u,v) {
+    return u[0]*v[0] + u[1]*v[1] + u[2]*v[2];
+  },
+
+  inner : function(u,v) {
+    return [u[0]*v[0], u[1]*v[1], u[2]*v[2]];
+  },
+
+  cross : function(u,v) {
+    return [
+      u[1]*v[2] - u[2]*v[1],
+      u[2]*v[0] - u[0]*v[2],
+      u[0]*v[1] - u[1]*v[0]
+    ];
+  }
+}
+
+Shader = function(gl){
+  this.gl = gl;
+  this.shaders = [];
+  this.uniformLocations = {};
+  this.attribLocations = {};
+  for (var i=1; i<arguments.length; i++) {
+    this.shaders.push(arguments[i]);
+  }
+}
+Shader.prototype = {
+  id : null,
+  gl : null,
+  compiled : false,
+  shader : null,
+  shaders : [],
+
+  destroy : function() {
+    if (this.shader != null) deleteShader(this.gl, this.shader);
+  },
+
+  compile : function() {
+    this.shader = loadShaderArray(this.gl, this.shaders);
+  },
+
+  use : function() {
+    if (this.shader == null)
+      this.compile();
+    this.gl.useProgram(this.shader.program);
+  },
+
+  uniform1fv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform1fv(loc, value);
+  },
+
+  uniform2fv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform2fv(loc, value);
+  },
+
+  uniform3fv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform3fv(loc, value);
+  },
+
+  uniform4fv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform4fv(loc, value);
+  },
+
+  uniform1f : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform1f(loc, value);
+  },
+
+  uniform2f : function(name, v1,v2) {
+    var loc = this.uniform(name);
+    this.gl.uniform2f(loc, v1,v2);
+  },
+
+  uniform3f : function(name, v1,v2,v3) {
+    var loc = this.uniform(name);
+    this.gl.uniform3f(loc, v1,v2,v3);
+  },
+
+  uniform4f : function(name, v1,v2,v3,v4) {
+    var loc = this.uniform(name);
+    this.gl.uniform4f(loc, v1, v2, v3, v4);
+  },
+
+  uniform1iv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform1iv(loc, value);
+  },
+
+  uniform2iv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform2iv(loc, value);
+  },
+
+  uniform3iv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform3iv(loc, value);
+  },
+
+  uniform4iv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform4iv(loc, value);
+  },
+
+  uniform1i : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniform1i(loc, value);
+  },
+
+  uniform2i : function(name, v1,v2) {
+    var loc = this.uniform(name);
+    this.gl.uniform2i(loc, v1,v2);
+  },
+
+  uniform3i : function(name, v1,v2,v3) {
+    var loc = this.uniform(name);
+    this.gl.uniform3i(loc, v1,v2,v3);
+  },
+
+  uniform4i : function(name, v1,v2,v3,v4) {
+    var loc = this.uniform(name);
+    this.gl.uniform4i(loc, v1, v2, v3, v4);
+  },
+
+  uniformMatrix4fv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniformMatrix4fv(loc, false, value);
+  },
+
+  uniformMatrix3fv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniformMatrix3fv(loc, false, value);
+  },
+
+  uniformMatrix2fv : function(name, value) {
+    var loc = this.uniform(name);
+    this.gl.uniformMatrix2fv(loc, false, value);
+  },
+
+  attrib : function(name) {
+    if (this.attribLocations[name] == null) {
+      var loc = this.gl.getAttribLocation(this.shader.program, name);
+      this.attribLocations[name] = loc;
+    }
+    return this.attribLocations[name];
+  },
+
+  uniform : function(name) {
+    if (this.uniformLocations[name] == null) {
+      var loc = this.gl.getUniformLocation(this.shader.program, name);
+      this.uniformLocations[name] = loc;
+    }
+    return this.uniformLocations[name];
+  }
+}
+Filter = function(gl, shader) {
+  Shader.apply(this, arguments);
+}
+Filter.prototype = new Shader();
+Filter.prototype.apply = function(init) {
+  this.use();
+  var va = this.attrib("Vertex");
+  var ta = this.attrib("Tex");
+  var vbo = Quad.getCachedVBO(this.gl);
+  if (init) init(this);
+  vbo.draw(va, null, ta);
+}
+
+
+VBO = function(gl) {
+  this.gl = gl;
+  this.data = [];
+  this.elementsVBO = null;
+  for (var i=1; i<arguments.length; i++) {
+    if (arguments[i].elements)
+      this.elements = arguments[i];
+    else
+      this.data.push(arguments[i]);
+  }
+}
+
+VBO.prototype = {
+  initialized : false,
+  length : 0,
+  vbos : null,
+  type : 'TRIANGLES',
+  elementsVBO : null,
+  elements : null,
+
+  setData : function() {
+    this.destroy();
+    this.data = [];
+    for (var i=0; i<arguments.length; i++) {
+      if (arguments[i].elements)
+        this.elements = arguments[i];
+      else
+        this.data.push(arguments[i]);
+    }
+  },
+
+  destroy : function() {
+    if (this.vbos != null)
+      for (var i=0; i<this.vbos.length; i++)
+        this.gl.deleteBuffer(this.vbos[i]);
+    if (this.elementsVBO != null)
+      this.gl.deleteBuffer(this.elementsVBO);
+    this.length = this.elementsLength = 0;
+    this.vbos = this.elementsVBO = null;
+    this.initialized = false;
+  },
+
+  init : function() {
+    this.destroy();
+    var gl = this.gl;
+
+    gl.getError();
+    var vbos = [];
+    var length = 0;
+    for (var i=0; i<this.data.length; i++)
+      vbos.push(gl.createBuffer());
+    if (this.elements != null)
+      this.elementsVBO = gl.createBuffer();
+    try {
+      throwError(gl, "genBuffers");
+      for (var i = 0; i<this.data.length; i++) {
+        var d = this.data[i];
+        var dlen = Math.floor(d.data.length / d.size);
+        if (i == 0 || dlen < length)
+            length = dlen;
+        if (!d.floatArray)
+          d.floatArray = new Float32Array(d.data);
+        gl.bindBuffer(gl.ARRAY_BUFFER, vbos[i]);
+        throwError(gl, "bindBuffer");
+        gl.bufferData(gl.ARRAY_BUFFER, d.floatArray, gl.STATIC_DRAW);
+        throwError(gl, "bufferData");
+      }
+      if (this.elementsVBO != null) {
+        var d = this.elements;
+        this.elementsLength = d.data.length;
+        this.elementsType = d.type == gl.UNSIGNED_BYTE ? d.type : gl.UNSIGNED_SHORT;
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO);
+        throwError(gl, "bindBuffer ELEMENT_ARRAY_BUFFER");
+        if (this.elementsType == gl.UNSIGNED_SHORT && !d.ushortArray) {
+          d.ushortArray = new Uint16Array(d.data);
+          gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ushortArray, gl.STATIC_DRAW);
+        } else if (this.elementsType == gl.UNSIGNED_BYTE && !d.ubyteArray) {
+          d.ubyteArray = new Uint8Array(d.data);
+          gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ubyteArray, gl.STATIC_DRAW);
+        }
+        throwError(gl, "bufferData ELEMENT_ARRAY_BUFFER");
+      }
+    } catch(e) {
+      for (var i=0; i<vbos.length; i++)
+        gl.deleteBuffer(vbos[i]);
+      throw(e);
+    }
+
+    gl.bindBuffer(gl.ARRAY_BUFFER, null);
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+
+    this.length = length;
+    this.vbos = vbos;
+
+    this.initialized = true;
+  },
+
+  use : function() {
+    if (!this.initialized) this.init();
+    var gl = this.gl;
+    for (var i=0; i<arguments.length; i++) {
+      if (arguments[i] == null) continue;
+      gl.bindBuffer(gl.ARRAY_BUFFER, this.vbos[i]);
+      gl.vertexAttribPointer(arguments[i], this.data[i].size, gl.FLOAT, false, 0, 0);
+      gl.enableVertexAttribArray(arguments[i]);
+    }
+    if (this.elementsVBO != null) {
+      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO);
+    }
+  },
+
+  draw : function() {
+    var args = [];
+    this.use.apply(this, arguments);
+    var gl = this.gl;
+    if (this.elementsVBO != null) {
+      gl.drawElements(gl[this.type], this.elementsLength, this.elementsType, 0);
+    } else {
+      gl.drawArrays(gl[this.type], 0, this.length);
+    }
+  }
+}
+
+FBO = function(gl, width, height, use_depth) {
+  this.gl = gl;
+  this.width = width;
+  this.height = height;
+  if (use_depth != null)
+    this.useDepth = use_depth;
+}
+FBO.prototype = {
+  initialized : false,
+  useDepth : true,
+  fbo : null,
+  rbo : null,
+  texture : null,
+
+  destroy : function() {
+    if (this.fbo) this.gl.deleteFramebuffer(this.fbo);
+    if (this.rbo) this.gl.deleteRenderbuffer(this.rbo);
+    if (this.texture) this.gl.deleteTexture(this.texture);
+  },
+
+  init : function() {
+    var gl = this.gl;
+    var w = this.width, h = this.height;
+    var fbo = this.fbo != null ? this.fbo : gl.createFramebuffer();
+    var rb;
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+    checkError(gl, "FBO.init bindFramebuffer");
+    if (this.useDepth) {
+      rb = this.rbo != null ? this.rbo : gl.createRenderbuffer();
+      gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+      checkError(gl, "FBO.init bindRenderbuffer");
+      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
+      checkError(gl, "FBO.init renderbufferStorage");
+    }
+
+    var tex = this.texture != null ? this.texture : gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    try {
+      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+    } catch (e) { // argh, no null texture support
+      var tmp = this.getTempCanvas(w,h);
+      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tmp);
+    }
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+    checkError(gl, "FBO.init tex");
+
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+    checkError(gl, "FBO.init bind tex");
+
+    if (this.useDepth) {
+      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
+      checkError(gl, "FBO.init bind depth buffer");
+    }
+
+    var fbstat = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+    if (fbstat != gl.FRAMEBUFFER_COMPLETE) {
+      var glv;
+      for (var v in gl) {
+        try { glv = gl[v]; } catch (e) { glv = null; }
+        if (glv == fbstat) { fbstat = v; break; }}
+        log("Framebuffer status: " + fbstat);
+    }
+    checkError(gl, "FBO.init check fbo");
+
+    this.fbo = fbo;
+    this.rbo = rb;
+    this.texture = tex;
+    this.initialized = true;
+  },
+
+  getTempCanvas : function(w, h) {
+    if (!FBO.tempCanvas) {
+      FBO.tempCanvas = document.createElement('canvas');
+    }
+    FBO.tempCanvas.width = w;
+    FBO.tempCanvas.height = h;
+    return FBO.tempCanvas;
+  },
+
+  use : function() {
+    if (!this.initialized) this.init();
+    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
+  }
+}
+
+function GLError(err, msg, fileName, lineNumber) {
+  this.message = msg;
+  this.glError = err;
+}
+
+GLError.prototype = new Error();
+
+function makeGLErrorWrapper(gl, fname) {
+  return (function() {
+    try {
+      var rv = gl[fname].apply(gl, arguments);
+      var err = gl.getError();
+      if (err != gl.NO_ERROR) {
+        throw(new GLError(
+            err, "GL error "+getGLErrorAsString(gl, err)+" in "+fname));
+      }
+      return rv;
+    } catch (e) {
+      if (e.glError !== undefined) {
+        throw e;
+      }
+      throw(new Error("Threw " + e.name +
+                      " in " + fname + "\n" +
+                      e.message + "\n" +
+                      arguments.callee.caller));
+    }
+  });
+}
+
+function wrapGLContext(gl) {
+  var wrap = {};
+  for (var i in gl) {
+    try {
+      if (typeof gl[i] == 'function') {
+          wrap[i] = makeGLErrorWrapper(gl, i);
+      } else {
+          wrap[i] = gl[i];
+      }
+    } catch (e) {
+      // log("wrapGLContext: Error accessing " + i);
+    }
+  }
+  wrap.getError = function(){ return gl.getError(); };
+  return wrap;
+}
+
+function getGLContext(canvas) {
+  return canvas.getContext(GL_CONTEXT_ID, {antialias: false});
+}
+
+// Assert that f generates a specific GL error.
+function assertGLError(gl, err, name, f) {
+  if (f == null) { f = name; name = null; }
+  var r = false;
+  var glErr = 0;
+  try { f(); } catch(e) { r=true; glErr = e.glError; }
+  if (glErr !== err) {
+    if (glErr === undefined) {
+      testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f);
+    } else {
+      testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) +
+                 " actual: " + getGLErrorAsString(gl, glErr), name, f);
+    }
+    return false;
+  }
+  return true;
+}
+
+// Assert that f generates some GL error. Used in situations where it's
+// ambigious which of multiple possible errors will be generated.
+function assertSomeGLError(gl, name, f) {
+  if (f == null) { f = name; name = null; }
+  var r = false;
+  var glErr = 0;
+  var err = 0;
+  try { f(); } catch(e) { r=true; glErr = e.glError; }
+  if (glErr === 0) {
+    if (glErr === undefined) {
+      testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f);
+    } else {
+      testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) +
+                 " actual: " + getGLErrorAsString(gl, glErr), name, f);
+    }
+    return false;
+  }
+  return true;
+}
+
+// Assert that f throws an exception but does not generate a GL error.
+function assertThrowNoGLError(gl, name, f) {
+  if (f == null) { f = name; name = null; }
+  var r = false;
+  var glErr = undefined;
+  var exp;
+  try { f(); } catch(e) { r=true; glErr = e.glError; exp = e;}
+  if (!r) {
+    testFailed(
+      "assertThrowNoGLError: should have thrown exception", name, f);
+    return false;
+  } else {
+    if (glErr !== undefined) {
+      testFailed(
+        "assertThrowNoGLError: should be no GL error but generated: " +
+        getGLErrorAsString(gl, glErr), name, f);
+      return false;
+    }
+  }
+  testPassed("assertThrowNoGLError", name, f);
+  return true;
+}
+
+Quad = {
+  vertices : [
+    -1,-1,0,
+    1,-1,0,
+    -1,1,0,
+    1,-1,0,
+    1,1,0,
+    -1,1,0
+  ],
+  normals : [
+    0,0,-1,
+    0,0,-1,
+    0,0,-1,
+    0,0,-1,
+    0,0,-1,
+    0,0,-1
+  ],
+  texcoords : [
+    0,0,
+    1,0,
+    0,1,
+    1,0,
+    1,1,
+    0,1
+  ],
+  indices : [0,1,2,1,5,2],
+  makeVBO : function(gl) {
+    return new VBO(gl,
+        {size:3, data: Quad.vertices},
+        {size:3, data: Quad.normals},
+        {size:2, data: Quad.texcoords}
+    )
+  },
+  cache: {},
+  getCachedVBO : function(gl) {
+    if (!this.cache[gl])
+      this.cache[gl] = this.makeVBO(gl);
+    return this.cache[gl];
+  }
+}
+Cube = {
+  vertices : [  0.5, -0.5,  0.5, // +X
+                0.5, -0.5, -0.5,
+                0.5,  0.5, -0.5,
+                0.5,  0.5,  0.5,
+
+                0.5,  0.5,  0.5, // +Y
+                0.5,  0.5, -0.5,
+                -0.5,  0.5, -0.5,
+                -0.5,  0.5,  0.5,
+
+                0.5,  0.5,  0.5, // +Z
+                -0.5,  0.5,  0.5,
+                -0.5, -0.5,  0.5,
+                0.5, -0.5,  0.5,
+
+                -0.5, -0.5,  0.5, // -X
+                -0.5,  0.5,  0.5,
+                -0.5,  0.5, -0.5,
+                -0.5, -0.5, -0.5,
+
+                -0.5, -0.5,  0.5, // -Y
+                -0.5, -0.5, -0.5,
+                0.5, -0.5, -0.5,
+                0.5, -0.5,  0.5,
+
+                -0.5, -0.5, -0.5, // -Z
+                -0.5,  0.5, -0.5,
+                0.5,  0.5, -0.5,
+                0.5, -0.5, -0.5,
+      ],
+
+  normals : [ 1, 0, 0,
+              1, 0, 0,
+              1, 0, 0,
+              1, 0, 0,
+
+              0, 1, 0,
+              0, 1, 0,
+              0, 1, 0,
+              0, 1, 0,
+
+              0, 0, 1,
+              0, 0, 1,
+              0, 0, 1,
+              0, 0, 1,
+
+              -1, 0, 0,
+              -1, 0, 0,
+              -1, 0, 0,
+              -1, 0, 0,
+
+              0,-1, 0,
+              0,-1, 0,
+              0,-1, 0,
+              0,-1, 0,
+
+              0, 0,-1,
+              0, 0,-1,
+              0, 0,-1,
+              0, 0,-1
+      ],
+
+  indices : [],
+  create : function(){
+    for (var i = 0; i < 6; i++) {
+      Cube.indices.push(i*4 + 0);
+      Cube.indices.push(i*4 + 1);
+      Cube.indices.push(i*4 + 3);
+      Cube.indices.push(i*4 + 1);
+      Cube.indices.push(i*4 + 2);
+      Cube.indices.push(i*4 + 3);
+    }
+  },
+
+  makeVBO : function(gl) {
+    return new VBO(gl,
+        {size:3, data: Cube.vertices},
+        {size:3, data: Cube.normals},
+        {elements: true, data: Cube.indices}
+    )
+  },
+  cache : {},
+  getCachedVBO : function(gl) {
+    if (!this.cache[gl])
+      this.cache[gl] = this.makeVBO(gl);
+    return this.cache[gl];
+  }
+}
+Cube.create();
+
+Sphere = {
+  vertices : [],
+  normals : [],
+  indices : [],
+  create : function(){
+    var r = 0.75;
+    function vert(theta, phi)
+    {
+      var r = 0.75;
+      var x, y, z, nx, ny, nz;
+
+      nx = Math.sin(theta) * Math.cos(phi);
+      ny = Math.sin(phi);
+      nz = Math.cos(theta) * Math.cos(phi);
+      Sphere.normals.push(nx);
+      Sphere.normals.push(ny);
+      Sphere.normals.push(nz);
+
+      x = r * Math.sin(theta) * Math.cos(phi);
+      y = r * Math.sin(phi);
+      z = r * Math.cos(theta) * Math.cos(phi);
+      Sphere.vertices.push(x);
+      Sphere.vertices.push(y);
+      Sphere.vertices.push(z);
+    }
+    for (var phi = -Math.PI/2; phi < Math.PI/2; phi += Math.PI/20) {
+      var phi2 = phi + Math.PI/20;
+      for (var theta = -Math.PI/2; theta <= Math.PI/2; theta += Math.PI/20) {
+        vert(theta, phi);
+        vert(theta, phi2);
+      }
+    }
+  }
+}
+
+Sphere.create();
+
+initGL_CONTEXT_ID = function(){
+  var c = document.createElement('canvas');
+  var contextNames = ['webgl', 'experimental-webgl'];
+  GL_CONTEXT_ID = null;
+  for (var i=0; i<contextNames.length; i++) {
+    try {
+      if (c.getContext(contextNames[i])) {
+        GL_CONTEXT_ID = contextNames[i];
+        break;
+      }
+    } catch (e) {
+    }
+  }
+  if (!GL_CONTEXT_ID) {
+    log("No WebGL context found. Unable to run tests.");
+  }
+}
+
+initGL_CONTEXT_ID();