Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / LayoutTests / fast / canvas / webgl / resources / webgl-test-utils.js
blobcb6d17f2aa00aa6ee4caf6bbf1bd9134530e168e
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() {
7 /**
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);
17 /**
18 * Wrapped logging function.
19 * @param {string} msg The message to log.
21 var error = function(msg) {
22 if (window.console) {
23 if (window.console.error) {
24 window.console.error(msg);
26 else if (window.console.log) {
27 window.console.log(msg);
32 /**
33 * Turn off all logging.
35 var loggingOff = function() {
36 log = function() {};
37 error = function() {};
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.
46 var glEnumToString = function(gl, value) {
47 for (var p in gl) {
48 if (gl[p] == value) {
49 return p;
52 return "0x" + value.toString(16);
55 var lastError = "";
57 /**
58 * Returns the last compiler/linker error.
59 * @return {string} The last compiler/linker error.
61 var getLastError = function() {
62 return lastError;
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.
71 var endsWith = function(haystack, needle) {
72 return haystack.substr(haystack.length - needle.length) === needle;
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.
81 var startsWith = function(haystack, needle) {
82 return haystack.substr(0, needle.length) === needle;
85 /**
86 * A vertex shader for a single texture.
87 * @type {string}
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');
98 /**
99 * A fragment shader for a single texture.
100 * @type {string}
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');
111 * A vertex shader for a single texture.
112 * @type {string}
114 var simpleColorVertexShader = [
115 'attribute vec4 vPosition;',
116 'void main() {',
117 ' gl_Position = vPosition;',
118 '}'].join('\n');
121 * A fragment shader for a color.
122 * @type {string}
124 var simpleColorFragmentShader = [
125 'precision mediump float;',
126 'uniform vec4 u_color;',
127 'void main() {',
128 ' gl_FragData[0] = u_color;',
129 '}'].join('\n');
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) {
146 return loadShader(
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);
164 if (element) {
165 shader = loadShaderFromScript(gl, shader);
166 } else {
167 shader = loadShader(gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER);
170 gl.attachShader(program, shader);
172 if (opt_attribs) {
173 for (var ii = 0; ii < opt_attribs.length; ++ii) {
174 gl.bindAttribLocation(
175 program,
176 opt_locations ? opt_locations[ii] : ii,
177 opt_attribs[ii]);
180 gl.linkProgram(program);
182 // Check the link status
183 var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
184 if (!linked) {
185 // something went wrong with the link
186 lastError = gl.getProgramInfoLog (program);
187 error("Error in program linking:" + lastError);
189 gl.deleteProgram(program);
190 return null;
193 gl.useProgram(program);
194 return 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);
210 if (!vs || !fs) {
211 return null;
213 var program = setupProgram(
215 [vs, fs],
216 ['vPosition', 'texCoord0'],
217 [opt_positionLocation, opt_texcoordLocation]);
218 if (!program) {
219 gl.deleteShader(fs);
220 gl.deleteShader(vs);
222 gl.useProgram(program);
223 return 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
232 * created.
234 var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) {
235 opt_positionLocation = opt_positionLocation || 0;
236 opt_texcoordLocation = opt_texcoordLocation || 1;
237 var objects = [];
239 var vertexObject = gl.createBuffer();
240 gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
241 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
242 1.0, 1.0, 0.0,
243 -1.0, 1.0, 0.0,
244 -1.0, -1.0, 0.0,
245 1.0, 1.0, 0.0,
246 -1.0, -1.0, 0.0,
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([
255 1.0, 1.0,
256 0.0, 1.0,
257 0.0, 0.0,
258 1.0, 1.0,
259 0.0, 0.0,
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);
264 return objects;
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);
279 return program;
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],
293 ['vPosition'],
294 [opt_positionLocation]);
295 setupUnitQuad(gl, opt_positionLocation);
296 return program;
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;
308 var objects = [];
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);
315 var poffset = 0;
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;
323 poffset += 3;
327 var tbase = 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;
343 index += 1;
344 tbase += 6;
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);
353 objects.push(buf);
355 var buf = gl.createBuffer();
356 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf);
357 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
358 objects.push(buf);
360 return objects;
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) {
379 var off = ii * 4;
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);
386 gl.texImage2D(
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);
403 return tex;
406 var ubyteToFloat = function(c) {
407 return c / 255;
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
415 * 1.
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
430 * 255.
432 var drawUByteColorQuad = function(gl, color) {
433 var floatColor = [];
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];
449 gl.clearColor(
450 opt_color[0] / 255,
451 opt_color[1] / 255,
452 opt_color[2] / 255,
453 opt_color[3] / 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];
468 gl.clearColor(
469 opt_color[0] / 255,
470 opt_color[1] / 255,
471 opt_color[2] / 255,
472 opt_color[3] / 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) {
495 var offset = i * 4;
496 for (var j = 0; j < color.length; ++j) {
497 if (Math.abs(buf[offset + j] - color[j]) > errorRange) {
498 testFailed(msg);
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);
505 return;
509 testPassed(msg);
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
530 * image has loaded
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);
543 callback(image);
545 image.src = url;
546 return texture;
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);
560 var context = null;
561 var names = ["webgl", "webgl"];
562 for (var i = 0; i < names.length; ++i) {
563 try {
564 context = opt_canvas.getContext(names[i], opt_attributes);
565 } catch (e) {
567 if (context) {
568 break;
571 if (!context) {
572 testFailed("Unable to fetch WebGL rendering context for Canvas");
574 return context;
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) {
585 return "NO_ERROR";
587 for (var name in gl) {
588 if (gl[name] === err) {
589 return name;
592 return err.toString();
596 * Wraps a WebGL function with a function that throws an exception if there is
597 * an error.
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) {
603 return function() {
604 var rv = context[fname].apply(context, arguments);
605 var err = context.getError();
606 if (err != 0)
607 throw "GL error " + getGLErrorAsString(err) + " in " + fname;
608 return rv;
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);
620 var wrap = {};
621 for (var i in context) {
622 try {
623 if (typeof context[i] == 'function') {
624 wrap[i] = createGLErrorWrapper(context, i);
625 } else {
626 wrap[i] = context[i];
628 } catch (e) {
629 error("createContextWrapperThatThrowsOnGLError: Error accessing " + i);
632 wrap.getError = function() {
633 return context.getError();
635 return wrap;
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) {
645 var exception;
646 try {
647 eval(evalStr);
648 } catch (e) {
649 exception = e;
651 if (exception) {
652 testFailed(evalStr + " threw exception " + exception);
653 } else {
654 var err = gl.getError();
655 if (err != glError) {
656 testFailed(evalStr + " expected: " + getGLErrorAsString(gl, glError) + ". Was " + getGLErrorAsString(gl, err) + ".");
657 } else {
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);
675 } else {
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) {
688 // Link the program
689 gl.linkProgram(program);
691 // Check the link status
692 var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
693 if (!linked) {
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
707 * shader source.
708 * @param {string} fshader The id of the script tag that contains the fragment
709 * shader source.
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);
719 if (!gl) {
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) {
728 return null;
731 // Create the program object
732 program = gl.createProgram();
734 if (!program) {
735 return null;
738 // Attach our two shaders to the program
739 gl.attachShader (program, vertexShader);
740 gl.attachShader (program, fragmentShader);
742 // Bind attributes
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);
752 gl.clearDepth(1);
754 gl.enable(gl.DEPTH_TEST);
755 gl.enable(gl.BLEND);
756 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
758 gl.program = program;
759 return gl;
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 + '"';
771 var request;
772 if (window.XMLHttpRequest) {
773 request = new XMLHttpRequest();
774 if (request.overrideMimeType) {
775 request.overrideMimeType('text/plain');
777 } else {
778 throw 'XMLHttpRequest is disabled';
780 try {
781 request.open('GET', url, true);
782 request.onreadystatechange = function() {
783 if (request.readyState == 4) {
784 var text = '';
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
787 // start at 100).
788 // https://developer.mozilla.org/En/Using_XMLHttpRequest
789 var success = request.status == 200 || request.status == 0;
790 if (success) {
791 text = request.responseText;
793 log("loaded: " + url);
794 callback(success, text);
797 request.send(null);
798 } catch (e) {
799 log("failed to load: " + url);
800 callback(false, '');
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
807 * the list.
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) {
814 var files = [];
816 var getFileListImpl = function(url, callback) {
817 var files = [];
818 if (url.substr(url.length - 4) == '.txt') {
819 loadTextFileAsync(url, function() {
820 return function(success, text) {
821 if (!success) {
822 callback(false, '');
823 return;
825 var lines = text.split('\n');
826 var prefix = '';
827 var lastSlash = url.lastIndexOf('/');
828 if (lastSlash >= 0) {
829 prefix = url.substr(0, lastSlash + 1);
831 var fail = false;
832 var count = 1;
833 var index = 0;
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 &&
837 str[0] != '#' &&
838 str[0] != ";" &&
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;
844 ++count;
845 getFileListImpl(new_url, function(index) {
846 return function(success, new_files) {
847 log("got files: " + new_files.length);
848 if (success) {
849 files[index] = new_files;
851 finish(success);
853 }(index++));
854 } else {
855 var s = "";
856 var p = "";
857 for (var jj = 0; jj < names.length; ++jj) {
858 s += p + prefix + names[jj];
859 p = " ";
861 files[index++] = s;
865 finish(true);
867 function finish(success) {
868 if (!success) {
869 fail = true;
871 --count;
872 log("count: " + count);
873 if (!count) {
874 callback(!fail, files);
878 }());
880 } else {
881 files.push(url);
882 callback(true, files);
886 getFileListImpl(url, function(success, files) {
887 // flatten
888 var flat = [];
889 flatten(files);
890 function flatten(files) {
891 for (var ii = 0; ii < files.length; ++ii) {
892 var value = files[ii];
893 if (typeof(value) == "string") {
894 flat.push(value);
895 } else {
896 flatten(value);
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);
912 xhr.send();
913 return xhr.responseText.replace(/\r/g, "");
916 var readFileList = function(url) {
917 var files = [];
918 if (url.substr(url.length - 4) == '.txt') {
919 var lines = readFile(url).split('\n');
920 var prefix = '';
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 &&
928 str[0] != '#' &&
929 str[0] != ";" &&
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));
935 } else {
936 var s = "";
937 var p = "";
938 for (var jj = 0; jj < names.length; ++jj) {
939 s += p + prefix + names[jj];
940 p = " ";
942 files.push(s);
946 } else {
947 files.push(url);
949 return files;
953 * Loads a shader.
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+"'");
966 return null;
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));
974 return null;
977 // Compile the shader
978 gl.compileShader(shader);
980 // Check the compile status
981 var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
982 if (!compiled) {
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);
987 return null;
990 return 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 = "";
1018 var shaderType;
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");
1032 return null;
1036 return loadShader(
1037 gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
1038 opt_errorCallback);
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);
1046 return 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();
1060 gl.attachShader(
1061 program,
1062 loadShaderFromFile(
1063 gl, vertexShaderPath, gl.VERTEX_SHADER, opt_errorCallback));
1064 gl.attachShader(
1065 program,
1066 loadShaderFromFile(
1067 gl, fragmentShaderPath, gl.FRAGMENT_SHADER, opt_errorCallback));
1068 linkProgram(gl, program, opt_errorCallback);
1069 return program;
1073 * Loads shaders from script tags, creates a program, attaches the shaders and
1074 * links.
1075 * @param {!WebGLContext} gl The WebGLContext to use.
1076 * @param {string} vertexScriptId The id of the script tag that contains the
1077 * vertex shader.
1078 * @param {string} fragmentScriptId The id of the script tag that contains the
1079 * fragment shader.
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();
1086 gl.attachShader(
1087 program,
1088 loadShaderFromScript(
1089 gl, vertexScriptId, gl.VERTEX_SHADER, opt_errorCallback));
1090 gl.attachShader(
1091 program,
1092 loadShaderFromScript(
1093 gl, fragmentScriptId, gl.FRAGMENT_SHADER, opt_errorCallback));
1094 linkProgram(gl, program, opt_errorCallback);
1095 return program;
1099 * Loads shaders from source, creates a program, attaches the shaders and
1100 * links.
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();
1110 gl.attachShader(
1111 program,
1112 loadShader(
1113 gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback));
1114 gl.attachShader(
1115 program,
1116 loadShader(
1117 gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback));
1118 linkProgram(gl, program, opt_errorCallback);
1119 return program;
1122 var basePath;
1123 var getBasePath = function() {
1124 if (!basePath) {
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;
1129 var l = src.length;
1130 if (src.substr(l - expectedBase.length) == expectedBase) {
1131 basePath = src.substr(0, l - expectedBase.length);
1135 return basePath;
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() {
1157 callback(img);
1159 img.src = url;
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) {
1169 var count = 1;
1170 var images = { };
1171 function countDown() {
1172 --count;
1173 if (count == 0) {
1174 callback(images);
1177 function imageLoaded(url) {
1178 return function(img) {
1179 images[url] = img;
1180 countDown();
1183 for (var ii = 0; ii < urls.length; ++ii) {
1184 ++count;
1185 loadImageAsync(urls[ii], imageLoaded(urls[ii]));
1187 countDown();
1190 var getUrlArguments = function() {
1191 var args = {};
1192 try {
1193 var s = window.location.href;
1194 var q = s.indexOf("?");
1195 var e = s.indexOf("#");
1196 if (e < 0) {
1197 e = s.length;
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]);
1205 args[key] = value;
1207 } catch (e) {
1208 throw "could not parse url";
1210 return args;
1213 // Add your prefix here.
1214 var browserPrefixes = [
1216 "MOZ_",
1217 "OP_",
1218 "WEBKIT_"
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
1227 * found.
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
1245 * found.
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);
1251 if (ext) {
1252 return ext;
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);
1279 }(functionName);
1282 return function(callback, element) {
1283 return window.setTimeout(callback, 1000 / 70);
1285 }();
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;
1301 })();
1303 var waitForComposite = function(frames, callback) {
1304 var countDown = function() {
1305 if (frames == 0) {
1306 callback();
1307 } else {
1308 --frames;
1309 requestAnimFrame(countDown);
1312 countDown();
1315 return {
1316 cancelAnimFrame: cancelAnimFrame,
1317 create3DContext: create3DContext,
1318 create3DContextWithWrapperThatThrowsOnGLError:
1319 create3DContextWithWrapperThatThrowsOnGLError,
1320 checkCanvas: checkCanvas,
1321 checkCanvasRect: checkCanvasRect,
1322 createColoredTexture: createColoredTexture,
1323 drawQuad: drawQuad,
1324 drawIndexedQuad: drawIndexedQuad,
1325 drawUByteColorQuad: drawUByteColorQuad,
1326 drawFloatColorQuad: drawFloatColorQuad,
1327 endsWith: endsWith,
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,
1349 log: log,
1350 loggingOff: loggingOff,
1351 error: error,
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,
1363 readFile: readFile,
1364 readFileList: readFileList,
1365 requestAnimFrame: requestAnimFrame,
1366 waitForComposite: waitForComposite,
1368 none: false
1371 }());