UNPKG

@pixelshift.ai/gl-conformance

Version:
1,282 lines (1,141 loc) 34.5 kB
/* Utilities for the OpenGL ES 2.0 HTML Canvas context */ /* ** 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. */ function loadTexture(gl, elem, mipmaps) { var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, elem); if (mipmaps != false) gl.generateMipmap(gl.TEXTURE_2D); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); if (mipmaps) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); else gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); return tex; } function getShader(gl, id) { var shaderScript = document.getElementById(id); if (!shaderScript) { throw(new Error("No shader element with id: "+id)); } var str = shaderScript.text; var shader; if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); } else { throw(new Error("Unknown shader type "+shaderScript.type)); } gl.shaderSource(shader, str); gl.compileShader(shader); if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) != 1) { var ilog = gl.getShaderInfoLog(shader); gl.deleteShader(shader); throw(new Error("Failed to compile shader "+shaderScript.id + ", Shader info log: " + ilog)); } return shader; } function loadShaderArray(gl, shaders) { var id = gl.createProgram(); var shaderObjs = []; for (var i=0; i<shaders.length; ++i) { try { var sh = getShader(gl, shaders[i]); shaderObjs.push(sh); gl.attachShader(id, sh); } catch (e) { var pr = {program: id, shaders: shaderObjs}; deleteShader(gl, pr); throw (e); } } var prog = {program: id, shaders: shaderObjs}; gl.linkProgram(id); gl.validateProgram(id); if (gl.getProgramParameter(id, gl.LINK_STATUS) != 1) { deleteShader(gl,prog); throw(new Error("Failed to link shader")); } if (gl.getProgramParameter(id, gl.VALIDATE_STATUS) != 1) { deleteShader(gl,prog); throw(new Error("Failed to validate shader")); } return prog; } function loadShader(gl) { var sh = []; for (var i=1; i<arguments.length; ++i) sh.push(arguments[i]); return loadShaderArray(gl, sh); } function deleteShader(gl, sh) { gl.useProgram(null); sh.shaders.forEach(function(s){ gl.detachShader(sh.program, s); gl.deleteShader(s); }); gl.deleteProgram(sh.program); } function getGLErrorAsString(ctx, err) { if (err === ctx.NO_ERROR) { return "NO_ERROR"; } for (var name in ctx) { if (ctx[name] === err) { return name; } } return err.toString(); } function checkError(gl, msg) { var e = gl.getError(); if (e != gl.NO_ERROR) { log("Error " + getGLErrorAsString(gl, e) + " at " + msg); } return e; } function throwError(gl, msg) { var e = gl.getError(); if (e != 0) { throw(new Error("Error " + getGLErrorAsString(gl, e) + " at " + msg)); } } Math.cot = function(z) { return 1.0 / Math.tan(z); } /* Matrix utilities, using the OpenGL element order where the last 4 elements are the translation column. Uses flat arrays as matrices for performance. Most operations have in-place variants to avoid allocating temporary matrices. Naming logic: Matrix.method operates on a 4x4 Matrix and returns a new Matrix. Matrix.method3x3 operates on a 3x3 Matrix and returns a new Matrix. Not all operations have a 3x3 version (as 3x3 is usually only used for the normal matrix: Matrix.transpose3x3(Matrix.inverseTo3x3(mat4x4))) Matrix.method[3x3]InPlace(args, target) stores its result in the target matrix. Matrix.scale([sx, sy, sz]) -- non-uniform scale by vector Matrix.scale1(s) -- uniform scale by scalar Matrix.scale3(sx, sy, sz) -- non-uniform scale by scalars Ditto for translate. */ Matrix = { identity : [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ], newIdentity : function() { return [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]; }, newIdentity3x3 : function() { return [ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ]; }, copyMatrix : function(src, dst) { for (var i=0; i<16; i++) dst[i] = src[i]; return dst; }, to3x3 : function(m) { return [ m[0], m[1], m[2], m[4], m[5], m[6], m[8], m[9], m[10] ]; }, // orthonormal matrix inverse inverseON : function(m) { var n = this.transpose4x4(m); var t = [m[12], m[13], m[14]]; n[3] = n[7] = n[11] = 0; n[12] = -Vec3.dot([n[0], n[4], n[8]], t); n[13] = -Vec3.dot([n[1], n[5], n[9]], t); n[14] = -Vec3.dot([n[2], n[6], n[10]], t); return n; }, inverseTo3x3 : function(m) { return this.inverse4x4to3x3InPlace(m, this.newIdentity3x3()); }, inverseTo3x3InPlace : function(m,n) { var a11 = m[10]*m[5]-m[6]*m[9], a21 = -m[10]*m[1]+m[2]*m[9], a31 = m[6]*m[1]-m[2]*m[5], a12 = -m[10]*m[4]+m[6]*m[8], a22 = m[10]*m[0]-m[2]*m[8], a32 = -m[6]*m[0]+m[2]*m[4], a13 = m[9]*m[4]-m[5]*m[8], a23 = -m[9]*m[0]+m[1]*m[8], a33 = m[5]*m[0]-m[1]*m[4]; var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13); if (det == 0) // no inverse return [1,0,0,0,1,0,0,0,1]; var idet = 1 / det; n[0] = idet*a11; n[1] = idet*a21; n[2] = idet*a31; n[3] = idet*a12; n[4] = idet*a22; n[5] = idet*a32; n[6] = idet*a13; n[7] = idet*a23; n[8] = idet*a33; return n; }, inverse3x3 : function(m) { return this.inverse3x3InPlace(m, this.newIdentity3x3()); }, inverse3x3InPlace : function(m,n) { var a11 = m[8]*m[4]-m[5]*m[7], a21 = -m[8]*m[1]+m[2]*m[7], a31 = m[5]*m[1]-m[2]*m[4], a12 = -m[8]*m[3]+m[5]*m[6], a22 = m[8]*m[0]-m[2]*m[6], a32 = -m[5]*m[0]+m[2]*m[3], a13 = m[7]*m[4]-m[4]*m[8], a23 = -m[7]*m[0]+m[1]*m[6], a33 = m[4]*m[0]-m[1]*m[3]; var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13); if (det == 0) // no inverse return [1,0,0,0,1,0,0,0,1]; var idet = 1 / det; n[0] = idet*a11; n[1] = idet*a21; n[2] = idet*a31; n[3] = idet*a12; n[4] = idet*a22; n[5] = idet*a32; n[6] = idet*a13; n[7] = idet*a23; n[8] = idet*a33; return n; }, frustum : function (left, right, bottom, top, znear, zfar) { var X = 2*znear/(right-left); var Y = 2*znear/(top-bottom); var A = (right+left)/(right-left); var B = (top+bottom)/(top-bottom); var C = -(zfar+znear)/(zfar-znear); var D = -2*zfar*znear/(zfar-znear); return [ X, 0, 0, 0, 0, Y, 0, 0, A, B, C, -1, 0, 0, D, 0 ]; }, perspective : function (fovy, aspect, znear, zfar) { var ymax = znear * Math.tan(fovy * Math.PI / 360.0); var ymin = -ymax; var xmin = ymin * aspect; var xmax = ymax * aspect; return this.frustum(xmin, xmax, ymin, ymax, znear, zfar); }, mul4x4 : function (a,b) { return this.mul4x4InPlace(a,b,this.newIdentity()); }, mul4x4InPlace : function (a, b, c) { c[0] = b[0] * a[0] + b[0+1] * a[4] + b[0+2] * a[8] + b[0+3] * a[12]; c[0+1] = b[0] * a[1] + b[0+1] * a[5] + b[0+2] * a[9] + b[0+3] * a[13]; c[0+2] = b[0] * a[2] + b[0+1] * a[6] + b[0+2] * a[10] + b[0+3] * a[14]; c[0+3] = b[0] * a[3] + b[0+1] * a[7] + b[0+2] * a[11] + b[0+3] * a[15]; c[4] = b[4] * a[0] + b[4+1] * a[4] + b[4+2] * a[8] + b[4+3] * a[12]; c[4+1] = b[4] * a[1] + b[4+1] * a[5] + b[4+2] * a[9] + b[4+3] * a[13]; c[4+2] = b[4] * a[2] + b[4+1] * a[6] + b[4+2] * a[10] + b[4+3] * a[14]; c[4+3] = b[4] * a[3] + b[4+1] * a[7] + b[4+2] * a[11] + b[4+3] * a[15]; c[8] = b[8] * a[0] + b[8+1] * a[4] + b[8+2] * a[8] + b[8+3] * a[12]; c[8+1] = b[8] * a[1] + b[8+1] * a[5] + b[8+2] * a[9] + b[8+3] * a[13]; c[8+2] = b[8] * a[2] + b[8+1] * a[6] + b[8+2] * a[10] + b[8+3] * a[14]; c[8+3] = b[8] * a[3] + b[8+1] * a[7] + b[8+2] * a[11] + b[8+3] * a[15]; c[12] = b[12] * a[0] + b[12+1] * a[4] + b[12+2] * a[8] + b[12+3] * a[12]; c[12+1] = b[12] * a[1] + b[12+1] * a[5] + b[12+2] * a[9] + b[12+3] * a[13]; c[12+2] = b[12] * a[2] + b[12+1] * a[6] + b[12+2] * a[10] + b[12+3] * a[14]; c[12+3] = b[12] * a[3] + b[12+1] * a[7] + b[12+2] * a[11] + b[12+3] * a[15]; return c; }, mulv4 : function (a, v) { c = new Array(4); for (var i=0; i<4; ++i) { var x = 0; for (var k=0; k<4; ++k) x += v[k] * a[k*4+i]; c[i] = x; } return c; }, rotate : function (angle, axis) { axis = Vec3.normalize(axis); var x=axis[0], y=axis[1], z=axis[2]; var c = Math.cos(angle); var c1 = 1-c; var s = Math.sin(angle); return [ x*x*c1+c, y*x*c1+z*s, z*x*c1-y*s, 0, x*y*c1-z*s, y*y*c1+c, y*z*c1+x*s, 0, x*z*c1+y*s, y*z*c1-x*s, z*z*c1+c, 0, 0,0,0,1 ]; }, rotateInPlace : function(angle, axis, m) { axis = Vec3.normalize(axis); var x=axis[0], y=axis[1], z=axis[2]; var c = Math.cos(angle); var c1 = 1-c; var s = Math.sin(angle); var tmpMatrix = this.tmpMatrix; var tmpMatrix2 = this.tmpMatrix2; tmpMatrix[0] = x*x*c1+c; tmpMatrix[1] = y*x*c1+z*s; tmpMatrix[2] = z*x*c1-y*s; tmpMatrix[3] = 0; tmpMatrix[4] = x*y*c1-z*s; tmpMatrix[5] = y*y*c1+c; tmpMatrix[6] = y*z*c1+x*s; tmpMatrix[7] = 0; tmpMatrix[8] = x*z*c1+y*s; tmpMatrix[9] = y*z*c1-x*s; tmpMatrix[10] = z*z*c1+c; tmpMatrix[11] = 0; tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1; this.copyMatrix(m, tmpMatrix2); return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m); }, scale : function(v) { return [ v[0], 0, 0, 0, 0, v[1], 0, 0, 0, 0, v[2], 0, 0, 0, 0, 1 ]; }, scale3 : function(x,y,z) { return [ x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 ]; }, scale1 : function(s) { return [ s, 0, 0, 0, 0, s, 0, 0, 0, 0, s, 0, 0, 0, 0, 1 ]; }, scale3InPlace : function(x, y, z, m) { var tmpMatrix = this.tmpMatrix; var tmpMatrix2 = this.tmpMatrix2; tmpMatrix[0] = x; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0; tmpMatrix[4] = 0; tmpMatrix[5] = y; tmpMatrix[6] = 0; tmpMatrix[7] = 0; tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = z; tmpMatrix[11] = 0; tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1; this.copyMatrix(m, tmpMatrix2); return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m); }, scale1InPlace : function(s, m) { return this.scale3InPlace(s, s, s, m); }, scaleInPlace : function(s, m) { return this.scale3InPlace(s[0],s[1],s[2],m); }, translate3 : function(x,y,z) { return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 ]; }, translate : function(v) { return this.translate3(v[0], v[1], v[2]); }, tmpMatrix : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], tmpMatrix2 : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], translate3InPlace : function(x,y,z,m) { var tmpMatrix = this.tmpMatrix; var tmpMatrix2 = this.tmpMatrix2; tmpMatrix[0] = 1; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0; tmpMatrix[4] = 0; tmpMatrix[5] = 1; tmpMatrix[6] = 0; tmpMatrix[7] = 0; tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = 1; tmpMatrix[11] = 0; tmpMatrix[12] = x; tmpMatrix[13] = y; tmpMatrix[14] = z; tmpMatrix[15] = 1; this.copyMatrix(m, tmpMatrix2); return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m); }, translateInPlace : function(v,m){ return this.translate3InPlace(v[0], v[1], v[2], m); }, lookAt : function (eye, center, up) { var z = Vec3.direction(eye, center); var x = Vec3.normalizeInPlace(Vec3.cross(up, z)); var y = Vec3.normalizeInPlace(Vec3.cross(z, x)); var m = [ x[0], y[0], z[0], 0, x[1], y[1], z[1], 0, x[2], y[2], z[2], 0, 0, 0, 0, 1 ]; var t = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -eye[0], -eye[1], -eye[2], 1 ]; return this.mul4x4(m,t); }, transpose4x4 : function(m) { return [ m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15] ]; }, transpose4x4InPlace : function(m) { var tmp = 0.0; tmp = m[1]; m[1] = m[4]; m[4] = tmp; tmp = m[2]; m[2] = m[8]; m[8] = tmp; tmp = m[3]; m[3] = m[12]; m[12] = tmp; tmp = m[6]; m[6] = m[9]; m[9] = tmp; tmp = m[7]; m[7] = m[13]; m[13] = tmp; tmp = m[11]; m[11] = m[14]; m[14] = tmp; return m; }, transpose3x3 : function(m) { return [ m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8] ]; }, transpose3x3InPlace : function(m) { var tmp = 0.0; tmp = m[1]; m[1] = m[3]; m[3] = tmp; tmp = m[2]; m[2] = m[6]; m[6] = tmp; tmp = m[5]; m[5] = m[7]; m[7] = tmp; return m; }, } Vec3 = { make : function() { return [0,0,0]; }, copy : function(v) { return [v[0],v[1],v[2]]; }, add : function (u,v) { return [u[0]+v[0], u[1]+v[1], u[2]+v[2]]; }, sub : function (u,v) { return [u[0]-v[0], u[1]-v[1], u[2]-v[2]]; }, negate : function (u) { return [-u[0], -u[1], -u[2]]; }, direction : function (u,v) { return this.normalizeInPlace(this.sub(u,v)); }, normalizeInPlace : function(v) { var imag = 1.0 / Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); v[0] *= imag; v[1] *= imag; v[2] *= imag; return v; }, normalize : function(v) { return this.normalizeInPlace(this.copy(v)); }, scale : function(f, v) { return [f*v[0], f*v[1], f*v[2]]; }, dot : function(u,v) { return u[0]*v[0] + u[1]*v[1] + u[2]*v[2]; }, inner : function(u,v) { return [u[0]*v[0], u[1]*v[1], u[2]*v[2]]; }, cross : function(u,v) { return [ u[1]*v[2] - u[2]*v[1], u[2]*v[0] - u[0]*v[2], u[0]*v[1] - u[1]*v[0] ]; } } Shader = function(gl){ this.gl = gl; this.shaders = []; this.uniformLocations = {}; this.attribLocations = {}; for (var i=1; i<arguments.length; i++) { this.shaders.push(arguments[i]); } } Shader.prototype = { id : null, gl : null, compiled : false, shader : null, shaders : [], destroy : function() { if (this.shader != null) deleteShader(this.gl, this.shader); }, compile : function() { this.shader = loadShaderArray(this.gl, this.shaders); }, use : function() { if (this.shader == null) this.compile(); this.gl.useProgram(this.shader.program); }, uniform1fv : function(name, value) { var loc = this.uniform(name); this.gl.uniform1fv(loc, value); }, uniform2fv : function(name, value) { var loc = this.uniform(name); this.gl.uniform2fv(loc, value); }, uniform3fv : function(name, value) { var loc = this.uniform(name); this.gl.uniform3fv(loc, value); }, uniform4fv : function(name, value) { var loc = this.uniform(name); this.gl.uniform4fv(loc, value); }, uniform1f : function(name, value) { var loc = this.uniform(name); this.gl.uniform1f(loc, value); }, uniform2f : function(name, v1,v2) { var loc = this.uniform(name); this.gl.uniform2f(loc, v1,v2); }, uniform3f : function(name, v1,v2,v3) { var loc = this.uniform(name); this.gl.uniform3f(loc, v1,v2,v3); }, uniform4f : function(name, v1,v2,v3,v4) { var loc = this.uniform(name); this.gl.uniform4f(loc, v1, v2, v3, v4); }, uniform1iv : function(name, value) { var loc = this.uniform(name); this.gl.uniform1iv(loc, value); }, uniform2iv : function(name, value) { var loc = this.uniform(name); this.gl.uniform2iv(loc, value); }, uniform3iv : function(name, value) { var loc = this.uniform(name); this.gl.uniform3iv(loc, value); }, uniform4iv : function(name, value) { var loc = this.uniform(name); this.gl.uniform4iv(loc, value); }, uniform1i : function(name, value) { var loc = this.uniform(name); this.gl.uniform1i(loc, value); }, uniform2i : function(name, v1,v2) { var loc = this.uniform(name); this.gl.uniform2i(loc, v1,v2); }, uniform3i : function(name, v1,v2,v3) { var loc = this.uniform(name); this.gl.uniform3i(loc, v1,v2,v3); }, uniform4i : function(name, v1,v2,v3,v4) { var loc = this.uniform(name); this.gl.uniform4i(loc, v1, v2, v3, v4); }, uniformMatrix4fv : function(name, value) { var loc = this.uniform(name); this.gl.uniformMatrix4fv(loc, false, value); }, uniformMatrix3fv : function(name, value) { var loc = this.uniform(name); this.gl.uniformMatrix3fv(loc, false, value); }, uniformMatrix2fv : function(name, value) { var loc = this.uniform(name); this.gl.uniformMatrix2fv(loc, false, value); }, attrib : function(name) { if (this.attribLocations[name] == null) { var loc = this.gl.getAttribLocation(this.shader.program, name); this.attribLocations[name] = loc; } return this.attribLocations[name]; }, uniform : function(name) { if (this.uniformLocations[name] == null) { var loc = this.gl.getUniformLocation(this.shader.program, name); this.uniformLocations[name] = loc; } return this.uniformLocations[name]; } } Filter = function(gl, shader) { Shader.apply(this, arguments); } Filter.prototype = new Shader(); Filter.prototype.apply = function(init) { this.use(); var va = this.attrib("Vertex"); var ta = this.attrib("Tex"); var vbo = Quad.getCachedVBO(this.gl); if (init) init(this); vbo.draw(va, null, ta); } VBO = function(gl) { this.gl = gl; this.data = []; this.elementsVBO = null; for (var i=1; i<arguments.length; i++) { if (arguments[i].elements) this.elements = arguments[i]; else this.data.push(arguments[i]); } } VBO.prototype = { initialized : false, length : 0, vbos : null, type : 'TRIANGLES', elementsVBO : null, elements : null, setData : function() { this.destroy(); this.data = []; for (var i=0; i<arguments.length; i++) { if (arguments[i].elements) this.elements = arguments[i]; else this.data.push(arguments[i]); } }, destroy : function() { if (this.vbos != null) for (var i=0; i<this.vbos.length; i++) this.gl.deleteBuffer(this.vbos[i]); if (this.elementsVBO != null) this.gl.deleteBuffer(this.elementsVBO); this.length = this.elementsLength = 0; this.vbos = this.elementsVBO = null; this.initialized = false; }, init : function() { this.destroy(); var gl = this.gl; gl.getError(); var vbos = []; var length = 0; for (var i=0; i<this.data.length; i++) vbos.push(gl.createBuffer()); if (this.elements != null) this.elementsVBO = gl.createBuffer(); try { throwError(gl, "genBuffers"); for (var i = 0; i<this.data.length; i++) { var d = this.data[i]; var dlen = Math.floor(d.data.length / d.size); if (i == 0 || dlen < length) length = dlen; if (!d.floatArray) d.floatArray = new Float32Array(d.data); gl.bindBuffer(gl.ARRAY_BUFFER, vbos[i]); throwError(gl, "bindBuffer"); gl.bufferData(gl.ARRAY_BUFFER, d.floatArray, gl.STATIC_DRAW); throwError(gl, "bufferData"); } if (this.elementsVBO != null) { var d = this.elements; this.elementsLength = d.data.length; this.elementsType = d.type == gl.UNSIGNED_BYTE ? d.type : gl.UNSIGNED_SHORT; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO); throwError(gl, "bindBuffer ELEMENT_ARRAY_BUFFER"); if (this.elementsType == gl.UNSIGNED_SHORT && !d.ushortArray) { d.ushortArray = new Uint16Array(d.data); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ushortArray, gl.STATIC_DRAW); } else if (this.elementsType == gl.UNSIGNED_BYTE && !d.ubyteArray) { d.ubyteArray = new Uint8Array(d.data); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ubyteArray, gl.STATIC_DRAW); } throwError(gl, "bufferData ELEMENT_ARRAY_BUFFER"); } } catch(e) { for (var i=0; i<vbos.length; i++) gl.deleteBuffer(vbos[i]); throw(e); } gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); this.length = length; this.vbos = vbos; this.initialized = true; }, use : function() { if (!this.initialized) this.init(); var gl = this.gl; for (var i=0; i<arguments.length; i++) { if (arguments[i] == null || arguments[i] == -1) continue; gl.bindBuffer(gl.ARRAY_BUFFER, this.vbos[i]); gl.vertexAttribPointer(arguments[i], this.data[i].size, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(arguments[i]); } if (this.elementsVBO != null) { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO); } }, draw : function() { var args = []; this.use.apply(this, arguments); var gl = this.gl; if (this.elementsVBO != null) { gl.drawElements(gl[this.type], this.elementsLength, this.elementsType, 0); } else { gl.drawArrays(gl[this.type], 0, this.length); } } } FBO = function(gl, width, height, use_depth) { this.gl = gl; this.width = width; this.height = height; if (use_depth != null) this.useDepth = use_depth; } FBO.prototype = { initialized : false, useDepth : true, fbo : null, rbo : null, texture : null, destroy : function() { if (this.fbo) this.gl.deleteFramebuffer(this.fbo); if (this.rbo) this.gl.deleteRenderbuffer(this.rbo); if (this.texture) this.gl.deleteTexture(this.texture); }, init : function() { var gl = this.gl; var w = this.width, h = this.height; var fbo = this.fbo != null ? this.fbo : gl.createFramebuffer(); var rb; gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); checkError(gl, "FBO.init bindFramebuffer"); if (this.useDepth) { rb = this.rbo != null ? this.rbo : gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, rb); checkError(gl, "FBO.init bindRenderbuffer"); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h); checkError(gl, "FBO.init renderbufferStorage"); } var tex = this.texture != null ? this.texture : gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); try { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); } catch (e) { // argh, no null texture support var tmp = this.getTempCanvas(w,h); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tmp); } gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); checkError(gl, "FBO.init tex"); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0); checkError(gl, "FBO.init bind tex"); if (this.useDepth) { gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb); checkError(gl, "FBO.init bind depth buffer"); } var fbstat = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (fbstat != gl.FRAMEBUFFER_COMPLETE) { var glv; for (var v in gl) { try { glv = gl[v]; } catch (e) { glv = null; } if (glv == fbstat) { fbstat = v; break; }} log("Framebuffer status: " + fbstat); } checkError(gl, "FBO.init check fbo"); this.fbo = fbo; this.rbo = rb; this.texture = tex; this.initialized = true; }, getTempCanvas : function(w, h) { if (!FBO.tempCanvas) { FBO.tempCanvas = document.createElement('canvas'); } FBO.tempCanvas.width = w; FBO.tempCanvas.height = h; return FBO.tempCanvas; }, use : function() { if (!this.initialized) this.init(); this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo); } } function GLError(err, msg, fileName, lineNumber) { this.message = msg; this.glError = err; } GLError.prototype = new Error(); function makeGLErrorWrapper(gl, fname) { return (function() { try { var rv = gl[fname].apply(gl, arguments); var err = gl.getError(); if (err != gl.NO_ERROR) { throw(new GLError( err, "GL error "+getGLErrorAsString(gl, err)+" in "+fname)); } return rv; } catch (e) { if (e.glError !== undefined) { throw e; } throw(new Error("Threw " + e.name + " in " + fname + "\n" + e.message + "\n" + arguments.callee.caller)); } }); } function wrapGLContext(gl) { var wrap = {}; for (var i in gl) { try { if (typeof gl[i] == 'function') { wrap[i] = makeGLErrorWrapper(gl, i); } else { wrap[i] = gl[i]; } } catch (e) { // log("wrapGLContext: Error accessing " + i); } } wrap.getError = function(){ return gl.getError(); }; return wrap; } function getGLContext(canvas) { return canvas.getContext(GL_CONTEXT_ID, {antialias: false}); } // Assert that f generates a specific GL error. function assertGLError(gl, err, name, f) { if (f == null) { f = name; name = null; } var r = false; var glErr = 0; try { f(); } catch(e) { r=true; glErr = e.glError; } if (glErr !== err) { if (glErr === undefined) { testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f); } else { testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) + " actual: " + getGLErrorAsString(gl, glErr), name, f); } return false; } return true; } // Assert that f generates a GL error from a list. function assertGLErrorIn(gl, expectedErrorList, name, f) { if (f == null) { f = name; name = null; } var actualError = 0; try { f(); } catch(e) { if ('glError' in e) { actualError = e.glError; } else { testFailed("assertGLError: UNEXPCETED EXCEPTION", name, f); return false; } } var expectedErrorStrList = []; var expectedErrorSet = {}; for (var i in expectedErrorList) { var cur = expectedErrorList[i]; expectedErrorSet[cur] = true; expectedErrorStrList.push(getGLErrorAsString(gl, cur)); } var expectedErrorListStr = "[" + expectedErrorStrList.join(", ") + "]"; if (actualError in expectedErrorSet) { return true; } testFailed("assertGLError: expected: " + expectedErrorListStr + " actual: " + getGLErrorAsString(gl, actualError), name, f); return false; } // Assert that f generates some GL error. Used in situations where it's // ambigious which of multiple possible errors will be generated. function assertSomeGLError(gl, name, f) { if (f == null) { f = name; name = null; } var r = false; var glErr = 0; var err = 0; try { f(); } catch(e) { r=true; glErr = e.glError; } if (glErr === 0) { if (glErr === undefined) { testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f); } else { testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) + " actual: " + getGLErrorAsString(gl, glErr), name, f); } return false; } return true; } // Assert that f throws an exception but does not generate a GL error. function assertThrowNoGLError(gl, name, f) { if (f == null) { f = name; name = null; } var r = false; var glErr = undefined; var exp; try { f(); } catch(e) { r=true; glErr = e.glError; exp = e;} if (!r) { testFailed( "assertThrowNoGLError: should have thrown exception", name, f); return false; } else { if (glErr !== undefined) { testFailed( "assertThrowNoGLError: should be no GL error but generated: " + getGLErrorAsString(gl, glErr), name, f); return false; } } testPassed("assertThrowNoGLError", name, f); return true; } Quad = { vertices : [ -1,-1,0, 1,-1,0, -1,1,0, 1,-1,0, 1,1,0, -1,1,0 ], normals : [ 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 ], texcoords : [ 0,0, 1,0, 0,1, 1,0, 1,1, 0,1 ], indices : [0,1,2,1,5,2], makeVBO : function(gl) { return new VBO(gl, {size:3, data: Quad.vertices}, {size:3, data: Quad.normals}, {size:2, data: Quad.texcoords} ) }, cache: {}, getCachedVBO : function(gl) { if (!this.cache[gl]) this.cache[gl] = this.makeVBO(gl); return this.cache[gl]; } } Cube = { vertices : [ 0.5, -0.5, 0.5, // +X 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, // +Y 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, // +Z -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, // -X -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, // -Y -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, // -Z -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, ], normals : [ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1 ], indices : [], create : function(){ for (var i = 0; i < 6; i++) { Cube.indices.push(i*4 + 0); Cube.indices.push(i*4 + 1); Cube.indices.push(i*4 + 3); Cube.indices.push(i*4 + 1); Cube.indices.push(i*4 + 2); Cube.indices.push(i*4 + 3); } }, makeVBO : function(gl) { return new VBO(gl, {size:3, data: Cube.vertices}, {size:3, data: Cube.normals}, {elements: true, data: Cube.indices} ) }, cache : {}, getCachedVBO : function(gl) { if (!this.cache[gl]) this.cache[gl] = this.makeVBO(gl); return this.cache[gl]; } } Cube.create(); Sphere = { vertices : [], normals : [], indices : [], create : function(){ var r = 0.75; function vert(theta, phi) { var r = 0.75; var x, y, z, nx, ny, nz; nx = Math.sin(theta) * Math.cos(phi); ny = Math.sin(phi); nz = Math.cos(theta) * Math.cos(phi); Sphere.normals.push(nx); Sphere.normals.push(ny); Sphere.normals.push(nz); x = r * Math.sin(theta) * Math.cos(phi); y = r * Math.sin(phi); z = r * Math.cos(theta) * Math.cos(phi); Sphere.vertices.push(x); Sphere.vertices.push(y); Sphere.vertices.push(z); } for (var phi = -Math.PI/2; phi < Math.PI/2; phi += Math.PI/20) { var phi2 = phi + Math.PI/20; for (var theta = -Math.PI/2; theta <= Math.PI/2; theta += Math.PI/20) { vert(theta, phi); vert(theta, phi2); } } } } Sphere.create(); initGL_CONTEXT_ID = function(){ var c = document.createElement('canvas'); var contextNames = ['webgl', 'experimental-webgl']; GL_CONTEXT_ID = null; for (var i=0; i<contextNames.length; i++) { try { if (c.getContext(contextNames[i])) { GL_CONTEXT_ID = contextNames[i]; break; } } catch (e) { } } if (!GL_CONTEXT_ID) { log("No WebGL context found. Unable to run tests."); } } initGL_CONTEXT_ID();