b9c03e5fd1d76e861d7550ecc661edb75af1b273
[WebKit-https.git] / LayoutTests / fast / canvas / webgl / resources / webgl-test-utils.js
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.
4
5 WebGLTestUtils = (function() {
6
7 /**
8  * Wrapped logging function.
9  * @param {string} msg The message to log.
10  */
11 var log = function(msg) {
12   if (window.console && window.console.log) {
13     window.console.log(msg);
14   }
15 };
16
17 /**
18  * Wrapped logging function.
19  * @param {string} msg The message to log.
20  */
21 var error = function(msg) {
22   if (window.console) {
23     if (window.console.error) {
24       window.console.error(msg);
25     }
26     else if (window.console.log) {
27       window.console.log(msg);
28     }
29   }
30 };
31
32 /**
33  * Turn off all logging.
34  */
35 var loggingOff = function() {
36   log = function() {};
37   error = function() {};
38 };
39
40 /**
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.
45  */
46 var glEnumToString = function(gl, value) {
47   for (var p in gl) {
48     if (gl[p] == value) {
49       return p;
50     }
51   }
52   return "0x" + value.toString(16);
53 };
54
55 var lastError = "";
56
57 /**
58  * Returns the last compiler/linker error.
59  * @return {string} The last compiler/linker error.
60  */
61 var getLastError = function() {
62   return lastError;
63 };
64
65 /**
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.
70  */
71 var endsWith = function(haystack, needle) {
72   return haystack.substr(haystack.length - needle.length) === needle;
73 };
74
75 /**
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.
80  */
81 var startsWith = function(haystack, needle) {
82   return haystack.substr(0, needle.length) === needle;
83 };
84
85 /**
86  * A vertex shader for a single texture.
87  * @type {string}
88  */
89 var simpleTextureVertexShader = [
90   'attribute vec4 vPosition;',
91   'attribute vec2 texCoord0;',
92   'varying vec2 texCoord;',
93   'void main() {',
94   '    gl_Position = vPosition;',
95   '    texCoord = texCoord0;',
96   '}'].join('\n');
97
98 /**
99  * A fragment shader for a single texture.
100  * @type {string}
101  */
102 var simpleTextureFragmentShader = [
103   'precision mediump float;',
104   'uniform sampler2D tex;',
105   'varying vec2 texCoord;',
106   'void main() {',
107   '    gl_FragData[0] = texture2D(tex, texCoord);',
108   '}'].join('\n');
109
110 /**
111  * Creates a simple texture vertex shader.
112  * @param {!WebGLContext} gl The WebGLContext to use.
113  * @return {!WebGLShader}
114  */
115 var setupSimpleTextureVertexShader = function(gl) {
116     return loadShader(gl, simpleTextureVertexShader, gl.VERTEX_SHADER);
117 };
118
119 /**
120  * Creates a simple texture fragment shader.
121  * @param {!WebGLContext} gl The WebGLContext to use.
122  * @return {!WebGLShader}
123  */
124 var setupSimpleTextureFragmentShader = function(gl) {
125     return loadShader(
126         gl, simpleTextureFragmentShader, gl.FRAGMENT_SHADER);
127 };
128
129 /**
130  * Creates a program, attaches shaders, binds attrib locations, links the
131  * program and calls useProgram.
132  * @param {!Array.<!WebGLShader>} shaders The shaders to attach .
133  * @param {!Array.<string>} opt_attribs The attribs names.
134  * @param {!Array.<number>} opt_locations The locations for the attribs.
135  */
136 var setupProgram = function(gl, shaders, opt_attribs, opt_locations) {
137   var program = gl.createProgram();
138   for (var ii = 0; ii < shaders.length; ++ii) {
139     gl.attachShader(program, shaders[ii]);
140   }
141   if (opt_attribs) {
142     for (var ii = 0; ii < opt_attribs.length; ++ii) {
143       gl.bindAttribLocation(
144           program,
145           opt_locations ? opt_locations[ii] : ii,
146           opt_attribs[ii]);
147     }
148   }
149   gl.linkProgram(program);
150
151   // Check the link status
152   var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
153   if (!linked) {
154       // something went wrong with the link
155       lastError = gl.getProgramInfoLog (program);
156       error("Error in program linking:" + lastError);
157
158       gl.deleteProgram(program);
159       return null;
160   }
161
162   gl.useProgram(program);
163   return program;
164 };
165
166 /**
167  * Creates a simple texture program.
168  * @param {!WebGLContext} gl The WebGLContext to use.
169  * @param {number} opt_positionLocation The attrib location for position.
170  * @param {number} opt_texcoordLocation The attrib location for texture coords.
171  * @return {WebGLProgram}
172  */
173 var setupSimpleTextureProgram = function(
174     gl, opt_positionLocation, opt_texcoordLocation) {
175   opt_positionLocation = opt_positionLocation || 0;
176   opt_texcoordLocation = opt_texcoordLocation || 1;
177   var vs = setupSimpleTextureVertexShader(gl);
178   var fs = setupSimpleTextureFragmentShader(gl);
179   if (!vs || !fs) {
180     return null;
181   }
182   var program = setupProgram(
183       gl,
184       [vs, fs],
185       ['vPosition', 'texCoord0'],
186       [opt_positionLocation, opt_texcoordLocation]);
187   if (!program) {
188     gl.deleteShader(fs);
189     gl.deleteShader(vs);
190   }
191   gl.useProgram(program);
192   return program;
193 };
194
195 /**
196  * Creates buffers for a textured unit quad and attaches them to vertex attribs.
197  * @param {!WebGLContext} gl The WebGLContext to use.
198  * @param {number} opt_positionLocation The attrib location for position.
199  * @param {number} opt_texcoordLocation The attrib location for texture coords.
200  * @return {!Array.<WebGLBuffer>} The buffer objects that were
201  *      created.
202  */
203 var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) {
204   opt_positionLocation = opt_positionLocation || 0;
205   opt_texcoordLocation = opt_texcoordLocation || 1;
206   var objects = [];
207
208   var vertexObject = gl.createBuffer();
209   gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
210   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
211        1.0,  1.0, 0.0,
212       -1.0,  1.0, 0.0,
213       -1.0, -1.0, 0.0,
214        1.0,  1.0, 0.0,
215       -1.0, -1.0, 0.0,
216        1.0, -1.0, 0.0]), gl.STATIC_DRAW);
217   gl.enableVertexAttribArray(opt_positionLocation);
218   gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0);
219   objects.push(vertexObject);
220
221   var vertexObject = gl.createBuffer();
222   gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
223   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
224       1.0, 1.0,
225       0.0, 1.0,
226       0.0, 0.0,
227       1.0, 1.0,
228       0.0, 0.0,
229       1.0, 0.0]), gl.STATIC_DRAW);
230   gl.enableVertexAttribArray(opt_texcoordLocation);
231   gl.vertexAttribPointer(opt_texcoordLocation, 2, gl.FLOAT, false, 0, 0);
232   objects.push(vertexObject);
233   return objects;
234 };
235
236 /**
237  * Creates a program and buffers for rendering a textured quad.
238  * @param {!WebGLContext} gl The WebGLContext to use.
239  * @param {number} opt_positionLocation The attrib location for position.
240  * @param {number} opt_texcoordLocation The attrib location for texture coords.
241  * @return {!WebGLProgram}
242  */
243 var setupTexturedQuad = function(
244     gl, opt_positionLocation, opt_texcoordLocation) {
245   var program = setupSimpleTextureProgram(
246       gl, opt_positionLocation, opt_texcoordLocation);
247   setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation);
248   return program;
249 };
250
251 /**
252  * Creates a unit quad with only positions of a given rez
253  * @param {!WebGLContext} gl The WebGLContext to use.
254  * @param {number} gridRez The resolution of the mesh grid.
255  * @param {number} opt_positionLocation The attrib location for position.
256  */
257 var setupQuad = function (
258     gl, gridRes, opt_positionLocation, opt_flipOddTriangles) {
259   var positionLocation = opt_positionLocation || 0;
260   var objects = [];
261
262   var vertsAcross = gridRes + 1;
263   var numVerts = vertsAcross * vertsAcross;
264   var positions = new Float32Array(numVerts * 3);
265   var indices = new Uint16Array(6 * gridRes * gridRes);
266
267   var poffset = 0;
268
269   for (var yy = 0; yy <= gridRes; ++yy) {
270     for (var xx = 0; xx <= gridRes; ++xx) {
271       positions[poffset + 0] = -1 + 2 * xx / gridRes;
272       positions[poffset + 1] = -1 + 2 * yy / gridRes;
273       positions[poffset + 2] = 0;
274
275       poffset += 3;
276     }
277   }
278
279   var tbase = 0;
280   for (var yy = 0; yy < gridRes; ++yy) {
281     var index = yy * vertsAcross;
282     for (var xx = 0; xx < gridRes; ++xx) {
283       indices[tbase + 0] = index + 0;
284       indices[tbase + 1] = index + 1;
285       indices[tbase + 2] = index + vertsAcross;
286       indices[tbase + 3] = index + vertsAcross;
287       indices[tbase + 4] = index + 1;
288       indices[tbase + 5] = index + vertsAcross + 1;
289
290       if (opt_flipOddTriangles) {
291         indices[tbase + 4] = index + vertsAcross + 1;
292         indices[tbase + 5] = index + 1;
293       }
294
295       index += 1;
296       tbase += 6;
297     }
298   }
299
300   var buf = gl.createBuffer();
301   gl.bindBuffer(gl.ARRAY_BUFFER, buf);
302   gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
303   gl.enableVertexAttribArray(positionLocation);
304   gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
305   objects.push(buf);
306
307   var buf = gl.createBuffer();
308   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf);
309   gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
310   objects.push(buf);
311
312   return objects;
313 };
314
315 /**
316  * Fills the given texture with a solid color
317  * @param {!WebGLContext} gl The WebGLContext to use.
318  * @param {!WebGLTexture} tex The texture to fill.
319  * @param {number} width The width of the texture to create.
320  * @param {number} height The height of the texture to create.
321  * @param {!Array.<number>} color The color to fill with. A 4 element array
322  *        where each element is in the range 0 to 255.
323  * @param {number} opt_level The level of the texture to fill. Default = 0.
324  */
325 var fillTexture = function(gl, tex, width, height, color, opt_level) {
326   opt_level = opt_level || 0;
327   var numPixels = width * height;
328   var size = numPixels * 4;
329   var buf = new Uint8Array(size);
330   for (var ii = 0; ii < numPixels; ++ii) {
331     var off = ii * 4;
332     buf[off + 0] = color[0];
333     buf[off + 1] = color[1];
334     buf[off + 2] = color[2];
335     buf[off + 3] = color[3];
336   }
337   gl.bindTexture(gl.TEXTURE_2D, tex);
338   gl.texImage2D(
339       gl.TEXTURE_2D, opt_level, gl.RGBA, width, height, 0,
340       gl.RGBA, gl.UNSIGNED_BYTE, buf);
341   };
342
343 /**
344  * Creates a textures and fills it with a solid color
345  * @param {!WebGLContext} gl The WebGLContext to use.
346  * @param {number} width The width of the texture to create.
347  * @param {number} height The height of the texture to create.
348  * @param {!Array.<number>} color The color to fill with. A 4 element array
349  *        where each element is in the range 0 to 255.
350  * @return {!WebGLTexture}
351  */
352 var createColoredTexture = function(gl, width, height, color) {
353   var tex = gl.createTexture();
354   fillTexture(gl, tex, width, height, color);
355   return tex;
356 };
357
358 /**
359  * Draws a previously setup quad.
360  * @param {!WebGLContext} gl The WebGLContext to use.
361  * @param {!Array.<number>} opt_color The color to fill clear with before
362  *        drawing. A 4 element array where each element is in the range 0 to
363  *        255. Default [255, 255, 255, 255]
364  */
365 var drawQuad = function(gl, opt_color) {
366   opt_color = opt_color || [255, 255, 255, 255];
367   gl.clearColor(
368       opt_color[0] / 255,
369       opt_color[1] / 255,
370       opt_color[2] / 255,
371       opt_color[3] / 255);
372   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
373   gl.drawArrays(gl.TRIANGLES, 0, 6);
374 };
375
376 /**
377  * Checks that a portion of a canvas is 1 color.
378  * @param {!WebGLContext} gl The WebGLContext to use.
379  * @param {number} x left corner of region to check.
380  * @param {number} y bottom corner of region to check.
381  * @param {number} width width of region to check.
382  * @param {number} height width of region to check.
383  * @param {!Array.<number>} color The color to fill clear with before drawing. A
384  *        4 element array where each element is in the range 0 to 255.
385  * @param {string} msg Message to associate with success. Eg ("should be red").
386  * @param {number} errorRange Optional. Acceptable error in
387  *        color checking. 0 by default.
388  */
389 var checkCanvasRect = function(gl, x, y, width, height, color, msg, errorRange) {
390   errorRange = errorRange || 0;
391   var buf = new Uint8Array(width * height * 4);
392   gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
393   for (var i = 0; i < width * height; ++i) {
394     var offset = i * 4;
395     for (var j = 0; j < color.length; ++j) {
396       if (Math.abs(buf[offset + j] - color[j]) > errorRange) {
397         testFailed(msg);
398         var was = buf[offset + 0].toString();
399         for (j = 1; j < color.length; ++j) {
400           was += "," + buf[offset + j];
401         }
402         debug('at (' + (i % width) + ', ' + Math.floor(i / width) +
403               ') expected: ' + color + ' was ' + was);
404         return;
405       }
406     }
407   }
408   testPassed(msg);
409 };
410
411 /**
412  * Checks that an entire canvas is 1 color.
413  * @param {!WebGLContext} gl The WebGLContext to use.
414  * @param {!Array.<number>} color The color to fill clear with before drawing. A
415  *        4 element array where each element is in the range 0 to 255.
416  * @param {string} msg Message to associate with success. Eg ("should be red").
417  * @param {number} errorRange Optional. Acceptable error in
418  *        color checking. 0 by default.
419  */
420 var checkCanvas = function(gl, color, msg, errorRange) {
421   checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, color, msg, errorRange);
422 };
423
424 /**
425  * Loads a texture, calls callback when finished.
426  * @param {!WebGLContext} gl The WebGLContext to use.
427  * @param {string} url URL of image to load
428  * @param {function(!Image): void} callback Function that gets called after
429  *        image has loaded
430  * @return {!WebGLTexture} The created texture.
431  */
432 var loadTexture = function(gl, url, callback) {
433     var texture = gl.createTexture();
434     gl.bindTexture(gl.TEXTURE_2D, texture);
435     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
436     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
437     var image = new Image();
438     image.onload = function() {
439         gl.bindTexture(gl.TEXTURE_2D, texture);
440         gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
441         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
442         callback(image);
443     };
444     image.src = url;
445     return texture;
446 };
447
448 /**
449  * Creates a webgl context.
450  * @param {!Canvas} opt_canvas The canvas tag to get context from. If one is not
451  *     passed in one will be created.
452  * @return {!WebGLContext} The created context.
453  */
454 var create3DContext = function(opt_canvas, opt_attributes) {
455   opt_canvas = opt_canvas || document.createElement("canvas");
456   var context = null;
457   try {
458     context = opt_canvas.getContext("webgl", opt_attributes);
459   } catch(e) {}
460   if (!context) {
461     try {
462       context = opt_canvas.getContext("experimental-webgl", opt_attributes);
463     } catch(e) {}
464   }
465   if (!context) {
466     testFailed("Unable to fetch WebGL rendering context for Canvas");
467   }
468   return context;
469 }
470
471 /**
472  * Gets a GLError value as a string.
473  * @param {!WebGLContext} gl The WebGLContext to use.
474  * @param {number} err The webgl error as retrieved from gl.getError().
475  * @return {string} the error as a string.
476  */
477 var getGLErrorAsString = function(gl, err) {
478   if (err === gl.NO_ERROR) {
479     return "NO_ERROR";
480   }
481   for (var name in gl) {
482     if (gl[name] === err) {
483       return name;
484     }
485   }
486   return err.toString();
487 };
488
489 /**
490  * Wraps a WebGL function with a function that throws an exception if there is
491  * an error.
492  * @param {!WebGLContext} gl The WebGLContext to use.
493  * @param {string} fname Name of function to wrap.
494  * @return {function} The wrapped function.
495  */
496 var createGLErrorWrapper = function(context, fname) {
497   return function() {
498     var rv = context[fname].apply(context, arguments);
499     var err = context.getError();
500     if (err != 0)
501       throw "GL error " + getGLErrorAsString(err) + " in " + fname;
502     return rv;
503   };
504 };
505
506 /**
507  * Creates a WebGL context where all functions are wrapped to throw an exception
508  * if there is an error.
509  * @param {!Canvas} canvas The HTML canvas to get a context from.
510  * @return {!Object} The wrapped context.
511  */
512 function create3DContextWithWrapperThatThrowsOnGLError(canvas) {
513   var context = create3DContext(canvas);
514   var wrap = {};
515   for (var i in context) {
516     try {
517       if (typeof context[i] == 'function') {
518         wrap[i] = createGLErrorWrapper(context, i);
519       } else {
520         wrap[i] = context[i];
521       }
522     } catch (e) {
523       error("createContextWrapperThatThrowsOnGLError: Error accessing " + i);
524     }
525   }
526   wrap.getError = function() {
527       return context.getError();
528   };
529   return wrap;
530 };
531
532 /**
533  * Tests that an evaluated expression generates a specific GL error.
534  * @param {!WebGLContext} gl The WebGLContext to use.
535  * @param {number} glError The expected gl error.
536  * @param {string} evalSTr The string to evaluate.
537  */
538 var shouldGenerateGLError = function(gl, glError, evalStr) {
539   var exception;
540   try {
541     eval(evalStr);
542   } catch (e) {
543     exception = e;
544   }
545   if (exception) {
546     testFailed(evalStr + " threw exception " + exception);
547   } else {
548     var err = gl.getError();
549     if (err != glError) {
550       testFailed(evalStr + " expected: " + getGLErrorAsString(gl, glError) + ". Was " + getGLErrorAsString(gl, err) + ".");
551     } else {
552       testPassed(evalStr + " was expected value: " + getGLErrorAsString(gl, glError) + ".");
553     }
554   }
555 };
556
557 /**
558  * Tests that the first error GL returns is the specified error.
559  * @param {!WebGLContext} gl The WebGLContext to use.
560  * @param {number} glError The expected gl error.
561  * @param {string} opt_msg
562  */
563 var glErrorShouldBe = function(gl, glError, opt_msg) {
564   opt_msg = opt_msg || "";
565   var err = gl.getError();
566   if (err != glError) {
567     testFailed("getError expected: " + getGLErrorAsString(gl, glError) +
568                ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg);
569   } else {
570     testPassed("getError was expected value: " +
571                 getGLErrorAsString(gl, glError) + " : " + opt_msg);
572   }
573 };
574
575 /**
576  * Links a WebGL program, throws if there are errors.
577  * @param {!WebGLContext} gl The WebGLContext to use.
578  * @param {!WebGLProgram} program The WebGLProgram to link.
579  * @param {function(string): void) opt_errorCallback callback for errors. 
580  */
581 var linkProgram = function(gl, program, opt_errorCallback) {
582   // Link the program
583   gl.linkProgram(program);
584
585   // Check the link status
586   var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
587   if (!linked) {
588     // something went wrong with the link
589     var error = gl.getProgramInfoLog (program);
590
591     testFailed("Error in program linking:" + error);
592
593     gl.deleteProgram(program);
594   }
595 };
596
597 /**
598  * Sets up WebGL with shaders.
599  * @param {string} canvasName The id of the canvas.
600  * @param {string} vshader The id of the script tag that contains the vertex
601  *     shader source.
602  * @param {string} fshader The id of the script tag that contains the fragment
603  *     shader source.
604  * @param {!Array.<string>} attribs An array of attrib names used to bind
605  *     attribs to the ordinal of the name in this array.
606  * @param {!Array.<number>} opt_clearColor The color to cla
607  * @return {!WebGLContext} The created WebGLContext.
608  */
609 var setupWebGLWithShaders = function(
610    canvasName, vshader, fshader, attribs) {
611   var canvas = document.getElementById(canvasName);
612   var gl = create3DContext(canvas);
613   if (!gl) {
614     testFailed("No WebGL context found");
615   }
616
617   // create our shaders
618   var vertexShader = loadShaderFromScript(gl, vshader);
619   var fragmentShader = loadShaderFromScript(gl, fshader);
620
621   if (!vertexShader || !fragmentShader) {
622     return null;
623   }
624
625   // Create the program object
626   program = gl.createProgram();
627
628   if (!program) {
629     return null;
630   }
631
632   // Attach our two shaders to the program
633   gl.attachShader (program, vertexShader);
634   gl.attachShader (program, fragmentShader);
635
636   // Bind attributes
637   for (var i in attribs) {
638     gl.bindAttribLocation (program, i, attribs[i]);
639   }
640
641   linkProgram(gl, program);
642
643   gl.useProgram(program);
644
645   gl.clearColor(0,0,0,1);
646   gl.clearDepth(1);
647
648   gl.enable(gl.DEPTH_TEST);
649   gl.enable(gl.BLEND);
650   gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
651
652   gl.program = program;
653   return gl;
654 };
655
656 /**
657  * Loads text from an external file. This function is synchronous.
658  * @param {string} url The url of the external file.
659  * @param {!function(bool, string): void} callback that is sent a bool for
660  *     success and the string.
661  */
662 var loadTextFileAsync = function(url, callback) {
663   log ("loading: " + url);
664   var error = 'loadTextFileSynchronous failed to load url "' + url + '"';
665   var request;
666   if (window.XMLHttpRequest) {
667     request = new XMLHttpRequest();
668     if (request.overrideMimeType) {
669       request.overrideMimeType('text/plain');
670     }
671   } else {
672     throw 'XMLHttpRequest is disabled';
673   }
674   try {
675     request.open('GET', url, true);
676     request.onreadystatechange = function() {
677       if (request.readyState == 4) {
678         var text = '';
679         // HTTP reports success with a 200 status. The file protocol reports
680         // success with zero. HTTP does not use zero as a status code (they
681         // start at 100).
682         // https://developer.mozilla.org/En/Using_XMLHttpRequest
683         var success = request.status == 200 || request.status == 0;
684         if (success) {
685           text = request.responseText;
686         }
687         log("loaded: " + url);
688         callback(success, text);
689       }
690     };
691     request.send(null);
692   } catch (e) {
693     log("failed to load: " + url);
694     callback(false, '');
695   }
696 };
697
698 /**
699  * Recursively loads a file as a list. Each line is parsed for a relative
700  * path. If the file ends in .txt the contents of that file is inserted in
701  * the list.
702  *
703  * @param {string} url The url of the external file.
704  * @param {!function(bool, Array<string>): void} callback that is sent a bool
705  *     for success and the array of strings.
706  */
707 var getFileListAsync = function(url, callback) {
708   var files = [];
709
710   var getFileListImpl = function(url, callback) {
711     var files = [];
712     if (url.substr(url.length - 4) == '.txt') {
713       loadTextFileAsync(url, function() {
714         return function(success, text) {
715           if (!success) {
716             callback(false, '');
717             return;
718           }
719           var lines = text.split('\n');
720           var prefix = '';
721           var lastSlash = url.lastIndexOf('/');
722           if (lastSlash >= 0) {
723             prefix = url.substr(0, lastSlash + 1);
724           }
725           var fail = false;
726           var count = 1;
727           var index = 0;
728           for (var ii = 0; ii < lines.length; ++ii) {
729             var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
730             if (str.length > 4 &&
731                 str[0] != '#' &&
732                 str[0] != ";" &&
733                 str.substr(0, 2) != "//") {
734               var names = str.split(/ +/);
735               new_url = prefix + str;
736               if (names.length == 1) {
737                 new_url = prefix + str;
738                 ++count;
739                 getFileListImpl(new_url, function(index) {
740                   return function(success, new_files) {
741                     log("got files: " + new_files.length);
742                     if (success) {
743                       files[index] = new_files;
744                     }
745                     finish(success);
746                   };
747                 }(index++));
748               } else {
749                 var s = "";
750                 var p = "";
751                 for (var jj = 0; jj < names.length; ++jj) {
752                   s += p + prefix + names[jj];
753                   p = " ";
754                 }
755                 files[index++] = s;
756               }
757             }
758           }
759           finish(true);
760
761           function finish(success) {
762             if (!success) {
763               fail = true;
764             }
765             --count;
766             log("count: " + count);
767             if (!count) {
768               callback(!fail, files);
769             }
770           }
771         }
772       }());
773
774     } else {
775       files.push(url);
776       callback(true, files);
777     }
778   };
779
780   getFileListImpl(url, function(success, files) {
781     // flatten
782     var flat = [];
783     flatten(files);
784     function flatten(files) {
785       for (var ii = 0; ii < files.length; ++ii) {
786         var value = files[ii];
787         if (typeof(value) == "string") {
788           flat.push(value);
789         } else {
790           flatten(value);
791         }
792       }
793     }
794     callback(success, flat);
795   });
796 };
797
798 /**
799  * Gets a file from a file/URL
800  * @param {string} file the URL of the file to get.
801  * @return {string} The contents of the file.
802  */
803 var readFile = function(file) {
804   var xhr = new XMLHttpRequest();
805   xhr.open("GET", file, false);
806   xhr.send();
807   return xhr.responseText.replace(/\r/g, "");
808 };
809
810 var readFileList = function(url) {
811   var files = [];
812   if (url.substr(url.length - 4) == '.txt') {
813     var lines = readFile(url).split('\n');
814     var prefix = '';
815     var lastSlash = url.lastIndexOf('/');
816     if (lastSlash >= 0) {
817       prefix = url.substr(0, lastSlash + 1);
818     }
819     for (var ii = 0; ii < lines.length; ++ii) {
820       var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, '');
821       if (str.length > 4 &&
822           str[0] != '#' &&
823           str[0] != ";" &&
824           str.substr(0, 2) != "//") {
825         var names = str.split(/ +/);
826         if (names.length == 1) {
827           new_url = prefix + str;
828           files = files.concat(readFileList(new_url));
829         } else {
830           var s = "";
831           var p = "";
832           for (var jj = 0; jj < names.length; ++jj) {
833             s += p + prefix + names[jj];
834             p = " ";
835           }
836           files.push(s);
837         }
838       }
839     }
840   } else {
841     files.push(url);
842   }
843   return files;
844 };
845
846 /**
847  * Loads a shader.
848  * @param {!WebGLContext} gl The WebGLContext to use.
849  * @param {string} shaderSource The shader source.
850  * @param {number} shaderType The type of shader. 
851  * @param {function(string): void) opt_errorCallback callback for errors. 
852  * @return {!WebGLShader} The created shader.
853  */
854 var loadShader = function(gl, shaderSource, shaderType, opt_errorCallback) {
855   var errFn = opt_errorCallback || error;
856   // Create the shader object
857   var shader = gl.createShader(shaderType);
858   if (shader == null) {
859     errFn("*** Error: unable to create shader '"+shaderSource+"'");
860     return null;
861   }
862
863   // Load the shader source
864   gl.shaderSource(shader, shaderSource);
865   var err = gl.getError();
866   if (err != gl.NO_ERROR) {
867     errFn("*** Error loading shader '" + shader + "':" + glEnumToString(gl, err));
868     return null;
869   }
870
871   // Compile the shader
872   gl.compileShader(shader);
873
874   // Check the compile status
875   var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
876   if (!compiled) {
877     // Something went wrong during compilation; get the error
878     lastError = gl.getShaderInfoLog(shader);
879     errFn("*** Error compiling shader '" + shader + "':" + lastError);
880     gl.deleteShader(shader);
881     return null;
882   }
883
884   return shader;
885 }
886
887 /**
888  * Loads a shader from a URL.
889  * @param {!WebGLContext} gl The WebGLContext to use.
890  * @param {file} file The URL of the shader source.
891  * @param {number} type The type of shader.
892  * @param {function(string): void) opt_errorCallback callback for errors. 
893  * @return {!WebGLShader} The created shader.
894  */
895 var loadShaderFromFile = function(gl, file, type, opt_errorCallback) {
896   var shaderSource = readFile(file);
897   return loadShader(gl, shaderSource, type, opt_errorCallback);
898 };
899
900 /**
901  * Loads a shader from a script tag.
902  * @param {!WebGLContext} gl The WebGLContext to use.
903  * @param {string} scriptId The id of the script tag.
904  * @param {number} opt_shaderType The type of shader. If not passed in it will
905  *     be derived from the type of the script tag.
906  * @param {function(string): void) opt_errorCallback callback for errors. 
907  * @return {!WebGLShader} The created shader.
908  */
909 var loadShaderFromScript = function(
910     gl, scriptId, opt_shaderType, opt_errorCallback) {
911   var shaderSource = "";
912   var shaderType;
913   var shaderScript = document.getElementById(scriptId);
914   if (!shaderScript) {
915     throw("*** Error: unknown script element" + scriptId);
916   }
917   shaderSource = shaderScript.text;
918
919   if (!opt_shaderType) {
920     if (shaderScript.type == "x-shader/x-vertex") {
921       shaderType = gl.VERTEX_SHADER;
922     } else if (shaderScript.type == "x-shader/x-fragment") {
923       shaderType = gl.FRAGMENT_SHADER;
924     } else if (shaderType != gl.VERTEX_SHADER && shaderType != gl.FRAGMENT_SHADER) {
925       throw("*** Error: unknown shader type");
926       return null;
927     }
928   }
929
930   return loadShader(
931       gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
932       opt_errorCallback);
933 };
934
935 var loadStandardProgram = function(gl) {
936   var program = gl.createProgram();
937   gl.attachShader(program, loadStandardVertexShader(gl));
938   gl.attachShader(program, loadStandardFragmentShader(gl));
939   linkProgram(gl, program);
940   return program;
941 };
942
943 /**
944  * Loads shaders from files, creates a program, attaches the shaders and links.
945  * @param {!WebGLContext} gl The WebGLContext to use.
946  * @param {string} vertexShaderPath The URL of the vertex shader.
947  * @param {string} fragmentShaderPath The URL of the fragment shader.
948  * @param {function(string): void) opt_errorCallback callback for errors. 
949  * @return {!WebGLProgram} The created program.
950  */
951 var loadProgramFromFile = function(
952     gl, vertexShaderPath, fragmentShaderPath, opt_errorCallback) {
953   var program = gl.createProgram();
954   gl.attachShader(
955       program,
956       loadShaderFromFile(
957           gl, vertexShaderPath, gl.VERTEX_SHADER, opt_errorCallback));
958   gl.attachShader(
959       program,
960       loadShaderFromFile(
961           gl, fragmentShaderPath, gl.FRAGMENT_SHADER, opt_errorCallback));
962   linkProgram(gl, program, opt_errorCallback);
963   return program;
964 };
965
966 /**
967  * Loads shaders from script tags, creates a program, attaches the shaders and
968  * links.
969  * @param {!WebGLContext} gl The WebGLContext to use.
970  * @param {string} vertexScriptId The id of the script tag that contains the
971  *        vertex shader.
972  * @param {string} fragmentScriptId The id of the script tag that contains the
973  *        fragment shader.
974  * @param {function(string): void) opt_errorCallback callback for errors. 
975  * @return {!WebGLProgram} The created program.
976  */
977 var loadProgramFromScript = function loadProgramFromScript(
978     gl, vertexScriptId, fragmentScriptId, opt_errorCallback) {
979   var program = gl.createProgram();
980   gl.attachShader(
981       program,
982       loadShaderFromScript(
983           gl, vertexScriptId, gl.VERTEX_SHADER, opt_errorCallback));
984   gl.attachShader(
985       program,
986       loadShaderFromScript(
987           gl, fragmentScriptId,  gl.FRAGMENT_SHADER, opt_errorCallback));
988   linkProgram(gl, program, opt_errorCallback);
989   return program;
990 };
991
992 /**
993  * Loads shaders from source, creates a program, attaches the shaders and
994  * links.
995  * @param {!WebGLContext} gl The WebGLContext to use.
996  * @param {string} vertexShader The vertex shader.
997  * @param {string} fragmentShader The fragment shader.
998  * @param {function(string): void) opt_errorCallback callback for errors. 
999  * @return {!WebGLProgram} The created program.
1000  */
1001 var loadProgram = function(
1002     gl, vertexShader, fragmentShader, opt_errorCallback) {
1003   var program = gl.createProgram();
1004   gl.attachShader(
1005       program,
1006       loadShader(
1007           gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback));
1008   gl.attachShader(
1009       program,
1010       loadShader(
1011           gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback));
1012   linkProgram(gl, program, opt_errorCallback);
1013   return program;
1014 };
1015
1016 var basePath;
1017 var getBasePath = function() {
1018   if (!basePath) {
1019     var expectedBase = "webgl-test-utils.js";
1020     var scripts = document.getElementsByTagName('script');
1021     for (var script, i = 0; script = scripts[i]; i++) {
1022       var src = script.src;
1023       var l = src.length;
1024       if (src.substr(l - expectedBase.length) == expectedBase) {
1025         basePath = src.substr(0, l - expectedBase.length);
1026       }
1027     }
1028   }
1029   return basePath;
1030 };
1031
1032 var loadStandardVertexShader = function(gl) {
1033   return loadShaderFromFile(
1034       gl, getBasePath() + "vertexShader.vert", gl.VERTEX_SHADER);
1035 };
1036
1037 var loadStandardFragmentShader = function(gl) {
1038   return loadShaderFromFile(
1039       gl, getBasePath() + "fragmentShader.frag", gl.FRAGMENT_SHADER);
1040 };
1041
1042 /**
1043  * Loads an image asynchronously.
1044  * @param {string} url URL of image to load.
1045  * @param {!function(!Element): void} callback Function to call
1046  *     with loaded image.
1047  */
1048 var loadImageAsync = function(url, callback) {
1049   var img = document.createElement('img');
1050   img.onload = function() {
1051     callback(img);
1052   };
1053   img.src = url;
1054 };
1055
1056 /**
1057  * Loads an array of images.
1058  * @param {!Array.<string>} urls URLs of images to load.
1059  * @param {!function(!{string, img}): void} callback. Callback
1060  *     that gets passed map of urls to img tags.
1061  */
1062 var loadImagesAsync = function(urls, callback) {
1063   var count = 1;
1064   var images = { };
1065   function countDown() {
1066     --count;
1067     if (count == 0) {
1068       callback(images);
1069     }
1070   }
1071   function imageLoaded(url) {
1072     return function(img) {
1073       images[url] = img;
1074       countDown();
1075     }
1076   }
1077   for (var ii = 0; ii < urls.length; ++ii) {
1078     ++count;
1079     loadImageAsync(urls[ii], imageLoaded(urls[ii]));
1080   }
1081   countDown();
1082 };
1083
1084 var getUrlArguments = function() {
1085   var args = {};
1086   try {
1087     var s = window.location.href;
1088     var q = s.indexOf("?");
1089     var e = s.indexOf("#");
1090     if (e < 0) {
1091       e = s.length;
1092     }
1093     var query = s.substring(q + 1, e);
1094     var pairs = query.split("&");
1095     for (var ii = 0; ii < pairs.length; ++ii) {
1096       var keyValue = pairs[ii].split("=");
1097       var key = keyValue[0];
1098       var value = decodeURIComponent(keyValue[1]);
1099       args[key] = value;
1100     }
1101   } catch (e) {
1102     throw "could not parse url";
1103   }
1104   return args;
1105 };
1106
1107 /**
1108  * Provides requestAnimationFrame in a cross browser way.
1109  */
1110 var requestAnimFrameImpl_;
1111
1112 var requestAnimFrame = function(callback, element) {
1113   if (!requestAnimFrameImpl_) {
1114     requestAnimFrameImpl_ = function() {
1115       var functionNames = [
1116         "requestAnimationFrame",
1117         "webkitRequestAnimationFrame",
1118         "mozRequestAnimationFrame",
1119         "oRequestAnimationFrame",
1120         "msRequestAnimationFrame"
1121       ];
1122       for (var jj = 0; jj < functionNames.length; ++jj) {
1123         var functionName = functionNames[jj];
1124         if (window[functionName]) {
1125           return function(name) {
1126             return function(callback, element) {
1127               return window[name].call(window, callback, element);
1128             };
1129           }(functionName);
1130         }
1131       }
1132       return function(callback, element) {
1133            return window.setTimeout(callback, 1000 / 70);
1134         };
1135     }();
1136   }
1137
1138   return requestAnimFrameImpl_(callback, element);
1139 };
1140
1141 /**
1142  * Provides cancelAnimationFrame in a cross browser way.
1143  */
1144 var cancelRequestAnimFrame = (function() {
1145   return window.cancelAnimationFrame ||
1146          window.webkitCancelAnimationFrame ||
1147          window.mozCancelAnimationFrame ||
1148          window.oCancelAnimationFrame ||
1149          window.msCancelAnimationFrame ||
1150          window.clearTimeout;
1151 })();
1152
1153 var waitFrames = function(frames, callback) {
1154   var countDown = function() {
1155     if (frames == 0) {
1156       callback();
1157     } else {
1158       --frames;
1159       requestAnimFrame(countDown);
1160     }
1161   };
1162   countDown();
1163 };
1164
1165 return {
1166   cancelAnimFrame: cancelAnimFrame,
1167   create3DContext: create3DContext,
1168   create3DContextWithWrapperThatThrowsOnGLError:
1169     create3DContextWithWrapperThatThrowsOnGLError,
1170   checkCanvas: checkCanvas,
1171   checkCanvasRect: checkCanvasRect,
1172   createColoredTexture: createColoredTexture,
1173   drawQuad: drawQuad,
1174   endsWith: endsWith,
1175   getFileListAsync: getFileListAsync,
1176   getLastError: getLastError,
1177   getUrlArguments: getUrlArguments,
1178   glEnumToString: glEnumToString,
1179   glErrorShouldBe: glErrorShouldBe,
1180   fillTexture: fillTexture,
1181   loadImageAsync: loadImageAsync,
1182   loadImagesAsync: loadImagesAsync,
1183   loadProgram: loadProgram,
1184   loadProgramFromFile: loadProgramFromFile,
1185   loadProgramFromScript: loadProgramFromScript,
1186   loadShader: loadShader,
1187   loadShaderFromFile: loadShaderFromFile,
1188   loadShaderFromScript: loadShaderFromScript,
1189   loadStandardProgram: loadStandardProgram,
1190   loadStandardVertexShader: loadStandardVertexShader,
1191   loadStandardFragmentShader: loadStandardFragmentShader,
1192   loadTextFileAsync: loadTextFileAsync,
1193   loadTexture: loadTexture,
1194   log: log,
1195   loggingOff: loggingOff,
1196   error: error,
1197   setupProgram: setupProgram,
1198   setupQuad: setupQuad,
1199   setupSimpleTextureFragmentShader: setupSimpleTextureFragmentShader,
1200   setupSimpleTextureProgram: setupSimpleTextureProgram,
1201   setupSimpleTextureVertexShader: setupSimpleTextureVertexShader,
1202   setupTexturedQuad: setupTexturedQuad,
1203   setupUnitQuad: setupUnitQuad,
1204   setupWebGLWithShaders: setupWebGLWithShaders,
1205   startsWith: startsWith,
1206   shouldGenerateGLError: shouldGenerateGLError,
1207   readFile: readFile,
1208   readFileList: readFileList,
1209   requestAnimFrame: requestAnimFrame,
1210   waitFrames: waitFrames,
1211
1212   none: false
1213 };
1214
1215 }());
1216
1217