util.js (33992B)
1 /* 2 Utilities for the OpenGL ES 2.0 HTML Canvas context 3 */ 4 5 /* 6 Copyright (c) 2019 The Khronos Group Inc. 7 Use of this source code is governed by an MIT-style license that can be 8 found in the LICENSE.txt file. 9 */ 10 11 function loadTexture(gl, elem, mipmaps) { 12 var tex = gl.createTexture(); 13 gl.bindTexture(gl.TEXTURE_2D, tex); 14 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, elem); 15 if (mipmaps != false) 16 gl.generateMipmap(gl.TEXTURE_2D); 17 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 18 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 19 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 20 if (mipmaps) 21 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 22 else 23 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 24 return tex; 25 } 26 27 function getShader(gl, id) { 28 var shaderScript = document.getElementById(id); 29 if (!shaderScript) { 30 throw(new Error("No shader element with id: "+id)); 31 } 32 33 var str = ""; 34 var k = shaderScript.firstChild; 35 while (k) { 36 if (k.nodeType == 3) 37 str += k.textContent; 38 k = k.nextSibling; 39 } 40 41 var shader; 42 if (shaderScript.type == "x-shader/x-fragment") { 43 shader = gl.createShader(gl.FRAGMENT_SHADER); 44 } else if (shaderScript.type == "x-shader/x-vertex") { 45 shader = gl.createShader(gl.VERTEX_SHADER); 46 } else { 47 throw(new Error("Unknown shader type "+shaderScript.type)); 48 } 49 50 gl.shaderSource(shader, str); 51 gl.compileShader(shader); 52 53 if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) != 1) { 54 var ilog = gl.getShaderInfoLog(shader); 55 gl.deleteShader(shader); 56 throw(new Error("Failed to compile shader "+shaderScript.id + ", Shader info log: " + ilog)); 57 } 58 return shader; 59 } 60 61 function loadShaderArray(gl, shaders) { 62 var id = gl.createProgram(); 63 var shaderObjs = []; 64 for (var i=0; i<shaders.length; ++i) { 65 try { 66 var sh = getShader(gl, shaders[i]); 67 shaderObjs.push(sh); 68 gl.attachShader(id, sh); 69 } catch (e) { 70 var pr = {program: id, shaders: shaderObjs}; 71 deleteShader(gl, pr); 72 throw (e); 73 } 74 } 75 var prog = {program: id, shaders: shaderObjs}; 76 gl.linkProgram(id); 77 gl.validateProgram(id); 78 if (gl.getProgramParameter(id, gl.LINK_STATUS) != 1) { 79 deleteShader(gl,prog); 80 throw(new Error("Failed to link shader")); 81 } 82 if (gl.getProgramParameter(id, gl.VALIDATE_STATUS) != 1) { 83 deleteShader(gl,prog); 84 throw(new Error("Failed to validate shader")); 85 } 86 return prog; 87 } 88 function loadShader(gl) { 89 var sh = []; 90 for (var i=1; i<arguments.length; ++i) 91 sh.push(arguments[i]); 92 return loadShaderArray(gl, sh); 93 } 94 95 function deleteShader(gl, sh) { 96 gl.useProgram(null); 97 sh.shaders.forEach(function(s){ 98 gl.detachShader(sh.program, s); 99 gl.deleteShader(s); 100 }); 101 gl.deleteProgram(sh.program); 102 } 103 104 function getGLErrorAsString(ctx, err) { 105 if (err === ctx.NO_ERROR) { 106 return "NO_ERROR"; 107 } 108 for (var name in ctx) { 109 if (ctx[name] === err) { 110 return name; 111 } 112 } 113 return err.toString(); 114 } 115 116 function checkError(gl, msg) { 117 var e = gl.getError(); 118 if (e != gl.NO_ERROR) { 119 log("Error " + getGLErrorAsString(gl, e) + " at " + msg); 120 } 121 return e; 122 } 123 124 function throwError(gl, msg) { 125 var e = gl.getError(); 126 if (e != 0) { 127 throw(new Error("Error " + getGLErrorAsString(gl, e) + " at " + msg)); 128 } 129 } 130 131 Math.cot = function(z) { return 1.0 / Math.tan(z); } 132 133 /* 134 Matrix utilities, using the OpenGL element order where 135 the last 4 elements are the translation column. 136 137 Uses flat arrays as matrices for performance. 138 139 Most operations have in-place variants to avoid allocating temporary matrices. 140 141 Naming logic: 142 Matrix.method operates on a 4x4 Matrix and returns a new Matrix. 143 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))) 144 Matrix.method[3x3]InPlace(args, target) stores its result in the target matrix. 145 146 Matrix.scale([sx, sy, sz]) -- non-uniform scale by vector 147 Matrix.scale1(s) -- uniform scale by scalar 148 Matrix.scale3(sx, sy, sz) -- non-uniform scale by scalars 149 150 Ditto for translate. 151 */ 152 Matrix = { 153 identity : [ 154 1.0, 0.0, 0.0, 0.0, 155 0.0, 1.0, 0.0, 0.0, 156 0.0, 0.0, 1.0, 0.0, 157 0.0, 0.0, 0.0, 1.0 158 ], 159 160 newIdentity : function() { 161 return [ 162 1.0, 0.0, 0.0, 0.0, 163 0.0, 1.0, 0.0, 0.0, 164 0.0, 0.0, 1.0, 0.0, 165 0.0, 0.0, 0.0, 1.0 166 ]; 167 }, 168 169 newIdentity3x3 : function() { 170 return [ 171 1.0, 0.0, 0.0, 172 0.0, 1.0, 0.0, 173 0.0, 0.0, 1.0 174 ]; 175 }, 176 177 copyMatrix : function(src, dst) { 178 for (var i=0; i<16; i++) dst[i] = src[i]; 179 return dst; 180 }, 181 182 to3x3 : function(m) { 183 return [ 184 m[0], m[1], m[2], 185 m[4], m[5], m[6], 186 m[8], m[9], m[10] 187 ]; 188 }, 189 190 // orthonormal matrix inverse 191 inverseON : function(m) { 192 var n = this.transpose4x4(m); 193 var t = [m[12], m[13], m[14]]; 194 n[3] = n[7] = n[11] = 0; 195 n[12] = -Vec3.dot([n[0], n[4], n[8]], t); 196 n[13] = -Vec3.dot([n[1], n[5], n[9]], t); 197 n[14] = -Vec3.dot([n[2], n[6], n[10]], t); 198 return n; 199 }, 200 201 inverseTo3x3 : function(m) { 202 return this.inverse4x4to3x3InPlace(m, this.newIdentity3x3()); 203 }, 204 205 inverseTo3x3InPlace : function(m,n) { 206 var a11 = m[10]*m[5]-m[6]*m[9], 207 a21 = -m[10]*m[1]+m[2]*m[9], 208 a31 = m[6]*m[1]-m[2]*m[5], 209 a12 = -m[10]*m[4]+m[6]*m[8], 210 a22 = m[10]*m[0]-m[2]*m[8], 211 a32 = -m[6]*m[0]+m[2]*m[4], 212 a13 = m[9]*m[4]-m[5]*m[8], 213 a23 = -m[9]*m[0]+m[1]*m[8], 214 a33 = m[5]*m[0]-m[1]*m[4]; 215 var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13); 216 if (det == 0) // no inverse 217 return [1,0,0,0,1,0,0,0,1]; 218 var idet = 1 / det; 219 n[0] = idet*a11; 220 n[1] = idet*a21; 221 n[2] = idet*a31; 222 n[3] = idet*a12; 223 n[4] = idet*a22; 224 n[5] = idet*a32; 225 n[6] = idet*a13; 226 n[7] = idet*a23; 227 n[8] = idet*a33; 228 return n; 229 }, 230 231 inverse3x3 : function(m) { 232 return this.inverse3x3InPlace(m, this.newIdentity3x3()); 233 }, 234 235 inverse3x3InPlace : function(m,n) { 236 var a11 = m[8]*m[4]-m[5]*m[7], 237 a21 = -m[8]*m[1]+m[2]*m[7], 238 a31 = m[5]*m[1]-m[2]*m[4], 239 a12 = -m[8]*m[3]+m[5]*m[6], 240 a22 = m[8]*m[0]-m[2]*m[6], 241 a32 = -m[5]*m[0]+m[2]*m[3], 242 a13 = m[7]*m[4]-m[4]*m[8], 243 a23 = -m[7]*m[0]+m[1]*m[6], 244 a33 = m[4]*m[0]-m[1]*m[3]; 245 var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13); 246 if (det == 0) // no inverse 247 return [1,0,0,0,1,0,0,0,1]; 248 var idet = 1 / det; 249 n[0] = idet*a11; 250 n[1] = idet*a21; 251 n[2] = idet*a31; 252 n[3] = idet*a12; 253 n[4] = idet*a22; 254 n[5] = idet*a32; 255 n[6] = idet*a13; 256 n[7] = idet*a23; 257 n[8] = idet*a33; 258 return n; 259 }, 260 261 frustum : function (left, right, bottom, top, znear, zfar) { 262 var X = 2*znear/(right-left); 263 var Y = 2*znear/(top-bottom); 264 var A = (right+left)/(right-left); 265 var B = (top+bottom)/(top-bottom); 266 var C = -(zfar+znear)/(zfar-znear); 267 var D = -2*zfar*znear/(zfar-znear); 268 269 return [ 270 X, 0, 0, 0, 271 0, Y, 0, 0, 272 A, B, C, -1, 273 0, 0, D, 0 274 ]; 275 }, 276 277 perspective : function (fovy, aspect, znear, zfar) { 278 var ymax = znear * Math.tan(fovy * Math.PI / 360.0); 279 var ymin = -ymax; 280 var xmin = ymin * aspect; 281 var xmax = ymax * aspect; 282 283 return this.frustum(xmin, xmax, ymin, ymax, znear, zfar); 284 }, 285 286 mul4x4 : function (a,b) { 287 return this.mul4x4InPlace(a,b,this.newIdentity()); 288 }, 289 290 mul4x4InPlace : function (a, b, c) { 291 c[0] = b[0] * a[0] + 292 b[0+1] * a[4] + 293 b[0+2] * a[8] + 294 b[0+3] * a[12]; 295 c[0+1] = b[0] * a[1] + 296 b[0+1] * a[5] + 297 b[0+2] * a[9] + 298 b[0+3] * a[13]; 299 c[0+2] = b[0] * a[2] + 300 b[0+1] * a[6] + 301 b[0+2] * a[10] + 302 b[0+3] * a[14]; 303 c[0+3] = b[0] * a[3] + 304 b[0+1] * a[7] + 305 b[0+2] * a[11] + 306 b[0+3] * a[15]; 307 c[4] = b[4] * a[0] + 308 b[4+1] * a[4] + 309 b[4+2] * a[8] + 310 b[4+3] * a[12]; 311 c[4+1] = b[4] * a[1] + 312 b[4+1] * a[5] + 313 b[4+2] * a[9] + 314 b[4+3] * a[13]; 315 c[4+2] = b[4] * a[2] + 316 b[4+1] * a[6] + 317 b[4+2] * a[10] + 318 b[4+3] * a[14]; 319 c[4+3] = b[4] * a[3] + 320 b[4+1] * a[7] + 321 b[4+2] * a[11] + 322 b[4+3] * a[15]; 323 c[8] = b[8] * a[0] + 324 b[8+1] * a[4] + 325 b[8+2] * a[8] + 326 b[8+3] * a[12]; 327 c[8+1] = b[8] * a[1] + 328 b[8+1] * a[5] + 329 b[8+2] * a[9] + 330 b[8+3] * a[13]; 331 c[8+2] = b[8] * a[2] + 332 b[8+1] * a[6] + 333 b[8+2] * a[10] + 334 b[8+3] * a[14]; 335 c[8+3] = b[8] * a[3] + 336 b[8+1] * a[7] + 337 b[8+2] * a[11] + 338 b[8+3] * a[15]; 339 c[12] = b[12] * a[0] + 340 b[12+1] * a[4] + 341 b[12+2] * a[8] + 342 b[12+3] * a[12]; 343 c[12+1] = b[12] * a[1] + 344 b[12+1] * a[5] + 345 b[12+2] * a[9] + 346 b[12+3] * a[13]; 347 c[12+2] = b[12] * a[2] + 348 b[12+1] * a[6] + 349 b[12+2] * a[10] + 350 b[12+3] * a[14]; 351 c[12+3] = b[12] * a[3] + 352 b[12+1] * a[7] + 353 b[12+2] * a[11] + 354 b[12+3] * a[15]; 355 return c; 356 }, 357 358 mulv4 : function (a, v) { 359 c = new Array(4); 360 for (var i=0; i<4; ++i) { 361 var x = 0; 362 for (var k=0; k<4; ++k) 363 x += v[k] * a[k*4+i]; 364 c[i] = x; 365 } 366 return c; 367 }, 368 369 rotate : function (angle, axis) { 370 axis = Vec3.normalize(axis); 371 var x=axis[0], y=axis[1], z=axis[2]; 372 var c = Math.cos(angle); 373 var c1 = 1-c; 374 var s = Math.sin(angle); 375 return [ 376 x*x*c1+c, y*x*c1+z*s, z*x*c1-y*s, 0, 377 x*y*c1-z*s, y*y*c1+c, y*z*c1+x*s, 0, 378 x*z*c1+y*s, y*z*c1-x*s, z*z*c1+c, 0, 379 0,0,0,1 380 ]; 381 }, 382 rotateInPlace : function(angle, axis, m) { 383 axis = Vec3.normalize(axis); 384 var x=axis[0], y=axis[1], z=axis[2]; 385 var c = Math.cos(angle); 386 var c1 = 1-c; 387 var s = Math.sin(angle); 388 var tmpMatrix = this.tmpMatrix; 389 var tmpMatrix2 = this.tmpMatrix2; 390 tmpMatrix[0] = x*x*c1+c; tmpMatrix[1] = y*x*c1+z*s; tmpMatrix[2] = z*x*c1-y*s; tmpMatrix[3] = 0; 391 tmpMatrix[4] = x*y*c1-z*s; tmpMatrix[5] = y*y*c1+c; tmpMatrix[6] = y*z*c1+x*s; tmpMatrix[7] = 0; 392 tmpMatrix[8] = x*z*c1+y*s; tmpMatrix[9] = y*z*c1-x*s; tmpMatrix[10] = z*z*c1+c; tmpMatrix[11] = 0; 393 tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1; 394 this.copyMatrix(m, tmpMatrix2); 395 return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m); 396 }, 397 398 scale : function(v) { 399 return [ 400 v[0], 0, 0, 0, 401 0, v[1], 0, 0, 402 0, 0, v[2], 0, 403 0, 0, 0, 1 404 ]; 405 }, 406 scale3 : function(x,y,z) { 407 return [ 408 x, 0, 0, 0, 409 0, y, 0, 0, 410 0, 0, z, 0, 411 0, 0, 0, 1 412 ]; 413 }, 414 scale1 : function(s) { 415 return [ 416 s, 0, 0, 0, 417 0, s, 0, 0, 418 0, 0, s, 0, 419 0, 0, 0, 1 420 ]; 421 }, 422 scale3InPlace : function(x, y, z, m) { 423 var tmpMatrix = this.tmpMatrix; 424 var tmpMatrix2 = this.tmpMatrix2; 425 tmpMatrix[0] = x; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0; 426 tmpMatrix[4] = 0; tmpMatrix[5] = y; tmpMatrix[6] = 0; tmpMatrix[7] = 0; 427 tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = z; tmpMatrix[11] = 0; 428 tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1; 429 this.copyMatrix(m, tmpMatrix2); 430 return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m); 431 }, 432 scale1InPlace : function(s, m) { return this.scale3InPlace(s, s, s, m); }, 433 scaleInPlace : function(s, m) { return this.scale3InPlace(s[0],s[1],s[2],m); }, 434 435 translate3 : function(x,y,z) { 436 return [ 437 1, 0, 0, 0, 438 0, 1, 0, 0, 439 0, 0, 1, 0, 440 x, y, z, 1 441 ]; 442 }, 443 444 translate : function(v) { 445 return this.translate3(v[0], v[1], v[2]); 446 }, 447 tmpMatrix : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], 448 tmpMatrix2 : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], 449 translate3InPlace : function(x,y,z,m) { 450 var tmpMatrix = this.tmpMatrix; 451 var tmpMatrix2 = this.tmpMatrix2; 452 tmpMatrix[0] = 1; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0; 453 tmpMatrix[4] = 0; tmpMatrix[5] = 1; tmpMatrix[6] = 0; tmpMatrix[7] = 0; 454 tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = 1; tmpMatrix[11] = 0; 455 tmpMatrix[12] = x; tmpMatrix[13] = y; tmpMatrix[14] = z; tmpMatrix[15] = 1; 456 this.copyMatrix(m, tmpMatrix2); 457 return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m); 458 }, 459 translateInPlace : function(v,m){ return this.translate3InPlace(v[0], v[1], v[2], m); }, 460 461 lookAt : function (eye, center, up) { 462 var z = Vec3.direction(eye, center); 463 var x = Vec3.normalizeInPlace(Vec3.cross(up, z)); 464 var y = Vec3.normalizeInPlace(Vec3.cross(z, x)); 465 466 var m = [ 467 x[0], y[0], z[0], 0, 468 x[1], y[1], z[1], 0, 469 x[2], y[2], z[2], 0, 470 0, 0, 0, 1 471 ]; 472 473 var t = [ 474 1, 0, 0, 0, 475 0, 1, 0, 0, 476 0, 0, 1, 0, 477 -eye[0], -eye[1], -eye[2], 1 478 ]; 479 480 return this.mul4x4(m,t); 481 }, 482 483 transpose4x4 : function(m) { 484 return [ 485 m[0], m[4], m[8], m[12], 486 m[1], m[5], m[9], m[13], 487 m[2], m[6], m[10], m[14], 488 m[3], m[7], m[11], m[15] 489 ]; 490 }, 491 492 transpose4x4InPlace : function(m) { 493 var tmp = 0.0; 494 tmp = m[1]; m[1] = m[4]; m[4] = tmp; 495 tmp = m[2]; m[2] = m[8]; m[8] = tmp; 496 tmp = m[3]; m[3] = m[12]; m[12] = tmp; 497 tmp = m[6]; m[6] = m[9]; m[9] = tmp; 498 tmp = m[7]; m[7] = m[13]; m[13] = tmp; 499 tmp = m[11]; m[11] = m[14]; m[14] = tmp; 500 return m; 501 }, 502 503 transpose3x3 : function(m) { 504 return [ 505 m[0], m[3], m[6], 506 m[1], m[4], m[7], 507 m[2], m[5], m[8] 508 ]; 509 }, 510 511 transpose3x3InPlace : function(m) { 512 var tmp = 0.0; 513 tmp = m[1]; m[1] = m[3]; m[3] = tmp; 514 tmp = m[2]; m[2] = m[6]; m[6] = tmp; 515 tmp = m[5]; m[5] = m[7]; m[7] = tmp; 516 return m; 517 }, 518 } 519 520 Vec3 = { 521 make : function() { return [0,0,0]; }, 522 copy : function(v) { return [v[0],v[1],v[2]]; }, 523 524 add : function (u,v) { 525 return [u[0]+v[0], u[1]+v[1], u[2]+v[2]]; 526 }, 527 528 sub : function (u,v) { 529 return [u[0]-v[0], u[1]-v[1], u[2]-v[2]]; 530 }, 531 532 negate : function (u) { 533 return [-u[0], -u[1], -u[2]]; 534 }, 535 536 direction : function (u,v) { 537 return this.normalizeInPlace(this.sub(u,v)); 538 }, 539 540 normalizeInPlace : function(v) { 541 var imag = 1.0 / Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); 542 v[0] *= imag; v[1] *= imag; v[2] *= imag; 543 return v; 544 }, 545 546 normalize : function(v) { 547 return this.normalizeInPlace(this.copy(v)); 548 }, 549 550 scale : function(f, v) { 551 return [f*v[0], f*v[1], f*v[2]]; 552 }, 553 554 dot : function(u,v) { 555 return u[0]*v[0] + u[1]*v[1] + u[2]*v[2]; 556 }, 557 558 inner : function(u,v) { 559 return [u[0]*v[0], u[1]*v[1], u[2]*v[2]]; 560 }, 561 562 cross : function(u,v) { 563 return [ 564 u[1]*v[2] - u[2]*v[1], 565 u[2]*v[0] - u[0]*v[2], 566 u[0]*v[1] - u[1]*v[0] 567 ]; 568 } 569 } 570 571 Shader = function(gl){ 572 this.gl = gl; 573 this.shaders = []; 574 this.uniformLocations = {}; 575 this.attribLocations = {}; 576 for (var i=1; i<arguments.length; i++) { 577 this.shaders.push(arguments[i]); 578 } 579 } 580 Shader.prototype = { 581 id : null, 582 gl : null, 583 compiled : false, 584 shader : null, 585 shaders : [], 586 587 destroy : function() { 588 if (this.shader != null) deleteShader(this.gl, this.shader); 589 }, 590 591 compile : function() { 592 this.shader = loadShaderArray(this.gl, this.shaders); 593 }, 594 595 use : function() { 596 if (this.shader == null) 597 this.compile(); 598 this.gl.useProgram(this.shader.program); 599 }, 600 601 uniform1fv : function(name, value) { 602 var loc = this.uniform(name); 603 this.gl.uniform1fv(loc, value); 604 }, 605 606 uniform2fv : function(name, value) { 607 var loc = this.uniform(name); 608 this.gl.uniform2fv(loc, value); 609 }, 610 611 uniform3fv : function(name, value) { 612 var loc = this.uniform(name); 613 this.gl.uniform3fv(loc, value); 614 }, 615 616 uniform4fv : function(name, value) { 617 var loc = this.uniform(name); 618 this.gl.uniform4fv(loc, value); 619 }, 620 621 uniform1f : function(name, value) { 622 var loc = this.uniform(name); 623 this.gl.uniform1f(loc, value); 624 }, 625 626 uniform2f : function(name, v1,v2) { 627 var loc = this.uniform(name); 628 this.gl.uniform2f(loc, v1,v2); 629 }, 630 631 uniform3f : function(name, v1,v2,v3) { 632 var loc = this.uniform(name); 633 this.gl.uniform3f(loc, v1,v2,v3); 634 }, 635 636 uniform4f : function(name, v1,v2,v3,v4) { 637 var loc = this.uniform(name); 638 this.gl.uniform4f(loc, v1, v2, v3, v4); 639 }, 640 641 uniform1iv : function(name, value) { 642 var loc = this.uniform(name); 643 this.gl.uniform1iv(loc, value); 644 }, 645 646 uniform2iv : function(name, value) { 647 var loc = this.uniform(name); 648 this.gl.uniform2iv(loc, value); 649 }, 650 651 uniform3iv : function(name, value) { 652 var loc = this.uniform(name); 653 this.gl.uniform3iv(loc, value); 654 }, 655 656 uniform4iv : function(name, value) { 657 var loc = this.uniform(name); 658 this.gl.uniform4iv(loc, value); 659 }, 660 661 uniform1i : function(name, value) { 662 var loc = this.uniform(name); 663 this.gl.uniform1i(loc, value); 664 }, 665 666 uniform2i : function(name, v1,v2) { 667 var loc = this.uniform(name); 668 this.gl.uniform2i(loc, v1,v2); 669 }, 670 671 uniform3i : function(name, v1,v2,v3) { 672 var loc = this.uniform(name); 673 this.gl.uniform3i(loc, v1,v2,v3); 674 }, 675 676 uniform4i : function(name, v1,v2,v3,v4) { 677 var loc = this.uniform(name); 678 this.gl.uniform4i(loc, v1, v2, v3, v4); 679 }, 680 681 uniformMatrix4fv : function(name, value) { 682 var loc = this.uniform(name); 683 this.gl.uniformMatrix4fv(loc, false, value); 684 }, 685 686 uniformMatrix3fv : function(name, value) { 687 var loc = this.uniform(name); 688 this.gl.uniformMatrix3fv(loc, false, value); 689 }, 690 691 uniformMatrix2fv : function(name, value) { 692 var loc = this.uniform(name); 693 this.gl.uniformMatrix2fv(loc, false, value); 694 }, 695 696 attrib : function(name) { 697 if (this.attribLocations[name] == null) { 698 var loc = this.gl.getAttribLocation(this.shader.program, name); 699 this.attribLocations[name] = loc; 700 } 701 return this.attribLocations[name]; 702 }, 703 704 uniform : function(name) { 705 if (this.uniformLocations[name] == null) { 706 var loc = this.gl.getUniformLocation(this.shader.program, name); 707 this.uniformLocations[name] = loc; 708 } 709 return this.uniformLocations[name]; 710 } 711 } 712 Filter = function(gl, shader) { 713 Shader.apply(this, arguments); 714 } 715 Filter.prototype = new Shader(); 716 Filter.prototype.apply = function(init) { 717 this.use(); 718 var va = this.attrib("Vertex"); 719 var ta = this.attrib("Tex"); 720 var vbo = Quad.getCachedVBO(this.gl); 721 if (init) init(this); 722 vbo.draw(va, null, ta); 723 } 724 725 726 VBO = function(gl) { 727 this.gl = gl; 728 this.data = []; 729 this.elementsVBO = null; 730 for (var i=1; i<arguments.length; i++) { 731 if (arguments[i].elements) 732 this.elements = arguments[i]; 733 else 734 this.data.push(arguments[i]); 735 } 736 } 737 738 VBO.prototype = { 739 initialized : false, 740 length : 0, 741 vbos : null, 742 type : 'TRIANGLES', 743 elementsVBO : null, 744 elements : null, 745 746 setData : function() { 747 this.destroy(); 748 this.data = []; 749 for (var i=0; i<arguments.length; i++) { 750 if (arguments[i].elements) 751 this.elements = arguments[i]; 752 else 753 this.data.push(arguments[i]); 754 } 755 }, 756 757 destroy : function() { 758 if (this.vbos != null) 759 for (var i=0; i<this.vbos.length; i++) 760 this.gl.deleteBuffer(this.vbos[i]); 761 if (this.elementsVBO != null) 762 this.gl.deleteBuffer(this.elementsVBO); 763 this.length = this.elementsLength = 0; 764 this.vbos = this.elementsVBO = null; 765 this.initialized = false; 766 }, 767 768 init : function() { 769 this.destroy(); 770 var gl = this.gl; 771 772 gl.getError(); 773 var vbos = []; 774 var length = 0; 775 for (var i=0; i<this.data.length; i++) 776 vbos.push(gl.createBuffer()); 777 if (this.elements != null) 778 this.elementsVBO = gl.createBuffer(); 779 try { 780 throwError(gl, "genBuffers"); 781 for (var i = 0; i<this.data.length; i++) { 782 var d = this.data[i]; 783 var dlen = Math.floor(d.data.length / d.size); 784 if (i == 0 || dlen < length) 785 length = dlen; 786 if (!d.floatArray) 787 d.floatArray = new Float32Array(d.data); 788 gl.bindBuffer(gl.ARRAY_BUFFER, vbos[i]); 789 throwError(gl, "bindBuffer"); 790 gl.bufferData(gl.ARRAY_BUFFER, d.floatArray, gl.STATIC_DRAW); 791 throwError(gl, "bufferData"); 792 } 793 if (this.elementsVBO != null) { 794 var d = this.elements; 795 this.elementsLength = d.data.length; 796 this.elementsType = d.type == gl.UNSIGNED_BYTE ? d.type : gl.UNSIGNED_SHORT; 797 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO); 798 throwError(gl, "bindBuffer ELEMENT_ARRAY_BUFFER"); 799 if (this.elementsType == gl.UNSIGNED_SHORT && !d.ushortArray) { 800 d.ushortArray = new Uint16Array(d.data); 801 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ushortArray, gl.STATIC_DRAW); 802 } else if (this.elementsType == gl.UNSIGNED_BYTE && !d.ubyteArray) { 803 d.ubyteArray = new Uint8Array(d.data); 804 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ubyteArray, gl.STATIC_DRAW); 805 } 806 throwError(gl, "bufferData ELEMENT_ARRAY_BUFFER"); 807 } 808 } catch(e) { 809 for (var i=0; i<vbos.length; i++) 810 gl.deleteBuffer(vbos[i]); 811 throw(e); 812 } 813 814 gl.bindBuffer(gl.ARRAY_BUFFER, null); 815 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 816 817 this.length = length; 818 this.vbos = vbos; 819 820 this.initialized = true; 821 }, 822 823 use : function() { 824 if (!this.initialized) this.init(); 825 var gl = this.gl; 826 for (var i=0; i<arguments.length; i++) { 827 if (arguments[i] == null || arguments[i] == -1) continue; 828 gl.bindBuffer(gl.ARRAY_BUFFER, this.vbos[i]); 829 gl.vertexAttribPointer(arguments[i], this.data[i].size, gl.FLOAT, false, 0, 0); 830 gl.enableVertexAttribArray(arguments[i]); 831 } 832 if (this.elementsVBO != null) { 833 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO); 834 } 835 }, 836 837 draw : function() { 838 var args = []; 839 this.use.apply(this, arguments); 840 var gl = this.gl; 841 if (this.elementsVBO != null) { 842 gl.drawElements(gl[this.type], this.elementsLength, this.elementsType, 0); 843 } else { 844 gl.drawArrays(gl[this.type], 0, this.length); 845 } 846 } 847 } 848 849 FBO = function(gl, width, height, use_depth) { 850 this.gl = gl; 851 this.width = width; 852 this.height = height; 853 if (use_depth != null) 854 this.useDepth = use_depth; 855 } 856 FBO.prototype = { 857 initialized : false, 858 useDepth : true, 859 fbo : null, 860 rbo : null, 861 texture : null, 862 863 destroy : function() { 864 if (this.fbo) this.gl.deleteFramebuffer(this.fbo); 865 if (this.rbo) this.gl.deleteRenderbuffer(this.rbo); 866 if (this.texture) this.gl.deleteTexture(this.texture); 867 }, 868 869 init : function() { 870 var gl = this.gl; 871 var w = this.width, h = this.height; 872 var fbo = this.fbo != null ? this.fbo : gl.createFramebuffer(); 873 var rb; 874 875 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 876 checkError(gl, "FBO.init bindFramebuffer"); 877 if (this.useDepth) { 878 rb = this.rbo != null ? this.rbo : gl.createRenderbuffer(); 879 gl.bindRenderbuffer(gl.RENDERBUFFER, rb); 880 checkError(gl, "FBO.init bindRenderbuffer"); 881 gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h); 882 checkError(gl, "FBO.init renderbufferStorage"); 883 } 884 885 var tex = this.texture != null ? this.texture : gl.createTexture(); 886 gl.bindTexture(gl.TEXTURE_2D, tex); 887 try { 888 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 889 } catch (e) { // argh, no null texture support 890 var tmp = this.getTempCanvas(w,h); 891 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tmp); 892 } 893 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 894 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 895 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 896 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 897 checkError(gl, "FBO.init tex"); 898 899 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0); 900 checkError(gl, "FBO.init bind tex"); 901 902 if (this.useDepth) { 903 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb); 904 checkError(gl, "FBO.init bind depth buffer"); 905 } 906 907 var fbstat = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 908 if (fbstat != gl.FRAMEBUFFER_COMPLETE) { 909 var glv; 910 for (var v in gl) { 911 try { glv = gl[v]; } catch (e) { glv = null; } 912 if (glv == fbstat) { fbstat = v; break; }} 913 log("Framebuffer status: " + fbstat); 914 } 915 checkError(gl, "FBO.init check fbo"); 916 917 this.fbo = fbo; 918 this.rbo = rb; 919 this.texture = tex; 920 this.initialized = true; 921 }, 922 923 getTempCanvas : function(w, h) { 924 if (!FBO.tempCanvas) { 925 FBO.tempCanvas = document.createElement('canvas'); 926 } 927 FBO.tempCanvas.width = w; 928 FBO.tempCanvas.height = h; 929 return FBO.tempCanvas; 930 }, 931 932 use : function() { 933 if (!this.initialized) this.init(); 934 this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo); 935 } 936 } 937 938 function GLError(err, msg, fileName, lineNumber) { 939 this.message = msg; 940 this.glError = err; 941 } 942 943 GLError.prototype = new Error(); 944 945 function makeGLErrorWrapper(gl, fname) { 946 return (function() { 947 try { 948 var rv = gl[fname].apply(gl, arguments); 949 var err = gl.getError(); 950 if (err != gl.NO_ERROR) { 951 throw(new GLError( 952 err, "GL error "+getGLErrorAsString(gl, err)+" in "+fname)); 953 } 954 return rv; 955 } catch (e) { 956 if (e.glError !== undefined) { 957 throw e; 958 } 959 throw(new Error("Threw " + e.name + 960 " in " + fname + "\n" + 961 e.message + "\n" + 962 arguments.callee.caller)); 963 } 964 }); 965 } 966 967 function wrapGLContext(gl) { 968 var wrap = {}; 969 for (var i in gl) { 970 try { 971 if (typeof gl[i] == 'function') { 972 wrap[i] = makeGLErrorWrapper(gl, i); 973 } else { 974 wrap[i] = gl[i]; 975 } 976 } catch (e) { 977 // log("wrapGLContext: Error accessing " + i); 978 } 979 } 980 wrap.getError = function(){ return gl.getError(); }; 981 return wrap; 982 } 983 984 function getGLContext(canvas) { 985 return canvas.getContext(GL_CONTEXT_ID, {antialias: false}); 986 } 987 988 // Assert that f generates a specific GL error. 989 function assertGLError(gl, err, name, f) { 990 if (f == null) { f = name; name = null; } 991 var r = false; 992 var glErr = 0; 993 try { f(); } catch(e) { r=true; glErr = e.glError; } 994 if (glErr !== err) { 995 if (glErr === undefined) { 996 testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f); 997 } else { 998 testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) + 999 " actual: " + getGLErrorAsString(gl, glErr), name, f); 1000 } 1001 return false; 1002 } 1003 return true; 1004 } 1005 1006 // Assert that f generates a GL error from a list. 1007 function assertGLErrorIn(gl, expectedErrorList, name, f) { 1008 if (f == null) { f = name; name = null; } 1009 1010 var actualError = 0; 1011 try { 1012 f(); 1013 } catch(e) { 1014 if ('glError' in e) { 1015 actualError = e.glError; 1016 } else { 1017 testFailed("assertGLError: UNEXPCETED EXCEPTION", name, f); 1018 return false; 1019 } 1020 } 1021 1022 var expectedErrorStrList = []; 1023 var expectedErrorSet = {}; 1024 for (var i in expectedErrorList) { 1025 var cur = expectedErrorList[i]; 1026 expectedErrorSet[cur] = true; 1027 expectedErrorStrList.push(getGLErrorAsString(gl, cur)); 1028 } 1029 var expectedErrorListStr = "[" + expectedErrorStrList.join(", ") + "]"; 1030 1031 if (actualError in expectedErrorSet) { 1032 return true; 1033 } 1034 1035 testFailed("assertGLError: expected: " + expectedErrorListStr + 1036 " actual: " + getGLErrorAsString(gl, actualError), name, f); 1037 return false; 1038 } 1039 1040 // Assert that f generates some GL error. Used in situations where it's 1041 // ambigious which of multiple possible errors will be generated. 1042 function assertSomeGLError(gl, name, f) { 1043 if (f == null) { f = name; name = null; } 1044 var r = false; 1045 var glErr = 0; 1046 var err = 0; 1047 try { f(); } catch(e) { r=true; glErr = e.glError; } 1048 if (glErr === 0) { 1049 if (glErr === undefined) { 1050 testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f); 1051 } else { 1052 testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) + 1053 " actual: " + getGLErrorAsString(gl, glErr), name, f); 1054 } 1055 return false; 1056 } 1057 return true; 1058 } 1059 1060 // Assert that f throws an exception but does not generate a GL error. 1061 function assertThrowNoGLError(gl, name, f) { 1062 if (f == null) { f = name; name = null; } 1063 var r = false; 1064 var glErr = undefined; 1065 var exp; 1066 try { f(); } catch(e) { r=true; glErr = e.glError; exp = e;} 1067 if (!r) { 1068 testFailed( 1069 "assertThrowNoGLError: should have thrown exception", name, f); 1070 return false; 1071 } else { 1072 if (glErr !== undefined) { 1073 testFailed( 1074 "assertThrowNoGLError: should be no GL error but generated: " + 1075 getGLErrorAsString(gl, glErr), name, f); 1076 return false; 1077 } 1078 } 1079 testPassed("assertThrowNoGLError", name, f); 1080 return true; 1081 } 1082 1083 function assertThrows(gl, shouldThrow, info, func) { 1084 var didThrow = false; 1085 try { 1086 func(); 1087 } catch (e) { 1088 var didGLError = (e instanceof GLError); 1089 if (!didGLError) { 1090 didThrow = true; 1091 } 1092 } 1093 1094 var text = shouldThrow ? "Should throw: " 1095 : "Should not throw: "; 1096 var func = (didThrow == shouldThrow) ? testPassed : testFailed; 1097 1098 func(text + info); 1099 } 1100 1101 Quad = { 1102 vertices : [ 1103 -1,-1,0, 1104 1,-1,0, 1105 -1,1,0, 1106 1,-1,0, 1107 1,1,0, 1108 -1,1,0 1109 ], 1110 normals : [ 1111 0,0,-1, 1112 0,0,-1, 1113 0,0,-1, 1114 0,0,-1, 1115 0,0,-1, 1116 0,0,-1 1117 ], 1118 texcoords : [ 1119 0,0, 1120 1,0, 1121 0,1, 1122 1,0, 1123 1,1, 1124 0,1 1125 ], 1126 indices : [0,1,2,1,5,2], 1127 makeVBO : function(gl) { 1128 return new VBO(gl, 1129 {size:3, data: Quad.vertices}, 1130 {size:3, data: Quad.normals}, 1131 {size:2, data: Quad.texcoords} 1132 ) 1133 }, 1134 cache: {}, 1135 getCachedVBO : function(gl) { 1136 if (!this.cache[gl]) 1137 this.cache[gl] = this.makeVBO(gl); 1138 return this.cache[gl]; 1139 } 1140 } 1141 Cube = { 1142 vertices : [ 0.5, -0.5, 0.5, // +X 1143 0.5, -0.5, -0.5, 1144 0.5, 0.5, -0.5, 1145 0.5, 0.5, 0.5, 1146 1147 0.5, 0.5, 0.5, // +Y 1148 0.5, 0.5, -0.5, 1149 -0.5, 0.5, -0.5, 1150 -0.5, 0.5, 0.5, 1151 1152 0.5, 0.5, 0.5, // +Z 1153 -0.5, 0.5, 0.5, 1154 -0.5, -0.5, 0.5, 1155 0.5, -0.5, 0.5, 1156 1157 -0.5, -0.5, 0.5, // -X 1158 -0.5, 0.5, 0.5, 1159 -0.5, 0.5, -0.5, 1160 -0.5, -0.5, -0.5, 1161 1162 -0.5, -0.5, 0.5, // -Y 1163 -0.5, -0.5, -0.5, 1164 0.5, -0.5, -0.5, 1165 0.5, -0.5, 0.5, 1166 1167 -0.5, -0.5, -0.5, // -Z 1168 -0.5, 0.5, -0.5, 1169 0.5, 0.5, -0.5, 1170 0.5, -0.5, -0.5, 1171 ], 1172 1173 normals : [ 1, 0, 0, 1174 1, 0, 0, 1175 1, 0, 0, 1176 1, 0, 0, 1177 1178 0, 1, 0, 1179 0, 1, 0, 1180 0, 1, 0, 1181 0, 1, 0, 1182 1183 0, 0, 1, 1184 0, 0, 1, 1185 0, 0, 1, 1186 0, 0, 1, 1187 1188 -1, 0, 0, 1189 -1, 0, 0, 1190 -1, 0, 0, 1191 -1, 0, 0, 1192 1193 0,-1, 0, 1194 0,-1, 0, 1195 0,-1, 0, 1196 0,-1, 0, 1197 1198 0, 0,-1, 1199 0, 0,-1, 1200 0, 0,-1, 1201 0, 0,-1 1202 ], 1203 1204 indices : [], 1205 create : function(){ 1206 for (var i = 0; i < 6; i++) { 1207 Cube.indices.push(i*4 + 0); 1208 Cube.indices.push(i*4 + 1); 1209 Cube.indices.push(i*4 + 3); 1210 Cube.indices.push(i*4 + 1); 1211 Cube.indices.push(i*4 + 2); 1212 Cube.indices.push(i*4 + 3); 1213 } 1214 }, 1215 1216 makeVBO : function(gl) { 1217 return new VBO(gl, 1218 {size:3, data: Cube.vertices}, 1219 {size:3, data: Cube.normals}, 1220 {elements: true, data: Cube.indices} 1221 ) 1222 }, 1223 cache : {}, 1224 getCachedVBO : function(gl) { 1225 if (!this.cache[gl]) 1226 this.cache[gl] = this.makeVBO(gl); 1227 return this.cache[gl]; 1228 } 1229 } 1230 Cube.create(); 1231 1232 Sphere = { 1233 vertices : [], 1234 normals : [], 1235 indices : [], 1236 create : function(){ 1237 var r = 0.75; 1238 function vert(theta, phi) 1239 { 1240 var r = 0.75; 1241 var x, y, z, nx, ny, nz; 1242 1243 nx = Math.sin(theta) * Math.cos(phi); 1244 ny = Math.sin(phi); 1245 nz = Math.cos(theta) * Math.cos(phi); 1246 Sphere.normals.push(nx); 1247 Sphere.normals.push(ny); 1248 Sphere.normals.push(nz); 1249 1250 x = r * Math.sin(theta) * Math.cos(phi); 1251 y = r * Math.sin(phi); 1252 z = r * Math.cos(theta) * Math.cos(phi); 1253 Sphere.vertices.push(x); 1254 Sphere.vertices.push(y); 1255 Sphere.vertices.push(z); 1256 } 1257 for (var phi = -Math.PI/2; phi < Math.PI/2; phi += Math.PI/20) { 1258 var phi2 = phi + Math.PI/20; 1259 for (var theta = -Math.PI/2; theta <= Math.PI/2; theta += Math.PI/20) { 1260 vert(theta, phi); 1261 vert(theta, phi2); 1262 } 1263 } 1264 } 1265 } 1266 1267 Sphere.create(); 1268 1269 initGL_CONTEXT_ID = function(){ 1270 var c = document.createElement('canvas'); 1271 var contextNames = ['webgl', 'experimental-webgl']; 1272 GL_CONTEXT_ID = null; 1273 for (var i=0; i<contextNames.length; i++) { 1274 try { 1275 if (c.getContext(contextNames[i])) { 1276 GL_CONTEXT_ID = contextNames[i]; 1277 break; 1278 } 1279 } catch (e) { 1280 } 1281 } 1282 if (!GL_CONTEXT_ID) { 1283 log("No WebGL context found. Unable to run tests."); 1284 } 1285 } 1286 1287 initGL_CONTEXT_ID();