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