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