commit
063ceb5293
7 changed files with 3059 additions and 0 deletions
@ -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 <script> element containing the shader source string.
|
||||
|
// Load this shader and return the WebGLShader object corresponding to it.
|
||||
|
//
|
||||
|
function loadShader(ctx, shaderId) |
||||
|
{ |
||||
|
var shaderScript = document.getElementById(shaderId); |
||||
|
if (!shaderScript) { |
||||
|
log("*** Error: shader script '"+shaderId+"' not found"); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
if (shaderScript.type == "x-shader/x-vertex") |
||||
|
var shaderType = ctx.VERTEX_SHADER; |
||||
|
else if (shaderScript.type == "x-shader/x-fragment") |
||||
|
var shaderType = ctx.FRAGMENT_SHADER; |
||||
|
else { |
||||
|
log("*** Error: shader script '"+shaderId+"' of undefined type '"+shaderScript.type+"'"); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// Create the shader object
|
||||
|
var shader = ctx.createShader(shaderType); |
||||
|
|
||||
|
// Load the shader source
|
||||
|
ctx.shaderSource(shader, shaderScript.text); |
||||
|
|
||||
|
// Compile the shader
|
||||
|
ctx.compileShader(shader); |
||||
|
|
||||
|
// Check the compile status
|
||||
|
var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS); |
||||
|
if (!compiled && !ctx.isContextLost()) { |
||||
|
// Something went wrong during compilation; get the error
|
||||
|
var error = ctx.getShaderInfoLog(shader); |
||||
|
log("*** Error compiling shader '"+shaderId+"':"+error); |
||||
|
ctx.deleteShader(shader); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return shader; |
||||
|
} |
||||
|
|
||||
|
//
|
||||
|
// makeBox
|
||||
|
//
|
||||
|
// Create a box 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 makeBox(ctx) |
||||
|
{ |
||||
|
// box
|
||||
|
// v6----- v5
|
||||
|
// /| /|
|
||||
|
// v1------v0|
|
||||
|
// | | | |
|
||||
|
// | |v7---|-|v4
|
||||
|
// |/ |/
|
||||
|
// v2------v3
|
||||
|
//
|
||||
|
// vertex coords array
|
||||
|
var vertices = new Float32Array( |
||||
|
[ 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0-v1-v2-v3 front
|
||||
|
1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0-v3-v4-v5 right
|
||||
|
1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0-v5-v6-v1 top
|
||||
|
-1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1, 1, // v1-v6-v7-v2 left
|
||||
|
-1,-1,-1, 1,-1,-1, 1,-1, 1, -1,-1, 1, // v7-v4-v3-v2 bottom
|
||||
|
1,-1,-1, -1,-1,-1, -1, 1,-1, 1, 1,-1 ] // v4-v7-v6-v5 back
|
||||
|
); |
||||
|
|
||||
|
// normal array
|
||||
|
var normals = new Float32Array( |
||||
|
[ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2-v3 front
|
||||
|
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
|
||||
|
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6-v1 top
|
||||
|
-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7-v2 left
|
||||
|
0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, // v7-v4-v3-v2 bottom
|
||||
|
0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1 ] // v4-v7-v6-v5 back
|
||||
|
); |
||||
|
|
||||
|
|
||||
|
// texCoord array
|
||||
|
var texCoords = new Float32Array( |
||||
|
[ 1, 1, 0, 1, 0, 0, 1, 0, // v0-v1-v2-v3 front
|
||||
|
0, 1, 0, 0, 1, 0, 1, 1, // v0-v3-v4-v5 right
|
||||
|
1, 0, 1, 1, 0, 1, 0, 0, // v0-v5-v6-v1 top
|
||||
|
1, 1, 0, 1, 0, 0, 1, 0, // v1-v6-v7-v2 left
|
||||
|
0, 0, 1, 0, 1, 1, 0, 1, // v7-v4-v3-v2 bottom
|
||||
|
0, 0, 1, 0, 1, 1, 0, 1 ] // v4-v7-v6-v5 back
|
||||
|
); |
||||
|
|
||||
|
// index array
|
||||
|
var indices = new Uint8Array( |
||||
|
[ 0, 1, 2, 0, 2, 3, // front
|
||||
|
4, 5, 6, 4, 6, 7, // right
|
||||
|
8, 9,10, 8,10,11, // top
|
||||
|
12,13,14, 12,14,15, // left
|
||||
|
16,17,18, 16,18,19, // bottom
|
||||
|
20,21,22, 20,22,23 ] // back
|
||||
|
); |
||||
|
|
||||
|
var retval = { }; |
||||
|
|
||||
|
retval.normalObject = ctx.createBuffer(); |
||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject); |
||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW); |
||||
|
|
||||
|
retval.texCoordObject = ctx.createBuffer(); |
||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject); |
||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, texCoords, ctx.STATIC_DRAW); |
||||
|
|
||||
|
retval.vertexObject = ctx.createBuffer(); |
||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject); |
||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, vertices, 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; |
||||
|
} |
||||
|
|
||||
|
//
|
||||
|
// makeSphere
|
||||
|
//
|
||||
|
// Create a sphere with the passed number of latitude and longitude bands and the passed radius.
|
||||
|
// Sphere has 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 makeSphere(ctx, radius, lats, longs) |
||||
|
{ |
||||
|
var geometryData = [ ]; |
||||
|
var normalData = [ ]; |
||||
|
var texCoordData = [ ]; |
||||
|
var indexData = [ ]; |
||||
|
|
||||
|
for (var latNumber = 0; latNumber <= lats; ++latNumber) { |
||||
|
for (var longNumber = 0; longNumber <= longs; ++longNumber) { |
||||
|
var theta = latNumber * Math.PI / lats; |
||||
|
var phi = longNumber * 2 * Math.PI / longs; |
||||
|
var sinTheta = Math.sin(theta); |
||||
|
var sinPhi = Math.sin(phi); |
||||
|
var cosTheta = Math.cos(theta); |
||||
|
var cosPhi = Math.cos(phi); |
||||
|
|
||||
|
var x = cosPhi * sinTheta; |
||||
|
var y = cosTheta; |
||||
|
var z = sinPhi * sinTheta; |
||||
|
var u = 1-(longNumber/longs); |
||||
|
var v = latNumber/lats; |
||||
|
|
||||
|
normalData.push(x); |
||||
|
normalData.push(y); |
||||
|
normalData.push(z); |
||||
|
texCoordData.push(u); |
||||
|
texCoordData.push(v); |
||||
|
geometryData.push(radius * x); |
||||
|
geometryData.push(radius * y); |
||||
|
geometryData.push(radius * z); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (var latNumber = 0; latNumber < lats; ++latNumber) { |
||||
|
for (var longNumber = 0; longNumber < longs; ++longNumber) { |
||||
|
var first = (latNumber * (longs+1)) + longNumber; |
||||
|
var second = first + longs + 1; |
||||
|
indexData.push(first); |
||||
|
indexData.push(second); |
||||
|
indexData.push(first+1); |
||||
|
|
||||
|
indexData.push(second); |
||||
|
indexData.push(second+1); |
||||
|
indexData.push(first+1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var retval = { }; |
||||
|
|
||||
|
retval.normalObject = ctx.createBuffer(); |
||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject); |
||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(normalData), ctx.STATIC_DRAW); |
||||
|
|
||||
|
retval.texCoordObject = ctx.createBuffer(); |
||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject); |
||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(texCoordData), ctx.STATIC_DRAW); |
||||
|
|
||||
|
retval.vertexObject = ctx.createBuffer(); |
||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject); |
||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(geometryData), ctx.STATIC_DRAW); |
||||
|
|
||||
|
retval.numIndices = indexData.length; |
||||
|
retval.indexObject = ctx.createBuffer(); |
||||
|
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject); |
||||
|
ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), ctx.STREAM_DRAW); |
||||
|
|
||||
|
return retval; |
||||
|
} |
||||
|
|
||||
|
// Array of Objects curently loading
|
||||
|
var g_loadingObjects = []; |
||||
|
|
||||
|
// Clears all the Objects currently loading.
|
||||
|
// This is used to handle context lost events.
|
||||
|
function clearLoadingObjects() { |
||||
|
for (var ii = 0; ii < g_loadingObjects.length; ++ii) { |
||||
|
g_loadingObjects[ii].onreadystatechange = undefined; |
||||
|
} |
||||
|
g_loadingObjects = []; |
||||
|
} |
||||
|
|
||||
|
//
|
||||
|
// loadObj
|
||||
|
//
|
||||
|
// Load a .obj file from the passed URL. Return an object with a 'loaded' property set to false.
|
||||
|
// When the object load is complete, the 'loaded' property becomes true and the following
|
||||
|
// properties are set:
|
||||
|
//
|
||||
|
// 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 loadObj(ctx, url) |
||||
|
{ |
||||
|
var obj = { loaded : false }; |
||||
|
obj.ctx = ctx; |
||||
|
var req = new XMLHttpRequest(); |
||||
|
req.obj = obj; |
||||
|
g_loadingObjects.push(req); |
||||
|
req.onreadystatechange = function () { processLoadObj(req) }; |
||||
|
req.open("GET", url, true); |
||||
|
req.send(null); |
||||
|
return obj; |
||||
|
} |
||||
|
|
||||
|
function processLoadObj(req) |
||||
|
{ |
||||
|
log("req="+req) |
||||
|
// only if req shows "complete"
|
||||
|
if (req.readyState == 4) { |
||||
|
g_loadingObjects.splice(g_loadingObjects.indexOf(req), 1); |
||||
|
doLoadObj(req.obj, req.responseText); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function doLoadObj(obj, text) |
||||
|
{ |
||||
|
vertexArray = [ ]; |
||||
|
normalArray = [ ]; |
||||
|
textureArray = [ ]; |
||||
|
indexArray = [ ]; |
||||
|
|
||||
|
var vertex = [ ]; |
||||
|
var normal = [ ]; |
||||
|
var texture = [ ]; |
||||
|
var facemap = { }; |
||||
|
var index = 0; |
||||
|
|
||||
|
// This is a map which associates a range of indices with a name
|
||||
|
// The name comes from the 'g' tag (of the form "g NAME"). Indices
|
||||
|
// are part of one group until another 'g' tag is seen. If any indices
|
||||
|
// come before a 'g' tag, it is given the group name "_unnamed"
|
||||
|
// 'group' is an object whose property names are the group name and
|
||||
|
// whose value is a 2 element array with [<first index>, <num indices>]
|
||||
|
var groups = { }; |
||||
|
var currentGroup = [-1, 0]; |
||||
|
groups["_unnamed"] = currentGroup; |
||||
|
|
||||
|
var lines = text.split("\n"); |
||||
|
for (var lineIndex in lines) { |
||||
|
var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); |
||||
|
|
||||
|
// ignore comments
|
||||
|
if (line[0] == "#") |
||||
|
continue; |
||||
|
|
||||
|
var array = line.split(" "); |
||||
|
if (array[0] == "g") { |
||||
|
// new group
|
||||
|
currentGroup = [indexArray.length, 0]; |
||||
|
groups[array[1]] = currentGroup; |
||||
|
} |
||||
|
else if (array[0] == "v") { |
||||
|
// vertex
|
||||
|
vertex.push(parseFloat(array[1])); |
||||
|
vertex.push(parseFloat(array[2])); |
||||
|
vertex.push(parseFloat(array[3])); |
||||
|
} |
||||
|
else if (array[0] == "vt") { |
||||
|
// normal
|
||||
|
texture.push(parseFloat(array[1])); |
||||
|
texture.push(parseFloat(array[2])); |
||||
|
} |
||||
|
else if (array[0] == "vn") { |
||||
|
// normal
|
||||
|
normal.push(parseFloat(array[1])); |
||||
|
normal.push(parseFloat(array[2])); |
||||
|
normal.push(parseFloat(array[3])); |
||||
|
} |
||||
|
else if (array[0] == "f") { |
||||
|
// face
|
||||
|
if (array.length != 4) { |
||||
|
log("*** Error: face '"+line+"' not handled"); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
for (var i = 1; i < 4; ++i) { |
||||
|
if (!(array[i] in facemap)) { |
||||
|
// add a new entry to the map and arrays
|
||||
|
var f = array[i].split("/"); |
||||
|
var vtx, nor, tex; |
||||
|
|
||||
|
if (f.length == 1) { |
||||
|
vtx = parseInt(f[0]) - 1; |
||||
|
nor = vtx; |
||||
|
tex = vtx; |
||||
|
} |
||||
|
else if (f.length = 3) { |
||||
|
vtx = parseInt(f[0]) - 1; |
||||
|
tex = parseInt(f[1]) - 1; |
||||
|
nor = parseInt(f[2]) - 1; |
||||
|
} |
||||
|
else { |
||||
|
obj.ctx.console.log("*** Error: did not understand face '"+array[i]+"'"); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// do the vertices
|
||||
|
var x = 0; |
||||
|
var y = 0; |
||||
|
var z = 0; |
||||
|
if (vtx * 3 + 2 < vertex.length) { |
||||
|
x = vertex[vtx*3]; |
||||
|
y = vertex[vtx*3+1]; |
||||
|
z = vertex[vtx*3+2]; |
||||
|
} |
||||
|
vertexArray.push(x); |
||||
|
vertexArray.push(y); |
||||
|
vertexArray.push(z); |
||||
|
|
||||
|
// do the textures
|
||||
|
x = 0; |
||||
|
y = 0; |
||||
|
if (tex * 2 + 1 < texture.length) { |
||||
|
x = texture[tex*2]; |
||||
|
y = texture[tex*2+1]; |
||||
|
} |
||||
|
textureArray.push(x); |
||||
|
textureArray.push(y); |
||||
|
|
||||
|
// do the normals
|
||||
|
x = 0; |
||||
|
y = 0; |
||||
|
z = 1; |
||||
|
if (nor * 3 + 2 < normal.length) { |
||||
|
x = normal[nor*3]; |
||||
|
y = normal[nor*3+1]; |
||||
|
z = normal[nor*3+2]; |
||||
|
} |
||||
|
normalArray.push(x); |
||||
|
normalArray.push(y); |
||||
|
normalArray.push(z); |
||||
|
|
||||
|
facemap[array[i]] = index++; |
||||
|
} |
||||
|
|
||||
|
indexArray.push(facemap[array[i]]); |
||||
|
currentGroup[1]++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// set the VBOs
|
||||
|
obj.normalObject = obj.ctx.createBuffer(); |
||||
|
obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.normalObject); |
||||
|
obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(normalArray), obj.ctx.STATIC_DRAW); |
||||
|
|
||||
|
obj.texCoordObject = obj.ctx.createBuffer(); |
||||
|
obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.texCoordObject); |
||||
|
obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(textureArray), obj.ctx.STATIC_DRAW); |
||||
|
|
||||
|
obj.vertexObject = obj.ctx.createBuffer(); |
||||
|
obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.vertexObject); |
||||
|
obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(vertexArray), obj.ctx.STATIC_DRAW); |
||||
|
|
||||
|
obj.numIndices = indexArray.length; |
||||
|
obj.indexObject = obj.ctx.createBuffer(); |
||||
|
obj.ctx.bindBuffer(obj.ctx.ELEMENT_ARRAY_BUFFER, obj.indexObject); |
||||
|
obj.ctx.bufferData(obj.ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), obj.ctx.STREAM_DRAW); |
||||
|
|
||||
|
obj.groups = groups; |
||||
|
|
||||
|
obj.loaded = true; |
||||
|
} |
||||
|
|
||||
|
// Array of images curently loading
|
||||
|
var g_loadingImages = []; |
||||
|
|
||||
|
// Clears all the images currently loading.
|
||||
|
// This is used to handle context lost events.
|
||||
|
function clearLoadingImages() { |
||||
|
for (var ii = 0; ii < g_loadingImages.length; ++ii) { |
||||
|
g_loadingImages[ii].onload = undefined; |
||||
|
} |
||||
|
g_loadingImages = []; |
||||
|
} |
||||
|
|
||||
|
//
|
||||
|
// loadImageTexture
|
||||
|
//
|
||||
|
// Load the image at the passed url, place it in a new WebGLTexture object and return the WebGLTexture.
|
||||
|
//
|
||||
|
function loadImageTexture(ctx, url) |
||||
|
{ |
||||
|
var texture = ctx.createTexture(); |
||||
|
var image = new Image(); |
||||
|
g_loadingImages.push(image); |
||||
|
image.onload = function() { doLoadImageTexture(ctx, image, texture) } |
||||
|
image.src = url; |
||||
|
return texture; |
||||
|
} |
||||
|
|
||||
|
function doLoadImageTexture(ctx, image, texture) |
||||
|
{ |
||||
|
g_loadingImages.splice(g_loadingImages.indexOf(image), 1); |
||||
|
ctx.bindTexture(ctx.TEXTURE_2D, texture); |
||||
|
ctx.texImage2D(ctx.TEXTURE_2D, 0, ctx.RGBA, ctx.RGBA, ctx.UNSIGNED_BYTE, image); |
||||
|
ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR); |
||||
|
ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR); |
||||
|
ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE); |
||||
|
ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE); |
||||
|
//ctx.generateMipmap(ctx.TEXTURE_2D)
|
||||
|
ctx.bindTexture(ctx.TEXTURE_2D, null); |
||||
|
} |
||||
|
|
||||
|
//
|
||||
|
// Framerate object
|
||||
|
//
|
||||
|
// This object keeps track of framerate and displays it as the innerHTML text of the
|
||||
|
// HTML element with the passed id. Once created you call snapshot at the end
|
||||
|
// of every rendering cycle. Every 500ms the framerate is updated in the HTML element.
|
||||
|
//
|
||||
|
Framerate = function(id) |
||||
|
{ |
||||
|
this.numFramerates = 10; |
||||
|
this.framerateUpdateInterval = 500; |
||||
|
this.id = id; |
||||
|
|
||||
|
this.renderTime = -1; |
||||
|
this.framerates = [ ]; |
||||
|
self = this; |
||||
|
var fr = function() { self.updateFramerate() } |
||||
|
setInterval(fr, this.framerateUpdateInterval); |
||||
|
} |
||||
|
|
||||
|
Framerate.prototype.updateFramerate = function() |
||||
|
{ |
||||
|
var tot = 0; |
||||
|
for (var i = 0; i < this.framerates.length; ++i) |
||||
|
tot += this.framerates[i]; |
||||
|
|
||||
|
var framerate = tot / this.framerates.length; |
||||
|
framerate = Math.round(framerate); |
||||
|
document.getElementById(this.id).innerHTML = "Framerate:"+framerate+"fps"; |
||||
|
} |
||||
|
|
||||
|
Framerate.prototype.snapshot = function() |
||||
|
{ |
||||
|
if (this.renderTime < 0) |
||||
|
this.renderTime = new Date().getTime(); |
||||
|
else { |
||||
|
var newTime = new Date().getTime(); |
||||
|
var t = newTime - this.renderTime; |
||||
|
if (t == 0) |
||||
|
return; |
||||
|
var framerate = 1000/t; |
||||
|
this.framerates.push(framerate); |
||||
|
while (this.framerates.length > this.numFramerates) |
||||
|
this.framerates.shift(); |
||||
|
this.renderTime = newTime; |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,70 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
||||
|
<title>TP - WebGL</title> |
||||
|
<link rel="stylesheet" href="tp.css" type="text/css" media="all"> |
||||
|
|
||||
|
<script type="text/javascript" src="webgl-utils.js"></script> |
||||
|
<script type="text/javascript" src="webgl-debug.js"></script> |
||||
|
<script type="text/javascript" src="J3DI.js"></script> |
||||
|
<script type="text/javascript" src="J3DIMath.js"></script> |
||||
|
<script type="text/javascript" src="main.js"></script> |
||||
|
|
||||
|
<script id="nuanceurSommets" type="x-shader/x-vertex"> |
||||
|
uniform mat4 u_modelViewProjMatrix; |
||||
|
uniform mat4 u_normalMatrix; |
||||
|
uniform vec3 lightDir; |
||||
|
|
||||
|
attribute vec4 vPosition; |
||||
|
attribute vec4 vColor; |
||||
|
|
||||
|
varying float v_NdotL; |
||||
|
varying vec4 v_color; |
||||
|
|
||||
|
void main() |
||||
|
{ |
||||
|
// assigner la position du sommet |
||||
|
gl_Position = u_modelViewProjMatrix * vPosition; |
||||
|
|
||||
|
// assigner les coordonnées de texture |
||||
|
v_color = vColor; |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<script id="nuanceurFragments" type="x-shader/x-fragment"> |
||||
|
// informer du degré de précision qu'on veut dans les calculs |
||||
|
// (Plus à ce sujet: http://stackoverflow.com/questions/5366416/in-opengl-es-2-0-glsl-where-do-you-need-precision-specifiers/6336285#6336285 ) |
||||
|
precision mediump float; |
||||
|
|
||||
|
uniform sampler2D laTexture; |
||||
|
uniform vec2 positionSouris; |
||||
|
|
||||
|
varying float v_NdotL; |
||||
|
varying vec4 v_color; |
||||
|
|
||||
|
void main() |
||||
|
{ |
||||
|
// obtenir la couleur de la texture |
||||
|
vec4 coul = v_color; |
||||
|
if ( length( positionSouris - gl_FragCoord.xy ) < 50.0 ) |
||||
|
{ |
||||
|
vec4 coulCercle = vec4( 1.0, 0.0, 0.0, 0.7 ); |
||||
|
coul = coul * (1.0-coulCercle.a) + coulCercle * coulCercle.a; |
||||
|
// coul = coul * coulCercle; // réponse aussi acceptée |
||||
|
} |
||||
|
gl_FragColor = vec4( coul.rgb, coul.a ); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
</head> |
||||
|
|
||||
|
<body onload="TPdebut()"> |
||||
|
<canvas class="canevas" id="tp-canevas" height="600" width="900"> |
||||
|
Si vous voyez ceci, votre navigateur ne supporte pas webgl. |
||||
|
</canvas> |
||||
|
|
||||
|
<div class="text">Les <a href="http://www.khronos.org/registry/webgl/specs/latest/1.0/">spécifications de Webgl</a> sont<br>remplies d'informations intéressantes.</div> |
||||
|
|
||||
|
</body> |
||||
|
</html> |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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.<number, string>} |
||||
|
*/ |
||||
|
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 |
||||
|
}; |
||||
|
|
||||
|
}(); |
||||
|
|
||||
@ -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 '' + |
||||
|
'<table style="background-color: #8CE; width: 100%; height: 100%;"><tr>' + |
||||
|
'<td align="center">' + |
||||
|
'<div style="display: table-cell; vertical-align: middle;">' + |
||||
|
'<div style="">' + msg + '</div>' + |
||||
|
'</div>' + |
||||
|
'</td></tr></table>'; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Mesasge for getting a webgl browser |
||||
|
* @type {string} |
||||
|
*/ |
||||
|
var GET_A_WEBGL_BROWSER = '' + |
||||
|
'This page requires a browser that supports WebGL.<br/>' + |
||||
|
'<a href="http://get.webgl.org">Click here to upgrade your browser.</a>'; |
||||
|
|
||||
|
/** |
||||
|
* Mesasge for need better hardware |
||||
|
* @type {string} |
||||
|
*/ |
||||
|
var OTHER_PROBLEM = '' + |
||||
|
"It doesn't appear your computer can support WebGL.<br/>" + |
||||
|
'<a href="http://get.webgl.org/troubleshooting/">Click here for more information.</a>'; |
||||
|
|
||||
|
/** |
||||
|
* Creates a webgl context. If creation fails it will |
||||
|
* change the contents of the container of the <canvas> |
||||
|
* 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; |
||||
|
})(); |
||||
|
|
||||
|
|
||||
Loading…
Reference in new issue