Created
March 2, 2019 12:49
-
-
Save robertleeplummerjr/ac072ac5fde47211c3d758212feca87a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*eslint-disable */ | |
var _windowShim = require("../lib/shims/window-shim"); | |
var _documentShim = require("../lib/shims/document-shim"); | |
var _canvasShim = require("../lib/shims/canvas-shim"); | |
var _imageShim = require("../lib/shims/image-shim"); | |
var _rafShim = require("../lib/shims/raf-shim"); | |
var _RESOURCES = require("./resources.json"); | |
function extensions_webgl_draw_buffers(ENVIRONMENT) { | |
var HTMLElement = function() {}; | |
ENVIRONMENT.CONTEXT_LIST = []; | |
ENVIRONMENT.tape.end = (function(tape_end) { | |
return function() { | |
_rafShim.clear(); | |
ENVIRONMENT.CONTEXT_LIST.forEach(function(gl) { | |
(gl.destroy && gl.destroy()); | |
}); | |
ENVIRONMENT.CONTEXT_LIST = []; | |
tape_end.call(ENVIRONMENT.tape); | |
} | |
})(ENVIRONMENT.tape.end); | |
ENVIRONMENT._createContext = ENVIRONMENT.createContext; | |
ENVIRONMENT.createContext = function(w, h, o) { | |
var gl = ENVIRONMENT._createContext(w, h, o); | |
ENVIRONMENT.CONTEXT_LIST.push(gl); | |
return gl; | |
}; | |
ENVIRONMENT.document = _documentShim(ENVIRONMENT); | |
ENVIRONMENT.window = _windowShim(ENVIRONMENT); | |
ENVIRONMENT.scriptList = { | |
"vshader": { | |
"type": "x-shader/x-vertex", | |
"text": "\nattribute vec4 a_position;\nvoid main() {\n gl_Position = a_position;\n}\n" | |
}, | |
"fshader": { | |
"type": "x-shader/x-fragment", | |
"text": "\n#extension GL_EXT_draw_buffers : require\nprecision mediump float;\nuniform vec4 u_colors[$(numDrawingBuffers)];\nvoid main() {\n for (int i = 0; i < $(numDrawingBuffers); ++i) {\n gl_FragData[i] = u_colors[i];\n }\n}\n" | |
}, | |
"fshaderRed": { | |
"type": "x-shader/x-fragment", | |
"text": "\nprecision mediump float;\nvoid main() {\n gl_FragColor = vec4(1,0,0,1);\n}\n" | |
}, | |
"fshaderMacroDisabled": { | |
"type": "x-shader/x-fragment", | |
"text": "\n#ifdef GL_EXT_draw_buffers\n bad code here\n#endif\nprecision mediump float;\nvoid main() {\n gl_FragColor = vec4(0,0,0,0);\n}\n" | |
}, | |
"fshaderMacroEnabled": { | |
"type": "x-shader/x-fragment", | |
"text": "\n#ifdef GL_EXT_draw_buffers\n #if GL_EXT_draw_buffers == 1\n #define CODE\n #else\n #define CODE this_code_is_bad_it_should_have_compiled\n #endif\n#else\n #define CODE this_code_is_bad_it_should_have_compiled\n#endif\nCODE\nprecision mediump float;\nvoid main() {\n gl_FragColor = vec4(0,0,0,0);\n}\n" | |
}, | |
"fshaderBuiltInConstEnabled": { | |
"type": "x-shader/x-fragment", | |
"text": "\nprecision mediump float;\nvoid main() {\n gl_FragColor = (gl_MaxDrawBuffers == $(numDrawingBuffers)) ? vec4(0,1,0,1) : vec4(1,0,0,1);\n}\n" | |
} | |
}; | |
ENVIRONMENT.canvasList = [{ | |
"id": "canvas", | |
"width": "64", | |
"height": "64" | |
}].map(function(opts) { | |
return _canvasShim(ENVIRONMENT, opts); | |
}); | |
ENVIRONMENT.RESOURCES = _RESOURCES; | |
ENVIRONMENT.BASEPATH = "extensions"; | |
var document = ENVIRONMENT.document; | |
var window = ENVIRONMENT.window; | |
var Image = _imageShim; | |
var requestAnimationFrame = _rafShim.requestAnimationFrame; | |
var cancelAnimationFrame = _rafShim.cancelAnimationFrame;; | |
/* | |
** 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 CONSOLE = (1, eval)("console") | |
function enableJSTestPreVerboseLogging() {} | |
function initTestingHarnessWaitUntilDone() {} | |
function initTestingHarness() {} | |
function nonKhronosFrameworkNotifyDone() { | |
// WebKit Specific code. Add your code here. | |
ENVIRONMENT.tape.end() | |
} | |
function reportTestResultsToHarness(success, msg) { | |
//Garbage | |
} | |
function notifyFinishedToHarness() { | |
ENVIRONMENT.tape.end() | |
} | |
function description(msg) { | |
CONSOLE.log("DESCRIPTION:", msg) | |
} | |
function debug(msg) { | |
CONSOLE.log("DEBUG:", msg) | |
} | |
function escapeHTML(text) { | |
return text.replace(/&/g, "&").replace(/</g, "<"); | |
} | |
function testPassed(msg) { | |
ENVIRONMENT.tape.pass(msg) | |
} | |
function testFailed(msg) { | |
ENVIRONMENT.tape.fail(msg) | |
} | |
function areArraysEqual(_a, _b) { | |
try { | |
if (_a.length !== _b.length) | |
return false; | |
for (var i = 0; i < _a.length; i++) | |
if (_a[i] !== _b[i]) | |
return false; | |
} catch (ex) { | |
return false; | |
} | |
return true; | |
} | |
function isMinusZero(n) { | |
// the only way to tell 0 from -0 in JS is the fact that 1/-0 is | |
// -Infinity instead of Infinity | |
return n === 0 && 1 / n < 0; | |
} | |
function isResultCorrect(_actual, _expected) { | |
if (_expected === 0) | |
return _actual === _expected && (1 / _actual) === (1 / _expected); | |
if (_actual === _expected) | |
return true; | |
if (typeof(_expected) == "number" && isNaN(_expected)) | |
return typeof(_actual) == "number" && isNaN(_actual); | |
if (Object.prototype.toString.call(_expected) == Object.prototype.toString.call([])) | |
return areArraysEqual(_actual, _expected); | |
return false; | |
} | |
function stringify(v) { | |
if (v === 0 && 1 / v < 0) | |
return "-0"; | |
else return "" + v; | |
} | |
function evalAndLog(_a) { | |
if (typeof _a != "string") | |
debug("WARN: tryAndLog() expects a string argument"); | |
// Log first in case things go horribly wrong or this causes a sync event. | |
debug(_a); | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
testFailed(_a + " threw exception " + e); | |
} | |
return _av; | |
} | |
function shouldBe(_a, _b, quiet) { | |
if (typeof _a != "string" || typeof _b != "string") | |
debug("WARN: shouldBe() expects string arguments"); | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
var _bv = eval(_b); | |
if (exception) | |
testFailed(_a + " should be " + _bv + ". Threw exception " + exception); | |
else if (isResultCorrect(_av, _bv)) { | |
if (!quiet) { | |
testPassed(_a + " is " + _b); | |
} | |
} else if (typeof(_av) == typeof(_bv)) | |
testFailed(_a + " should be " + _bv + ". Was " + stringify(_av) + "."); | |
else | |
testFailed(_a + " should be " + _bv + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ")."); | |
} | |
function shouldNotBe(_a, _b, quiet) { | |
if (typeof _a != "string" || typeof _b != "string") | |
debug("WARN: shouldNotBe() expects string arguments"); | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
var _bv = eval(_b); | |
if (exception) | |
testFailed(_a + " should not be " + _bv + ". Threw exception " + exception); | |
else if (!isResultCorrect(_av, _bv)) { | |
if (!quiet) { | |
testPassed(_a + " is not " + _b); | |
} | |
} else | |
testFailed(_a + " should not be " + _bv + "."); | |
} | |
function shouldBeTrue(_a) { | |
shouldBe(_a, "true"); | |
} | |
function shouldBeFalse(_a) { | |
shouldBe(_a, "false"); | |
} | |
function shouldBeNaN(_a) { | |
shouldBe(_a, "NaN"); | |
} | |
function shouldBeNull(_a) { | |
shouldBe(_a, "null"); | |
} | |
function shouldBeEqualToString(a, b) { | |
var unevaledString = '"' + b.replace(/"/g, "\"") + '"'; | |
shouldBe(a, unevaledString); | |
} | |
function shouldEvaluateTo(actual, expected) { | |
// A general-purpose comparator. 'actual' should be a string to be | |
// evaluated, as for shouldBe(). 'expected' may be any type and will be | |
// used without being eval'ed. | |
if (expected == null) { | |
// Do this before the object test, since null is of type 'object'. | |
shouldBeNull(actual); | |
} else if (typeof expected == "undefined") { | |
shouldBeUndefined(actual); | |
} else if (typeof expected == "function") { | |
// All this fuss is to avoid the string-arg warning from shouldBe(). | |
try { | |
actualValue = eval(actual); | |
} catch (e) { | |
testFailed("Evaluating " + actual + ": Threw exception " + e); | |
return; | |
} | |
shouldBe("'" + actualValue.toString().replace(/\n/g, "") + "'", | |
"'" + expected.toString().replace(/\n/g, "") + "'"); | |
} else if (typeof expected == "object") { | |
shouldBeTrue(actual + " == '" + expected + "'"); | |
} else if (typeof expected == "string") { | |
shouldBe(actual, expected); | |
} else if (typeof expected == "boolean") { | |
shouldBe("typeof " + actual, "'boolean'"); | |
if (expected) | |
shouldBeTrue(actual); | |
else | |
shouldBeFalse(actual); | |
} else if (typeof expected == "number") { | |
shouldBe(actual, stringify(expected)); | |
} else { | |
debug(expected + " is unknown type " + typeof expected); | |
shouldBeTrue(actual, "'" + expected.toString() + "'"); | |
} | |
} | |
function shouldBeNonZero(_a) { | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
if (exception) | |
testFailed(_a + " should be non-zero. Threw exception " + exception); | |
else if (_av != 0) | |
testPassed(_a + " is non-zero."); | |
else | |
testFailed(_a + " should be non-zero. Was " + _av); | |
} | |
function shouldBeNonNull(_a) { | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
if (exception) | |
testFailed(_a + " should be non-null. Threw exception " + exception); | |
else if (_av != null) | |
testPassed(_a + " is non-null."); | |
else | |
testFailed(_a + " should be non-null. Was " + _av); | |
} | |
function shouldBeUndefined(_a) { | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
if (exception) | |
testFailed(_a + " should be undefined. Threw exception " + exception); | |
else if (typeof _av == "undefined") | |
testPassed(_a + " is undefined."); | |
else | |
testFailed(_a + " should be undefined. Was " + _av); | |
} | |
function shouldBeDefined(_a) { | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
if (exception) | |
testFailed(_a + " should be defined. Threw exception " + exception); | |
else if (_av !== undefined) | |
testPassed(_a + " is defined."); | |
else | |
testFailed(_a + " should be defined. Was " + _av); | |
} | |
function shouldBeGreaterThanOrEqual(_a, _b) { | |
if (typeof _a != "string" || typeof _b != "string") | |
debug("WARN: shouldBeGreaterThanOrEqual expects string arguments"); | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
var _bv = eval(_b); | |
if (exception) | |
testFailed(_a + " should be >= " + _b + ". Threw exception " + exception); | |
else if (typeof _av == "undefined" || _av < _bv) | |
testFailed(_a + " should be >= " + _b + ". Was " + _av + " (of type " + typeof _av + ")."); | |
else | |
testPassed(_a + " is >= " + _b); | |
} | |
function expectTrue(v, msg) { | |
if (v) { | |
testPassed(msg); | |
} else { | |
testFailed(msg); | |
} | |
} | |
function shouldThrow(_a, _e) { | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
var _ev; | |
if (_e) | |
_ev = eval(_e); | |
if (exception) { | |
if (typeof _e == "undefined" || exception == _ev) | |
testPassed(_a + " threw exception " + exception + "."); | |
else | |
testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Threw exception " + exception + "."); | |
} else if (typeof _av == "undefined") | |
testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was undefined."); | |
else | |
testFailed(_a + " should throw " + (typeof _e == "undefined" ? "an exception" : _ev) + ". Was " + _av + "."); | |
} | |
function shouldBeType(_a, _type) { | |
var exception; | |
var _av; | |
try { | |
_av = eval(_a); | |
} catch (e) { | |
exception = e; | |
} | |
var _typev = eval(_type); | |
if (_av instanceof _typev) { | |
testPassed(_a + " is an instance of " + _type); | |
} else { | |
testFailed(_a + " is not an instance of " + _type); | |
} | |
} | |
function assertMsg(assertion, msg) { | |
if (assertion) { | |
testPassed(msg); | |
} else { | |
testFailed(msg); | |
} | |
} | |
function gc() {} | |
function finishTest() { | |
ENVIRONMENT.tape.end() | |
};; | |
/* | |
** 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 canvas = null | |
function createTestUtils() { | |
"use strict" | |
var console = (1, eval)("console") | |
var path = require("path").posix | |
/** | |
* Wrapped logging function. | |
* @param {string} msg The message to log. | |
*/ | |
var log = function(msg) { | |
console.log(msg) | |
}; | |
/** | |
* Wrapped logging function. | |
* @param {string} msg The message to log. | |
*/ | |
var error = function(msg) { | |
console.log(msg) | |
}; | |
/** | |
* Turn off all logging. | |
*/ | |
var loggingOff = function() { | |
log = function() {}; | |
error = function() {}; | |
}; | |
/** | |
* Converts a WebGL enum to a string | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @param {number} value The enum value. | |
* @return {string} The enum as a string. | |
*/ | |
var glEnumToString = function(gl, value) { | |
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 {!WebGLContext} gl The WebGLContext to use. | |
* @return {!WebGLShader} | |
*/ | |
var setupSimpleTextureVertexShader = function(gl) { | |
return loadShader(gl, simpleTextureVertexShader, gl.VERTEX_SHADER); | |
}; | |
/** | |
* Creates a simple texture fragment shader. | |
* @param {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext to use. | |
* @return {!WebGLShader} | |
*/ | |
var setupNoTexCoordTextureVertexShader = function(gl) { | |
return loadShader(gl, noTexCoordTextureVertexShader, gl.VERTEX_SHADER); | |
}; | |
/** | |
* Creates a simple vertex color vertex shader. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @return {!WebGLShader} | |
*/ | |
var setupSimpleVertexColorVertexShader = function(gl) { | |
return loadShader(gl, simpleVertexColorVertexShader, gl.VERTEX_SHADER); | |
}; | |
/** | |
* Creates a simple vertex color fragment shader. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @return {!WebGLShader} | |
*/ | |
var setupSimpleVertexColorFragmentShader = function(gl) { | |
return loadShader( | |
gl, simpleVertexColorFragmentShader, gl.FRAGMENT_SHADER); | |
}; | |
/** | |
* Creates a simple color vertex shader. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @return {!WebGLShader} | |
*/ | |
var setupSimpleColorVertexShader = function(gl) { | |
return loadShader(gl, simpleColorVertexShader, gl.VERTEX_SHADER); | |
}; | |
/** | |
* Creates a simple color fragment shader. | |
* @param {!WebGLContext} gl The WebGLContext 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 {!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. | |
*/ | |
var setupProgram = function(gl, shaders, opt_attribs, opt_locations) { | |
var realShaders = []; | |
var program = gl.createProgram(); | |
var shaderType = undefined; | |
for (var ii = 0; ii < shaders.length; ++ii) { | |
var shader = shaders[ii]; | |
if (typeof shader == 'string') { | |
var element = ENVIRONMENT.scriptList[shader]; | |
if (element) { | |
if (element.type != "x-shader/x-vertex" && element.type != "x-shader/x-fragment") | |
shaderType = ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER; | |
shader = loadShaderFromScript(gl, shader, shaderType); | |
} else if (endsWith(shader, ".vert")) { | |
shader = loadShaderFromFile(gl, shader, gl.VERTEX_SHADER); | |
} else if (endsWith(shader, ".frag")) { | |
shader = loadShaderFromFile(gl, shader, gl.FRAGMENT_SHADER); | |
} else { | |
shader = loadShader(gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER); | |
} | |
} | |
gl.attachShader(program, shader); | |
} | |
if (opt_attribs) { | |
for (var 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 {!WebGLContext} gl The WebGLContext 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 texture program. | |
* @param {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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; | |
poffset += 3; | |
} | |
} | |
var buf = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buf); | |
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); | |
gl.enableVertexAttribArray(positionLocation); | |
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); | |
objects.push(buf); | |
if (options.colorLocation !== undefined) { | |
var colors = new Float32Array(numVerts * 4); | |
for (var yy = 0; yy <= gridRes; ++yy) { | |
for (var 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; | |
} | |
} | |
var 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 (var yy = 0; yy < gridRes; ++yy) { | |
var index = yy * vertsAcross; | |
for (var 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; | |
} | |
} | |
var 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; | |
}; | |
/** | |
* Fills the given texture with a solid color | |
* @param {!WebGLContext} gl The WebGLContext 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. A 4 element array | |
* where each element is in the range 0 to 255. | |
* @param {number} opt_level The level of the texture to fill. Default = 0. | |
*/ | |
var fillTexture = function(gl, tex, width, height, color, opt_level) { | |
opt_level = opt_level || 0; | |
var numPixels = width * height; | |
var size = numPixels * 4; | |
var buf = new Uint8Array(size); | |
for (var ii = 0; ii < numPixels; ++ii) { | |
var off = ii * 4; | |
buf[off + 0] = color[0]; | |
buf[off + 1] = color[1]; | |
buf[off + 2] = color[2]; | |
buf[off + 3] = color[3]; | |
} | |
gl.bindTexture(gl.TEXTURE_2D, tex); | |
gl.texImage2D( | |
gl.TEXTURE_2D, opt_level, gl.RGBA, width, height, 0, | |
gl.RGBA, gl.UNSIGNED_BYTE, buf); | |
}; | |
/** | |
* Creates a textures and fills it with a solid color | |
* @param {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext to use. | |
*/ | |
var drawUnitQuad = function(gl) { | |
gl.drawArrays(gl.TRIANGLES, 0, 6); | |
}; | |
/** | |
* Clears then Draws a previously setupUnitQuad. | |
* @param {!WebGLContext} gl The WebGLContext 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 previsouly settup with setupIndexedQuad. | |
* @param {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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); | |
}; | |
/** | |
* Checks that a portion of a canvas is 1 color. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @param {number} x left corner of region to check. | |
* @param {number} y bottom corner of region to check. | |
* @param {number} width width of region to check. | |
* @param {number} height width of region to check. | |
* @param {!Array.<number>} color The color to fill clear with before drawing. 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. | |
*/ | |
var checkCanvasRectColor = function(gl, x, y, width, height, color, opt_errorRange, sameFn, differentFn, logFn) { | |
var errorRange = opt_errorRange || 0; | |
if (!errorRange.length) { | |
errorRange = [errorRange, errorRange, errorRange, errorRange] | |
} | |
var buf; | |
if (gl instanceof WebGLRenderingContext) { | |
buf = 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) { | |
var offset = i * 4; | |
for (var j = 0; j < color.length; ++j) { | |
if (Math.abs(buf[offset + j] - color[j]) > errorRange[j]) { | |
differentFn(); | |
var was = buf[offset + 0].toString(); | |
for (j = 1; j < color.length; ++j) { | |
was += "," + buf[offset + j]; | |
} | |
logFn('at (' + (x + (i % width)) + ', ' + (y + Math.floor(i / width)) + | |
') expected: ' + color + ' was ' + was); | |
return; | |
} | |
} | |
} | |
sameFn(); | |
}; | |
/** | |
* Checks that a portion of a canvas is 1 color. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @param {number} x left corner of region to check. | |
* @param {number} y bottom corner of region to check. | |
* @param {number} width width of region to check. | |
* @param {number} height width of region to check. | |
* @param {!Array.<number>} color The color to fill clear with before drawing. 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) { | |
var msg = opt_msg; | |
if (msg === undefined) { | |
msg = "should be " + color.toString(); | |
} | |
checkCanvasRectColor( | |
gl, x, y, width, height, color, opt_errorRange, | |
function() { | |
testPassed(msg); | |
}, | |
function() { | |
testFailed(msg); | |
}, | |
debug); | |
}; | |
/** | |
* 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); | |
}; | |
/** | |
* Checks that an entire canvas is 1 color. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @param {!Array.<number>} color The color to fill clear with before drawing. 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) { | |
checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, color, msg, errorRange); | |
}; | |
/** | |
* Loads a texture, calls callback when finished. | |
* @param {!WebGLContext} gl The WebGLContext 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; | |
}; | |
/** | |
* 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() { | |
return {} | |
}; | |
/** | |
* 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. | |
* @return {!WebGLContext} The created context. | |
*/ | |
var create3DContext = function(opt_canvas, opt_attributes) { | |
var width = 500 | |
var height = 500 | |
if (opt_canvas) { | |
if (typeof opt_canvas === 'string') { | |
var canvasList = ENVIRONMENT.canvasList | |
for (var i = 0; i < canvasList.length; ++i) { | |
if (canvasList[i].id === opt_canvas) { | |
width = canvasList[i].width || 500 | |
height = canvasList[i].height || 500 | |
var ctx = ENVIRONMENT.createContext(width, height, opt_attributes) | |
ctx.canvas = canvasList[i] | |
ctx.canvas._gl = ctx | |
canvas = ctx.canvas | |
return ctx | |
} | |
} | |
} else { | |
width = opt_canvas.width || 512 | |
height = opt_canvas.height || 512 | |
var ctx = ENVIRONMENT.createContext(width, height, opt_attributes) | |
ctx.canvas = opt_canvas | |
ctx.canvas._gl = ctx | |
canvas = ctx.canvas | |
return ctx | |
} | |
} | |
var ctx = ENVIRONMENT.createContext(width, height, opt_attributes) | |
canvas = document.createElement("canvas") | |
ctx.canvas = canvas | |
ctx.canvas._gl = ctx | |
return ctx | |
} | |
/** | |
* Gets a GLError value as a string. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @param {number} err The webgl error as retrieved from gl.getError(). | |
* @return {string} the error as a string. | |
*/ | |
var getGLErrorAsString = function(gl, err) { | |
if (err === gl.NO_ERROR) { | |
return "NO_ERROR"; | |
} | |
for (var name in gl) { | |
if (gl[name] === err) { | |
return name; | |
} | |
} | |
return err.toString(); | |
}; | |
/** | |
* Wraps a WebGL function with a function that throws an exception if there is | |
* an error. | |
* @param {!WebGLContext} gl The WebGLContext 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 " + getGLErrorAsString(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 {!WebGLContext} gl The WebGLContext to use. | |
* @param {number} glError The expected gl error. | |
* @param {string} evalSTr The string to evaluate. | |
*/ | |
var shouldGenerateGLError = function(gl, glError, evalStr) { | |
var exception; | |
try { | |
eval(evalStr); | |
} catch (e) { | |
exception = e; | |
} | |
if (exception) { | |
testFailed(evalStr + " threw exception " + exception); | |
} else { | |
var err = gl.getError(); | |
if (err != glError) { | |
testFailed(evalStr + " expected: " + getGLErrorAsString(gl, glError) + ". Was " + getGLErrorAsString(gl, err) + "."); | |
} else { | |
testPassed(evalStr + " was expected value: " + getGLErrorAsString(gl, glError) + "."); | |
} | |
} | |
}; | |
/** | |
* Tests that the first error GL returns is the specified error. | |
* @param {!WebGLContext} gl The WebGLContext to use. | |
* @param {number} glError The expected gl error. | |
* @param {string} opt_msg | |
*/ | |
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: " : ": "); | |
testFailed(msg + expected + ". Was " + glEnumToString(gl, err) + " : " + opt_msg); | |
} else { | |
var msg = "getError was " + ((glErrors.length > 1) ? "one of: " : "expected value: "); | |
testPassed(msg + expected + " : " + opt_msg); | |
} | |
}; | |
/** | |
* Links a WebGL program, throws if there are errors. | |
* @param {!WebGLContext} gl The WebGLContext 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) { | |
setTimeout(function() { | |
var file = ENVIRONMENT.RESOURCES[path.join(ENVIRONMENT.BASEPATH, url)] | |
if (file) { | |
var buf = new Buffer(file, 'base64') | |
callback(true, buf.toString()) | |
} else { | |
throw new Error('error loading file: ' + url) | |
} | |
}, 1) | |
}; | |
/** | |
* 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 buf = new Buffer(ENVIRONMENT.RESOURCES[path.join('resources', file)], 'base64') | |
var text = buf.toString() | |
return text.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 {!WebGLContext} gl The WebGLContext to use. | |
* @param {string} shaderSource The shader source. | |
* @param {number} shaderType The type of shader. | |
* @param {function(string): void) opt_errorCallback callback for errors. | |
* @return {!WebGLShader} The created shader. | |
*/ | |
var loadShader = function(gl, shaderSource, shaderType, opt_errorCallback) { | |
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.getError() | |
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); | |
// 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 {!WebGLContext} gl The WebGLContext 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. | |
* @return {!WebGLShader} The created shader. | |
*/ | |
var loadShaderFromFile = function(gl, file, type, opt_errorCallback) { | |
var shaderSource = readFile(file); | |
return loadShader(gl, shaderSource, type, opt_errorCallback); | |
}; | |
/** | |
* Gets the content of script. | |
*/ | |
var getScript = function(scriptId) { | |
var shaderScript = ENVIRONMENT.scriptList[scriptId]; | |
if (!shaderScript) { | |
throw ("*** Error: unknown script element" + scriptId); | |
} | |
return shaderScript.text; | |
}; | |
/** | |
* Loads a shader from a script tag. | |
* @param {!WebGLContext} gl The WebGLContext 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. | |
* @return {!WebGLShader} The created shader. | |
*/ | |
var loadShaderFromScript = function( | |
gl, scriptId, opt_shaderType, opt_errorCallback) { | |
var shaderSource = ""; | |
var shaderType; | |
var shaderScript = ENVIRONMENT.scriptList[scriptId]; | |
if (!shaderScript) { | |
throw ("*** Error: unknown script element " + scriptId); | |
} | |
shaderSource = shaderScript.text; | |
if (!opt_shaderType) { | |
if (shaderScript.type == "x-shader/x-vertex") { | |
shaderType = gl.VERTEX_SHADER; | |
} else if (shaderScript.type == "x-shader/x-fragment") { | |
shaderType = gl.FRAGMENT_SHADER; | |
} else if (shaderType != gl.VERTEX_SHADER && shaderType != gl.FRAGMENT_SHADER) { | |
throw ("*** Error: unknown shader type"); | |
return null; | |
} | |
} | |
return loadShader( | |
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType, | |
opt_errorCallback); | |
}; | |
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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext 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. | |
* @return {!WebGLProgram} The created program. | |
*/ | |
var loadProgram = function( | |
gl, vertexShader, fragmentShader, opt_errorCallback) { | |
var program; | |
var vs = loadShader( | |
gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback); | |
var fs = loadShader( | |
gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback); | |
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 {!WebGLContext} gl The WebGLContext 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 {!WebGLContext} gl The WebGLContext to use. | |
* @param {WebGLProgram} The program to query for attribs. | |
* @return the map. | |
*/ | |
var getAttribMap = function(gl, program) { | |
return getActiveMap(gl, program, { | |
param: "ACTIVE_ATTRIBS", | |
activeFn: "getActiveAttrib", | |
locFn: "getAttribLocation" | |
}); | |
}; | |
/** | |
* Returns a map of uniform names to info about those uniform | |
* | |
* 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 {!WebGLContext} gl The WebGLContext 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() { | |
return '' | |
}; | |
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) { | |
throw new Error("Image not supported") | |
}; | |
/** | |
* 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) { | |
throw new Error("loadImagesAsync not supported") | |
}; | |
var getUrlArguments = function() { | |
return {} | |
}; | |
var makeImage = function(canvas) { | |
var imgData = canvas.getContext("2d").getImageData( | |
0, 0, canvas.width, canvas.height) | |
var img = new Image() | |
img._width = imgData.width | |
img._height = imgData.height | |
img._data = imgData.data | |
return img | |
}; | |
var insertImage = function(element, caption, img) {}; | |
var addShaderSource = function(element, label, source, opt_url) {}; | |
// 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; | |
} | |
} | |
}; | |
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 conaining 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 + "'"; | |
}); | |
}; | |
/** | |
* Provides requestAnimationFrame in a cross browser way. | |
*/ | |
var requestAnimFrameImpl_; | |
var requestAnimFrame = function(callback, element) { | |
if (!requestAnimFrameImpl_) { | |
requestAnimFrameImpl_ = function() { | |
return function(callback, element) { | |
return setTimeout(callback, 1000 / 70); | |
}; | |
}(); | |
} | |
return requestAnimFrameImpl_(callback, element); | |
}; | |
/** | |
* Provides cancelAnimationFrame in a cross browser way. | |
*/ | |
var cancelAnimFrame = (function() { | |
return clearTimeout; | |
})(); | |
/** | |
* Waits for the browser to composite the canvas associated with | |
* the WebGL context passed in. | |
*/ | |
var waitForComposite = function(gl, callback) { | |
if (typeof gl === 'function') { | |
callback = gl; | |
} | |
var frames = 5; | |
var countDown = function() { | |
if (frames == 0) { | |
callback(); | |
} else { | |
--frames; | |
requestAnimFrame(countDown); | |
} | |
}; | |
countDown(); | |
}; | |
/** | |
* 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(); | |
}; | |
/** | |
* 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(); | |
}; | |
/** | |
* 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; | |
}; | |
return { | |
addShaderSource: addShaderSource, | |
cancelAnimFrame: cancelAnimFrame, | |
create3DContext: create3DContext, | |
create3DContextWithWrapperThatThrowsOnGLError: create3DContextWithWrapperThatThrowsOnGLError, | |
checkCanvas: checkCanvas, | |
checkCanvasRect: checkCanvasRect, | |
checkCanvasRectColor: checkCanvasRectColor, | |
checkAreaInAndOut: checkAreaInAndOut, | |
createColoredTexture: createColoredTexture, | |
createProgram: createProgram, | |
clearAndDrawUnitQuad: clearAndDrawUnitQuad, | |
clearAndDrawIndexedQuad: clearAndDrawIndexedQuad, | |
drawUnitQuad: drawUnitQuad, | |
drawIndexedQuad: drawIndexedQuad, | |
drawUByteColorQuad: drawUByteColorQuad, | |
drawFloatColorQuad: drawFloatColorQuad, | |
endsWith: endsWith, | |
fillTexture: fillTexture, | |
getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes, | |
getFileListAsync: getFileListAsync, | |
getLastError: getLastError, | |
getScript: getScript, | |
getSupportedExtensionWithKnownPrefixes: getSupportedExtensionWithKnownPrefixes, | |
getUrlArguments: getUrlArguments, | |
getUrlOptions: getUrlOptions, | |
getAttribMap: getAttribMap, | |
getUniformMap: getUniformMap, | |
glEnumToString: glEnumToString, | |
glErrorShouldBe: glErrorShouldBe, | |
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, | |
error: error, | |
shallowCopyObject: shallowCopyObject, | |
setupColorQuad: setupColorQuad, | |
setupProgram: setupProgram, | |
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, | |
setupQuad: setupQuad, | |
setFloatDrawColor: setFloatDrawColor, | |
setUByteDrawColor: setUByteDrawColor, | |
startPlayingAndWaitForVideo: startPlayingAndWaitForVideo, | |
startsWith: startsWith, | |
shouldGenerateGLError: shouldGenerateGLError, | |
readFile: readFile, | |
readFileList: readFileList, | |
replaceParams: replaceParams, | |
requestAnimFrame: requestAnimFrame, | |
waitForComposite: waitForComposite, | |
runSteps: runSteps, | |
getExtensionPrefixedNames: getExtensionPrefixedNames, | |
getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes, | |
none: false | |
} | |
} | |
var WebGLTestUtils = createTestUtils();; | |
/******************* BEGIN TEST CASE *******************/ | |
"use strict"; | |
description("This test verifies the functionality of the WEBGL_draw_buffers extension, if it is available."); | |
debug(""); | |
var wtu = WebGLTestUtils; | |
var canvas = document.getElementById("canvas"); | |
var output = document.getElementById("console"); | |
var gl = wtu.create3DContext(canvas); | |
var ext = null; | |
var vao = null; | |
var extensionConstants = [{ | |
name: "MAX_COLOR_ATTACHMENTS_WEBGL", | |
enum: 0x8CDF, | |
expectedFn: function(v) { | |
return v >= 4; | |
}, | |
passMsg: " should be >= 4" | |
}, { | |
name: "MAX_DRAW_BUFFERS_WEBGL", | |
enum: 0x8824, | |
expectedFn: function(v) { | |
return v > 0; | |
}, | |
passMsg: " should be > 0" | |
}, | |
{ | |
name: "COLOR_ATTACHMENT0_WEBGL", | |
enum: 0x8CE0, | |
}, { | |
name: "COLOR_ATTACHMENT1_WEBGL", | |
enum: 0x8CE1, | |
}, { | |
name: "COLOR_ATTACHMENT2_WEBGL", | |
enum: 0x8CE2, | |
}, { | |
name: "COLOR_ATTACHMENT3_WEBGL", | |
enum: 0x8CE3, | |
}, { | |
name: "COLOR_ATTACHMENT4_WEBGL", | |
enum: 0x8CE4, | |
}, { | |
name: "COLOR_ATTACHMENT5_WEBGL", | |
enum: 0x8CE5, | |
}, { | |
name: "COLOR_ATTACHMENT6_WEBGL", | |
enum: 0x8CE6, | |
}, { | |
name: "COLOR_ATTACHMENT7_WEBGL", | |
enum: 0x8CE7, | |
}, { | |
name: "COLOR_ATTACHMENT8_WEBGL", | |
enum: 0x8CE8, | |
}, { | |
name: "COLOR_ATTACHMENT9_WEBGL", | |
enum: 0x8CE9, | |
}, { | |
name: "COLOR_ATTACHMENT10_WEBGL", | |
enum: 0x8CEA, | |
}, { | |
name: "COLOR_ATTACHMENT11_WEBGL", | |
enum: 0x8CEB, | |
}, { | |
name: "COLOR_ATTACHMENT12_WEBGL", | |
enum: 0x8CEC, | |
}, { | |
name: "COLOR_ATTACHMENT13_WEBGL", | |
enum: 0x8CED, | |
}, { | |
name: "COLOR_ATTACHMENT14_WEBGL", | |
enum: 0x8CEE, | |
}, { | |
name: "COLOR_ATTACHMENT15_WEBGL", | |
enum: 0x8CEF, | |
}, | |
{ | |
name: "DRAW_BUFFER0_WEBGL", | |
enum: 0x8825, | |
}, { | |
name: "DRAW_BUFFER1_WEBGL", | |
enum: 0x8826, | |
}, { | |
name: "DRAW_BUFFER2_WEBGL", | |
enum: 0x8827, | |
}, { | |
name: "DRAW_BUFFER3_WEBGL", | |
enum: 0x8828, | |
}, { | |
name: "DRAW_BUFFER4_WEBGL", | |
enum: 0x8829, | |
}, { | |
name: "DRAW_BUFFER5_WEBGL", | |
enum: 0x882A, | |
}, { | |
name: "DRAW_BUFFER6_WEBGL", | |
enum: 0x882B, | |
}, { | |
name: "DRAW_BUFFER7_WEBGL", | |
enum: 0x882C, | |
}, { | |
name: "DRAW_BUFFER8_WEBGL", | |
enum: 0x882D, | |
}, { | |
name: "DRAW_BUFFER9_WEBGL", | |
enum: 0x882E, | |
}, { | |
name: "DRAW_BUFFER10_WEBGL", | |
enum: 0x882F, | |
}, { | |
name: "DRAW_BUFFER11_WEBGL", | |
enum: 0x8830, | |
}, { | |
name: "DRAW_BUFFER12_WEBGL", | |
enum: 0x8831, | |
}, { | |
name: "DRAW_BUFFER13_WEBGL", | |
enum: 0x8832, | |
}, { | |
name: "DRAW_BUFFER14_WEBGL", | |
enum: 0x8833, | |
}, { | |
name: "DRAW_BUFFER15_WEBGL", | |
enum: 0x8834, | |
}, | |
]; | |
if (!gl) { | |
testFailed("WebGL context does not exist"); | |
} else { | |
testPassed("WebGL context exists"); | |
// Run tests with extension disabled | |
runEnumTestDisabled(); | |
runShadersTestDisabled(); | |
runAttachmentTestDisabled(); | |
debug(""); | |
// Query the extension and store globally so shouldBe can access it | |
ext = gl.getExtension("WEBGL_draw_buffers"); | |
if (!ext) { | |
testPassed("No WEBGL_draw_buffers support -- this is legal"); | |
runSupportedTest(false); | |
finishTest(); | |
} else { | |
testPassed("Successfully enabled WEBGL_draw_buffers extension"); | |
runSupportedTest(true); | |
runEnumTestEnabled(); | |
runShadersTestEnabled(); | |
runAttachmentTestEnabled(); | |
runDrawTests(); | |
runPreserveTests(); | |
} | |
} | |
function createExtDrawBuffersProgram(scriptId, sub) { | |
var fsource = wtu.getScript(scriptId); | |
fsource = wtu.replaceParams(fsource, sub); | |
wtu.addShaderSource(output, "fragment shader", fsource); | |
return wtu.setupProgram(gl, ["vshader", fsource], ["a_position"]); | |
} | |
function runSupportedTest(extensionEnabled) { | |
var supported = gl.getSupportedExtensions(); | |
if (supported.indexOf("WEBGL_draw_buffers") >= 0) { | |
if (extensionEnabled) { | |
testPassed("WEBGL_draw_buffers listed as supported and getExtension succeeded"); | |
} else { | |
testFailed("WEBGL_draw_buffers listed as supported but getExtension failed"); | |
} | |
} else { | |
if (extensionEnabled) { | |
testFailed("WEBGL_draw_buffers not listed as supported but getExtension succeeded"); | |
} else { | |
testPassed("WEBGL_draw_buffers not listed as supported and getExtension failed -- this is legal"); | |
} | |
} | |
} | |
function runEnumTestDisabled() { | |
debug(""); | |
debug("Testing binding enum with extension disabled"); | |
// Use the constant directly as we don't have the extension | |
extensionConstants.forEach(function(c) { | |
if (c.expectedFn) { | |
gl.getParameter(c.enum); | |
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, c.name + " should not be queryable if extension is disabled"); | |
} | |
}); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors"); | |
} | |
function runEnumTestEnabled() { | |
debug(""); | |
debug("Testing enums with extension enabled"); | |
extensionConstants.forEach(function(c) { | |
shouldBe("ext." + c.name, "0x" + c.enum.toString(16)); | |
if (c.expectedFn) { | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "before getParameter"); | |
debug(c.name + ": 0x" + ext[c.name].toString(16)); | |
expectTrue(c.expectedFn(gl.getParameter(ext[c.name])), "gl.getParameter(ext." + c.name + ")" + c.passMsg); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, c.name + " query should succeed if extension is enabled"); | |
} | |
}); | |
shouldBeTrue("gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL) >= gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL)"); | |
debug("Testing drawBuffersWEBGL with default drawing buffer"); | |
shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.BACK"); | |
wtu.shouldGenerateGLError(gl, gl.INVALID_OPERATION, "ext.drawBuffersWEBGL([])"); | |
wtu.shouldGenerateGLError(gl, gl.INVALID_OPERATION, "ext.drawBuffersWEBGL([gl.NONE, gl.NONE])"); | |
wtu.shouldGenerateGLError(gl, gl.INVALID_OPERATION, "ext.drawBuffersWEBGL([ext.COLOR_ATTACHMENT0_WEBGL])"); | |
shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.BACK"); | |
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "ext.drawBuffersWEBGL([gl.NONE])"); | |
shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.NONE"); | |
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "ext.drawBuffersWEBGL([gl.BACK])"); | |
shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.BACK"); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors"); | |
} | |
function testShaders(tests, sub) { | |
tests.forEach(function(test) { | |
var shaders = [wtu.getScript(test.shaders[0]), wtu.replaceParams(wtu.getScript(test.shaders[1]), sub)]; | |
wtu.addShaderSource(output, "vertex shader", shaders[0]); | |
wtu.addShaderSource(output, "fragment shader", shaders[1]); | |
var program = wtu.setupProgram(gl, shaders, ["a_position"]); | |
var programLinkedSuccessfully = (program != null); | |
var expectedProgramToLinkSuccessfully = (test.expectFailure == true); | |
expectTrue(programLinkedSuccessfully != expectedProgramToLinkSuccessfully, test.msg); | |
gl.deleteProgram(program); | |
}); | |
} | |
function runShadersTestDisabled() { | |
debug(""); | |
debug("test shaders disabled"); | |
testShaders([{ | |
shaders: ["vshader", "fshaderMacroDisabled"], | |
msg: "GL_EXT_draw_buffers should not be defined in GLSL", | |
}, { | |
shaders: ["vshader", "fshader"], | |
msg: "#extension GL_EXT_draw_buffers should not be allowed in GLSL", | |
expectFailure: true, | |
}, ], { | |
numDrawingBuffers: 1 | |
}); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors"); | |
} | |
function runShadersTestEnabled() { | |
debug(""); | |
debug("test shaders enabled"); | |
var sub = { | |
numDrawingBuffers: gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL) | |
}; | |
testShaders([{ | |
shaders: ["vshader", "fshaderMacroEnabled"], | |
msg: "GL_EXT_draw_buffers should be defined as 1 in GLSL", | |
}, { | |
shaders: ["vshader", "fshader"], | |
msg: "fragment shader containing the #extension directive should compile", | |
}, ], sub); | |
var program = createExtDrawBuffersProgram("fshaderBuiltInConstEnabled", sub); | |
wtu.setupUnitQuad(gl); | |
wtu.clearAndDrawUnitQuad(gl); | |
wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green"); | |
gl.deleteProgram(program); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors"); | |
} | |
function runAttachmentTestDisabled() { | |
debug(""); | |
debug("test attachment disabled"); | |
var tex = gl.createTexture(); | |
var fb = gl.createFramebuffer(); | |
gl.bindTexture(gl.TEXTURE_2D, tex); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + 1, gl.TEXTURE_2D, tex, 0); | |
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "should not be able to attach to gl.COLOR_ATTACHMENT1"); | |
gl.deleteFramebuffer(fb); | |
gl.deleteTexture(tex); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors"); | |
} | |
function makeArray(size, value) { | |
var array = [] | |
for (var ii = 0; ii < size; ++ii) { | |
array.push(value); | |
} | |
return array; | |
} | |
function makeColorAttachmentArray(size) { | |
var array = [] | |
for (var ii = 0; ii < size; ++ii) { | |
array.push(gl.COLOR_ATTACHMENT0 + ii); | |
} | |
return array; | |
} | |
function runAttachmentTestEnabled() { | |
debug(""); | |
debug("test attachment enabled"); | |
var maxDrawingBuffers = gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL); | |
var maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL); | |
var tex = gl.createTexture(); | |
var fb = gl.createFramebuffer(); | |
gl.bindTexture(gl.TEXTURE_2D, tex); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + maxColorAttachments, gl.TEXTURE_2D, tex, 0); | |
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "should not be able to attach pass the max attachment point: gl.COLOR_ATTACHMENT0 + " + maxColorAttachments); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + maxColorAttachments - 1, gl.TEXTURE_2D, tex, 0); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to attach to the max attachment point: gl.COLOR_ATTACHMENT0 + " + (maxColorAttachments - 1)); | |
ext.drawBuffersWEBGL(makeArray(maxDrawingBuffers, gl.NONE)); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffersWEBGL with array NONE of size " + maxColorAttachments); | |
var bufs = makeColorAttachmentArray(maxDrawingBuffers); | |
ext.drawBuffersWEBGL(bufs); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffersWEBGL with array attachments of size " + maxColorAttachments); | |
bufs[0] = gl.NONE; | |
ext.drawBuffersWEBGL(bufs); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffersWEBGL with mixed array attachments of size " + maxColorAttachments); | |
if (maxDrawingBuffers > 1) { | |
bufs[0] = ext.COLOR_ATTACHMENT1_WEBGL; | |
bufs[1] = ext.COLOR_ATTACHMENT0_WEBGL; | |
ext.drawBuffersWEBGL(bufs); | |
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "should not be able to call drawBuffersWEBGL with out of order attachments of size " + maxColorAttachments); | |
var bufs = makeColorAttachmentArray(Math.floor(maxDrawingBuffers / 2)); | |
ext.drawBuffersWEBGL(bufs); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffersWEBGL with short array of attachments of size " + bufs.length); | |
} | |
gl.deleteFramebuffer(fb); | |
gl.deleteTexture(tex); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors"); | |
} | |
function makeColorByIndex(index) { | |
var low = (index - 1) % 15 + 1; | |
var high = (index - 1) / 15; | |
var zeroOrOne = function(v) { | |
return v ? 1 : 0; | |
}; | |
var oneOrTwo = function(v) { | |
return v ? 2 : 1; | |
} | |
var makeComponent = function(b0, b1, b2) { | |
return Math.floor(255 * zeroOrOne(b0) / oneOrTwo(b1) / oneOrTwo(b2)); | |
}; | |
return [ | |
makeComponent(low & (1 << 0), high & (1 << 0), high & (1 << 4)), | |
makeComponent(low & (1 << 1), high & (1 << 1), high & (1 << 5)), | |
makeComponent(low & (1 << 2), high & (1 << 2), high & (1 << 6)), | |
makeComponent(low & (1 << 3), high & (1 << 3), high & (1 << 7)), | |
]; | |
} | |
function runDrawTests() { | |
debug(""); | |
debug("--------- draw tests -----------"); | |
var fb = gl.createFramebuffer(); | |
var fb2 = gl.createFramebuffer(); | |
var halfFB1 = gl.createFramebuffer(); | |
var halfFB2 = gl.createFramebuffer(); | |
var endsFB = gl.createFramebuffer(); | |
var middleFB = gl.createFramebuffer(); | |
var maxDrawingBuffers = gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL); | |
var maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL); | |
var maxUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS); | |
var maxUsable = Math.min(maxDrawingBuffers, maxColorAttachments, maxUniformVectors); | |
var half = Math.floor(maxUsable / 2); | |
var bufs = makeColorAttachmentArray(maxUsable); | |
var nones = makeArray(maxUsable, gl.NONE); | |
[fb, fb2, halfFB1, halfFB2, endsFB, middleFB].forEach(function(fbo) { | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
ext.drawBuffersWEBGL(bufs); | |
}); | |
var checkProgram = wtu.setupTexturedQuad(gl); | |
var redProgram = wtu.setupProgram(gl, ["vshader", "fshaderRed"], ["a_position"]); | |
var drawProgram = createExtDrawBuffersProgram("fshader", { | |
numDrawingBuffers: maxDrawingBuffers | |
}); | |
var width = 64; | |
var height = 64; | |
var attachments = []; | |
// Makes 6 framebuffers. | |
// fb and fb2 have all the attachments. | |
// halfFB1 has the first half of the attachments | |
// halfFB2 has the second half of the attachments | |
// endsFB has the first and last attachments | |
// middleFB has all but the first and last attachments | |
for (var ii = 0; ii < maxUsable; ++ii) { | |
var tex = gl.createTexture(); | |
gl.bindTexture(gl.TEXTURE_2D, tex); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, ii < half ? halfFB1 : halfFB2); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, (ii == 0 || ii == (maxUsable - 1)) ? endsFB : middleFB); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0); | |
var location = gl.getUniformLocation(drawProgram, "u_colors[" + ii + "]"); | |
var color = makeColorByIndex(ii + 1); | |
var floatColor = [color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255]; | |
gl.uniform4fv(location, floatColor); | |
attachments.push({ | |
texture: tex, | |
location: location, | |
color: color | |
}); | |
} | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE"); | |
var checkAttachmentsForColorFn = function(attachments, colorFn) { | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
gl.useProgram(checkProgram); | |
attachments.forEach(function(attachment, index) { | |
gl.bindTexture(gl.TEXTURE_2D, attachment.texture); | |
wtu.clearAndDrawUnitQuad(gl); | |
var expectedColor = colorFn(attachment, index); | |
var tolerance = 0; | |
expectedColor.forEach(function(v) { | |
if (v != 0 && v != 255) { | |
tolerance = 8; | |
} | |
}); | |
wtu.checkCanvas(gl, expectedColor, "attachment " + index + " should be " + expectedColor.toString(), tolerance); | |
}); | |
debug(""); | |
}; | |
var checkAttachmentsForColor = function(attachments, color) { | |
checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
return color || attachment.color; | |
}); | |
}; | |
var drawAndCheckAttachments = function(testFB, msg, testFn) { | |
debug("test clearing " + msg); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, testFB); | |
attachments.forEach(function(attachment, index) { | |
debug("attachment: " + index + " = " + wtu.glEnumToString(gl, gl.getParameter(ext.DRAW_BUFFER0_WEBGL + index)) + | |
", " + wtu.glEnumToString(gl, gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + index, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE))); | |
}); | |
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { | |
debug("framebuffer not complete"); | |
debug(""); | |
return; | |
} | |
// Clear all the attachments | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
gl.clearColor(0, 0, 0, 0); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
//checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
// return [0, 0, 0, 0]; | |
//}); | |
//debug("--"); | |
// Clear some attachments using testFB | |
gl.bindFramebuffer(gl.FRAMEBUFFER, testFB); | |
gl.clearColor(0, 1, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
return testFn(attachment, index) ? [0, 255, 0, 255] : [0, 0, 0, 0]; | |
}); | |
debug("test drawing to " + msg); | |
// Draw to some attachments using testFB | |
gl.useProgram(drawProgram); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, testFB); | |
wtu.drawUnitQuad(gl); | |
checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
return testFn(attachment, index) ? attachment.color : [0, 0, 0, 0]; | |
}); | |
}; | |
gl.useProgram(drawProgram); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
ext.drawBuffersWEBGL(bufs); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
ext.drawBuffersWEBGL(bufs); | |
wtu.drawUnitQuad(gl); | |
debug("test that each texture got the correct color."); | |
checkAttachmentsForColor(attachments); | |
debug("test clearing clears all the textures"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
gl.clearColor(0, 1, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
checkAttachmentsForColor(attachments, [0, 255, 0, 255]); | |
debug("test that NONE draws nothing"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
ext.drawBuffersWEBGL(nones); | |
gl.useProgram(redProgram); | |
wtu.clearAndDrawUnitQuad(gl); | |
checkAttachmentsForColor(attachments, [0, 255, 0, 255]); | |
debug("test that gl_FragColor broadcasts"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
ext.drawBuffersWEBGL(bufs); | |
gl.useProgram(redProgram); | |
wtu.drawUnitQuad(gl); | |
checkAttachmentsForColor(attachments, [255, 0, 0, 255]); | |
if (maxUsable > 1) { | |
var bufs1 = makeColorAttachmentArray(maxUsable); | |
var bufs2 = makeColorAttachmentArray(maxUsable); | |
for (var ii = 0; ii < maxUsable; ++ii) { | |
if (ii < half) { | |
bufs1[ii] = gl.NONE; | |
} else { | |
bufs2[ii] = gl.NONE; | |
} | |
} | |
debug("test setting first half to NONE and clearing"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
ext.drawBuffersWEBGL(bufs1); | |
gl.clearColor(0, 1, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
return index < half ? [255, 0, 0, 255] : [0, 255, 0, 255]; | |
}); | |
debug("test setting first half to NONE and drawing"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
gl.useProgram(drawProgram); | |
wtu.drawUnitQuad(gl); | |
checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
return index < half ? [255, 0, 0, 255] : attachment.color; | |
}); | |
debug("test setting second half to NONE and clearing"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
ext.drawBuffersWEBGL(bufs); | |
gl.clearColor(1, 0, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
ext.drawBuffersWEBGL(bufs2); | |
gl.clearColor(0, 0, 1, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
return index < half ? [0, 0, 255, 255] : [255, 0, 0, 255]; | |
}); | |
debug("test setting second half to NONE and drawing"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
gl.useProgram(drawProgram); | |
wtu.drawUnitQuad(gl); | |
checkAttachmentsForColorFn(attachments, function(attachment, index) { | |
return index < half ? attachment.color : [255, 0, 0, 255]; | |
}); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, halfFB1); | |
ext.drawBuffersWEBGL(bufs); | |
drawAndCheckAttachments( | |
halfFB1, "framebuffer that only has first half of attachments", | |
function(attachment, index) { | |
return index < half; | |
}); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, halfFB2); | |
ext.drawBuffersWEBGL(bufs); | |
drawAndCheckAttachments( | |
halfFB2, "framebuffer that only has second half of attachments", | |
function(attachment, index) { | |
return index >= half; | |
}); | |
if (maxUsable > 2) { | |
gl.bindFramebuffer(gl.FRAMEBUFFER, endsFB); | |
ext.drawBuffersWEBGL(bufs); | |
drawAndCheckAttachments( | |
endsFB, "framebuffer that only has first and last attachments", | |
function(attachment, index) { | |
return index == 0 || index == (maxUsable - 1); | |
}); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, middleFB); | |
ext.drawBuffersWEBGL(bufs); | |
drawAndCheckAttachments( | |
middleFB, | |
"framebuffer that has all but the first and last attachments", | |
function(attachment, index) { | |
return index != 0 && index != (maxUsable - 1); | |
}); | |
} | |
} | |
debug("test switching between fbos does not affect any color attachment contents"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
ext.drawBuffersWEBGL(nones); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
ext.drawBuffersWEBGL(bufs); | |
gl.clearColor(1, 0, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
checkAttachmentsForColor(attachments, [255, 0, 0, 255]); | |
// fb2 still has the NONE draw buffers from before, so this draw should be a no-op. | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
gl.useProgram(drawProgram); | |
wtu.drawUnitQuad(gl); | |
checkAttachmentsForColor(attachments, [255, 0, 0, 255]); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
gl.useProgram(drawProgram); | |
wtu.drawUnitQuad(gl); | |
checkAttachmentsForColor(attachments); | |
debug("test queries"); | |
debug("check framebuffer with all attachments on"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
for (var ii = 0; ii < maxUsable; ++ii) { | |
shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL + " + ii + ")", "gl.COLOR_ATTACHMENT0 + " + ii); | |
} | |
debug("check framebuffer with all attachments off"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
for (var ii = 0; ii < maxUsable; ++ii) { | |
shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL + " + ii + ")", "gl.NONE"); | |
} | |
debug("test attachment size mis-match"); | |
gl.bindTexture(gl.TEXTURE_2D, attachments[0].texture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width * 2, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb); | |
shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); | |
shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE"); | |
gl.deleteFramebuffer(fb); | |
gl.deleteFramebuffer(fb2); | |
gl.deleteFramebuffer(halfFB1); | |
gl.deleteFramebuffer(halfFB2); | |
attachments.forEach(function(attachment) { | |
gl.deleteTexture(attachment.texture); | |
}); | |
gl.deleteProgram(checkProgram); | |
gl.deleteProgram(redProgram); | |
gl.deleteProgram(drawProgram); | |
} | |
function runPreserveTests() { | |
debug(""); | |
debug("--------- preserve tests -----------"); | |
debug("Testing that frame buffer is cleared after compositing"); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
gl.clearColor(1, 1, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
wtu.checkCanvas(gl, [255, 255, 0, 255], "should be yellow"); | |
// set the draw buffer to NONE | |
ext.drawBuffersWEBGL([gl.NONE]); | |
gl.clearColor(1, 0, 1, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
// make sure the canvas is still clear | |
wtu.checkCanvas(gl, [255, 255, 0, 255], "should be yellow"); | |
wtu.waitForComposite(function() { | |
gl.clearColor(1, 0, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
wtu.checkCanvas(gl, [0, 0, 0, 0], "should be clear"); | |
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors"); | |
finishTest(); | |
}); | |
}; | |
/******************* END TEST CASE *******************/ | |
if (ENVIRONMENT.postHook) { | |
ENVIRONMENT.postHook(); | |
} | |
}; | |
module.exports = extensions_webgl_draw_buffers |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment