Backed out changeset b462e7b742d8 (bug 1908261) for causing multiple reftest failures...
[gecko.git] / dom / canvas / test / webgl-conf / checkout / conformance / more / util.js
blob78cc08a652b9f5d14e406fe4db77990dcf00521e
1 /*
2 Utilities for the OpenGL ES 2.0 HTML Canvas context
3 */
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 */
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;
27 function getShader(gl, id) {
28 var shaderScript = document.getElementById(id);
29 if (!shaderScript) {
30 throw(new Error("No shader element with id: "+id));
33 var str = "";
34 var k = shaderScript.firstChild;
35 while (k) {
36 if (k.nodeType == 3)
37 str += k.textContent;
38 k = k.nextSibling;
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));
50 gl.shaderSource(shader, str);
51 gl.compileShader(shader);
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));
58 return shader;
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);
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"));
82 if (gl.getProgramParameter(id, gl.VALIDATE_STATUS) != 1) {
83 deleteShader(gl,prog);
84 throw(new Error("Failed to validate shader"));
86 return prog;
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);
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);
101 gl.deleteProgram(sh.program);
104 function getGLErrorAsString(ctx, err) {
105 if (err === ctx.NO_ERROR) {
106 return "NO_ERROR";
108 for (var name in ctx) {
109 if (ctx[name] === err) {
110 return name;
113 return err.toString();
116 function checkError(gl, msg) {
117 var e = gl.getError();
118 if (e != gl.NO_ERROR) {
119 log("Error " + getGLErrorAsString(gl, e) + " at " + msg);
121 return e;
124 function throwError(gl, msg) {
125 var e = gl.getError();
126 if (e != 0) {
127 throw(new Error("Error " + getGLErrorAsString(gl, e) + " at " + msg));
131 Math.cot = function(z) { return 1.0 / Math.tan(z); }
134 Matrix utilities, using the OpenGL element order where
135 the last 4 elements are the translation column.
137 Uses flat arrays as matrices for performance.
139 Most operations have in-place variants to avoid allocating temporary matrices.
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.
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
150 Ditto for translate.
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
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
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
177 copyMatrix : function(src, dst) {
178 for (var i=0; i<16; i++) dst[i] = src[i];
179 return dst;
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]
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;
201 inverseTo3x3 : function(m) {
202 return this.inverse4x4to3x3InPlace(m, this.newIdentity3x3());
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;
231 inverse3x3 : function(m) {
232 return this.inverse3x3InPlace(m, this.newIdentity3x3());
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;
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);
269 return [
270 X, 0, 0, 0,
271 0, Y, 0, 0,
272 A, B, C, -1,
273 0, 0, D, 0
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;
283 return this.frustum(xmin, xmax, ymin, ymax, znear, zfar);
286 mul4x4 : function (a,b) {
287 return this.mul4x4InPlace(a,b,this.newIdentity());
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;
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;
366 return c;
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
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);
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
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
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
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);
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); },
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
444 translate : function(v) {
445 return this.translate3(v[0], v[1], v[2]);
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);
459 translateInPlace : function(v,m){ return this.translate3InPlace(v[0], v[1], v[2], m); },
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));
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
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
480 return this.mul4x4(m,t);
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]
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;
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]
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;
520 Vec3 = {
521 make : function() { return [0,0,0]; },
522 copy : function(v) { return [v[0],v[1],v[2]]; },
524 add : function (u,v) {
525 return [u[0]+v[0], u[1]+v[1], u[2]+v[2]];
528 sub : function (u,v) {
529 return [u[0]-v[0], u[1]-v[1], u[2]-v[2]];
532 negate : function (u) {
533 return [-u[0], -u[1], -u[2]];
536 direction : function (u,v) {
537 return this.normalizeInPlace(this.sub(u,v));
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;
546 normalize : function(v) {
547 return this.normalizeInPlace(this.copy(v));
550 scale : function(f, v) {
551 return [f*v[0], f*v[1], f*v[2]];
554 dot : function(u,v) {
555 return u[0]*v[0] + u[1]*v[1] + u[2]*v[2];
558 inner : function(u,v) {
559 return [u[0]*v[0], u[1]*v[1], u[2]*v[2]];
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]
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]);
580 Shader.prototype = {
581 id : null,
582 gl : null,
583 compiled : false,
584 shader : null,
585 shaders : [],
587 destroy : function() {
588 if (this.shader != null) deleteShader(this.gl, this.shader);
591 compile : function() {
592 this.shader = loadShaderArray(this.gl, this.shaders);
595 use : function() {
596 if (this.shader == null)
597 this.compile();
598 this.gl.useProgram(this.shader.program);
601 uniform1fv : function(name, value) {
602 var loc = this.uniform(name);
603 this.gl.uniform1fv(loc, value);
606 uniform2fv : function(name, value) {
607 var loc = this.uniform(name);
608 this.gl.uniform2fv(loc, value);
611 uniform3fv : function(name, value) {
612 var loc = this.uniform(name);
613 this.gl.uniform3fv(loc, value);
616 uniform4fv : function(name, value) {
617 var loc = this.uniform(name);
618 this.gl.uniform4fv(loc, value);
621 uniform1f : function(name, value) {
622 var loc = this.uniform(name);
623 this.gl.uniform1f(loc, value);
626 uniform2f : function(name, v1,v2) {
627 var loc = this.uniform(name);
628 this.gl.uniform2f(loc, v1,v2);
631 uniform3f : function(name, v1,v2,v3) {
632 var loc = this.uniform(name);
633 this.gl.uniform3f(loc, v1,v2,v3);
636 uniform4f : function(name, v1,v2,v3,v4) {
637 var loc = this.uniform(name);
638 this.gl.uniform4f(loc, v1, v2, v3, v4);
641 uniform1iv : function(name, value) {
642 var loc = this.uniform(name);
643 this.gl.uniform1iv(loc, value);
646 uniform2iv : function(name, value) {
647 var loc = this.uniform(name);
648 this.gl.uniform2iv(loc, value);
651 uniform3iv : function(name, value) {
652 var loc = this.uniform(name);
653 this.gl.uniform3iv(loc, value);
656 uniform4iv : function(name, value) {
657 var loc = this.uniform(name);
658 this.gl.uniform4iv(loc, value);
661 uniform1i : function(name, value) {
662 var loc = this.uniform(name);
663 this.gl.uniform1i(loc, value);
666 uniform2i : function(name, v1,v2) {
667 var loc = this.uniform(name);
668 this.gl.uniform2i(loc, v1,v2);
671 uniform3i : function(name, v1,v2,v3) {
672 var loc = this.uniform(name);
673 this.gl.uniform3i(loc, v1,v2,v3);
676 uniform4i : function(name, v1,v2,v3,v4) {
677 var loc = this.uniform(name);
678 this.gl.uniform4i(loc, v1, v2, v3, v4);
681 uniformMatrix4fv : function(name, value) {
682 var loc = this.uniform(name);
683 this.gl.uniformMatrix4fv(loc, false, value);
686 uniformMatrix3fv : function(name, value) {
687 var loc = this.uniform(name);
688 this.gl.uniformMatrix3fv(loc, false, value);
691 uniformMatrix2fv : function(name, value) {
692 var loc = this.uniform(name);
693 this.gl.uniformMatrix2fv(loc, false, value);
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;
701 return this.attribLocations[name];
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;
709 return this.uniformLocations[name];
712 Filter = function(gl, shader) {
713 Shader.apply(this, arguments);
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);
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]);
738 VBO.prototype = {
739 initialized : false,
740 length : 0,
741 vbos : null,
742 type : 'TRIANGLES',
743 elementsVBO : null,
744 elements : null,
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]);
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;
768 init : function() {
769 this.destroy();
770 var gl = this.gl;
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");
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);
806 throwError(gl, "bufferData ELEMENT_ARRAY_BUFFER");
808 } catch(e) {
809 for (var i=0; i<vbos.length; i++)
810 gl.deleteBuffer(vbos[i]);
811 throw(e);
814 gl.bindBuffer(gl.ARRAY_BUFFER, null);
815 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
817 this.length = length;
818 this.vbos = vbos;
820 this.initialized = true;
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]);
832 if (this.elementsVBO != null) {
833 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO);
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);
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;
856 FBO.prototype = {
857 initialized : false,
858 useDepth : true,
859 fbo : null,
860 rbo : null,
861 texture : null,
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);
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;
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");
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);
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");
899 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
900 checkError(gl, "FBO.init bind tex");
902 if (this.useDepth) {
903 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
904 checkError(gl, "FBO.init bind depth buffer");
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);
915 checkError(gl, "FBO.init check fbo");
917 this.fbo = fbo;
918 this.rbo = rb;
919 this.texture = tex;
920 this.initialized = true;
923 getTempCanvas : function(w, h) {
924 if (!FBO.tempCanvas) {
925 FBO.tempCanvas = document.createElement('canvas');
927 FBO.tempCanvas.width = w;
928 FBO.tempCanvas.height = h;
929 return FBO.tempCanvas;
932 use : function() {
933 if (!this.initialized) this.init();
934 this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
938 function GLError(err, msg, fileName, lineNumber) {
939 this.message = msg;
940 this.glError = err;
943 GLError.prototype = new Error();
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));
954 return rv;
955 } catch (e) {
956 if (e.glError !== undefined) {
957 throw e;
959 throw(new Error("Threw " + e.name +
960 " in " + fname + "\n" +
961 e.message + "\n" +
962 arguments.callee.caller));
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];
976 } catch (e) {
977 // log("wrapGLContext: Error accessing " + i);
980 wrap.getError = function(){ return gl.getError(); };
981 return wrap;
984 function getGLContext(canvas) {
985 return canvas.getContext(GL_CONTEXT_ID, {antialias: false});
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);
1001 return false;
1003 return true;
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; }
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;
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));
1029 var expectedErrorListStr = "[" + expectedErrorStrList.join(", ") + "]";
1031 if (actualError in expectedErrorSet) {
1032 return true;
1035 testFailed("assertGLError: expected: " + expectedErrorListStr +
1036 " actual: " + getGLErrorAsString(gl, actualError), name, f);
1037 return false;
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);
1055 return false;
1057 return true;
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;
1079 testPassed("assertThrowNoGLError", name, f);
1080 return true;
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;
1094 var text = shouldThrow ? "Should throw: "
1095 : "Should not throw: ";
1096 var func = (didThrow == shouldThrow) ? testPassed : testFailed;
1098 func(text + info);
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
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
1118 texcoords : [
1119 0,0,
1120 1,0,
1121 0,1,
1122 1,0,
1123 1,1,
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}
1134 cache: {},
1135 getCachedVBO : function(gl) {
1136 if (!this.cache[gl])
1137 this.cache[gl] = this.makeVBO(gl);
1138 return this.cache[gl];
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,
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,
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,
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,
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,
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,
1173 normals : [ 1, 0, 0,
1174 1, 0, 0,
1175 1, 0, 0,
1176 1, 0, 0,
1178 0, 1, 0,
1179 0, 1, 0,
1180 0, 1, 0,
1181 0, 1, 0,
1183 0, 0, 1,
1184 0, 0, 1,
1185 0, 0, 1,
1186 0, 0, 1,
1188 -1, 0, 0,
1189 -1, 0, 0,
1190 -1, 0, 0,
1191 -1, 0, 0,
1193 0,-1, 0,
1194 0,-1, 0,
1195 0,-1, 0,
1196 0,-1, 0,
1198 0, 0,-1,
1199 0, 0,-1,
1200 0, 0,-1,
1201 0, 0,-1
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);
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}
1223 cache : {},
1224 getCachedVBO : function(gl) {
1225 if (!this.cache[gl])
1226 this.cache[gl] = this.makeVBO(gl);
1227 return this.cache[gl];
1230 Cube.create();
1232 Sphere = {
1233 vertices : [],
1234 normals : [],
1235 indices : [],
1236 create : function(){
1237 var r = 0.75;
1238 function vert(theta, phi)
1240 var r = 0.75;
1241 var x, y, z, nx, ny, nz;
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);
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);
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);
1267 Sphere.create();
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;
1279 } catch (e) {
1282 if (!GL_CONTEXT_ID) {
1283 log("No WebGL context found. Unable to run tests.");
1287 initGL_CONTEXT_ID();