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