1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 WebGLTestUtils
= (function() {
8 * Wrapped logging function.
9 * @param {string} msg The message to log.
11 var log = function(msg
) {
12 if (window
.console
&& window
.console
.log
) {
13 window
.console
.log(msg
);
18 * Wrapped logging function.
19 * @param {string} msg The message to log.
21 var error = function(msg
) {
23 if (window
.console
.error
) {
24 window
.console
.error(msg
);
26 else if (window
.console
.log
) {
27 window
.console
.log(msg
);
33 * Turn off all logging.
35 var loggingOff = function() {
37 error = function() {};
41 * Converts a WebGL enum to a string
42 * @param {!WebGLContext} gl The WebGLContext to use.
43 * @param {number} value The enum value.
44 * @return {string} The enum as a string.
46 var glEnumToString = function(gl
, value
) {
52 return "0x" + value
.toString(16);
58 * Returns the last compiler/linker error.
59 * @return {string} The last compiler/linker error.
61 var getLastError = function() {
66 * Whether a haystack ends with a needle.
67 * @param {string} haystack String to search
68 * @param {string} needle String to search for.
69 * @param {boolean} True if haystack ends with needle.
71 var endsWith = function(haystack
, needle
) {
72 return haystack
.substr(haystack
.length
- needle
.length
) === needle
;
76 * Whether a haystack starts with a needle.
77 * @param {string} haystack String to search
78 * @param {string} needle String to search for.
79 * @param {boolean} True if haystack starts with needle.
81 var startsWith = function(haystack
, needle
) {
82 return haystack
.substr(0, needle
.length
) === needle
;
86 * A vertex shader for a single texture.
89 var simpleTextureVertexShader
= [
90 'attribute vec4 vPosition;',
91 'attribute vec2 texCoord0;',
92 'varying vec2 texCoord;',
94 ' gl_Position = vPosition;',
95 ' texCoord = texCoord0;',
99 * A fragment shader for a single texture.
102 var simpleTextureFragmentShader
= [
103 'precision mediump float;',
104 'uniform sampler2D tex;',
105 'varying vec2 texCoord;',
107 ' gl_FragData[0] = texture2D(tex, texCoord);',
111 * A vertex shader for a single texture.
114 var simpleColorVertexShader
= [
115 'attribute vec4 vPosition;',
117 ' gl_Position = vPosition;',
121 * A fragment shader for a color.
124 var simpleColorFragmentShader
= [
125 'precision mediump float;',
126 'uniform vec4 u_color;',
128 ' gl_FragData[0] = u_color;',
132 * Creates a simple texture vertex shader.
133 * @param {!WebGLContext} gl The WebGLContext to use.
134 * @return {!WebGLShader}
136 var setupSimpleTextureVertexShader = function(gl
) {
137 return loadShader(gl
, simpleTextureVertexShader
, gl
.VERTEX_SHADER
);
141 * Creates a simple texture fragment shader.
142 * @param {!WebGLContext} gl The WebGLContext to use.
143 * @return {!WebGLShader}
145 var setupSimpleTextureFragmentShader = function(gl
) {
147 gl
, simpleTextureFragmentShader
, gl
.FRAGMENT_SHADER
);
151 * Creates a program, attaches shaders, binds attrib locations, links the
152 * program and calls useProgram.
153 * @param {!Array.<!WebGLShader>} shaders The shaders to attach .
154 * @param {!Array.<string>} opt_attribs The attribs names.
155 * @param {!Array.<number>} opt_locations The locations for the attribs.
157 var setupProgram = function(gl
, shaders
, opt_attribs
, opt_locations
) {
158 var realShaders
= [];
159 var program
= gl
.createProgram();
160 for (var ii
= 0; ii
< shaders
.length
; ++ii
) {
161 var shader
= shaders
[ii
];
162 if (typeof shader
== 'string') {
163 var element
= document
.getElementById(shader
);
165 shader
= loadShaderFromScript(gl
, shader
);
167 shader
= loadShader(gl
, shader
, ii
? gl
.FRAGMENT_SHADER
: gl
.VERTEX_SHADER
);
170 gl
.attachShader(program
, shader
);
173 for (var ii
= 0; ii
< opt_attribs
.length
; ++ii
) {
174 gl
.bindAttribLocation(
176 opt_locations
? opt_locations
[ii
] : ii
,
180 gl
.linkProgram(program
);
182 // Check the link status
183 var linked
= gl
.getProgramParameter(program
, gl
.LINK_STATUS
);
185 // something went wrong with the link
186 lastError
= gl
.getProgramInfoLog (program
);
187 error("Error in program linking:" + lastError
);
189 gl
.deleteProgram(program
);
193 gl
.useProgram(program
);
198 * Creates a simple texture program.
199 * @param {!WebGLContext} gl The WebGLContext to use.
200 * @param {number} opt_positionLocation The attrib location for position.
201 * @param {number} opt_texcoordLocation The attrib location for texture coords.
202 * @return {WebGLProgram}
204 var setupSimpleTextureProgram = function(
205 gl
, opt_positionLocation
, opt_texcoordLocation
) {
206 opt_positionLocation
= opt_positionLocation
|| 0;
207 opt_texcoordLocation
= opt_texcoordLocation
|| 1;
208 var vs
= setupSimpleTextureVertexShader(gl
);
209 var fs
= setupSimpleTextureFragmentShader(gl
);
213 var program
= setupProgram(
216 ['vPosition', 'texCoord0'],
217 [opt_positionLocation
, opt_texcoordLocation
]);
222 gl
.useProgram(program
);
227 * Creates buffers for a textured unit quad and attaches them to vertex attribs.
228 * @param {!WebGLContext} gl The WebGLContext to use.
229 * @param {number} opt_positionLocation The attrib location for position.
230 * @param {number} opt_texcoordLocation The attrib location for texture coords.
231 * @return {!Array.<WebGLBuffer>} The buffer objects that were
234 var setupUnitQuad = function(gl
, opt_positionLocation
, opt_texcoordLocation
) {
235 opt_positionLocation
= opt_positionLocation
|| 0;
236 opt_texcoordLocation
= opt_texcoordLocation
|| 1;
239 var vertexObject
= gl
.createBuffer();
240 gl
.bindBuffer(gl
.ARRAY_BUFFER
, vertexObject
);
241 gl
.bufferData(gl
.ARRAY_BUFFER
, new Float32Array([
247 1.0, -1.0, 0.0]), gl
.STATIC_DRAW
);
248 gl
.enableVertexAttribArray(opt_positionLocation
);
249 gl
.vertexAttribPointer(opt_positionLocation
, 3, gl
.FLOAT
, false, 0, 0);
250 objects
.push(vertexObject
);
252 var vertexObject
= gl
.createBuffer();
253 gl
.bindBuffer(gl
.ARRAY_BUFFER
, vertexObject
);
254 gl
.bufferData(gl
.ARRAY_BUFFER
, new Float32Array([
260 1.0, 0.0]), gl
.STATIC_DRAW
);
261 gl
.enableVertexAttribArray(opt_texcoordLocation
);
262 gl
.vertexAttribPointer(opt_texcoordLocation
, 2, gl
.FLOAT
, false, 0, 0);
263 objects
.push(vertexObject
);
268 * Creates a program and buffers for rendering a textured quad.
269 * @param {!WebGLContext} gl The WebGLContext to use.
270 * @param {number} opt_positionLocation The attrib location for position.
271 * @param {number} opt_texcoordLocation The attrib location for texture coords.
272 * @return {!WebGLProgram}
274 var setupTexturedQuad = function(
275 gl
, opt_positionLocation
, opt_texcoordLocation
) {
276 var program
= setupSimpleTextureProgram(
277 gl
, opt_positionLocation
, opt_texcoordLocation
);
278 setupUnitQuad(gl
, opt_positionLocation
, opt_texcoordLocation
);
283 * Creates a program and buffers for rendering a color quad.
284 * @param {!WebGLContext} gl The WebGLContext to use.
285 * @param {number} opt_positionLocation The attrib location for position.
286 * @return {!WebGLProgram}
288 var setupColorQuad = function(gl
, opt_positionLocation
) {
289 opt_positionLocation
= opt_positionLocation
|| 0;
290 var program
= wtu
.setupProgram(
292 [simpleColorVertexShader
, simpleColorFragmentShader
],
294 [opt_positionLocation
]);
295 setupUnitQuad(gl
, opt_positionLocation
);
300 * Creates a unit quad with only positions of a given rez
301 * @param {!WebGLContext} gl The WebGLContext to use.
302 * @param {number} gridRez The resolution of the mesh grid.
303 * @param {number} opt_positionLocation The attrib location for position.
305 var setupQuad = function (
306 gl
, gridRes
, opt_positionLocation
, opt_flipOddTriangles
) {
307 var positionLocation
= opt_positionLocation
|| 0;
310 var vertsAcross
= gridRes
+ 1;
311 var numVerts
= vertsAcross
* vertsAcross
;
312 var positions
= new Float32Array(numVerts
* 3);
313 var indices
= new Uint16Array(6 * gridRes
* gridRes
);
317 for (var yy
= 0; yy
<= gridRes
; ++yy
) {
318 for (var xx
= 0; xx
<= gridRes
; ++xx
) {
319 positions
[poffset
+ 0] = -1 + 2 * xx
/ gridRes
;
320 positions
[poffset
+ 1] = -1 + 2 * yy
/ gridRes
;
321 positions
[poffset
+ 2] = 0;
328 for (var yy
= 0; yy
< gridRes
; ++yy
) {
329 var index
= yy
* vertsAcross
;
330 for (var xx
= 0; xx
< gridRes
; ++xx
) {
331 indices
[tbase
+ 0] = index
+ 0;
332 indices
[tbase
+ 1] = index
+ 1;
333 indices
[tbase
+ 2] = index
+ vertsAcross
;
334 indices
[tbase
+ 3] = index
+ vertsAcross
;
335 indices
[tbase
+ 4] = index
+ 1;
336 indices
[tbase
+ 5] = index
+ vertsAcross
+ 1;
338 if (opt_flipOddTriangles
) {
339 indices
[tbase
+ 4] = index
+ vertsAcross
+ 1;
340 indices
[tbase
+ 5] = index
+ 1;
348 var buf
= gl
.createBuffer();
349 gl
.bindBuffer(gl
.ARRAY_BUFFER
, buf
);
350 gl
.bufferData(gl
.ARRAY_BUFFER
, positions
, gl
.STATIC_DRAW
);
351 gl
.enableVertexAttribArray(positionLocation
);
352 gl
.vertexAttribPointer(positionLocation
, 3, gl
.FLOAT
, false, 0, 0);
355 var buf
= gl
.createBuffer();
356 gl
.bindBuffer(gl
.ELEMENT_ARRAY_BUFFER
, buf
);
357 gl
.bufferData(gl
.ELEMENT_ARRAY_BUFFER
, indices
, gl
.STATIC_DRAW
);
364 * Fills the given texture with a solid color
365 * @param {!WebGLContext} gl The WebGLContext to use.
366 * @param {!WebGLTexture} tex The texture to fill.
367 * @param {number} width The width of the texture to create.
368 * @param {number} height The height of the texture to create.
369 * @param {!Array.<number>} color The color to fill with. A 4 element array
370 * where each element is in the range 0 to 255.
371 * @param {number} opt_level The level of the texture to fill. Default = 0.
373 var fillTexture = function(gl
, tex
, width
, height
, color
, opt_level
) {
374 opt_level
= opt_level
|| 0;
375 var numPixels
= width
* height
;
376 var size
= numPixels
* 4;
377 var buf
= new Uint8Array(size
);
378 for (var ii
= 0; ii
< numPixels
; ++ii
) {
380 buf
[off
+ 0] = color
[0];
381 buf
[off
+ 1] = color
[1];
382 buf
[off
+ 2] = color
[2];
383 buf
[off
+ 3] = color
[3];
385 gl
.bindTexture(gl
.TEXTURE_2D
, tex
);
387 gl
.TEXTURE_2D
, opt_level
, gl
.RGBA
, width
, height
, 0,
388 gl
.RGBA
, gl
.UNSIGNED_BYTE
, buf
);
392 * Creates a textures and fills it with a solid color
393 * @param {!WebGLContext} gl The WebGLContext to use.
394 * @param {number} width The width of the texture to create.
395 * @param {number} height The height of the texture to create.
396 * @param {!Array.<number>} color The color to fill with. A 4 element array
397 * where each element is in the range 0 to 255.
398 * @return {!WebGLTexture}
400 var createColoredTexture = function(gl
, width
, height
, color
) {
401 var tex
= gl
.createTexture();
402 fillTexture(gl
, tex
, width
, height
, color
);
406 var ubyteToFloat = function(c
) {
411 * Draws a previously setup quad in the given color.
412 * @param {!WebGLContext} gl The WebGLContext to use.
413 * @param {!Array.<number>} color The color to draw with. A 4
414 * element array where each element is in the range 0 to
417 var drawFloatColorQuad = function(gl
, color
) {
418 var program
= gl
.getParameter(gl
.CURRENT_PROGRAM
);
419 var colorLocation
= gl
.getUniformLocation(program
, "u_color");
420 gl
.uniform4fv(colorLocation
, color
);
421 gl
.drawArrays(gl
.TRIANGLES
, 0, 6);
426 * Draws a previously setup quad in the given color.
427 * @param {!WebGLContext} gl The WebGLContext to use.
428 * @param {!Array.<number>} color The color to draw with. A 4
429 * element array where each element is in the range 0 to
432 var drawUByteColorQuad = function(gl
, color
) {
434 for (var ii
= 0; ii
< color
.length
; ++ii
) {
435 floatColor
[ii
] = ubyteToFloat(color
[ii
]);
437 drawFloatColorQuad(gl
, floatColor
);
441 * Draws a previously setup quad.
442 * @param {!WebGLContext} gl The WebGLContext to use.
443 * @param {!Array.<number>} opt_color The color to fill clear with before
444 * drawing. A 4 element array where each element is in the range 0 to
445 * 255. Default [255, 255, 255, 255]
447 var drawQuad = function(gl
, opt_color
) {
448 opt_color
= opt_color
|| [255, 255, 255, 255];
454 gl
.clear(gl
.COLOR_BUFFER_BIT
| gl
.DEPTH_BUFFER_BIT
);
455 gl
.drawArrays(gl
.TRIANGLES
, 0, 6);
459 * Draws a previously setup quad.
460 * @param {!WebGLContext} gl The WebGLContext to use.
461 * @param {number} gridRes Resolution of grid.
462 * @param {!Array.<number>} opt_color The color to fill clear with before
463 * drawing. A 4 element array where each element is in the range 0 to
464 * 255. Default [255, 255, 255, 255]
466 var drawIndexedQuad = function(gl
, gridRes
, opt_color
) {
467 opt_color
= opt_color
|| [255, 255, 255, 255];
473 gl
.clear(gl
.COLOR_BUFFER_BIT
| gl
.DEPTH_BUFFER_BIT
);
474 gl
.drawElements(gl
.TRIANGLES
, gridRes
* 6, gl
.UNSIGNED_SHORT
, 0);
478 * Checks that a portion of a canvas is 1 color.
479 * @param {!WebGLContext} gl The WebGLContext to use.
480 * @param {number} x left corner of region to check.
481 * @param {number} y bottom corner of region to check.
482 * @param {number} width width of region to check.
483 * @param {number} height width of region to check.
484 * @param {!Array.<number>} color The color to fill clear with before drawing. A
485 * 4 element array where each element is in the range 0 to 255.
486 * @param {string} msg Message to associate with success. Eg ("should be red").
487 * @param {number} errorRange Optional. Acceptable error in
488 * color checking. 0 by default.
490 var checkCanvasRect = function(gl
, x
, y
, width
, height
, color
, msg
, errorRange
) {
491 errorRange
= errorRange
|| 0;
492 var buf
= new Uint8Array(width
* height
* 4);
493 gl
.readPixels(x
, y
, width
, height
, gl
.RGBA
, gl
.UNSIGNED_BYTE
, buf
);
494 for (var i
= 0; i
< width
* height
; ++i
) {
496 for (var j
= 0; j
< color
.length
; ++j
) {
497 if (Math
.abs(buf
[offset
+ j
] - color
[j
]) > errorRange
) {
499 var was
= buf
[offset
+ 0].toString();
500 for (j
= 1; j
< color
.length
; ++j
) {
501 was
+= "," + buf
[offset
+ j
];
503 debug('at (' + (i
% width
) + ', ' + Math
.floor(i
/ width
) +
504 ') expected: ' + color
+ ' was ' + was
);
513 * Checks that an entire canvas is 1 color.
514 * @param {!WebGLContext} gl The WebGLContext to use.
515 * @param {!Array.<number>} color The color to fill clear with before drawing. A
516 * 4 element array where each element is in the range 0 to 255.
517 * @param {string} msg Message to associate with success. Eg ("should be red").
518 * @param {number} errorRange Optional. Acceptable error in
519 * color checking. 0 by default.
521 var checkCanvas = function(gl
, color
, msg
, errorRange
) {
522 checkCanvasRect(gl
, 0, 0, gl
.canvas
.width
, gl
.canvas
.height
, color
, msg
, errorRange
);
526 * Loads a texture, calls callback when finished.
527 * @param {!WebGLContext} gl The WebGLContext to use.
528 * @param {string} url URL of image to load
529 * @param {function(!Image): void} callback Function that gets called after
531 * @return {!WebGLTexture} The created texture.
533 var loadTexture = function(gl
, url
, callback
) {
534 var texture
= gl
.createTexture();
535 gl
.bindTexture(gl
.TEXTURE_2D
, texture
);
536 gl
.texParameteri(gl
.TEXTURE_2D
, gl
.TEXTURE_MIN_FILTER
, gl
.NEAREST
);
537 gl
.texParameteri(gl
.TEXTURE_2D
, gl
.TEXTURE_MAG_FILTER
, gl
.NEAREST
);
538 var image
= new Image();
539 image
.onload = function() {
540 gl
.bindTexture(gl
.TEXTURE_2D
, texture
);
541 gl
.pixelStorei(gl
.UNPACK_FLIP_Y_WEBGL
, true);
542 gl
.texImage2D(gl
.TEXTURE_2D
, 0, gl
.RGBA
, gl
.RGBA
, gl
.UNSIGNED_BYTE
, image
);
550 * Creates a webgl context.
551 * @param {!Canvas} opt_canvas The canvas tag to get context from. If one is not
552 * passed in one will be created.
553 * @return {!WebGLContext} The created context.
555 var create3DContext = function(opt_canvas
, opt_attributes
) {
556 opt_canvas
= opt_canvas
|| document
.createElement("canvas");
557 if (typeof opt_canvas
== 'string') {
558 opt_canvas
= document
.getElementById(opt_canvas
);
561 var names
= ["webgl", "webgl"];
562 for (var i
= 0; i
< names
.length
; ++i
) {
564 context
= opt_canvas
.getContext(names
[i
], opt_attributes
);
572 testFailed("Unable to fetch WebGL rendering context for Canvas");
578 * Gets a GLError value as a string.
579 * @param {!WebGLContext} gl The WebGLContext to use.
580 * @param {number} err The webgl error as retrieved from gl.getError().
581 * @return {string} the error as a string.
583 var getGLErrorAsString = function(gl
, err
) {
584 if (err
=== gl
.NO_ERROR
) {
587 for (var name
in gl
) {
588 if (gl
[name
] === err
) {
592 return err
.toString();
596 * Wraps a WebGL function with a function that throws an exception if there is
598 * @param {!WebGLContext} gl The WebGLContext to use.
599 * @param {string} fname Name of function to wrap.
600 * @return {function} The wrapped function.
602 var createGLErrorWrapper = function(context
, fname
) {
604 var rv
= context
[fname
].apply(context
, arguments
);
605 var err
= context
.getError();
607 throw "GL error " + getGLErrorAsString(err
) + " in " + fname
;
613 * Creates a WebGL context where all functions are wrapped to throw an exception
614 * if there is an error.
615 * @param {!Canvas} canvas The HTML canvas to get a context from.
616 * @return {!Object} The wrapped context.
618 function create3DContextWithWrapperThatThrowsOnGLError(canvas
) {
619 var context
= create3DContext(canvas
);
621 for (var i
in context
) {
623 if (typeof context
[i
] == 'function') {
624 wrap
[i
] = createGLErrorWrapper(context
, i
);
626 wrap
[i
] = context
[i
];
629 error("createContextWrapperThatThrowsOnGLError: Error accessing " + i
);
632 wrap
.getError = function() {
633 return context
.getError();
639 * Tests that an evaluated expression generates a specific GL error.
640 * @param {!WebGLContext} gl The WebGLContext to use.
641 * @param {number} glError The expected gl error.
642 * @param {string} evalSTr The string to evaluate.
644 var shouldGenerateGLError = function(gl
, glError
, evalStr
) {
652 testFailed(evalStr
+ " threw exception " + exception
);
654 var err
= gl
.getError();
655 if (err
!= glError
) {
656 testFailed(evalStr
+ " expected: " + getGLErrorAsString(gl
, glError
) + ". Was " + getGLErrorAsString(gl
, err
) + ".");
658 testPassed(evalStr
+ " was expected value: " + getGLErrorAsString(gl
, glError
) + ".");
664 * Tests that the first error GL returns is the specified error.
665 * @param {!WebGLContext} gl The WebGLContext to use.
666 * @param {number} glError The expected gl error.
667 * @param {string} opt_msg
669 var glErrorShouldBe = function(gl
, glError
, opt_msg
) {
670 opt_msg
= opt_msg
|| "";
671 var err
= gl
.getError();
672 if (err
!= glError
) {
673 testFailed("getError expected: " + getGLErrorAsString(gl
, glError
) +
674 ". Was " + getGLErrorAsString(gl
, err
) + " : " + opt_msg
);
676 testPassed("getError was expected value: " +
677 getGLErrorAsString(gl
, glError
) + " : " + opt_msg
);
682 * Links a WebGL program, throws if there are errors.
683 * @param {!WebGLContext} gl The WebGLContext to use.
684 * @param {!WebGLProgram} program The WebGLProgram to link.
685 * @param {function(string): void) opt_errorCallback callback for errors.
687 var linkProgram = function(gl
, program
, opt_errorCallback
) {
689 gl
.linkProgram(program
);
691 // Check the link status
692 var linked
= gl
.getProgramParameter(program
, gl
.LINK_STATUS
);
694 // something went wrong with the link
695 var error
= gl
.getProgramInfoLog (program
);
697 testFailed("Error in program linking:" + error
);
699 gl
.deleteProgram(program
);
704 * Sets up WebGL with shaders.
705 * @param {string} canvasName The id of the canvas.
706 * @param {string} vshader The id of the script tag that contains the vertex
708 * @param {string} fshader The id of the script tag that contains the fragment
710 * @param {!Array.<string>} attribs An array of attrib names used to bind
711 * attribs to the ordinal of the name in this array.
712 * @param {!Array.<number>} opt_clearColor The color to cla
713 * @return {!WebGLContext} The created WebGLContext.
715 var setupWebGLWithShaders = function(
716 canvasName
, vshader
, fshader
, attribs
) {
717 var canvas
= document
.getElementById(canvasName
);
718 var gl
= create3DContext(canvas
);
720 testFailed("No WebGL context found");
723 // create our shaders
724 var vertexShader
= loadShaderFromScript(gl
, vshader
);
725 var fragmentShader
= loadShaderFromScript(gl
, fshader
);
727 if (!vertexShader
|| !fragmentShader
) {
731 // Create the program object
732 program
= gl
.createProgram();
738 // Attach our two shaders to the program
739 gl
.attachShader (program
, vertexShader
);
740 gl
.attachShader (program
, fragmentShader
);
743 for (var i
in attribs
) {
744 gl
.bindAttribLocation (program
, i
, attribs
[i
]);
747 linkProgram(gl
, program
);
749 gl
.useProgram(program
);
751 gl
.clearColor(0,0,0,1);
754 gl
.enable(gl
.DEPTH_TEST
);
756 gl
.blendFunc(gl
.SRC_ALPHA
, gl
.ONE_MINUS_SRC_ALPHA
);
758 gl
.program
= program
;
763 * Loads text from an external file. This function is synchronous.
764 * @param {string} url The url of the external file.
765 * @param {!function(bool, string): void} callback that is sent a bool for
766 * success and the string.
768 var loadTextFileAsync = function(url
, callback
) {
769 log ("loading: " + url
);
770 var error
= 'loadTextFileSynchronous failed to load url "' + url
+ '"';
772 if (window
.XMLHttpRequest
) {
773 request
= new XMLHttpRequest();
774 if (request
.overrideMimeType
) {
775 request
.overrideMimeType('text/plain');
778 throw 'XMLHttpRequest is disabled';
781 request
.open('GET', url
, true);
782 request
.onreadystatechange = function() {
783 if (request
.readyState
== 4) {
785 // HTTP reports success with a 200 status. The file protocol reports
786 // success with zero. HTTP does not use zero as a status code (they
788 // https://developer.mozilla.org/En/Using_XMLHttpRequest
789 var success
= request
.status
== 200 || request
.status
== 0;
791 text
= request
.responseText
;
793 log("loaded: " + url
);
794 callback(success
, text
);
799 log("failed to load: " + url
);
805 * Recursively loads a file as a list. Each line is parsed for a relative
806 * path. If the file ends in .txt the contents of that file is inserted in
809 * @param {string} url The url of the external file.
810 * @param {!function(bool, Array<string>): void} callback that is sent a bool
811 * for success and the array of strings.
813 var getFileListAsync = function(url
, callback
) {
816 var getFileListImpl = function(url
, callback
) {
818 if (url
.substr(url
.length
- 4) == '.txt') {
819 loadTextFileAsync(url
, function() {
820 return function(success
, text
) {
825 var lines
= text
.split('\n');
827 var lastSlash
= url
.lastIndexOf('/');
828 if (lastSlash
>= 0) {
829 prefix
= url
.substr(0, lastSlash
+ 1);
834 for (var ii
= 0; ii
< lines
.length
; ++ii
) {
835 var str
= lines
[ii
].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
836 if (str
.length
> 4 &&
839 str
.substr(0, 2) != "//") {
840 var names
= str
.split(/ +/);
841 new_url
= prefix
+ str
;
842 if (names
.length
== 1) {
843 new_url
= prefix
+ str
;
845 getFileListImpl(new_url
, function(index
) {
846 return function(success
, new_files
) {
847 log("got files: " + new_files
.length
);
849 files
[index
] = new_files
;
857 for (var jj
= 0; jj
< names
.length
; ++jj
) {
858 s
+= p
+ prefix
+ names
[jj
];
867 function finish(success
) {
872 log("count: " + count
);
874 callback(!fail
, files
);
882 callback(true, files
);
886 getFileListImpl(url
, function(success
, files
) {
890 function flatten(files
) {
891 for (var ii
= 0; ii
< files
.length
; ++ii
) {
892 var value
= files
[ii
];
893 if (typeof(value
) == "string") {
900 callback(success
, flat
);
905 * Gets a file from a file/URL
906 * @param {string} file the URL of the file to get.
907 * @return {string} The contents of the file.
909 var readFile = function(file
) {
910 var xhr
= new XMLHttpRequest();
911 xhr
.open("GET", file
, false);
913 return xhr
.responseText
.replace(/\r/g, "");
916 var readFileList = function(url
) {
918 if (url
.substr(url
.length
- 4) == '.txt') {
919 var lines
= readFile(url
).split('\n');
921 var lastSlash
= url
.lastIndexOf('/');
922 if (lastSlash
>= 0) {
923 prefix
= url
.substr(0, lastSlash
+ 1);
925 for (var ii
= 0; ii
< lines
.length
; ++ii
) {
926 var str
= lines
[ii
].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
927 if (str
.length
> 4 &&
930 str
.substr(0, 2) != "//") {
931 var names
= str
.split(/ +/);
932 if (names
.length
== 1) {
933 new_url
= prefix
+ str
;
934 files
= files
.concat(readFileList(new_url
));
938 for (var jj
= 0; jj
< names
.length
; ++jj
) {
939 s
+= p
+ prefix
+ names
[jj
];
954 * @param {!WebGLContext} gl The WebGLContext to use.
955 * @param {string} shaderSource The shader source.
956 * @param {number} shaderType The type of shader.
957 * @param {function(string): void) opt_errorCallback callback for errors.
958 * @return {!WebGLShader} The created shader.
960 var loadShader = function(gl
, shaderSource
, shaderType
, opt_errorCallback
) {
961 var errFn
= opt_errorCallback
|| error
;
962 // Create the shader object
963 var shader
= gl
.createShader(shaderType
);
964 if (shader
== null) {
965 errFn("*** Error: unable to create shader '"+shaderSource
+"'");
969 // Load the shader source
970 gl
.shaderSource(shader
, shaderSource
);
971 var err
= gl
.getError();
972 if (err
!= gl
.NO_ERROR
) {
973 errFn("*** Error loading shader '" + shader
+ "':" + glEnumToString(gl
, err
));
977 // Compile the shader
978 gl
.compileShader(shader
);
980 // Check the compile status
981 var compiled
= gl
.getShaderParameter(shader
, gl
.COMPILE_STATUS
);
983 // Something went wrong during compilation; get the error
984 lastError
= gl
.getShaderInfoLog(shader
);
985 errFn("*** Error compiling shader '" + shader
+ "':" + lastError
);
986 gl
.deleteShader(shader
);
994 * Loads a shader from a URL.
995 * @param {!WebGLContext} gl The WebGLContext to use.
996 * @param {file} file The URL of the shader source.
997 * @param {number} type The type of shader.
998 * @param {function(string): void) opt_errorCallback callback for errors.
999 * @return {!WebGLShader} The created shader.
1001 var loadShaderFromFile = function(gl
, file
, type
, opt_errorCallback
) {
1002 var shaderSource
= readFile(file
);
1003 return loadShader(gl
, shaderSource
, type
, opt_errorCallback
);
1007 * Loads a shader from a script tag.
1008 * @param {!WebGLContext} gl The WebGLContext to use.
1009 * @param {string} scriptId The id of the script tag.
1010 * @param {number} opt_shaderType The type of shader. If not passed in it will
1011 * be derived from the type of the script tag.
1012 * @param {function(string): void) opt_errorCallback callback for errors.
1013 * @return {!WebGLShader} The created shader.
1015 var loadShaderFromScript = function(
1016 gl
, scriptId
, opt_shaderType
, opt_errorCallback
) {
1017 var shaderSource
= "";
1019 var shaderScript
= document
.getElementById(scriptId
);
1020 if (!shaderScript
) {
1021 throw("*** Error: unknown script element" + scriptId
);
1023 shaderSource
= shaderScript
.text
;
1025 if (!opt_shaderType
) {
1026 if (shaderScript
.type
== "x-shader/x-vertex") {
1027 shaderType
= gl
.VERTEX_SHADER
;
1028 } else if (shaderScript
.type
== "x-shader/x-fragment") {
1029 shaderType
= gl
.FRAGMENT_SHADER
;
1030 } else if (shaderType
!= gl
.VERTEX_SHADER
&& shaderType
!= gl
.FRAGMENT_SHADER
) {
1031 throw("*** Error: unknown shader type");
1037 gl
, shaderSource
, opt_shaderType
? opt_shaderType
: shaderType
,
1041 var loadStandardProgram = function(gl
) {
1042 var program
= gl
.createProgram();
1043 gl
.attachShader(program
, loadStandardVertexShader(gl
));
1044 gl
.attachShader(program
, loadStandardFragmentShader(gl
));
1045 linkProgram(gl
, program
);
1050 * Loads shaders from files, creates a program, attaches the shaders and links.
1051 * @param {!WebGLContext} gl The WebGLContext to use.
1052 * @param {string} vertexShaderPath The URL of the vertex shader.
1053 * @param {string} fragmentShaderPath The URL of the fragment shader.
1054 * @param {function(string): void) opt_errorCallback callback for errors.
1055 * @return {!WebGLProgram} The created program.
1057 var loadProgramFromFile = function(
1058 gl
, vertexShaderPath
, fragmentShaderPath
, opt_errorCallback
) {
1059 var program
= gl
.createProgram();
1063 gl
, vertexShaderPath
, gl
.VERTEX_SHADER
, opt_errorCallback
));
1067 gl
, fragmentShaderPath
, gl
.FRAGMENT_SHADER
, opt_errorCallback
));
1068 linkProgram(gl
, program
, opt_errorCallback
);
1073 * Loads shaders from script tags, creates a program, attaches the shaders and
1075 * @param {!WebGLContext} gl The WebGLContext to use.
1076 * @param {string} vertexScriptId The id of the script tag that contains the
1078 * @param {string} fragmentScriptId The id of the script tag that contains the
1080 * @param {function(string): void) opt_errorCallback callback for errors.
1081 * @return {!WebGLProgram} The created program.
1083 var loadProgramFromScript
= function loadProgramFromScript(
1084 gl
, vertexScriptId
, fragmentScriptId
, opt_errorCallback
) {
1085 var program
= gl
.createProgram();
1088 loadShaderFromScript(
1089 gl
, vertexScriptId
, gl
.VERTEX_SHADER
, opt_errorCallback
));
1092 loadShaderFromScript(
1093 gl
, fragmentScriptId
, gl
.FRAGMENT_SHADER
, opt_errorCallback
));
1094 linkProgram(gl
, program
, opt_errorCallback
);
1099 * Loads shaders from source, creates a program, attaches the shaders and
1101 * @param {!WebGLContext} gl The WebGLContext to use.
1102 * @param {string} vertexShader The vertex shader.
1103 * @param {string} fragmentShader The fragment shader.
1104 * @param {function(string): void) opt_errorCallback callback for errors.
1105 * @return {!WebGLProgram} The created program.
1107 var loadProgram = function(
1108 gl
, vertexShader
, fragmentShader
, opt_errorCallback
) {
1109 var program
= gl
.createProgram();
1113 gl
, vertexShader
, gl
.VERTEX_SHADER
, opt_errorCallback
));
1117 gl
, fragmentShader
, gl
.FRAGMENT_SHADER
, opt_errorCallback
));
1118 linkProgram(gl
, program
, opt_errorCallback
);
1123 var getBasePath = function() {
1125 var expectedBase
= "webgl-test-utils.js";
1126 var scripts
= document
.getElementsByTagName('script');
1127 for (var script
, i
= 0; script
= scripts
[i
]; i
++) {
1128 var src
= script
.src
;
1130 if (src
.substr(l
- expectedBase
.length
) == expectedBase
) {
1131 basePath
= src
.substr(0, l
- expectedBase
.length
);
1138 var loadStandardVertexShader = function(gl
) {
1139 return loadShaderFromFile(
1140 gl
, getBasePath() + "vertexShader.vert", gl
.VERTEX_SHADER
);
1143 var loadStandardFragmentShader = function(gl
) {
1144 return loadShaderFromFile(
1145 gl
, getBasePath() + "fragmentShader.frag", gl
.FRAGMENT_SHADER
);
1149 * Loads an image asynchronously.
1150 * @param {string} url URL of image to load.
1151 * @param {!function(!Element): void} callback Function to call
1152 * with loaded image.
1154 var loadImageAsync = function(url
, callback
) {
1155 var img
= document
.createElement('img');
1156 img
.onload = function() {
1163 * Loads an array of images.
1164 * @param {!Array.<string>} urls URLs of images to load.
1165 * @param {!function(!{string, img}): void} callback. Callback
1166 * that gets passed map of urls to img tags.
1168 var loadImagesAsync = function(urls
, callback
) {
1171 function countDown() {
1177 function imageLoaded(url
) {
1178 return function(img
) {
1183 for (var ii
= 0; ii
< urls
.length
; ++ii
) {
1185 loadImageAsync(urls
[ii
], imageLoaded(urls
[ii
]));
1190 var getUrlArguments = function() {
1193 var s
= window
.location
.href
;
1194 var q
= s
.indexOf("?");
1195 var e
= s
.indexOf("#");
1199 var query
= s
.substring(q
+ 1, e
);
1200 var pairs
= query
.split("&");
1201 for (var ii
= 0; ii
< pairs
.length
; ++ii
) {
1202 var keyValue
= pairs
[ii
].split("=");
1203 var key
= keyValue
[0];
1204 var value
= decodeURIComponent(keyValue
[1]);
1208 throw "could not parse url";
1213 // Add your prefix here.
1214 var browserPrefixes
= [
1222 * Given an extension name like WEBGL_compressed_texture_s3tc
1223 * returns the name of the supported version extension, like
1224 * WEBKIT_WEBGL_compressed_teture_s3tc
1225 * @param {string} name Name of extension to look for
1226 * @return {string} name of extension found or undefined if not
1229 var getSupportedExtensionWithKnownPrefixes = function(gl
, name
) {
1230 var supported
= gl
.getSupportedExtensions();
1231 for (var ii
= 0; ii
< browserPrefixes
.length
; ++ii
) {
1232 var prefixedName
= browserPrefixes
[ii
] + name
;
1233 if (supported
.indexOf(prefixedName
) >= 0) {
1234 return prefixedName
;
1240 * Given an extension name like WEBGL_compressed_texture_s3tc
1241 * returns the supported version extension, like
1242 * WEBKIT_WEBGL_compressed_teture_s3tc
1243 * @param {string} name Name of extension to look for
1244 * @return {WebGLExtension} The extension or undefined if not
1247 var getExtensionWithKnownPrefixes = function(gl
, name
) {
1248 for (var ii
= 0; ii
< browserPrefixes
.length
; ++ii
) {
1249 var prefixedName
= browserPrefixes
[ii
] + name
;
1250 var ext
= gl
.getExtension(prefixedName
);
1258 * Provides requestAnimationFrame in a cross browser way.
1260 var requestAnimFrameImpl_
;
1262 var requestAnimFrame = function(callback
, element
) {
1263 if (!requestAnimFrameImpl_
) {
1264 requestAnimFrameImpl_ = function() {
1265 var functionNames
= [
1266 "requestAnimationFrame",
1267 "webkitRequestAnimationFrame",
1268 "mozRequestAnimationFrame",
1269 "oRequestAnimationFrame",
1270 "msRequestAnimationFrame"
1272 for (var jj
= 0; jj
< functionNames
.length
; ++jj
) {
1273 var functionName
= functionNames
[jj
];
1274 if (window
[functionName
]) {
1275 return function(name
) {
1276 return function(callback
, element
) {
1277 return window
[name
].call(window
, callback
, element
);
1282 return function(callback
, element
) {
1283 return window
.setTimeout(callback
, 1000 / 70);
1288 return requestAnimFrameImpl_(callback
, element
);
1292 * Provides cancelAnimationFrame in a cross browser way.
1294 var cancelAnimFrame
= (function() {
1295 return window
.cancelAnimationFrame
||
1296 window
.webkitCancelAnimationFrame
||
1297 window
.mozCancelAnimationFrame
||
1298 window
.oCancelAnimationFrame
||
1299 window
.msCancelAnimationFrame
||
1300 window
.clearTimeout
;
1303 var waitForComposite = function(frames
, callback
) {
1304 var countDown = function() {
1309 requestAnimFrame(countDown
);
1316 cancelAnimFrame
: cancelAnimFrame
,
1317 create3DContext
: create3DContext
,
1318 create3DContextWithWrapperThatThrowsOnGLError
:
1319 create3DContextWithWrapperThatThrowsOnGLError
,
1320 checkCanvas
: checkCanvas
,
1321 checkCanvasRect
: checkCanvasRect
,
1322 createColoredTexture
: createColoredTexture
,
1324 drawIndexedQuad
: drawIndexedQuad
,
1325 drawUByteColorQuad
: drawUByteColorQuad
,
1326 drawFloatColorQuad
: drawFloatColorQuad
,
1328 getExtensionWithKnownPrefixes
: getExtensionWithKnownPrefixes
,
1329 getFileListAsync
: getFileListAsync
,
1330 getLastError
: getLastError
,
1331 getSupportedExtensionWithKnownPrefixes
: getSupportedExtensionWithKnownPrefixes
,
1332 getUrlArguments
: getUrlArguments
,
1333 glEnumToString
: glEnumToString
,
1334 glErrorShouldBe
: glErrorShouldBe
,
1335 fillTexture
: fillTexture
,
1336 loadImageAsync
: loadImageAsync
,
1337 loadImagesAsync
: loadImagesAsync
,
1338 loadProgram
: loadProgram
,
1339 loadProgramFromFile
: loadProgramFromFile
,
1340 loadProgramFromScript
: loadProgramFromScript
,
1341 loadShader
: loadShader
,
1342 loadShaderFromFile
: loadShaderFromFile
,
1343 loadShaderFromScript
: loadShaderFromScript
,
1344 loadStandardProgram
: loadStandardProgram
,
1345 loadStandardVertexShader
: loadStandardVertexShader
,
1346 loadStandardFragmentShader
: loadStandardFragmentShader
,
1347 loadTextFileAsync
: loadTextFileAsync
,
1348 loadTexture
: loadTexture
,
1350 loggingOff
: loggingOff
,
1352 setupColorQuad
: setupColorQuad
,
1353 setupProgram
: setupProgram
,
1354 setupQuad
: setupQuad
,
1355 setupSimpleTextureFragmentShader
: setupSimpleTextureFragmentShader
,
1356 setupSimpleTextureProgram
: setupSimpleTextureProgram
,
1357 setupSimpleTextureVertexShader
: setupSimpleTextureVertexShader
,
1358 setupTexturedQuad
: setupTexturedQuad
,
1359 setupUnitQuad
: setupUnitQuad
,
1360 setupWebGLWithShaders
: setupWebGLWithShaders
,
1361 startsWith
: startsWith
,
1362 shouldGenerateGLError
: shouldGenerateGLError
,
1364 readFileList
: readFileList
,
1365 requestAnimFrame
: requestAnimFrame
,
1366 waitForComposite
: waitForComposite
,