michael@0: /* michael@0: Copyright (C) 2011 Apple Computer, Inc. All rights reserved. michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions michael@0: are met: michael@0: 1. Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: 2. Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY michael@0: EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE michael@0: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR michael@0: PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR michael@0: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, michael@0: EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, michael@0: PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR michael@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY michael@0: OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: function webglTestLog(msg) { michael@0: if (window.console && window.console.log) { michael@0: window.console.log(msg); michael@0: } michael@0: if (document.getElementById("console")) { michael@0: var log = document.getElementById("console"); michael@0: log.innerHTML += msg + "
"; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // create3DContext michael@0: // michael@0: // Returns the WebGLRenderingContext for any known implementation. michael@0: // michael@0: function create3DContext(canvas, attributes) michael@0: { michael@0: if (!canvas) michael@0: canvas = document.createElement("canvas"); michael@0: var names = ["webgl", "experimental-webgl"]; michael@0: var context = null; michael@0: for (var i = 0; i < names.length; ++i) { michael@0: try { michael@0: context = canvas.getContext(names[i], attributes); michael@0: } catch (e) { michael@0: } michael@0: if (context) { michael@0: break; michael@0: } michael@0: } michael@0: if (!context) { michael@0: throw "Unable to fetch WebGL rendering context for Canvas"; michael@0: } michael@0: return context; michael@0: } michael@0: michael@0: function createGLErrorWrapper(context, fname) { michael@0: return function() { michael@0: var rv = context[fname].apply(context, arguments); michael@0: var err = context.getError(); michael@0: if (err != 0) michael@0: throw "GL error " + err + " in " + fname; michael@0: return rv; michael@0: }; michael@0: } michael@0: michael@0: function create3DContextWithWrapperThatThrowsOnGLError(canvas, attributes) { michael@0: var context = create3DContext(canvas, attributes); michael@0: // Thanks to Ilmari Heikkinen for the idea on how to implement this so elegantly. michael@0: var wrap = {}; michael@0: for (var i in context) { michael@0: try { michael@0: if (typeof context[i] == 'function') { michael@0: wrap[i] = createGLErrorWrapper(context, i); michael@0: } else { michael@0: wrap[i] = context[i]; michael@0: } michael@0: } catch (e) { michael@0: webglTestLog("createContextWrapperThatThrowsOnGLError: Error accessing " + i); michael@0: } michael@0: } michael@0: wrap.getError = function() { michael@0: return context.getError(); michael@0: }; michael@0: return wrap; michael@0: } michael@0: michael@0: function getGLErrorAsString(ctx, err) { michael@0: if (err === ctx.NO_ERROR) { michael@0: return "NO_ERROR"; michael@0: } michael@0: for (var name in ctx) { michael@0: if (ctx[name] === err) { michael@0: return name; michael@0: } michael@0: } michael@0: return "0x" + err.toString(16); michael@0: } michael@0: michael@0: // Pass undefined for glError to test that it at least throws some error michael@0: function shouldGenerateGLError(ctx, glErrors, evalStr) { michael@0: if (!glErrors.length) { michael@0: glErrors = [glErrors]; michael@0: } michael@0: var exception; michael@0: try { michael@0: eval(evalStr); michael@0: } catch (e) { michael@0: exception = e; michael@0: } michael@0: if (exception) { michael@0: testFailed(evalStr + " threw exception " + exception); michael@0: } else { michael@0: var err = ctx.getError(); michael@0: if (glErrors.indexOf(err) < 0) { michael@0: var errStrs = []; michael@0: for (var ii = 0; ii < glErrors.length; ++ii) { michael@0: errStrs.push(getGLErrorAsString(ctx, glErrors[ii])); michael@0: } michael@0: testFailed(evalStr + " expected: " + errStrs.join(" or ") + ". Was " + getGLErrorAsString(ctx, err) + "."); michael@0: } else { michael@0: testPassed(evalStr + " generated expected GL error: " + getGLErrorAsString(ctx, err) + "."); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Tests that the first error GL returns is the specified error. michael@0: * @param {!WebGLContext} gl The WebGLContext to use. michael@0: * @param {number|!Array.} glError The expected gl michael@0: * error. Multiple errors can be passed in using an michael@0: * array. michael@0: * @param {string} opt_msg Optional additional message. michael@0: */ michael@0: function glErrorShouldBe(gl, glErrors, opt_msg) { michael@0: if (!glErrors.length) { michael@0: glErrors = [glErrors]; michael@0: } michael@0: opt_msg = opt_msg || ""; michael@0: var err = gl.getError(); michael@0: var ndx = glErrors.indexOf(err); michael@0: if (ndx < 0) { michael@0: if (glErrors.length == 1) { michael@0: testFailed("getError expected: " + getGLErrorAsString(gl, glErrors[0]) + michael@0: ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg); michael@0: } else { michael@0: var errs = []; michael@0: for (var ii = 0; ii < glErrors.length; ++ii) { michael@0: errs.push(getGLErrorAsString(gl, glErrors[ii])); michael@0: } michael@0: testFailed("getError expected one of: [" + errs.join(", ") + michael@0: "]. Was " + getGLErrorAsString(gl, err) + " : " + opt_msg); michael@0: } michael@0: } else { michael@0: testPassed("getError was expected value: " + michael@0: getGLErrorAsString(gl, err) + " : " + opt_msg); michael@0: } michael@0: }; michael@0: michael@0: // michael@0: // createProgram michael@0: // michael@0: // Create and return a program object, attaching each of the given shaders. michael@0: // michael@0: // If attribs are given, bind an attrib with that name at that index. michael@0: // michael@0: function createProgram(gl, vshaders, fshaders, attribs) michael@0: { michael@0: if (typeof(vshaders) == "string") michael@0: vshaders = [vshaders]; michael@0: if (typeof(fshaders) == "string") michael@0: fshaders = [fshaders]; michael@0: michael@0: var shaders = []; michael@0: var i; michael@0: michael@0: for (i = 0; i < vshaders.length; ++i) { michael@0: var shader = loadShader(gl, vshaders[i], gl.VERTEX_SHADER); michael@0: if (!shader) michael@0: return null; michael@0: shaders.push(shader); michael@0: } michael@0: michael@0: for (i = 0; i < fshaders.length; ++i) { michael@0: var shader = loadShader(gl, fshaders[i], gl.FRAGMENT_SHADER); michael@0: if (!shader) michael@0: return null; michael@0: shaders.push(shader); michael@0: } michael@0: michael@0: var prog = gl.createProgram(); michael@0: for (i = 0; i < shaders.length; ++i) { michael@0: gl.attachShader(prog, shaders[i]); michael@0: } michael@0: michael@0: if (attribs) { michael@0: for (var i = 0; i < attribs.length; ++i) { michael@0: gl.bindAttribLocation(prog, i, attribs[i]); michael@0: } michael@0: } michael@0: michael@0: gl.linkProgram(prog); michael@0: michael@0: // Check the link status michael@0: var linked = gl.getProgramParameter(prog, gl.LINK_STATUS); michael@0: if (!linked) { michael@0: // something went wrong with the link michael@0: var error = gl.getProgramInfoLog(prog); michael@0: webglTestLog("Error in program linking:" + error); michael@0: michael@0: gl.deleteProgram(prog); michael@0: for (i = 0; i < shaders.length; ++i) michael@0: gl.deleteShader(shaders[i]); michael@0: return null; michael@0: } michael@0: michael@0: return prog; michael@0: } michael@0: michael@0: // michael@0: // initWebGL michael@0: // michael@0: // Initialize the Canvas element with the passed name as a WebGL object and return the michael@0: // WebGLRenderingContext. michael@0: // michael@0: // Load shaders with the passed names and create a program with them. Return this program michael@0: // in the 'program' property of the returned context. michael@0: // michael@0: // For each string in the passed attribs array, bind an attrib with that name at that index. michael@0: // Once the attribs are bound, link the program and then use it. michael@0: // michael@0: // Set the clear color to the passed array (4 values) and set the clear depth to the passed value. michael@0: // Enable depth testing and blending with a blend func of (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) michael@0: // michael@0: function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth, contextAttribs) michael@0: { michael@0: var canvas = document.getElementById(canvasName); michael@0: var gl = create3DContext(canvas, contextAttribs); michael@0: if (!gl) { michael@0: alert("No WebGL context found"); michael@0: return null; michael@0: } michael@0: michael@0: // Create the program object michael@0: gl.program = createProgram(gl, vshader, fshader, attribs); michael@0: if (!gl.program) michael@0: return null; michael@0: michael@0: gl.useProgram(gl.program); michael@0: michael@0: gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); michael@0: gl.clearDepth(clearDepth); michael@0: michael@0: gl.enable(gl.DEPTH_TEST); michael@0: gl.enable(gl.BLEND); michael@0: gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); michael@0: michael@0: return gl; michael@0: } michael@0: michael@0: // michael@0: // getShaderSource michael@0: // michael@0: // Load the source from the passed shader file. michael@0: // michael@0: function getShaderSource(file) michael@0: { michael@0: var xhr = new XMLHttpRequest(); michael@0: xhr.open("GET", file, false); michael@0: xhr.send(); michael@0: return xhr.responseText; michael@0: } michael@0: michael@0: michael@0: // michael@0: // loadShader michael@0: // michael@0: // 'shader' is either the id of a