|
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|string>} shaders The shaders to |
|
133 * attach, or the source, or the id of a script to get |
|
134 * the source from. |
|
135 * @param {!Array.<string>} opt_attribs The attribs names. |
|
136 * @param {!Array.<number>} opt_locations The locations for the attribs. |
|
137 */ |
|
138 var setupProgram = function(gl, shaders, opt_attribs, opt_locations) { |
|
139 var realShaders = []; |
|
140 var program = gl.createProgram(); |
|
141 for (var ii = 0; ii < shaders.length; ++ii) { |
|
142 var shader = shaders[ii]; |
|
143 if (typeof shader == 'string') { |
|
144 var element = document.getElementById(shader); |
|
145 if (element) { |
|
146 shader = loadShaderFromScript(gl, shader); |
|
147 } else { |
|
148 shader = loadShader(gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER); |
|
149 } |
|
150 } |
|
151 gl.attachShader(program, shader); |
|
152 } |
|
153 if (opt_attribs) { |
|
154 for (var ii = 0; ii < opt_attribs.length; ++ii) { |
|
155 gl.bindAttribLocation( |
|
156 program, |
|
157 opt_locations ? opt_locations[ii] : ii, |
|
158 opt_attribs[ii]); |
|
159 } |
|
160 } |
|
161 gl.linkProgram(program); |
|
162 |
|
163 // Check the link status |
|
164 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); |
|
165 if (!linked) { |
|
166 // something went wrong with the link |
|
167 lastError = gl.getProgramInfoLog (program); |
|
168 error("Error in program linking:" + lastError); |
|
169 |
|
170 gl.deleteProgram(program); |
|
171 return null; |
|
172 } |
|
173 |
|
174 gl.useProgram(program); |
|
175 return program; |
|
176 }; |
|
177 |
|
178 /** |
|
179 * Creates a simple texture program. |
|
180 * @param {!WebGLContext} gl The WebGLContext to use. |
|
181 * @param {number} opt_positionLocation The attrib location for position. |
|
182 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
|
183 * @return {WebGLProgram} |
|
184 */ |
|
185 var setupSimpleTextureProgram = function( |
|
186 gl, opt_positionLocation, opt_texcoordLocation) { |
|
187 opt_positionLocation = opt_positionLocation || 0; |
|
188 opt_texcoordLocation = opt_texcoordLocation || 1; |
|
189 var vs = setupSimpleTextureVertexShader(gl); |
|
190 var fs = setupSimpleTextureFragmentShader(gl); |
|
191 if (!vs || !fs) { |
|
192 return null; |
|
193 } |
|
194 var program = setupProgram( |
|
195 gl, |
|
196 [vs, fs], |
|
197 ['vPosition', 'texCoord0'], |
|
198 [opt_positionLocation, opt_texcoordLocation]); |
|
199 if (!program) { |
|
200 gl.deleteShader(fs); |
|
201 gl.deleteShader(vs); |
|
202 } |
|
203 gl.useProgram(program); |
|
204 return program; |
|
205 }; |
|
206 |
|
207 /** |
|
208 * Creates buffers for a textured unit quad and attaches them to vertex attribs. |
|
209 * @param {!WebGLContext} gl The WebGLContext to use. |
|
210 * @param {number} opt_positionLocation The attrib location for position. |
|
211 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
|
212 * @return {!Array.<WebGLBuffer>} The buffer objects that were |
|
213 * created. |
|
214 */ |
|
215 var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) { |
|
216 return setupUnitQuadWithTexCoords(gl, [ 0.0, 0.0 ], [ 1.0, 1.0 ], |
|
217 opt_positionLocation, opt_texcoordLocation); |
|
218 }; |
|
219 |
|
220 /** |
|
221 * Draws a previously setupUnitQuad. |
|
222 * @param {!WebGLContext} gl The WebGLContext to use. |
|
223 */ |
|
224 var drawUnitQuad = function(gl) { |
|
225 gl.drawArrays(gl.TRIANGLES, 0, 6); |
|
226 }; |
|
227 |
|
228 /** |
|
229 * Clears then Draws a previously setupUnitQuad. |
|
230 * @param {!WebGLContext} gl The WebGLContext to use. |
|
231 * @param {!Array.<number>} opt_color The color to fill clear with before |
|
232 * drawing. A 4 element array where each element is in the range 0 to |
|
233 * 255. Default [255, 255, 255, 255] |
|
234 */ |
|
235 var clearAndDrawUnitQuad = function(gl, opt_color) { |
|
236 opt_color = opt_color || [255, 255, 255, 255]; |
|
237 |
|
238 // Save and restore. |
|
239 var prevClearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE); |
|
240 |
|
241 gl.clearColor(opt_color[0] / 255, |
|
242 opt_color[1] / 255, |
|
243 opt_color[2] / 255, |
|
244 opt_color[3] / 255); |
|
245 gl.clear(gl.COLOR_BUFFER_BIT); |
|
246 drawUnitQuad(gl); |
|
247 |
|
248 gl.clearColor(prevClearColor[0], |
|
249 prevClearColor[1], |
|
250 prevClearColor[2], |
|
251 prevClearColor[3]); |
|
252 }; |
|
253 |
|
254 /** |
|
255 * Creates buffers for a textured unit quad with specified lower left |
|
256 * and upper right texture coordinates, and attaches them to vertex |
|
257 * attribs. |
|
258 * @param {!WebGLContext} gl The WebGLContext to use. |
|
259 * @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the lower left corner. |
|
260 * @param {!Array.<number>} upperRightTexCoords The texture coordinates for the upper right corner. |
|
261 * @param {number} opt_positionLocation The attrib location for position. |
|
262 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
|
263 * @return {!Array.<WebGLBuffer>} The buffer objects that were |
|
264 * created. |
|
265 */ |
|
266 var setupUnitQuadWithTexCoords = function( |
|
267 gl, lowerLeftTexCoords, upperRightTexCoords, |
|
268 opt_positionLocation, opt_texcoordLocation) { |
|
269 opt_positionLocation = opt_positionLocation || 0; |
|
270 opt_texcoordLocation = opt_texcoordLocation || 1; |
|
271 var objects = []; |
|
272 |
|
273 var vertexObject = gl.createBuffer(); |
|
274 gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); |
|
275 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ |
|
276 1.0, 1.0, 0.0, |
|
277 -1.0, 1.0, 0.0, |
|
278 -1.0, -1.0, 0.0, |
|
279 1.0, 1.0, 0.0, |
|
280 -1.0, -1.0, 0.0, |
|
281 1.0, -1.0, 0.0]), gl.STATIC_DRAW); |
|
282 gl.enableVertexAttribArray(opt_positionLocation); |
|
283 gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0); |
|
284 objects.push(vertexObject); |
|
285 |
|
286 var llx = lowerLeftTexCoords[0]; |
|
287 var lly = lowerLeftTexCoords[1]; |
|
288 var urx = upperRightTexCoords[0]; |
|
289 var ury = upperRightTexCoords[1]; |
|
290 |
|
291 var vertexObject = gl.createBuffer(); |
|
292 gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); |
|
293 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ |
|
294 urx, ury, |
|
295 llx, ury, |
|
296 llx, lly, |
|
297 urx, ury, |
|
298 llx, lly, |
|
299 urx, lly]), gl.STATIC_DRAW); |
|
300 gl.enableVertexAttribArray(opt_texcoordLocation); |
|
301 gl.vertexAttribPointer(opt_texcoordLocation, 2, gl.FLOAT, false, 0, 0); |
|
302 objects.push(vertexObject); |
|
303 return objects; |
|
304 }; |
|
305 |
|
306 /** |
|
307 * Creates a program and buffers for rendering a textured quad. |
|
308 * @param {!WebGLContext} gl The WebGLContext to use. |
|
309 * @param {number} opt_positionLocation The attrib location for position. |
|
310 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
|
311 * @return {!WebGLProgram} |
|
312 */ |
|
313 var setupTexturedQuad = function( |
|
314 gl, opt_positionLocation, opt_texcoordLocation) { |
|
315 var program = setupSimpleTextureProgram( |
|
316 gl, opt_positionLocation, opt_texcoordLocation); |
|
317 setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation); |
|
318 return program; |
|
319 }; |
|
320 |
|
321 /** |
|
322 * Creates a program and buffers for rendering a textured quad with |
|
323 * specified lower left and upper right texture coordinates. |
|
324 * @param {!WebGLContext} gl The WebGLContext to use. |
|
325 * @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the lower left corner. |
|
326 * @param {!Array.<number>} upperRightTexCoords The texture coordinates for the upper right corner. |
|
327 * @param {number} opt_positionLocation The attrib location for position. |
|
328 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
|
329 * @return {!WebGLProgram} |
|
330 */ |
|
331 var setupTexturedQuadWithTexCoords = function( |
|
332 gl, lowerLeftTexCoords, upperRightTexCoords, |
|
333 opt_positionLocation, opt_texcoordLocation) { |
|
334 var program = setupSimpleTextureProgram( |
|
335 gl, opt_positionLocation, opt_texcoordLocation); |
|
336 setupUnitQuadWithTexCoords(gl, lowerLeftTexCoords, upperRightTexCoords, |
|
337 opt_positionLocation, opt_texcoordLocation); |
|
338 return program; |
|
339 }; |
|
340 |
|
341 /** |
|
342 * Creates a unit quad with only positions of a given resolution. |
|
343 * @param {!WebGLContext} gl The WebGLContext to use. |
|
344 * @param {number} gridRes The resolution of the mesh grid, expressed in the number of triangles across and down. |
|
345 * @param {number} opt_positionLocation The attrib location for position. |
|
346 */ |
|
347 var setupQuad = function ( |
|
348 gl, gridRes, opt_positionLocation, opt_flipOddTriangles) { |
|
349 var positionLocation = opt_positionLocation || 0; |
|
350 var objects = []; |
|
351 |
|
352 var vertsAcross = gridRes + 1; |
|
353 var numVerts = vertsAcross * vertsAcross; |
|
354 var positions = new Float32Array(numVerts * 3); |
|
355 var indices = new Uint16Array(6 * gridRes * gridRes); |
|
356 |
|
357 var poffset = 0; |
|
358 |
|
359 for (var yy = 0; yy <= gridRes; ++yy) { |
|
360 for (var xx = 0; xx <= gridRes; ++xx) { |
|
361 positions[poffset + 0] = -1 + 2 * xx / gridRes; |
|
362 positions[poffset + 1] = -1 + 2 * yy / gridRes; |
|
363 positions[poffset + 2] = 0; |
|
364 |
|
365 poffset += 3; |
|
366 } |
|
367 } |
|
368 |
|
369 var tbase = 0; |
|
370 for (var yy = 0; yy < gridRes; ++yy) { |
|
371 var index = yy * vertsAcross; |
|
372 for (var xx = 0; xx < gridRes; ++xx) { |
|
373 indices[tbase + 0] = index + 0; |
|
374 indices[tbase + 1] = index + 1; |
|
375 indices[tbase + 2] = index + vertsAcross; |
|
376 indices[tbase + 3] = index + vertsAcross; |
|
377 indices[tbase + 4] = index + 1; |
|
378 indices[tbase + 5] = index + vertsAcross + 1; |
|
379 |
|
380 if (opt_flipOddTriangles) { |
|
381 indices[tbase + 4] = index + vertsAcross + 1; |
|
382 indices[tbase + 5] = index + 1; |
|
383 } |
|
384 |
|
385 index += 1; |
|
386 tbase += 6; |
|
387 } |
|
388 } |
|
389 |
|
390 var buf = gl.createBuffer(); |
|
391 gl.bindBuffer(gl.ARRAY_BUFFER, buf); |
|
392 gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); |
|
393 gl.enableVertexAttribArray(positionLocation); |
|
394 gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); |
|
395 objects.push(buf); |
|
396 |
|
397 var buf = gl.createBuffer(); |
|
398 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf); |
|
399 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); |
|
400 objects.push(buf); |
|
401 |
|
402 return objects; |
|
403 }; |
|
404 |
|
405 /** |
|
406 * Fills the given texture with a solid color |
|
407 * @param {!WebGLContext} gl The WebGLContext to use. |
|
408 * @param {!WebGLTexture} tex The texture to fill. |
|
409 * @param {number} width The width of the texture to create. |
|
410 * @param {number} height The height of the texture to create. |
|
411 * @param {!Array.<number>} color The color to fill with. A 4 element array |
|
412 * where each element is in the range 0 to 255. |
|
413 * @param {number} opt_level The level of the texture to fill. Default = 0. |
|
414 */ |
|
415 var fillTexture = function(gl, tex, width, height, color, opt_level) { |
|
416 opt_level = opt_level || 0; |
|
417 var numPixels = width * height; |
|
418 var size = numPixels * 4; |
|
419 var buf = new Uint8Array(size); |
|
420 for (var ii = 0; ii < numPixels; ++ii) { |
|
421 var off = ii * 4; |
|
422 buf[off + 0] = color[0]; |
|
423 buf[off + 1] = color[1]; |
|
424 buf[off + 2] = color[2]; |
|
425 buf[off + 3] = color[3]; |
|
426 } |
|
427 gl.bindTexture(gl.TEXTURE_2D, tex); |
|
428 gl.texImage2D( |
|
429 gl.TEXTURE_2D, opt_level, gl.RGBA, width, height, 0, |
|
430 gl.RGBA, gl.UNSIGNED_BYTE, buf); |
|
431 }; |
|
432 |
|
433 /** |
|
434 * Creates a textures and fills it with a solid color |
|
435 * @param {!WebGLContext} gl The WebGLContext to use. |
|
436 * @param {number} width The width of the texture to create. |
|
437 * @param {number} height The height of the texture to create. |
|
438 * @param {!Array.<number>} color The color to fill with. A 4 element array |
|
439 * where each element is in the range 0 to 255. |
|
440 * @return {!WebGLTexture} |
|
441 */ |
|
442 var createColoredTexture = function(gl, width, height, color) { |
|
443 var tex = gl.createTexture(); |
|
444 fillTexture(gl, tex, width, height, color); |
|
445 return tex; |
|
446 }; |
|
447 |
|
448 /** |
|
449 * Draws a previously setup quad. |
|
450 * @param {!WebGLContext} gl The WebGLContext to use. |
|
451 * @param {!Array.<number>} opt_color The color to fill clear with before |
|
452 * drawing. A 4 element array where each element is in the range 0 to |
|
453 * 255. Default [255, 255, 255, 255] |
|
454 */ |
|
455 var drawQuad = function(gl, opt_color) { |
|
456 opt_color = opt_color || [255, 255, 255, 255]; |
|
457 gl.clearColor( |
|
458 opt_color[0] / 255, |
|
459 opt_color[1] / 255, |
|
460 opt_color[2] / 255, |
|
461 opt_color[3] / 255); |
|
462 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
|
463 gl.drawArrays(gl.TRIANGLES, 0, 6); |
|
464 }; |
|
465 |
|
466 /** |
|
467 * Checks that a portion of a canvas is 1 color. |
|
468 * @param {!WebGLContext} gl The WebGLContext to use. |
|
469 * @param {number} x left corner of region to check. |
|
470 * @param {number} y bottom corner of region to check. |
|
471 * @param {number} width width of region to check. |
|
472 * @param {number} height width of region to check. |
|
473 * @param {!Array.<number>} color The color to fill clear with before drawing. A |
|
474 * 4 element array where each element is in the range 0 to 255. |
|
475 * @param {string} msg Message to associate with success. Eg ("should be red"). |
|
476 * @param {number} errorRange Optional. Acceptable error in |
|
477 * color checking. 0 by default. |
|
478 */ |
|
479 var checkCanvasRect = function(gl, x, y, width, height, color, msg, errorRange) { |
|
480 errorRange = errorRange || 0; |
|
481 var buf = new Uint8Array(width * height * 4); |
|
482 gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf); |
|
483 for (var i = 0; i < width * height; ++i) { |
|
484 var offset = i * 4; |
|
485 for (var j = 0; j < color.length; ++j) { |
|
486 if (Math.abs(buf[offset + j] - color[j]) > errorRange) { |
|
487 var was = buf[offset + 0].toString(); |
|
488 for (j = 1; j < color.length; ++j) { |
|
489 was += "," + buf[offset + j]; |
|
490 } |
|
491 |
|
492 var cv = document.createElement('canvas'); |
|
493 cv.height = height; |
|
494 cv.width = width; |
|
495 var ctx = cv.getContext('2d'); |
|
496 ctx.fillStyle="rgba(" + color[0] + ", " + color[1] + ", " + color[2] + ", 255)"; |
|
497 ctx.fillRect(0, 0, width, height); |
|
498 testFailedRender(msg, ctx, buf, width, height); |
|
499 |
|
500 debug('at (' + (i % width) + ', ' + Math.floor(i / width) + |
|
501 ') expected: ' + color + ' was ' + was); |
|
502 return; |
|
503 } |
|
504 } |
|
505 } |
|
506 testPassed(msg); |
|
507 }; |
|
508 |
|
509 /** |
|
510 * Checks that an entire canvas is 1 color. |
|
511 * @param {!WebGLContext} gl The WebGLContext to use. |
|
512 * @param {!Array.<number>} color The color to fill clear with before drawing. A |
|
513 * 4 element array where each element is in the range 0 to 255. |
|
514 * @param {string} msg Message to associate with success. Eg ("should be red"). |
|
515 * @param {number} errorRange Optional. Acceptable error in |
|
516 * color checking. 0 by default. |
|
517 */ |
|
518 var checkCanvas = function(gl, color, msg, errorRange) { |
|
519 checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, color, msg, errorRange); |
|
520 }; |
|
521 |
|
522 /** |
|
523 * Loads a texture, calls callback when finished. |
|
524 * @param {!WebGLContext} gl The WebGLContext to use. |
|
525 * @param {string} url URL of image to load |
|
526 * @param {function(!Image): void} callback Function that gets called after |
|
527 * image has loaded |
|
528 * @return {!WebGLTexture} The created texture. |
|
529 */ |
|
530 var loadTexture = function(gl, url, callback) { |
|
531 var texture = gl.createTexture(); |
|
532 gl.bindTexture(gl.TEXTURE_2D, texture); |
|
533 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
|
534 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
|
535 var image = new Image(); |
|
536 image.onload = function() { |
|
537 gl.bindTexture(gl.TEXTURE_2D, texture); |
|
538 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); |
|
539 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); |
|
540 callback(image); |
|
541 }; |
|
542 image.src = url; |
|
543 return texture; |
|
544 }; |
|
545 |
|
546 /** |
|
547 * Creates a webgl context. |
|
548 * @param {!Canvas|string} opt_canvas The canvas tag to get |
|
549 * context from. If one is not passed in one will be |
|
550 * created. If it's a string it's assumed to be the id of a |
|
551 * canvas. |
|
552 * @return {!WebGLContext} The created context. |
|
553 */ |
|
554 var create3DContext = function(opt_canvas, opt_attributes) { |
|
555 opt_canvas = opt_canvas || document.createElement("canvas"); |
|
556 if (typeof opt_canvas == 'string') { |
|
557 opt_canvas = document.getElementById(opt_canvas); |
|
558 } |
|
559 var context = null; |
|
560 var names = ["webgl", "experimental-webgl"]; |
|
561 for (var i = 0; i < names.length; ++i) { |
|
562 try { |
|
563 context = opt_canvas.getContext(names[i], opt_attributes); |
|
564 } catch (e) { |
|
565 } |
|
566 if (context) { |
|
567 break; |
|
568 } |
|
569 } |
|
570 if (!context) { |
|
571 testFailed("Unable to fetch WebGL rendering context for Canvas"); |
|
572 } |
|
573 return context; |
|
574 } |
|
575 |
|
576 /** |
|
577 * Gets a GLError value as a string. |
|
578 * @param {!WebGLContext} gl The WebGLContext to use. |
|
579 * @param {number} err The webgl error as retrieved from gl.getError(). |
|
580 * @return {string} the error as a string. |
|
581 */ |
|
582 var getGLErrorAsString = function(gl, err) { |
|
583 if (err === gl.NO_ERROR) { |
|
584 return "NO_ERROR"; |
|
585 } |
|
586 for (var name in gl) { |
|
587 if (gl[name] === err) { |
|
588 return name; |
|
589 } |
|
590 } |
|
591 return err.toString(); |
|
592 }; |
|
593 |
|
594 /** |
|
595 * Wraps a WebGL function with a function that throws an exception if there is |
|
596 * an error. |
|
597 * @param {!WebGLContext} gl The WebGLContext to use. |
|
598 * @param {string} fname Name of function to wrap. |
|
599 * @return {function} The wrapped function. |
|
600 */ |
|
601 var createGLErrorWrapper = function(context, fname) { |
|
602 return function() { |
|
603 var rv = context[fname].apply(context, arguments); |
|
604 var err = context.getError(); |
|
605 if (err != 0) |
|
606 throw "GL error " + getGLErrorAsString(err) + " in " + fname; |
|
607 return rv; |
|
608 }; |
|
609 }; |
|
610 |
|
611 /** |
|
612 * Creates a WebGL context where all functions are wrapped to throw an exception |
|
613 * if there is an error. |
|
614 * @param {!Canvas} canvas The HTML canvas to get a context from. |
|
615 * @return {!Object} The wrapped context. |
|
616 */ |
|
617 function create3DContextWithWrapperThatThrowsOnGLError(canvas) { |
|
618 var context = create3DContext(canvas); |
|
619 var wrap = {}; |
|
620 for (var i in context) { |
|
621 try { |
|
622 if (typeof context[i] == 'function') { |
|
623 wrap[i] = createGLErrorWrapper(context, i); |
|
624 } else { |
|
625 wrap[i] = context[i]; |
|
626 } |
|
627 } catch (e) { |
|
628 error("createContextWrapperThatThrowsOnGLError: Error accessing " + i); |
|
629 } |
|
630 } |
|
631 wrap.getError = function() { |
|
632 return context.getError(); |
|
633 }; |
|
634 return wrap; |
|
635 }; |
|
636 |
|
637 /** |
|
638 * Tests that an evaluated expression generates a specific GL error. |
|
639 * @param {!WebGLContext} gl The WebGLContext to use. |
|
640 * @param {number} glError The expected gl error. |
|
641 * @param {string} evalSTr The string to evaluate. |
|
642 */ |
|
643 var shouldGenerateGLError = function(gl, glError, evalStr) { |
|
644 var exception; |
|
645 try { |
|
646 eval(evalStr); |
|
647 } catch (e) { |
|
648 exception = e; |
|
649 } |
|
650 if (exception) { |
|
651 testFailed(evalStr + " threw exception " + exception); |
|
652 } else { |
|
653 var err = gl.getError(); |
|
654 if (err != glError) { |
|
655 testFailed(evalStr + " expected: " + getGLErrorAsString(gl, glError) + ". Was " + getGLErrorAsString(gl, err) + "."); |
|
656 } else { |
|
657 testPassed(evalStr + " was expected value: " + getGLErrorAsString(gl, glError) + "."); |
|
658 } |
|
659 } |
|
660 }; |
|
661 |
|
662 /** |
|
663 * Tests that the first error GL returns is the specified error. |
|
664 * @param {!WebGLContext} gl The WebGLContext to use. |
|
665 * @param {number} glError The expected gl error. |
|
666 * @param {string} opt_msg |
|
667 */ |
|
668 var glErrorShouldBe = function(gl, glError, opt_msg) { |
|
669 opt_msg = opt_msg || ""; |
|
670 var err = gl.getError(); |
|
671 if (err != glError) { |
|
672 testFailed("getError expected: " + getGLErrorAsString(gl, glError) + |
|
673 ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg); |
|
674 } else { |
|
675 testPassed("getError was expected value: " + |
|
676 getGLErrorAsString(gl, glError) + " : " + opt_msg); |
|
677 } |
|
678 }; |
|
679 |
|
680 /** |
|
681 * Links a WebGL program, throws if there are errors. |
|
682 * @param {!WebGLContext} gl The WebGLContext to use. |
|
683 * @param {!WebGLProgram} program The WebGLProgram to link. |
|
684 * @param {function(string): void) opt_errorCallback callback for errors. |
|
685 */ |
|
686 var linkProgram = function(gl, program, opt_errorCallback) { |
|
687 errFn = opt_errorCallback || testFailed; |
|
688 // Link the program |
|
689 gl.linkProgram(program); |
|
690 |
|
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); |
|
696 |
|
697 errFn("Error in program linking:" + error); |
|
698 |
|
699 gl.deleteProgram(program); |
|
700 } |
|
701 }; |
|
702 |
|
703 /** |
|
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. |
|
714 */ |
|
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"); |
|
721 } |
|
722 |
|
723 // create our shaders |
|
724 var vertexShader = loadShaderFromScript(gl, vshader); |
|
725 var fragmentShader = loadShaderFromScript(gl, fshader); |
|
726 |
|
727 if (!vertexShader || !fragmentShader) { |
|
728 return null; |
|
729 } |
|
730 |
|
731 // Create the program object |
|
732 program = gl.createProgram(); |
|
733 |
|
734 if (!program) { |
|
735 return null; |
|
736 } |
|
737 |
|
738 // Attach our two shaders to the program |
|
739 gl.attachShader (program, vertexShader); |
|
740 gl.attachShader (program, fragmentShader); |
|
741 |
|
742 // Bind attributes |
|
743 for (var i in attribs) { |
|
744 gl.bindAttribLocation (program, i, attribs[i]); |
|
745 } |
|
746 |
|
747 linkProgram(gl, program); |
|
748 |
|
749 gl.useProgram(program); |
|
750 |
|
751 gl.clearColor(0,0,0,1); |
|
752 gl.clearDepth(1); |
|
753 |
|
754 gl.enable(gl.DEPTH_TEST); |
|
755 gl.enable(gl.BLEND); |
|
756 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); |
|
757 |
|
758 gl.program = program; |
|
759 return gl; |
|
760 }; |
|
761 |
|
762 /** |
|
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. |
|
767 */ |
|
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'); |
|
776 } |
|
777 } else { |
|
778 throw 'XMLHttpRequest is disabled'; |
|
779 } |
|
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; |
|
792 } |
|
793 log("loaded: " + url); |
|
794 callback(success, text); |
|
795 } |
|
796 }; |
|
797 request.send(null); |
|
798 } catch (e) { |
|
799 log("failed to load: " + url); |
|
800 callback(false, ''); |
|
801 } |
|
802 }; |
|
803 |
|
804 // Add your prefix here. |
|
805 var browserPrefixes = [ |
|
806 "", |
|
807 "MOZ_", |
|
808 "OP_", |
|
809 "WEBKIT_" |
|
810 ]; |
|
811 |
|
812 /** |
|
813 * Given an extension name like WEBGL_compressed_texture_s3tc |
|
814 * returns the name of the supported version extension, like |
|
815 * WEBKIT_WEBGL_compressed_teture_s3tc |
|
816 * @param {string} name Name of extension to look for |
|
817 * @return {string} name of extension found or undefined if not |
|
818 * found. |
|
819 */ |
|
820 var getSupportedExtensionWithKnownPrefixes = function(gl, name) { |
|
821 var supported = gl.getSupportedExtensions(); |
|
822 for (var ii = 0; ii < browserPrefixes.length; ++ii) { |
|
823 var prefixedName = browserPrefixes[ii] + name; |
|
824 if (supported.indexOf(prefixedName) >= 0) { |
|
825 return prefixedName; |
|
826 } |
|
827 } |
|
828 }; |
|
829 |
|
830 /** |
|
831 * Given an extension name like WEBGL_compressed_texture_s3tc |
|
832 * returns the supported version extension, like |
|
833 * WEBKIT_WEBGL_compressed_teture_s3tc |
|
834 * @param {string} name Name of extension to look for |
|
835 * @return {WebGLExtension} The extension or undefined if not |
|
836 * found. |
|
837 */ |
|
838 var getExtensionWithKnownPrefixes = function(gl, name) { |
|
839 for (var ii = 0; ii < browserPrefixes.length; ++ii) { |
|
840 var prefixedName = browserPrefixes[ii] + name; |
|
841 var ext = gl.getExtension(prefixedName); |
|
842 if (ext) { |
|
843 return ext; |
|
844 } |
|
845 } |
|
846 }; |
|
847 |
|
848 /** |
|
849 * Recursively loads a file as a list. Each line is parsed for a relative |
|
850 * path. If the file ends in .txt the contents of that file is inserted in |
|
851 * the list. |
|
852 * |
|
853 * @param {string} url The url of the external file. |
|
854 * @param {!function(bool, Array<string>): void} callback that is sent a bool |
|
855 * for success and the array of strings. |
|
856 */ |
|
857 var getFileListAsync = function(url, callback) { |
|
858 var files = []; |
|
859 |
|
860 var getFileListImpl = function(url, callback) { |
|
861 var files = []; |
|
862 if (url.substr(url.length - 4) == '.txt') { |
|
863 loadTextFileAsync(url, function() { |
|
864 return function(success, text) { |
|
865 if (!success) { |
|
866 callback(false, ''); |
|
867 return; |
|
868 } |
|
869 var lines = text.split('\n'); |
|
870 var prefix = ''; |
|
871 var lastSlash = url.lastIndexOf('/'); |
|
872 if (lastSlash >= 0) { |
|
873 prefix = url.substr(0, lastSlash + 1); |
|
874 } |
|
875 var fail = false; |
|
876 var count = 1; |
|
877 var index = 0; |
|
878 for (var ii = 0; ii < lines.length; ++ii) { |
|
879 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
|
880 if (str.length > 4 && |
|
881 str[0] != '#' && |
|
882 str[0] != ";" && |
|
883 str.substr(0, 2) != "//") { |
|
884 var names = str.split(/ +/); |
|
885 new_url = prefix + str; |
|
886 if (names.length == 1) { |
|
887 new_url = prefix + str; |
|
888 ++count; |
|
889 getFileListImpl(new_url, function(index) { |
|
890 return function(success, new_files) { |
|
891 log("got files: " + new_files.length); |
|
892 if (success) { |
|
893 files[index] = new_files; |
|
894 } |
|
895 finish(success); |
|
896 }; |
|
897 }(index++)); |
|
898 } else { |
|
899 var s = ""; |
|
900 var p = ""; |
|
901 for (var jj = 0; jj < names.length; ++jj) { |
|
902 s += p + prefix + names[jj]; |
|
903 p = " "; |
|
904 } |
|
905 files[index++] = s; |
|
906 } |
|
907 } |
|
908 } |
|
909 finish(true); |
|
910 |
|
911 function finish(success) { |
|
912 if (!success) { |
|
913 fail = true; |
|
914 } |
|
915 --count; |
|
916 log("count: " + count); |
|
917 if (!count) { |
|
918 callback(!fail, files); |
|
919 } |
|
920 } |
|
921 } |
|
922 }()); |
|
923 |
|
924 } else { |
|
925 files.push(url); |
|
926 callback(true, files); |
|
927 } |
|
928 }; |
|
929 |
|
930 getFileListImpl(url, function(success, files) { |
|
931 // flatten |
|
932 var flat = []; |
|
933 flatten(files); |
|
934 function flatten(files) { |
|
935 for (var ii = 0; ii < files.length; ++ii) { |
|
936 var value = files[ii]; |
|
937 if (typeof(value) == "string") { |
|
938 flat.push(value); |
|
939 } else { |
|
940 flatten(value); |
|
941 } |
|
942 } |
|
943 } |
|
944 callback(success, flat); |
|
945 }); |
|
946 }; |
|
947 |
|
948 /** |
|
949 * Gets a file from a file/URL |
|
950 * @param {string} file the URL of the file to get. |
|
951 * @return {string} The contents of the file. |
|
952 */ |
|
953 var readFile = function(file) { |
|
954 var xhr = new XMLHttpRequest(); |
|
955 xhr.open("GET", file, false); |
|
956 xhr.send(); |
|
957 return xhr.responseText.replace(/\r/g, ""); |
|
958 }; |
|
959 |
|
960 var readFileList = function(url) { |
|
961 var files = []; |
|
962 if (url.substr(url.length - 4) == '.txt') { |
|
963 var lines = readFile(url).split('\n'); |
|
964 var prefix = ''; |
|
965 var lastSlash = url.lastIndexOf('/'); |
|
966 if (lastSlash >= 0) { |
|
967 prefix = url.substr(0, lastSlash + 1); |
|
968 } |
|
969 for (var ii = 0; ii < lines.length; ++ii) { |
|
970 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
|
971 if (str.length > 4 && |
|
972 str[0] != '#' && |
|
973 str[0] != ";" && |
|
974 str.substr(0, 2) != "//") { |
|
975 var names = str.split(/ +/); |
|
976 if (names.length == 1) { |
|
977 new_url = prefix + str; |
|
978 files = files.concat(readFileList(new_url)); |
|
979 } else { |
|
980 var s = ""; |
|
981 var p = ""; |
|
982 for (var jj = 0; jj < names.length; ++jj) { |
|
983 s += p + prefix + names[jj]; |
|
984 p = " "; |
|
985 } |
|
986 files.push(s); |
|
987 } |
|
988 } |
|
989 } |
|
990 } else { |
|
991 files.push(url); |
|
992 } |
|
993 return files; |
|
994 }; |
|
995 |
|
996 /** |
|
997 * Loads a shader. |
|
998 * @param {!WebGLContext} gl The WebGLContext to use. |
|
999 * @param {string} shaderSource The shader source. |
|
1000 * @param {number} shaderType The type of shader. |
|
1001 * @param {function(string): void) opt_errorCallback callback for errors. |
|
1002 * @return {!WebGLShader} The created shader. |
|
1003 */ |
|
1004 var loadShader = function(gl, shaderSource, shaderType, opt_errorCallback) { |
|
1005 var errFn = opt_errorCallback || error; |
|
1006 // Create the shader object |
|
1007 var shader = gl.createShader(shaderType); |
|
1008 if (shader == null) { |
|
1009 errFn("*** Error: unable to create shader '"+shaderSource+"'"); |
|
1010 return null; |
|
1011 } |
|
1012 |
|
1013 // Load the shader source |
|
1014 gl.shaderSource(shader, shaderSource); |
|
1015 var err = gl.getError(); |
|
1016 if (err != gl.NO_ERROR) { |
|
1017 errFn("*** Error loading shader '" + shader + "':" + glEnumToString(gl, err)); |
|
1018 return null; |
|
1019 } |
|
1020 |
|
1021 // Compile the shader |
|
1022 gl.compileShader(shader); |
|
1023 |
|
1024 // Check the compile status |
|
1025 var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); |
|
1026 if (!compiled) { |
|
1027 // Something went wrong during compilation; get the error |
|
1028 lastError = gl.getShaderInfoLog(shader); |
|
1029 errFn("*** Error compiling shader '" + shader + "':" + lastError); |
|
1030 gl.deleteShader(shader); |
|
1031 return null; |
|
1032 } |
|
1033 |
|
1034 return shader; |
|
1035 } |
|
1036 |
|
1037 /** |
|
1038 * Loads a shader from a URL. |
|
1039 * @param {!WebGLContext} gl The WebGLContext to use. |
|
1040 * @param {file} file The URL of the shader source. |
|
1041 * @param {number} type The type of shader. |
|
1042 * @param {function(string): void) opt_errorCallback callback for errors. |
|
1043 * @return {!WebGLShader} The created shader. |
|
1044 */ |
|
1045 var loadShaderFromFile = function(gl, file, type, opt_errorCallback) { |
|
1046 var shaderSource = readFile(file); |
|
1047 return loadShader(gl, shaderSource, type, opt_errorCallback); |
|
1048 }; |
|
1049 |
|
1050 /** |
|
1051 * Gets the content of script. |
|
1052 */ |
|
1053 var getScript = function(scriptId) { |
|
1054 var shaderScript = document.getElementById(scriptId); |
|
1055 if (!shaderScript) { |
|
1056 throw("*** Error: unknown script element" + scriptId); |
|
1057 } |
|
1058 return shaderScript.text; |
|
1059 }; |
|
1060 |
|
1061 /** |
|
1062 * Loads a shader from a script tag. |
|
1063 * @param {!WebGLContext} gl The WebGLContext to use. |
|
1064 * @param {string} scriptId The id of the script tag. |
|
1065 * @param {number} opt_shaderType The type of shader. If not passed in it will |
|
1066 * be derived from the type of the script tag. |
|
1067 * @param {function(string): void) opt_errorCallback callback for errors. |
|
1068 * @return {!WebGLShader} The created shader. |
|
1069 */ |
|
1070 var loadShaderFromScript = function( |
|
1071 gl, scriptId, opt_shaderType, opt_errorCallback) { |
|
1072 var shaderSource = ""; |
|
1073 var shaderType; |
|
1074 var shaderScript = document.getElementById(scriptId); |
|
1075 if (!shaderScript) { |
|
1076 throw("*** Error: unknown script element " + scriptId); |
|
1077 } |
|
1078 shaderSource = shaderScript.text; |
|
1079 |
|
1080 if (!opt_shaderType) { |
|
1081 if (shaderScript.type == "x-shader/x-vertex") { |
|
1082 shaderType = gl.VERTEX_SHADER; |
|
1083 } else if (shaderScript.type == "x-shader/x-fragment") { |
|
1084 shaderType = gl.FRAGMENT_SHADER; |
|
1085 } else if (shaderType != gl.VERTEX_SHADER && shaderType != gl.FRAGMENT_SHADER) { |
|
1086 throw("*** Error: unknown shader type"); |
|
1087 return null; |
|
1088 } |
|
1089 } |
|
1090 |
|
1091 return loadShader( |
|
1092 gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType, |
|
1093 opt_errorCallback); |
|
1094 }; |
|
1095 |
|
1096 var loadStandardProgram = function(gl) { |
|
1097 var program = gl.createProgram(); |
|
1098 gl.attachShader(program, loadStandardVertexShader(gl)); |
|
1099 gl.attachShader(program, loadStandardFragmentShader(gl)); |
|
1100 linkProgram(gl, program); |
|
1101 return program; |
|
1102 }; |
|
1103 |
|
1104 /** |
|
1105 * Loads shaders from files, creates a program, attaches the shaders and links. |
|
1106 * @param {!WebGLContext} gl The WebGLContext to use. |
|
1107 * @param {string} vertexShaderPath The URL of the vertex shader. |
|
1108 * @param {string} fragmentShaderPath The URL of the fragment shader. |
|
1109 * @param {function(string): void) opt_errorCallback callback for errors. |
|
1110 * @return {!WebGLProgram} The created program. |
|
1111 */ |
|
1112 var loadProgramFromFile = function( |
|
1113 gl, vertexShaderPath, fragmentShaderPath, opt_errorCallback) { |
|
1114 var program = gl.createProgram(); |
|
1115 gl.attachShader( |
|
1116 program, |
|
1117 loadShaderFromFile( |
|
1118 gl, vertexShaderPath, gl.VERTEX_SHADER, opt_errorCallback)); |
|
1119 gl.attachShader( |
|
1120 program, |
|
1121 loadShaderFromFile( |
|
1122 gl, fragmentShaderPath, gl.FRAGMENT_SHADER, opt_errorCallback)); |
|
1123 linkProgram(gl, program, opt_errorCallback); |
|
1124 return program; |
|
1125 }; |
|
1126 |
|
1127 /** |
|
1128 * Loads shaders from script tags, creates a program, attaches the shaders and |
|
1129 * links. |
|
1130 * @param {!WebGLContext} gl The WebGLContext to use. |
|
1131 * @param {string} vertexScriptId The id of the script tag that contains the |
|
1132 * vertex shader. |
|
1133 * @param {string} fragmentScriptId The id of the script tag that contains the |
|
1134 * fragment shader. |
|
1135 * @param {function(string): void) opt_errorCallback callback for errors. |
|
1136 * @return {!WebGLProgram} The created program. |
|
1137 */ |
|
1138 var loadProgramFromScript = function loadProgramFromScript( |
|
1139 gl, vertexScriptId, fragmentScriptId, opt_errorCallback) { |
|
1140 var program = gl.createProgram(); |
|
1141 gl.attachShader( |
|
1142 program, |
|
1143 loadShaderFromScript( |
|
1144 gl, vertexScriptId, gl.VERTEX_SHADER, opt_errorCallback)); |
|
1145 gl.attachShader( |
|
1146 program, |
|
1147 loadShaderFromScript( |
|
1148 gl, fragmentScriptId, gl.FRAGMENT_SHADER, opt_errorCallback)); |
|
1149 linkProgram(gl, program, opt_errorCallback); |
|
1150 return program; |
|
1151 }; |
|
1152 |
|
1153 /** |
|
1154 * Loads shaders from source, creates a program, attaches the shaders and |
|
1155 * links. |
|
1156 * @param {!WebGLContext} gl The WebGLContext to use. |
|
1157 * @param {string} vertexShader The vertex shader. |
|
1158 * @param {string} fragmentShader The fragment shader. |
|
1159 * @param {function(string): void) opt_errorCallback callback for errors. |
|
1160 * @return {!WebGLProgram} The created program. |
|
1161 */ |
|
1162 var loadProgram = function( |
|
1163 gl, vertexShader, fragmentShader, opt_errorCallback) { |
|
1164 var program = gl.createProgram(); |
|
1165 gl.attachShader( |
|
1166 program, |
|
1167 loadShader( |
|
1168 gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback)); |
|
1169 gl.attachShader( |
|
1170 program, |
|
1171 loadShader( |
|
1172 gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback)); |
|
1173 linkProgram(gl, program, opt_errorCallback); |
|
1174 return program; |
|
1175 }; |
|
1176 |
|
1177 /** |
|
1178 * Loads shaders from source, creates a program, attaches the shaders and |
|
1179 * links but expects error. |
|
1180 * |
|
1181 * GLSL 1.0.17 10.27 effectively says that compileShader can |
|
1182 * always succeed as long as linkProgram fails so we can't |
|
1183 * rely on compileShader failing. This function expects |
|
1184 * one of the shader to fail OR linking to fail. |
|
1185 * |
|
1186 * @param {!WebGLContext} gl The WebGLContext to use. |
|
1187 * @param {string} vertexShaderScriptId The vertex shader. |
|
1188 * @param {string} fragmentShaderScriptId The fragment shader. |
|
1189 * @return {WebGLProgram} The created program. |
|
1190 */ |
|
1191 var loadProgramFromScriptExpectError = function( |
|
1192 gl, vertexShaderScriptId, fragmentShaderScriptId) { |
|
1193 var vertexShader = loadShaderFromScript(gl, vertexShaderScriptId); |
|
1194 if (!vertexShader) { |
|
1195 return null; |
|
1196 } |
|
1197 var fragmentShader = loadShaderFromScript(gl, fragmentShaderScriptId); |
|
1198 if (!fragmentShader) { |
|
1199 return null; |
|
1200 } |
|
1201 var linkSuccess = true; |
|
1202 var program = gl.createProgram(); |
|
1203 gl.attachShader(program, vertexShader); |
|
1204 gl.attachShader(program, fragmentShader); |
|
1205 linkSuccess = true; |
|
1206 linkProgram(gl, program, function() { |
|
1207 linkSuccess = false; |
|
1208 }); |
|
1209 return linkSuccess ? program : null; |
|
1210 }; |
|
1211 |
|
1212 var basePath; |
|
1213 var getBasePath = function() { |
|
1214 if (!basePath) { |
|
1215 var expectedBase = "webgl-test-utils.js"; |
|
1216 var scripts = document.getElementsByTagName('script'); |
|
1217 for (var script, i = 0; script = scripts[i]; i++) { |
|
1218 var src = script.src; |
|
1219 var l = src.length; |
|
1220 if (src.substr(l - expectedBase.length) == expectedBase) { |
|
1221 basePath = src.substr(0, l - expectedBase.length); |
|
1222 } |
|
1223 } |
|
1224 } |
|
1225 return basePath; |
|
1226 }; |
|
1227 |
|
1228 var loadStandardVertexShader = function(gl) { |
|
1229 return loadShaderFromFile( |
|
1230 gl, getBasePath() + "vertexShader.vert", gl.VERTEX_SHADER); |
|
1231 }; |
|
1232 |
|
1233 var loadStandardFragmentShader = function(gl) { |
|
1234 return loadShaderFromFile( |
|
1235 gl, getBasePath() + "fragmentShader.frag", gl.FRAGMENT_SHADER); |
|
1236 }; |
|
1237 |
|
1238 /** |
|
1239 * Loads an image asynchronously. |
|
1240 * @param {string} url URL of image to load. |
|
1241 * @param {!function(!Element): void} callback Function to call |
|
1242 * with loaded image. |
|
1243 */ |
|
1244 var loadImageAsync = function(url, callback) { |
|
1245 var img = document.createElement('img'); |
|
1246 img.onload = function() { |
|
1247 callback(img); |
|
1248 }; |
|
1249 img.src = url; |
|
1250 }; |
|
1251 |
|
1252 /** |
|
1253 * Loads an array of images. |
|
1254 * @param {!Array.<string>} urls URLs of images to load. |
|
1255 * @param {!function(!{string, img}): void} callback. Callback |
|
1256 * that gets passed map of urls to img tags. |
|
1257 */ |
|
1258 var loadImagesAsync = function(urls, callback) { |
|
1259 var count = 1; |
|
1260 var images = { }; |
|
1261 function countDown() { |
|
1262 --count; |
|
1263 if (count == 0) { |
|
1264 callback(images); |
|
1265 } |
|
1266 } |
|
1267 function imageLoaded(url) { |
|
1268 return function(img) { |
|
1269 images[url] = img; |
|
1270 countDown(); |
|
1271 } |
|
1272 } |
|
1273 for (var ii = 0; ii < urls.length; ++ii) { |
|
1274 ++count; |
|
1275 loadImageAsync(urls[ii], imageLoaded(urls[ii])); |
|
1276 } |
|
1277 countDown(); |
|
1278 }; |
|
1279 |
|
1280 var getUrlArguments = function() { |
|
1281 var args = {}; |
|
1282 try { |
|
1283 var s = window.location.href; |
|
1284 var q = s.indexOf("?"); |
|
1285 var e = s.indexOf("#"); |
|
1286 if (e < 0) { |
|
1287 e = s.length; |
|
1288 } |
|
1289 var query = s.substring(q + 1, e); |
|
1290 var pairs = query.split("&"); |
|
1291 for (var ii = 0; ii < pairs.length; ++ii) { |
|
1292 var keyValue = pairs[ii].split("="); |
|
1293 var key = keyValue[0]; |
|
1294 var value = decodeURIComponent(keyValue[1]); |
|
1295 args[key] = value; |
|
1296 } |
|
1297 } catch (e) { |
|
1298 throw "could not parse url"; |
|
1299 } |
|
1300 return args; |
|
1301 }; |
|
1302 |
|
1303 var makeImage = function(canvas) { |
|
1304 var img = document.createElement('img'); |
|
1305 img.src = canvas.toDataURL(); |
|
1306 return img; |
|
1307 }; |
|
1308 |
|
1309 var insertImage = function(element, caption, img) { |
|
1310 var div = document.createElement("div"); |
|
1311 div.appendChild(img); |
|
1312 var label = document.createElement("div"); |
|
1313 label.appendChild(document.createTextNode(caption)); |
|
1314 div.appendChild(label); |
|
1315 element.appendChild(div); |
|
1316 }; |
|
1317 |
|
1318 var addShaderSource = function(element, label, source) { |
|
1319 var div = document.createElement("div"); |
|
1320 var s = document.createElement("pre"); |
|
1321 s.className = "shader-source"; |
|
1322 s.style.display = "none"; |
|
1323 var ol = document.createElement("ol"); |
|
1324 //s.appendChild(document.createTextNode(source)); |
|
1325 var lines = source.split("\n"); |
|
1326 for (var ii = 0; ii < lines.length; ++ii) { |
|
1327 var line = lines[ii]; |
|
1328 var li = document.createElement("li"); |
|
1329 li.appendChild(document.createTextNode(line)); |
|
1330 ol.appendChild(li); |
|
1331 } |
|
1332 s.appendChild(ol); |
|
1333 var l = document.createElement("a"); |
|
1334 l.href = "show-shader-source"; |
|
1335 l.appendChild(document.createTextNode(label)); |
|
1336 l.addEventListener('click', function(event) { |
|
1337 if (event.preventDefault) { |
|
1338 event.preventDefault(); |
|
1339 } |
|
1340 s.style.display = (s.style.display == 'none') ? 'block' : 'none'; |
|
1341 return false; |
|
1342 }, false); |
|
1343 div.appendChild(l); |
|
1344 div.appendChild(s); |
|
1345 element.appendChild(div); |
|
1346 } |
|
1347 |
|
1348 return { |
|
1349 addShaderSource: addShaderSource, |
|
1350 clearAndDrawUnitQuad : clearAndDrawUnitQuad, |
|
1351 create3DContext: create3DContext, |
|
1352 create3DContextWithWrapperThatThrowsOnGLError: |
|
1353 create3DContextWithWrapperThatThrowsOnGLError, |
|
1354 checkCanvas: checkCanvas, |
|
1355 checkCanvasRect: checkCanvasRect, |
|
1356 createColoredTexture: createColoredTexture, |
|
1357 drawQuad: drawQuad, |
|
1358 drawUnitQuad: drawUnitQuad, |
|
1359 endsWith: endsWith, |
|
1360 getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes, |
|
1361 getFileListAsync: getFileListAsync, |
|
1362 getLastError: getLastError, |
|
1363 getScript: getScript, |
|
1364 getSupportedExtensionWithKnownPrefixes: getSupportedExtensionWithKnownPrefixes, |
|
1365 getUrlArguments: getUrlArguments, |
|
1366 glEnumToString: glEnumToString, |
|
1367 glErrorShouldBe: glErrorShouldBe, |
|
1368 fillTexture: fillTexture, |
|
1369 insertImage: insertImage, |
|
1370 loadImageAsync: loadImageAsync, |
|
1371 loadImagesAsync: loadImagesAsync, |
|
1372 loadProgram: loadProgram, |
|
1373 loadProgramFromFile: loadProgramFromFile, |
|
1374 loadProgramFromScript: loadProgramFromScript, |
|
1375 loadProgramFromScriptExpectError: loadProgramFromScriptExpectError, |
|
1376 loadShader: loadShader, |
|
1377 loadShaderFromFile: loadShaderFromFile, |
|
1378 loadShaderFromScript: loadShaderFromScript, |
|
1379 loadStandardProgram: loadStandardProgram, |
|
1380 loadStandardVertexShader: loadStandardVertexShader, |
|
1381 loadStandardFragmentShader: loadStandardFragmentShader, |
|
1382 loadTextFileAsync: loadTextFileAsync, |
|
1383 loadTexture: loadTexture, |
|
1384 log: log, |
|
1385 loggingOff: loggingOff, |
|
1386 makeImage: makeImage, |
|
1387 error: error, |
|
1388 setupProgram: setupProgram, |
|
1389 setupQuad: setupQuad, |
|
1390 setupSimpleTextureFragmentShader: setupSimpleTextureFragmentShader, |
|
1391 setupSimpleTextureProgram: setupSimpleTextureProgram, |
|
1392 setupSimpleTextureVertexShader: setupSimpleTextureVertexShader, |
|
1393 setupTexturedQuad: setupTexturedQuad, |
|
1394 setupTexturedQuadWithTexCoords: setupTexturedQuadWithTexCoords, |
|
1395 setupUnitQuad: setupUnitQuad, |
|
1396 setupUnitQuadWithTexCoords: setupUnitQuadWithTexCoords, |
|
1397 setupWebGLWithShaders: setupWebGLWithShaders, |
|
1398 startsWith: startsWith, |
|
1399 shouldGenerateGLError: shouldGenerateGLError, |
|
1400 readFile: readFile, |
|
1401 readFileList: readFileList, |
|
1402 |
|
1403 none: false |
|
1404 }; |
|
1405 |
|
1406 }()); |
|
1407 |
|
1408 |