2 Utilities for the OpenGL ES 2.0 HTML Canvas context
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.
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
);
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
);
21 gl
.texParameteri(gl
.TEXTURE_2D
, gl
.TEXTURE_MIN_FILTER
, gl
.LINEAR_MIPMAP_LINEAR
);
23 gl
.texParameteri(gl
.TEXTURE_2D
, gl
.TEXTURE_MIN_FILTER
, gl
.LINEAR
);
27 function getShader(gl
, id
) {
28 var shaderScript
= document
.getElementById(id
);
30 throw(new Error("No shader element with id: "+id
));
34 var k
= shaderScript
.firstChild
;
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
);
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
));
61 function loadShaderArray(gl
, shaders
) {
62 var id
= gl
.createProgram();
64 for (var i
=0; i
<shaders
.length
; ++i
) {
66 var sh
= getShader(gl
, shaders
[i
]);
68 gl
.attachShader(id
, sh
);
70 var pr
= {program
: id
, shaders
: shaderObjs
};
75 var prog
= {program
: id
, shaders
: shaderObjs
};
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"));
88 function loadShader(gl
) {
90 for (var i
=1; i
<arguments
.length
; ++i
)
91 sh
.push(arguments
[i
]);
92 return loadShaderArray(gl
, sh
);
95 function deleteShader(gl
, sh
) {
97 sh
.shaders
.forEach(function(s
){
98 gl
.detachShader(sh
.program
, s
);
101 gl
.deleteProgram(sh
.program
);
104 function getGLErrorAsString(ctx
, err
) {
105 if (err
=== ctx
.NO_ERROR
) {
108 for (var name
in ctx
) {
109 if (ctx
[name
] === err
) {
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
);
124 function throwError(gl
, msg
) {
125 var e
= gl
.getError();
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.
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
160 newIdentity : function() {
169 newIdentity3x3 : function() {
177 copyMatrix : function(src
, dst
) {
178 for (var i
=0; i
<16; i
++) dst
[i
] = src
[i
];
182 to3x3 : function(m
) {
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
);
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];
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];
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
);
277 perspective : function (fovy
, aspect
, znear
, zfar
) {
278 var ymax
= znear
* Math
.tan(fovy
* Math
.PI
/ 360.0);
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
) {
295 c
[0+1] = b
[0] * a
[1] +
299 c
[0+2] = b
[0] * a
[2] +
303 c
[0+3] = b
[0] * a
[3] +
311 c
[4+1] = b
[4] * a
[1] +
315 c
[4+2] = b
[4] * a
[2] +
319 c
[4+3] = b
[4] * a
[3] +
327 c
[8+1] = b
[8] * a
[1] +
331 c
[8+2] = b
[8] * a
[2] +
335 c
[8+3] = b
[8] * a
[3] +
339 c
[12] = b
[12] * a
[0] +
343 c
[12+1] = b
[12] * a
[1] +
347 c
[12+2] = b
[12] * a
[2] +
351 c
[12+3] = b
[12] * a
[3] +
358 mulv4 : function (a
, v
) {
360 for (var i
=0; i
<4; ++i
) {
362 for (var k
=0; k
<4; ++k
)
363 x
+= v
[k
] * a
[k
*4+i
];
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
);
374 var s
= Math
.sin(angle
);
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,
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
);
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
) {
406 scale3 : function(x
,y
,z
) {
414 scale1 : function(s
) {
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
) {
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
));
477 -eye
[0], -eye
[1], -eye
[2], 1
480 return this.mul4x4(m
,t
);
483 transpose4x4 : function(m
) {
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
) {
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
;
503 transpose3x3 : function(m
) {
511 transpose3x3InPlace : function(m
) {
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
;
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
;
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
) {
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
){
574 this.uniformLocations
= {};
575 this.attribLocations
= {};
576 for (var i
=1; i
<arguments
.length
; i
++) {
577 this.shaders
.push(arguments
[i
]);
587 destroy : function() {
588 if (this.shader
!= null) deleteShader(this.gl
, this.shader
);
591 compile : function() {
592 this.shader
= loadShaderArray(this.gl
, this.shaders
);
596 if (this.shader
== null)
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
) {
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
);
729 this.elementsVBO
= null;
730 for (var i
=1; i
<arguments
.length
; i
++) {
731 if (arguments
[i
].elements
)
732 this.elements
= arguments
[i
];
734 this.data
.push(arguments
[i
]);
746 setData : function() {
749 for (var i
=0; i
<arguments
.length
; i
++) {
750 if (arguments
[i
].elements
)
751 this.elements
= arguments
[i
];
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;
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();
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
)
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");
809 for (var i
=0; i
<vbos
.length
; i
++)
810 gl
.deleteBuffer(vbos
[i
]);
814 gl
.bindBuffer(gl
.ARRAY_BUFFER
, null);
815 gl
.bindBuffer(gl
.ELEMENT_ARRAY_BUFFER
, null);
817 this.length
= length
;
820 this.initialized
= true;
824 if (!this.initialized
) this.init();
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
);
839 this.use.apply(this, arguments
);
841 if (this.elementsVBO
!= null) {
842 gl
.drawElements(gl
[this.type
], this.elementsLength
, this.elementsType
, 0);
844 gl
.drawArrays(gl
[this.type
], 0, this.length
);
849 FBO = function(gl
, width
, height
, use_depth
) {
852 this.height
= height
;
853 if (use_depth
!= null)
854 this.useDepth
= use_depth
;
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
);
871 var w
= this.width
, h
= this.height
;
872 var fbo
= this.fbo
!= null ? this.fbo
: gl
.createFramebuffer();
875 gl
.bindFramebuffer(gl
.FRAMEBUFFER
, fbo
);
876 checkError(gl
, "FBO.init bindFramebuffer");
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
);
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");
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
) {
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");
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
;
933 if (!this.initialized
) this.init();
934 this.gl
.bindFramebuffer(this.gl
.FRAMEBUFFER
, this.fbo
);
938 function GLError(err
, msg
, fileName
, lineNumber
) {
943 GLError
.prototype = new Error();
945 function makeGLErrorWrapper(gl
, fname
) {
948 var rv
= gl
[fname
].apply(gl
, arguments
);
949 var err
= gl
.getError();
950 if (err
!= gl
.NO_ERROR
) {
952 err
, "GL error "+getGLErrorAsString(gl
, err
)+" in "+fname
));
956 if (e
.glError
!== undefined) {
959 throw(new Error("Threw " + e
.name
+
960 " in " + fname
+ "\n" +
962 arguments
.callee
.caller
));
967 function wrapGLContext(gl
) {
971 if (typeof gl
[i
] == 'function') {
972 wrap
[i
] = makeGLErrorWrapper(gl
, i
);
977 // log("wrapGLContext: Error accessing " + i);
980 wrap
.getError = function(){ return gl
.getError(); };
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; }
993 try { f(); } catch(e
) { r
=true; glErr
= e
.glError
; }
995 if (glErr
=== undefined) {
996 testFailed("assertGLError: UNEXPECTED EXCEPTION", name
, f
);
998 testFailed("assertGLError: expected: " + getGLErrorAsString(gl
, err
) +
999 " actual: " + getGLErrorAsString(gl
, glErr
), name
, f
);
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;
1014 if ('glError' in e
) {
1015 actualError
= e
.glError
;
1017 testFailed("assertGLError: UNEXPCETED EXCEPTION", name
, f
);
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
) {
1035 testFailed("assertGLError: expected: " + expectedErrorListStr
+
1036 " actual: " + getGLErrorAsString(gl
, actualError
), name
, f
);
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; }
1047 try { f(); } catch(e
) { r
=true; glErr
= e
.glError
; }
1049 if (glErr
=== undefined) {
1050 testFailed("assertGLError: UNEXPECTED EXCEPTION", name
, f
);
1052 testFailed("assertGLError: expected: " + getGLErrorAsString(gl
, err
) +
1053 " actual: " + getGLErrorAsString(gl
, glErr
), name
, f
);
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; }
1064 var glErr
= undefined;
1066 try { f(); } catch(e
) { r
=true; glErr
= e
.glError
; exp
= e
;}
1069 "assertThrowNoGLError: should have thrown exception", name
, f
);
1072 if (glErr
!== undefined) {
1074 "assertThrowNoGLError: should be no GL error but generated: " +
1075 getGLErrorAsString(gl
, glErr
), name
, f
);
1079 testPassed("assertThrowNoGLError", name
, f
);
1083 function assertThrows(gl
, shouldThrow
, info
, func
) {
1084 var didThrow
= false;
1088 var didGLError
= (e
instanceof GLError
);
1094 var text
= shouldThrow
? "Should throw: "
1095 : "Should not throw: ";
1096 var func
= (didThrow
== shouldThrow
) ? testPassed
: testFailed
;
1126 indices
: [0,1,2,1,5,2],
1127 makeVBO : function(gl
) {
1129 {size
:3, data
: Quad
.vertices
},
1130 {size
:3, data
: Quad
.normals
},
1131 {size
:2, data
: Quad
.texcoords
}
1135 getCachedVBO : function(gl
) {
1136 if (!this.cache
[gl
])
1137 this.cache
[gl
] = this.makeVBO(gl
);
1138 return this.cache
[gl
];
1142 vertices
: [ 0.5, -0.5, 0.5, // +X
1147 0.5, 0.5, 0.5, // +Y
1152 0.5, 0.5, 0.5, // +Z
1157 -0.5, -0.5, 0.5, // -X
1162 -0.5, -0.5, 0.5, // -Y
1167 -0.5, -0.5, -0.5, // -Z
1173 normals
: [ 1, 0, 0,
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
) {
1218 {size
:3, data
: Cube
.vertices
},
1219 {size
:3, data
: Cube
.normals
},
1220 {elements
: true, data
: Cube
.indices
}
1224 getCachedVBO : function(gl
) {
1225 if (!this.cache
[gl
])
1226 this.cache
[gl
] = this.makeVBO(gl
);
1227 return this.cache
[gl
];
1236 create : function(){
1238 function vert(theta
, phi
)
1241 var x
, y
, z
, nx
, ny
, nz
;
1243 nx
= Math
.sin(theta
) * Math
.cos(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) {
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
++) {
1275 if (c
.getContext(contextNames
[i
])) {
1276 GL_CONTEXT_ID
= contextNames
[i
];
1282 if (!GL_CONTEXT_ID
) {
1283 log("No WebGL context found. Unable to run tests.");
1287 initGL_CONTEXT_ID();