commit 063ceb5293527324d03873521448dfa9c9bb60d1 Author: lhark Date: Mon Oct 23 14:31:42 2017 -0400 Commit initial. Import depuis webgl-tp6 diff --git a/J3DI.js b/J3DI.js new file mode 100644 index 0000000..7964fdd --- /dev/null +++ b/J3DI.js @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2009 Apple 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 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 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. + */ + +// +// initWebGL +// +// Initialize the Canvas element with the passed name as a WebGL object and return the +// WebGLRenderingContext. +function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth) +{ + var canvas = document.getElementById(canvasName); + return gl = WebGLUtils.setupWebGL(canvas); +} + +function log(msg) { + if (window.console && window.console.log) { + window.console.log(msg); + } +} + +// 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) +// +// A console function is added to the context: console(string). This can be replaced +// by the caller. By default, it maps to the window.console() function on WebKit and to +// an empty function on other browsers. +// +function simpleSetup(gl, vshader, fshader, attribs, clearColor, clearDepth) +{ + // create our shaders + var vertexShader = loadShader(gl, vshader); + var fragmentShader = loadShader(gl, fshader); + + // Create the program object + var program = gl.createProgram(); + + // Attach our two shaders to the program + gl.attachShader (program, vertexShader); + gl.attachShader (program, fragmentShader); + + // Bind attributes + for (var i = 0; i < attribs.length; ++i) + gl.bindAttribLocation (program, i, attribs[i]); + + // Link the program + gl.linkProgram(program); + + // Check the link status + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked && !gl.isContextLost()) { + // something went wrong with the link + var error = gl.getProgramInfoLog (program); + log("Error in program linking:"+error); + + gl.deleteProgram(program); + gl.deleteProgram(fragmentShader); + gl.deleteProgram(vertexShader); + + return null; + } + + gl.useProgram(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 program; +} + +// +// loadShader +// +// 'shaderId' is the id of a + + + + + + + + + + + + + + Si vous voyez ceci, votre navigateur ne supporte pas webgl. + + +
Les spécifications de Webgl sont
remplies d'informations intéressantes.
+ + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..21fab56 --- /dev/null +++ b/main.js @@ -0,0 +1,254 @@ +// Prénoms, noms et matricule des membres de l'équipe: +// - Prénom1 NOM1 (matricule1) +// - Prénom2 NOM2 (matricule2) + + +// déclaration d'une structure pour contenir toutes les variables globales +var glob = { }; + +function handleMouseDown(event) +{ + glob.mouseDown = true; + glob.positionSourisX = event.clientX-15; + glob.positionSourisY = event.clientY-15; +} + +function handleMouseUp(event) +{ + glob.mouseDown = false; + glob.positionSourisX = null; + glob.positionSourisY = null; +} + +function handleMouseMove(event) +{ + if (!glob.mouseDown) return; + glob.positionSourisX = event.clientX-15; + glob.positionSourisY = event.clientY-15; +} + +// +// makeRepere +// +// Create a repere with vertices, normals and texCoords. Create VBOs for each as well as the index array. +// Return an object with the following properties: +// +// normalObject WebGLBuffer object for normals +// texCoordObject WebGLBuffer object for texCoords +// vertexObject WebGLBuffer object for vertices +// indexObject WebGLBuffer object for indices +// numIndices The number of indices in the indexObject +// +function makeRepere(ctx) +{ + // box + // v2 + // | + // | + // | + // v0 o------v1 + // / + // v3 + // + // vertex coords array + var vertices = new Float32Array( + [ 0, 0, 0, 1, 0, 0, // v0 -> v1 vec X + 0, 0, 0, 0, 1, 0, // v0 -> v2 vec Y + 0, 0, 0, 0, 0, 1 ]// v0 -> v3 vec Z + ); + + // colors array + var colors = new Float32Array( + [ 1, 0, 0, 1, 0, 0, // v0 -> v1 vec X + 0, 1, 0, 0, 1, 0, // v0 -> v2 vec Y + 0, 0, 1, 0, 0, 1 ]// v0 -> v3 vec Z + ); + + // index array + var indices = new Uint8Array( + [ 0, 1, + 2, 3, + 4, 5 ] + ); + + var retval = { }; + + retval.vertexObject = ctx.createBuffer(); + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject); + ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW); + + retval.colorObject = ctx.createBuffer(); + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.colorObject); + ctx.bufferData(ctx.ARRAY_BUFFER, colors, ctx.STATIC_DRAW); + + ctx.bindBuffer(ctx.ARRAY_BUFFER, null); + + retval.indexObject = ctx.createBuffer(); + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject); + ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, indices, ctx.STATIC_DRAW); + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); + + retval.numIndices = indices.length; + + return retval; +} +function TPchargerTextures() +{ + // Charger une image utilisée comme texture. (Retourne un objet WebGLTexture) + glob.texture1 = loadImageTexture( gl, "images/Brouillard.jpg" ); + glob.texture2 = loadImageTexture( gl, "images/Exploration.jpg" ); + //glob.texture3 = loadImageTexture( gl, "images/Modelisation.jpg" ); +} + +function TPcreerModele() +{ + // Créer une boîte. Au retour, 'glob.box' contient une structure avec + // les VBOs pour les sommets, normales, coordonnées de texture et connectivité. + glob.box = makeRepere( gl ); + + // Initialiser les attributs pour les sommets, les normales et les coordonnées de texture + // (dans le même ordre qu'à l'appel à simpleSetup() dans la fonction TPinitialiser()) + gl.enableVertexAttribArray( 0 ); + gl.bindBuffer( gl.ARRAY_BUFFER, glob.box.vertexObject ); + gl.vertexAttribPointer( 0, 3, gl.FLOAT, false, 0, 0 ); + + gl.enableVertexAttribArray( 1 ); + gl.bindBuffer( gl.ARRAY_BUFFER, glob.box.colorObject ); + gl.vertexAttribPointer( 1, 3, gl.FLOAT, false, 0, 0 ); + + // Lier le tableau de connectivité + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, glob.box.indexObject ); +} + +function TPinitialiser() +{ + // Initialiser webgl + var gl = initWebGL( "tp-canevas" ); // L'identificateur du canevas + if (!gl) return; + + glob.program = simpleSetup( gl, + // Les identificateurs des deux nuanceurs + "nuanceurSommets", "nuanceurFragments", + // Les attributs utilisés par les nuanceurs (donnés dans le même ordre que leur indice) + [ "vPosition", "vColor" ], + // La couleur de fond et la profondeur + [ 0, 0, 0.3, 1 ], 100); + + // Les angles courants de rotation + glob.angleRotX = 0, glob.angleRotY = 0, glob.angleRotZ = 0; + // Les incréments à chaque affichage + glob.incrRotX = 0.2; glob.incrRotY = 0.3; glob.incrRotZ = 0.4; + + // Créer les matrices nécessaires et assigner les assigner dans le programme + glob.modelViewMatrix = new J3DIMatrix4(); + // glob.u_modelViewMatrixLoc n'est pas utile car modelViewMatrix n'est pas utilisé dans les nuanceurs + glob.mvpMatrix = new J3DIMatrix4(); + glob.u_modelViewProjMatrixLoc = gl.getUniformLocation( glob.program, "u_modelViewProjMatrix" ); + glob.normalMatrix = new J3DIMatrix4(); + glob.u_normalMatrixLoc = gl.getUniformLocation( glob.program, "u_normalMatrix" ); + + // terminer l'initialisation + TPchargerTextures(); + TPcreerModele(); + + glob.mouseDown = false; + glob.positionSourisX = null; + glob.positionSourisY = null; + glob.canevas.onmousedown = handleMouseDown; + glob.canevas.onmouseup = handleMouseUp; + glob.canevas.onmousemove = handleMouseMove; + + // Initialiser les variables uniformes pour les nuanceurs + gl.uniform3f( gl.getUniformLocation( glob.program, "lightDir" ), 0, 0, 1 ); + gl.uniform1i( gl.getUniformLocation( glob.program, "laTexture" ), 0 ); + gl.uniform2f( gl.getUniformLocation( glob.program, "positionSouris" ), glob.positionSourisX, glob.positionSourisY ); + + return gl; +} + +function TPafficherModele( gl, num ) +{ + // Incrémenter les angles de rotation + glob.angleRotX += glob.incrRotX; if ( glob.angleRotX >= 360.0 ) glob.angleRotX -= 360.0; + glob.angleRotY += glob.incrRotY; if ( glob.angleRotY >= 360.0 ) glob.angleRotY -= 360.0; + glob.angleRotZ += glob.incrRotZ; if ( glob.angleRotZ >= 360.0 ) glob.angleRotZ -= 360.0; + + // Construire la matrice de modélisation + glob.modelViewMatrix.makeIdentity(); + glob.modelViewMatrix.lookat( 0, 0, 7, 0, 0, 0, 0, 1, 0 ); + var sens = ( num == 1 ) ? +1 : -1; + glob.modelViewMatrix.rotate( sens*glob.angleRotX, 1.0, 0.0, 0.0 ); + glob.modelViewMatrix.rotate( sens*glob.angleRotY, 0.0, 1.0, 0.0 ); + glob.modelViewMatrix.rotate( sens*glob.angleRotZ, 0.0, 0.0, 1.0 ); + + // Construire le produit de matrice "modélisation * projection" et la passer aux nuanceurs + glob.mvpMatrix.load( glob.perspectiveMatrix ); + glob.mvpMatrix.multiply( glob.modelViewMatrix ); + glob.mvpMatrix.setUniform( gl, glob.u_modelViewProjMatrixLoc, false ); + + // Construire la matrice de transformation des normales et la passer aux nuanceurs + glob.normalMatrix.load( glob.modelViewMatrix ); + glob.normalMatrix.invert(); + glob.normalMatrix.transpose(); + glob.normalMatrix.setUniform( gl, glob.u_normalMatrixLoc, false ); + + // Activer la texture à utiliser + gl.bindTexture( gl.TEXTURE_2D, ( num == 1 ) ? glob.texture1 : glob.texture2 ); + + gl.uniform2f( gl.getUniformLocation( glob.program, "positionSouris" ), glob.positionSourisX, glob.canevas.height-glob.positionSourisY ); + + // Tracer le cube + gl.drawElements( gl.LINES, glob.box.numIndices, gl.UNSIGNED_BYTE, 0 ); +} + +function TPafficherScene(gl) +{ + glob.perspectiveMatrix = new J3DIMatrix4(); + glob.perspectiveMatrix.perspective( 40, glob.canevas.width / glob.canevas.height, 1, 10 ); + + // Effacer le canevas + gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); + + // Définir la clôture 1 + gl.viewport( 0, 0, glob.canevas.width, glob.canevas.height ); + // Tracer le modèle + TPafficherModele( gl, 1 ); +} + +var requestId; +function TPdebut() +{ + glob.canevas = document.getElementById('tp-canevas'); + //glob.canevas = WebGLDebugUtils.makeLostContextSimulatingCanvas(c); + // indiquer de perdre le contexte afin de tester + //glob.canevas.loseContextInNCalls(15); + glob.canevas.addEventListener('webglcontextlost', handleContextLost, false); + glob.canevas.addEventListener('webglcontextrestored', handleContextRestored, false); + + var gl = TPinitialiser(); + if ( !gl ) return; + + var displayFunc = function() + { + TPafficherScene(gl); + requestId = window.requestAnimFrame( displayFunc, glob.canevas ); + }; + displayFunc(); + + function handleContextLost(e) + { + e.preventDefault(); + clearLoadingImages(); + if ( requestId !== undefined ) + { + window.cancelAnimFrame(requestId); + requestId = undefined; + } + } + + function handleContextRestored() + { + TPinitialiser(); + displayFunc(); + } +} diff --git a/tp.css b/tp.css new file mode 100644 index 0000000..8a45107 --- /dev/null +++ b/tp.css @@ -0,0 +1,16 @@ +.text { + position: absolute; + top: 20px; + left: 20px; + font-size: 1em; + color: orange; +} + +a:link { color: yellow; } +a:hover { color: #bbaa00; } + +.canevas { + height: 600px; + width: 900px; + border: 5px solid orange; +} diff --git a/webgl-debug.js b/webgl-debug.js new file mode 100644 index 0000000..26a8872 --- /dev/null +++ b/webgl-debug.js @@ -0,0 +1,875 @@ +/* +** 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. +*/ + +// Various functions for helping debug WebGL apps. + +WebGLDebugUtils = function() { + +/** + * Wrapped logging function. + * @param {string} msg Message to log. + */ +var log = function(msg) { + if (window.console && window.console.log) { + window.console.log(msg); + } +}; + +/** + * Wrapped error logging function. + * @param {string} msg Message to log. + */ +var error = function(msg) { + if (window.console && window.console.error) { + window.console.error(msg); + } else { + log(msg); + } +}; + +/** + * Which arguements are enums. + * @type {!Object.} + */ +var glValidEnumContexts = { + + // Generic setters and getters + + 'enable': { 0:true }, + 'disable': { 0:true }, + 'getParameter': { 0:true }, + + // Rendering + + 'drawArrays': { 0:true }, + 'drawElements': { 0:true, 2:true }, + + // Shaders + + 'createShader': { 0:true }, + 'getShaderParameter': { 1:true }, + 'getProgramParameter': { 1:true }, + + // Vertex attributes + + 'getVertexAttrib': { 1:true }, + 'vertexAttribPointer': { 2:true }, + + // Textures + + 'bindTexture': { 0:true }, + 'activeTexture': { 0:true }, + 'getTexParameter': { 0:true, 1:true }, + 'texParameterf': { 0:true, 1:true }, + 'texParameteri': { 0:true, 1:true, 2:true }, + 'texImage2D': { 0:true, 2:true, 6:true, 7:true }, + 'texSubImage2D': { 0:true, 6:true, 7:true }, + 'copyTexImage2D': { 0:true, 2:true }, + 'copyTexSubImage2D': { 0:true }, + 'generateMipmap': { 0:true }, + + // Buffer objects + + 'bindBuffer': { 0:true }, + 'bufferData': { 0:true, 2:true }, + 'bufferSubData': { 0:true }, + 'getBufferParameter': { 0:true, 1:true }, + + // Renderbuffers and framebuffers + + 'pixelStorei': { 0:true, 1:true }, + 'readPixels': { 4:true, 5:true }, + 'bindRenderbuffer': { 0:true }, + 'bindFramebuffer': { 0:true }, + 'checkFramebufferStatus': { 0:true }, + 'framebufferRenderbuffer': { 0:true, 1:true, 2:true }, + 'framebufferTexture2D': { 0:true, 1:true, 2:true }, + 'getFramebufferAttachmentParameter': { 0:true, 1:true, 2:true }, + 'getRenderbufferParameter': { 0:true, 1:true }, + 'renderbufferStorage': { 0:true, 1:true }, + + // Frame buffer operations (clear, blend, depth test, stencil) + + 'clear': { 0:true }, + 'depthFunc': { 0:true }, + 'blendFunc': { 0:true, 1:true }, + 'blendFuncSeparate': { 0:true, 1:true, 2:true, 3:true }, + 'blendEquation': { 0:true }, + 'blendEquationSeparate': { 0:true, 1:true }, + 'stencilFunc': { 0:true }, + 'stencilFuncSeparate': { 0:true, 1:true }, + 'stencilMaskSeparate': { 0:true }, + 'stencilOp': { 0:true, 1:true, 2:true }, + 'stencilOpSeparate': { 0:true, 1:true, 2:true, 3:true }, + + // Culling + + 'cullFace': { 0:true }, + 'frontFace': { 0:true }, +}; + +/** + * Map of numbers to names. + * @type {Object} + */ +var glEnums = null; + +/** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebGL context. If + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ +function init(ctx) { + if (glEnums == null) { + glEnums = { }; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'number') { + glEnums[ctx[propertyName]] = propertyName; + } + } + } +} + +/** + * Checks the utils have been initialized. + */ +function checkInit() { + if (glEnums == null) { + throw 'WebGLDebugUtils.init(ctx) not called'; + } +} + +/** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ +function mightBeEnum(value) { + checkInit(); + return (glEnums[value] !== undefined); +} + +/** + * Gets an string version of an WebGL enum. + * + * Example: + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ +function glEnumToString(value) { + checkInit(); + var name = glEnums[value]; + return (name !== undefined) ? name : + ("*UNKNOWN WebGL ENUM (0x" + value.toString(16) + ")"); +} + +/** + * Returns the string version of a WebGL argument. + * Attempts to convert enum arguments to strings. + * @param {string} functionName the name of the WebGL function. + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ +function glFunctionArgToString(functionName, argumentIndex, value) { + var funcInfo = glValidEnumContexts[functionName]; + if (funcInfo !== undefined) { + if (funcInfo[argumentIndex]) { + return glEnumToString(value); + } + } + if (value === null) { + return "null"; + } else if (value === undefined) { + return "undefined"; + } else { + return value.toString(); + } +} + +/** + * Converts the arguments of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * @param {string} functionName the name of the WebGL function. + * @param {number} args The arguments. + * @return {string} The arguments as a string. + */ +function glFunctionArgsToString(functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + for (var ii = 0; ii < args.length; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, ii, args[ii]); + } + return argStr; +}; + + +function makePropertyWrapper(wrapper, original, propertyName) { + //log("wrap prop: " + propertyName); + wrapper.__defineGetter__(propertyName, function() { + return original[propertyName]; + }); + // TODO(gmane): this needs to handle properties that take more than + // one value? + wrapper.__defineSetter__(propertyName, function(value) { + //log("set: " + propertyName); + original[propertyName] = value; + }); +} + +// Makes a function that calls a function on another object. +function makeFunctionWrapper(original, functionName) { + //log("wrap fn: " + functionName); + var f = original[functionName]; + return function() { + //log("call: " + functionName); + var result = f.apply(original, arguments); + return result; + }; +} + +/** + * Given a WebGL context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not gl.NO_ERROR. + * + * @param {!WebGLRenderingContext} ctx The webgl context to + * wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc + * The function to call when gl.getError returns an + * error. If not specified the default function calls + * console.log with a message. + * @param {!function(funcName, args): void} opt_onFunc The + * function to call when each webgl function is called. + * You can use this to log all calls for example. + */ +function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc) { + init(ctx); + opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + for (var ii = 0; ii < args.length; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, ii, args[ii]); + } + error("WebGL error "+ glEnumToString(err) + " in "+ functionName + + "(" + argStr + ")"); + }; + + // Holds booleans for each GL error so after we get the error ourselves + // we can still return it to the client app. + var glErrorShadow = { }; + + // Makes a function that calls a WebGL function and then calls getError. + function makeErrorWrapper(ctx, functionName) { + return function() { + if (opt_onFunc) { + opt_onFunc(functionName, arguments); + } + var result = ctx[functionName].apply(ctx, arguments); + var err = ctx.getError(); + if (err != 0) { + glErrorShadow[err] = true; + opt_onErrorFunc(err, functionName, arguments); + } + return result; + }; + } + + // Make a an object that has a copy of every property of the WebGL context + // but wraps all functions. + var wrapper = {}; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); + } else { + makePropertyWrapper(wrapper, ctx, propertyName); + } + } + + // Override the getError function with one that returns our saved results. + wrapper.getError = function() { + for (var err in glErrorShadow) { + if (glErrorShadow.hasOwnProperty(err)) { + if (glErrorShadow[err]) { + glErrorShadow[err] = false; + return err; + } + } + } + return ctx.NO_ERROR; + }; + + return wrapper; +} + +function resetToInitialState(ctx) { + var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); + var tmp = ctx.createBuffer(); + ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); + for (var ii = 0; ii < numAttribs; ++ii) { + ctx.disableVertexAttribArray(ii); + ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); + ctx.vertexAttrib1f(ii, 0); + } + ctx.deleteBuffer(tmp); + + var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); + for (var ii = 0; ii < numTextureUnits; ++ii) { + ctx.activeTexture(ctx.TEXTURE0 + ii); + ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); + ctx.bindTexture(ctx.TEXTURE_2D, null); + } + + ctx.activeTexture(ctx.TEXTURE0); + ctx.useProgram(null); + ctx.bindBuffer(ctx.ARRAY_BUFFER, null); + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); + ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); + ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); + ctx.disable(ctx.BLEND); + ctx.disable(ctx.CULL_FACE); + ctx.disable(ctx.DEPTH_TEST); + ctx.disable(ctx.DITHER); + ctx.disable(ctx.SCISSOR_TEST); + ctx.blendColor(0, 0, 0, 0); + ctx.blendEquation(ctx.FUNC_ADD); + ctx.blendFunc(ctx.ONE, ctx.ZERO); + ctx.clearColor(0, 0, 0, 0); + ctx.clearDepth(1); + ctx.clearStencil(-1); + ctx.colorMask(true, true, true, true); + ctx.cullFace(ctx.BACK); + ctx.depthFunc(ctx.LESS); + ctx.depthMask(true); + ctx.depthRange(0, 1); + ctx.frontFace(ctx.CCW); + ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); + ctx.lineWidth(1); + ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); + ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + // TODO: Delete this IF. + if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { + ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); + } + ctx.polygonOffset(0, 0); + ctx.sampleCoverage(1, false); + ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); + ctx.stencilMask(0xFFFFFFFF); + ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); + ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); + + // TODO: This should NOT be needed but Firefox fails with 'hint' + while(ctx.getError()); +} + +function makeLostContextSimulatingCanvas(canvas) { + var unwrappedContext_; + var wrappedContext_; + var onLost_ = []; + var onRestored_ = []; + var wrappedContext_ = {}; + var contextId_ = 1; + var contextLost_ = false; + var resourceId_ = 0; + var resourceDb_ = []; + var numCallsToLoseContext_ = 0; + var numCalls_ = 0; + var canRestore_ = false; + var restoreTimeout_ = 0; + + // Holds booleans for each GL error so can simulate errors. + var glErrorShadow_ = { }; + + canvas.getContext = function(f) { + return function() { + var ctx = f.apply(canvas, arguments); + // Did we get a context and is it a WebGL context? + if (ctx instanceof WebGLRenderingContext) { + if (ctx != unwrappedContext_) { + if (unwrappedContext_) { + throw "got different context" + } + unwrappedContext_ = ctx; + wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); + } + return wrappedContext_; + } + return ctx; + } + }(canvas.getContext); + + function wrapEvent(listener) { + if (typeof(listener) == "function") { + return listener; + } else { + return function(info) { + listener.handleEvent(info); + } + } + } + + var addOnContextLostListener = function(listener) { + onLost_.push(wrapEvent(listener)); + }; + + var addOnContextRestoredListener = function(listener) { + onRestored_.push(wrapEvent(listener)); + }; + + + function wrapAddEventListener(canvas) { + var f = canvas.addEventListener; + canvas.addEventListener = function(type, listener, bubble) { + switch (type) { + case 'webglcontextlost': + addOnContextLostListener(listener); + break; + case 'webglcontextrestored': + addOnContextRestoredListener(listener); + break; + default: + f.apply(canvas, arguments); + } + }; + } + + wrapAddEventListener(canvas); + + canvas.loseContext = function() { + if (!contextLost_) { + contextLost_ = true; + numCallsToLoseContext_ = 0; + ++contextId_; + while (unwrappedContext_.getError()); + clearErrors(); + glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; + var event = makeWebGLContextEvent("context lost"); + var callbacks = onLost_.slice(); + setTimeout(function() { + //log("numCallbacks:" + callbacks.length); + for (var ii = 0; ii < callbacks.length; ++ii) { + //log("calling callback:" + ii); + callbacks[ii](event); + } + if (restoreTimeout_ >= 0) { + setTimeout(function() { + canvas.restoreContext(); + }, restoreTimeout_); + } + }, 0); + } + }; + + canvas.restoreContext = function() { + if (contextLost_) { + if (onRestored_.length) { + setTimeout(function() { + if (!canRestore_) { + throw "can not restore. webglcontestlost listener did not call event.preventDefault"; + } + freeResources(); + resetToInitialState(unwrappedContext_); + contextLost_ = false; + numCalls_ = 0; + canRestore_ = false; + var callbacks = onRestored_.slice(); + var event = makeWebGLContextEvent("context restored"); + for (var ii = 0; ii < callbacks.length; ++ii) { + callbacks[ii](event); + } + }, 0); + } + } + }; + + canvas.loseContextInNCalls = function(numCalls) { + if (contextLost_) { + throw "You can not ask a lost contet to be lost"; + } + numCallsToLoseContext_ = numCalls_ + numCalls; + }; + + canvas.getNumCalls = function() { + return numCalls_; + }; + + canvas.setRestoreTimeout = function(timeout) { + restoreTimeout_ = timeout; + }; + + function isWebGLObject(obj) { + //return false; + return (obj instanceof WebGLBuffer || + obj instanceof WebGLFramebuffer || + obj instanceof WebGLProgram || + obj instanceof WebGLRenderbuffer || + obj instanceof WebGLShader || + obj instanceof WebGLTexture); + } + + function checkResources(args) { + for (var ii = 0; ii < args.length; ++ii) { + var arg = args[ii]; + if (isWebGLObject(arg)) { + return arg.__webglDebugContextLostId__ == contextId_; + } + } + return true; + } + + function clearErrors() { + var k = Object.keys(glErrorShadow_); + for (var ii = 0; ii < k.length; ++ii) { + delete glErrorShadow_[k]; + } + } + + function loseContextIfTime() { + ++numCalls_; + if (!contextLost_) { + if (numCallsToLoseContext_ == numCalls_) { + canvas.loseContext(); + } + } + } + + // Makes a function that simulates WebGL when out of context. + function makeLostContextFunctionWrapper(ctx, functionName) { + var f = ctx[functionName]; + return function() { + // log("calling:" + functionName); + // Only call the functions if the context is not lost. + loseContextIfTime(); + if (!contextLost_) { + //if (!checkResources(arguments)) { + // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; + // return; + //} + var result = f.apply(ctx, arguments); + return result; + } + }; + } + + function freeResources() { + for (var ii = 0; ii < resourceDb_.length; ++ii) { + var resource = resourceDb_[ii]; + if (resource instanceof WebGLBuffer) { + unwrappedContext_.deleteBuffer(resource); + } else if (resource instanceof WebGLFramebuffer) { + unwrappedContext_.deleteFramebuffer(resource); + } else if (resource instanceof WebGLProgram) { + unwrappedContext_.deleteProgram(resource); + } else if (resource instanceof WebGLRenderbuffer) { + unwrappedContext_.deleteRenderbuffer(resource); + } else if (resource instanceof WebGLShader) { + unwrappedContext_.deleteShader(resource); + } else if (resource instanceof WebGLTexture) { + unwrappedContext_.deleteTexture(resource); + } + } + } + + function makeWebGLContextEvent(statusMessage) { + return { + statusMessage: statusMessage, + preventDefault: function() { + canRestore_ = true; + } + }; + } + + return canvas; + + function makeLostContextSimulatingContext(ctx) { + // copy all functions and properties to wrapper + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrappedContext_[propertyName] = makeLostContextFunctionWrapper( + ctx, propertyName); + } else { + makePropertyWrapper(wrappedContext_, ctx, propertyName); + } + } + + // Wrap a few functions specially. + wrappedContext_.getError = function() { + loseContextIfTime(); + if (!contextLost_) { + var err; + while (err = unwrappedContext_.getError()) { + glErrorShadow_[err] = true; + } + } + for (var err in glErrorShadow_) { + if (glErrorShadow_[err]) { + delete glErrorShadow_[err]; + return err; + } + } + return wrappedContext_.NO_ERROR; + }; + + var creationFunctions = [ + "createBuffer", + "createFramebuffer", + "createProgram", + "createRenderbuffer", + "createShader", + "createTexture" + ]; + for (var ii = 0; ii < creationFunctions.length; ++ii) { + var functionName = creationFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + var obj = f.apply(ctx, arguments); + obj.__webglDebugContextLostId__ = contextId_; + resourceDb_.push(obj); + return obj; + }; + }(ctx[functionName]); + } + + var functionsThatShouldReturnNull = [ + "getActiveAttrib", + "getActiveUniform", + "getBufferParameter", + "getContextAttributes", + "getAttachedShaders", + "getFramebufferAttachmentParameter", + "getParameter", + "getProgramParameter", + "getProgramInfoLog", + "getRenderbufferParameter", + "getShaderParameter", + "getShaderInfoLog", + "getShaderSource", + "getTexParameter", + "getUniform", + "getUniformLocation", + "getVertexAttrib" + ]; + for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { + var functionName = functionsThatShouldReturnNull[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + var isFunctions = [ + "isBuffer", + "isEnabled", + "isFramebuffer", + "isProgram", + "isRenderbuffer", + "isShader", + "isTexture" + ]; + for (var ii = 0; ii < isFunctions.length; ++ii) { + var functionName = isFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return false; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + wrappedContext_.checkFramebufferStatus = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.checkFramebufferStatus); + + wrappedContext_.getAttribLocation = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return -1; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getAttribLocation); + + wrappedContext_.getVertexAttribOffset = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return 0; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getVertexAttribOffset); + + wrappedContext_.isContextLost = function() { + return contextLost_; + }; + + return wrappedContext_; + } +} + +return { + /** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebGL context. If + } + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ + 'init': init, + + /** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ + 'mightBeEnum': mightBeEnum, + + /** + * Gets an string version of an WebGL enum. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ + 'glEnumToString': glEnumToString, + + /** + * Converts the argument of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 0, gl.TEXTURE_2D); + * + * would return 'TEXTURE_2D' + * + * @param {string} functionName the name of the WebGL function. + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ + 'glFunctionArgToString': glFunctionArgToString, + + /** + * Converts the arguments of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * @param {string} functionName the name of the WebGL function. + * @param {number} args The arguments. + * @return {string} The arguments as a string. + */ + 'glFunctionArgsToString': glFunctionArgsToString, + + /** + * Given a WebGL context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not NO_ERROR. + * + * You can supply your own function if you want. For example, if you'd like + * an exception thrown on any GL error you could do this + * + * function throwOnGLError(err, funcName, args) { + * throw WebGLDebugUtils.glEnumToString(err) + + * " was caused by call to " + funcName; + * }; + * + * ctx = WebGLDebugUtils.makeDebugContext( + * canvas.getContext("webgl"), throwOnGLError); + * + * @param {!WebGLRenderingContext} ctx The webgl context to wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc The function + * to call when gl.getError returns an error. If not specified the default + * function calls console.log with a message. + * @param {!function(funcName, args): void} opt_onFunc The + * function to call when each webgl function is called. You + * can use this to log all calls for example. + */ + 'makeDebugContext': makeDebugContext, + + /** + * Given a canvas element returns a wrapped canvas element that will + * simulate lost context. The canvas returned adds the following functions. + * + * loseContext: + * simulates a lost context event. + * + * restoreContext: + * simulates the context being restored. + * + * lostContextInNCalls: + * loses the context after N gl calls. + * + * getNumCalls: + * tells you how many gl calls there have been so far. + * + * setRestoreTimeout: + * sets the number of milliseconds until the context is restored + * after it has been lost. Defaults to 0. Pass -1 to prevent + * automatic restoring. + * + * @param {!Canvas} canvas The canvas element to wrap. + */ + 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, + + /** + * Resets a context to the initial state. + * @param {!WebGLRenderingContext} ctx The webgl context to + * reset. + */ + 'resetToInitialState': resetToInitialState +}; + +}(); + diff --git a/webgl-utils.js b/webgl-utils.js new file mode 100644 index 0000000..4f14f7f --- /dev/null +++ b/webgl-utils.js @@ -0,0 +1,176 @@ +/* + * Copyright 2010, Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 THE COPYRIGHT + * OWNER 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. + */ + + +/** + * @fileoverview This file contains functions every webgl program will need + * a version of one way or another. + * + * Instead of setting up a context manually it is recommended to + * use. This will check for success or failure. On failure it + * will attempt to present an approriate message to the user. + * + * gl = WebGLUtils.setupWebGL(canvas); + * + * For animated WebGL apps use of setTimeout or setInterval are + * discouraged. It is recommended you structure your rendering + * loop like this. + * + * function render() { + * window.requestAnimFrame(render, canvas); + * + * // do rendering + * ... + * } + * render(); + * + * This will call your rendering function up to the refresh rate + * of your display but will stop rendering if your app is not + * visible. + */ + +WebGLUtils = function() { + +/** + * Creates the HTLM for a failure message + * @param {string} canvasContainerId id of container of th + * canvas. + * @return {string} The html. + */ +var makeFailHTML = function(msg) { + return '' + + '' + + '
' + + '
' + + '
' + msg + '
' + + '
' + + '
'; +}; + +/** + * Mesasge for getting a webgl browser + * @type {string} + */ +var GET_A_WEBGL_BROWSER = '' + + 'This page requires a browser that supports WebGL.
' + + 'Click here to upgrade your browser.'; + +/** + * Mesasge for need better hardware + * @type {string} + */ +var OTHER_PROBLEM = '' + + "It doesn't appear your computer can support WebGL.
" + + 'Click here for more information.'; + +/** + * Creates a webgl context. If creation fails it will + * change the contents of the container of the + * tag to an error message with the correct links for WebGL. + * @param {Element} canvas. The canvas element to create a + * context from. + * @param {WebGLContextCreationAttirbutes} opt_attribs Any + * creation attributes you want to pass in. + * @return {WebGLRenderingContext} The created context. + */ +var setupWebGL = function(canvas, opt_attribs) { + function showLink(str) { + var container = canvas.parentNode; + if (container) { + container.innerHTML = makeFailHTML(str); + } + }; + + if (!window.WebGLRenderingContext) { + showLink(GET_A_WEBGL_BROWSER); + return null; + } + + var context = create3DContext(canvas, opt_attribs); + if (!context) { + showLink(OTHER_PROBLEM); + } + return context; +}; + +/** + * Creates a webgl context. + * @param {!Canvas} canvas The canvas tag to get context + * from. If one is not passed in one will be created. + * @return {!WebGLContext} The created context. + */ +var create3DContext = function(canvas, opt_attribs) { + var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; + var context = null; + for (var ii = 0; ii < names.length; ++ii) { + try { + context = canvas.getContext(names[ii], opt_attribs); + } catch(e) {} + if (context) { + break; + } + } + return context; +} + +return { + create3DContext: create3DContext, + setupWebGL: setupWebGL +}; +}(); + +/** + * Provides requestAnimationFrame in a cross browser way. + */ +window.requestAnimFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { + return window.setTimeout(callback, 1000/60); + }; +})(); + +/** + * Provides cancelAnimationFrame in a cross browser way. + */ +window.cancelAnimFrame = (function() { + return window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.mozCancelAnimationFrame || + window.oCancelAnimationFrame || + window.msCancelAnimationFrame || + window.clearTimeout; +})(); + +