OLD | NEW |
(Empty) | |
| 1 /* |
| 2 ** Copyright (c) 2012 The Khronos Group Inc. |
| 3 ** |
| 4 ** Permission is hereby granted, free of charge, to any person obtaining a |
| 5 ** copy of this software and/or associated documentation files (the |
| 6 ** "Materials"), to deal in the Materials without restriction, including |
| 7 ** without limitation the rights to use, copy, modify, merge, publish, |
| 8 ** distribute, sublicense, and/or sell copies of the Materials, and to |
| 9 ** permit persons to whom the Materials are furnished to do so, subject to |
| 10 ** the following conditions: |
| 11 ** |
| 12 ** The above copyright notice and this permission notice shall be included |
| 13 ** in all copies or substantial portions of the Materials. |
| 14 ** |
| 15 ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 16 ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 17 ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| 18 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| 19 ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| 20 ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| 21 ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
| 22 */ |
| 23 |
| 24 WebGLTestUtils = (function() { |
| 25 |
| 26 /** |
| 27 * Wrapped logging function. |
| 28 * @param {string} msg The message to log. |
| 29 */ |
| 30 var log = function(msg) { |
| 31 if (window.console && window.console.log) { |
| 32 window.console.log(msg); |
| 33 } |
| 34 }; |
| 35 |
| 36 /** |
| 37 * Wrapped logging function. |
| 38 * @param {string} msg The message to log. |
| 39 */ |
| 40 var error = function(msg) { |
| 41 if (window.console) { |
| 42 if (window.console.error) { |
| 43 window.console.error(msg); |
| 44 } |
| 45 else if (window.console.log) { |
| 46 window.console.log(msg); |
| 47 } |
| 48 } |
| 49 }; |
| 50 |
| 51 /** |
| 52 * Turn off all logging. |
| 53 */ |
| 54 var loggingOff = function() { |
| 55 log = function() {}; |
| 56 error = function() {}; |
| 57 }; |
| 58 |
| 59 /** |
| 60 * Converts a WebGL enum to a string |
| 61 * @param {!WebGLContext} gl The WebGLContext to use. |
| 62 * @param {number} value The enum value. |
| 63 * @return {string} The enum as a string. |
| 64 */ |
| 65 var glEnumToString = function(gl, value) { |
| 66 for (var p in gl) { |
| 67 if (gl[p] == value) { |
| 68 return p; |
| 69 } |
| 70 } |
| 71 return "0x" + value.toString(16); |
| 72 }; |
| 73 |
| 74 var lastError = ""; |
| 75 |
| 76 /** |
| 77 * Returns the last compiler/linker error. |
| 78 * @return {string} The last compiler/linker error. |
| 79 */ |
| 80 var getLastError = function() { |
| 81 return lastError; |
| 82 }; |
| 83 |
| 84 /** |
| 85 * Whether a haystack ends with a needle. |
| 86 * @param {string} haystack String to search |
| 87 * @param {string} needle String to search for. |
| 88 * @param {boolean} True if haystack ends with needle. |
| 89 */ |
| 90 var endsWith = function(haystack, needle) { |
| 91 return haystack.substr(haystack.length - needle.length) === needle; |
| 92 }; |
| 93 |
| 94 /** |
| 95 * Whether a haystack starts with a needle. |
| 96 * @param {string} haystack String to search |
| 97 * @param {string} needle String to search for. |
| 98 * @param {boolean} True if haystack starts with needle. |
| 99 */ |
| 100 var startsWith = function(haystack, needle) { |
| 101 return haystack.substr(0, needle.length) === needle; |
| 102 }; |
| 103 |
| 104 /** |
| 105 * A vertex shader for a single texture. |
| 106 * @type {string} |
| 107 */ |
| 108 var simpleTextureVertexShader = [ |
| 109 'attribute vec4 vPosition;', |
| 110 'attribute vec2 texCoord0;', |
| 111 'varying vec2 texCoord;', |
| 112 'void main() {', |
| 113 ' gl_Position = vPosition;', |
| 114 ' texCoord = texCoord0;', |
| 115 '}'].join('\n'); |
| 116 |
| 117 /** |
| 118 * A fragment shader for a single texture. |
| 119 * @type {string} |
| 120 */ |
| 121 var simpleTextureFragmentShader = [ |
| 122 'precision mediump float;', |
| 123 'uniform sampler2D tex;', |
| 124 'varying vec2 texCoord;', |
| 125 'void main() {', |
| 126 ' gl_FragData[0] = texture2D(tex, texCoord);', |
| 127 '}'].join('\n'); |
| 128 |
| 129 /** |
| 130 * A vertex shader for a single texture. |
| 131 * @type {string} |
| 132 */ |
| 133 var noTexCoordTextureVertexShader = [ |
| 134 'attribute vec4 vPosition;', |
| 135 'varying vec2 texCoord;', |
| 136 'void main() {', |
| 137 ' gl_Position = vPosition;', |
| 138 ' texCoord = vPosition.xy * 0.5 + 0.5;', |
| 139 '}'].join('\n'); |
| 140 |
| 141 /** |
| 142 * A vertex shader for a single texture. |
| 143 * @type {string} |
| 144 */ |
| 145 var simpleColorVertexShader = [ |
| 146 'attribute vec4 vPosition;', |
| 147 'void main() {', |
| 148 ' gl_Position = vPosition;', |
| 149 '}'].join('\n'); |
| 150 |
| 151 /** |
| 152 * A fragment shader for a color. |
| 153 * @type {string} |
| 154 */ |
| 155 var simpleColorFragmentShader = [ |
| 156 'precision mediump float;', |
| 157 'uniform vec4 u_color;', |
| 158 'void main() {', |
| 159 ' gl_FragData[0] = u_color;', |
| 160 '}'].join('\n'); |
| 161 |
| 162 /** |
| 163 * Creates a simple texture vertex shader. |
| 164 * @param {!WebGLContext} gl The WebGLContext to use. |
| 165 * @return {!WebGLShader} |
| 166 */ |
| 167 var setupSimpleTextureVertexShader = function(gl) { |
| 168 return loadShader(gl, simpleTextureVertexShader, gl.VERTEX_SHADER); |
| 169 }; |
| 170 |
| 171 /** |
| 172 * Creates a simple texture fragment shader. |
| 173 * @param {!WebGLContext} gl The WebGLContext to use. |
| 174 * @return {!WebGLShader} |
| 175 */ |
| 176 var setupSimpleTextureFragmentShader = function(gl) { |
| 177 return loadShader( |
| 178 gl, simpleTextureFragmentShader, gl.FRAGMENT_SHADER); |
| 179 }; |
| 180 |
| 181 /** |
| 182 * Creates a texture vertex shader that doesn't need texcoords. |
| 183 * @param {!WebGLContext} gl The WebGLContext to use. |
| 184 * @return {!WebGLShader} |
| 185 */ |
| 186 var setupNoTexCoordTextureVertexShader = function(gl) { |
| 187 return loadShader(gl, noTexCoordTextureVertexShader, gl.VERTEX_SHADER); |
| 188 }; |
| 189 |
| 190 /** |
| 191 * Creates a program, attaches shaders, binds attrib locations, links the |
| 192 * program and calls useProgram. |
| 193 * @param {!Array.<!WebGLShader|string>} shaders The shaders to |
| 194 * attach, or the source, or the id of a script to get |
| 195 * the source from. |
| 196 * @param {!Array.<string>} opt_attribs The attribs names. |
| 197 * @param {!Array.<number>} opt_locations The locations for the attribs. |
| 198 */ |
| 199 var setupProgram = function(gl, shaders, opt_attribs, opt_locations) { |
| 200 var realShaders = []; |
| 201 var program = gl.createProgram(); |
| 202 for (var ii = 0; ii < shaders.length; ++ii) { |
| 203 var shader = shaders[ii]; |
| 204 if (typeof shader == 'string') { |
| 205 var element = document.getElementById(shader); |
| 206 if (element) { |
| 207 shader = loadShaderFromScript(gl, shader); |
| 208 } else { |
| 209 shader = loadShader(gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHAD
ER); |
| 210 } |
| 211 } |
| 212 gl.attachShader(program, shader); |
| 213 } |
| 214 if (opt_attribs) { |
| 215 for (var ii = 0; ii < opt_attribs.length; ++ii) { |
| 216 gl.bindAttribLocation( |
| 217 program, |
| 218 opt_locations ? opt_locations[ii] : ii, |
| 219 opt_attribs[ii]); |
| 220 } |
| 221 } |
| 222 gl.linkProgram(program); |
| 223 |
| 224 // Check the link status |
| 225 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); |
| 226 if (!linked) { |
| 227 // something went wrong with the link |
| 228 lastError = gl.getProgramInfoLog (program); |
| 229 error("Error in program linking:" + lastError); |
| 230 |
| 231 gl.deleteProgram(program); |
| 232 return null; |
| 233 } |
| 234 |
| 235 gl.useProgram(program); |
| 236 return program; |
| 237 }; |
| 238 |
| 239 /** |
| 240 * Creates a simple texture program. |
| 241 * @param {!WebGLContext} gl The WebGLContext to use. |
| 242 * @param {number} opt_positionLocation The attrib location for position. |
| 243 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
| 244 * @return {WebGLProgram} |
| 245 */ |
| 246 var setupSimpleTextureProgram = function( |
| 247 gl, opt_positionLocation, opt_texcoordLocation) { |
| 248 opt_positionLocation = opt_positionLocation || 0; |
| 249 opt_texcoordLocation = opt_texcoordLocation || 1; |
| 250 var vs = setupSimpleTextureVertexShader(gl); |
| 251 var fs = setupSimpleTextureFragmentShader(gl); |
| 252 if (!vs || !fs) { |
| 253 return null; |
| 254 } |
| 255 var program = setupProgram( |
| 256 gl, |
| 257 [vs, fs], |
| 258 ['vPosition', 'texCoord0'], |
| 259 [opt_positionLocation, opt_texcoordLocation]); |
| 260 if (!program) { |
| 261 gl.deleteShader(fs); |
| 262 gl.deleteShader(vs); |
| 263 } |
| 264 gl.useProgram(program); |
| 265 return program; |
| 266 }; |
| 267 |
| 268 /** |
| 269 * Creates a simple texture program. |
| 270 * @param {!WebGLContext} gl The WebGLContext to use. |
| 271 * @return {WebGLProgram} |
| 272 */ |
| 273 var setupNoTexCoordTextureProgram = function(gl) { |
| 274 var vs = setupNoTexCoordTextureVertexShader(gl); |
| 275 var fs = setupSimpleTextureFragmentShader(gl); |
| 276 if (!vs || !fs) { |
| 277 return null; |
| 278 } |
| 279 var program = setupProgram( |
| 280 gl, |
| 281 [vs, fs], |
| 282 ['vPosition'], |
| 283 [0]); |
| 284 if (!program) { |
| 285 gl.deleteShader(fs); |
| 286 gl.deleteShader(vs); |
| 287 } |
| 288 gl.useProgram(program); |
| 289 return program; |
| 290 }; |
| 291 |
| 292 /** |
| 293 * Creates a simple texture program. |
| 294 * @param {!WebGLContext} gl The WebGLContext to use. |
| 295 * @param {number} opt_positionLocation The attrib location for position. |
| 296 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
| 297 * @return {WebGLProgram} |
| 298 */ |
| 299 var setupSimpleTextureProgram = function( |
| 300 gl, opt_positionLocation, opt_texcoordLocation) { |
| 301 opt_positionLocation = opt_positionLocation || 0; |
| 302 opt_texcoordLocation = opt_texcoordLocation || 1; |
| 303 var vs = setupSimpleTextureVertexShader(gl); |
| 304 var fs = setupSimpleTextureFragmentShader(gl); |
| 305 if (!vs || !fs) { |
| 306 return null; |
| 307 } |
| 308 var program = setupProgram( |
| 309 gl, |
| 310 [vs, fs], |
| 311 ['vPosition', 'texCoord0'], |
| 312 [opt_positionLocation, opt_texcoordLocation]); |
| 313 if (!program) { |
| 314 gl.deleteShader(fs); |
| 315 gl.deleteShader(vs); |
| 316 } |
| 317 gl.useProgram(program); |
| 318 return program; |
| 319 }; |
| 320 |
| 321 /** |
| 322 * Creates buffers for a textured unit quad and attaches them to vertex attribs. |
| 323 * @param {!WebGLContext} gl The WebGLContext to use. |
| 324 * @param {number} opt_positionLocation The attrib location for position. |
| 325 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
| 326 * @return {!Array.<WebGLBuffer>} The buffer objects that were |
| 327 * created. |
| 328 */ |
| 329 var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) { |
| 330 return setupUnitQuadWithTexCoords(gl, [ 0.0, 0.0 ], [ 1.0, 1.0 ], |
| 331 opt_positionLocation, opt_texcoordLocation); |
| 332 }; |
| 333 |
| 334 /** |
| 335 * Creates buffers for a textured unit quad with specified lower left |
| 336 * and upper right texture coordinates, and attaches them to vertex |
| 337 * attribs. |
| 338 * @param {!WebGLContext} gl The WebGLContext to use. |
| 339 * @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the l
ower left corner. |
| 340 * @param {!Array.<number>} upperRightTexCoords The texture coordinates for the
upper right corner. |
| 341 * @param {number} opt_positionLocation The attrib location for position. |
| 342 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
| 343 * @return {!Array.<WebGLBuffer>} The buffer objects that were |
| 344 * created. |
| 345 */ |
| 346 var setupUnitQuadWithTexCoords = function( |
| 347 gl, lowerLeftTexCoords, upperRightTexCoords, |
| 348 opt_positionLocation, opt_texcoordLocation) { |
| 349 opt_positionLocation = opt_positionLocation || 0; |
| 350 opt_texcoordLocation = opt_texcoordLocation || 1; |
| 351 var objects = []; |
| 352 |
| 353 var vertexObject = gl.createBuffer(); |
| 354 gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); |
| 355 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ |
| 356 1.0, 1.0, 0.0, |
| 357 -1.0, 1.0, 0.0, |
| 358 -1.0, -1.0, 0.0, |
| 359 1.0, 1.0, 0.0, |
| 360 -1.0, -1.0, 0.0, |
| 361 1.0, -1.0, 0.0]), gl.STATIC_DRAW); |
| 362 gl.enableVertexAttribArray(opt_positionLocation); |
| 363 gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0); |
| 364 objects.push(vertexObject); |
| 365 |
| 366 var llx = lowerLeftTexCoords[0]; |
| 367 var lly = lowerLeftTexCoords[1]; |
| 368 var urx = upperRightTexCoords[0]; |
| 369 var ury = upperRightTexCoords[1]; |
| 370 |
| 371 var vertexObject = gl.createBuffer(); |
| 372 gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); |
| 373 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ |
| 374 urx, ury, |
| 375 llx, ury, |
| 376 llx, lly, |
| 377 urx, ury, |
| 378 llx, lly, |
| 379 urx, lly]), gl.STATIC_DRAW); |
| 380 gl.enableVertexAttribArray(opt_texcoordLocation); |
| 381 gl.vertexAttribPointer(opt_texcoordLocation, 2, gl.FLOAT, false, 0, 0); |
| 382 objects.push(vertexObject); |
| 383 return objects; |
| 384 }; |
| 385 |
| 386 /** |
| 387 * Creates a program and buffers for rendering a textured quad. |
| 388 * @param {!WebGLContext} gl The WebGLContext to use. |
| 389 * @param {number} opt_positionLocation The attrib location for position. |
| 390 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
| 391 * @return {!WebGLProgram} |
| 392 */ |
| 393 var setupTexturedQuad = function( |
| 394 gl, opt_positionLocation, opt_texcoordLocation) { |
| 395 var program = setupSimpleTextureProgram( |
| 396 gl, opt_positionLocation, opt_texcoordLocation); |
| 397 setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation); |
| 398 return program; |
| 399 }; |
| 400 |
| 401 /** |
| 402 * Creates a program and buffers for rendering a color quad. |
| 403 * @param {!WebGLContext} gl The WebGLContext to use. |
| 404 * @param {number} opt_positionLocation The attrib location for position. |
| 405 * @return {!WebGLProgram} |
| 406 */ |
| 407 var setupColorQuad = function(gl, opt_positionLocation) { |
| 408 opt_positionLocation = opt_positionLocation || 0; |
| 409 var program = wtu.setupProgram( |
| 410 gl, |
| 411 [simpleColorVertexShader, simpleColorFragmentShader], |
| 412 ['vPosition'], |
| 413 [opt_positionLocation]); |
| 414 setupUnitQuad(gl, opt_positionLocation); |
| 415 return program; |
| 416 }; |
| 417 |
| 418 /** |
| 419 * Creates a program and buffers for rendering a textured quad with |
| 420 * specified lower left and upper right texture coordinates. |
| 421 * @param {!WebGLContext} gl The WebGLContext to use. |
| 422 * @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the l
ower left corner. |
| 423 * @param {!Array.<number>} upperRightTexCoords The texture coordinates for the
upper right corner. |
| 424 * @param {number} opt_positionLocation The attrib location for position. |
| 425 * @param {number} opt_texcoordLocation The attrib location for texture coords. |
| 426 * @return {!WebGLProgram} |
| 427 */ |
| 428 var setupTexturedQuadWithTexCoords = function( |
| 429 gl, lowerLeftTexCoords, upperRightTexCoords, |
| 430 opt_positionLocation, opt_texcoordLocation) { |
| 431 var program = setupSimpleTextureProgram( |
| 432 gl, opt_positionLocation, opt_texcoordLocation); |
| 433 setupUnitQuadWithTexCoords(gl, lowerLeftTexCoords, upperRightTexCoords, |
| 434 opt_positionLocation, opt_texcoordLocation); |
| 435 return program; |
| 436 }; |
| 437 |
| 438 /** |
| 439 * Creates a unit quad with only positions of a given resolution. |
| 440 * @param {!WebGLContext} gl The WebGLContext to use. |
| 441 * @param {number} gridRes The resolution of the mesh grid, |
| 442 * expressed in the number of quads across and down. |
| 443 * @param {number} opt_positionLocation The attrib location for position. |
| 444 */ |
| 445 var setupQuad = function ( |
| 446 gl, gridRes, opt_positionLocation, opt_flipOddTriangles) { |
| 447 setupQuadWithOptions(gl, |
| 448 { gridRes: gridRes, |
| 449 positionLocation: opt_positionLocation, |
| 450 flipOddTriangles: opt_flipOddTriangles |
| 451 }); |
| 452 }; |
| 453 |
| 454 /** |
| 455 * Creates a quad with various options. |
| 456 * @param {!WebGLContext} gl The WebGLContext to use. |
| 457 * |
| 458 * Options: |
| 459 * gridRes: number of quads across and down grid. |
| 460 * positionLocation: attrib location for position |
| 461 * flipOddTriangles: reverse order of vertices of every other |
| 462 * triangle |
| 463 * positionOffset: offset added to each vertex |
| 464 * positionMult: multipier for each vertex |
| 465 * colorLocation: attrib location for vertex colors. If |
| 466 * undefined no vertex colors will be created. |
| 467 */ |
| 468 var setupQuadWithOptions = function (gl, options) { |
| 469 var positionLocation = options.positionLocation || 0; |
| 470 var objects = []; |
| 471 |
| 472 var gridRes = options.gridRes || 1; |
| 473 var positionOffset = options.positionOffset || 0; |
| 474 var positionMult = options.positionMult || 1; |
| 475 var vertsAcross = gridRes + 1; |
| 476 var numVerts = vertsAcross * vertsAcross; |
| 477 var positions = new Float32Array(numVerts * 3); |
| 478 var indices = new Uint16Array(6 * gridRes * gridRes); |
| 479 var poffset = 0; |
| 480 |
| 481 for (var yy = 0; yy <= gridRes; ++yy) { |
| 482 for (var xx = 0; xx <= gridRes; ++xx) { |
| 483 positions[poffset + 0] = (-1 + 2 * xx / gridRes) * positionMult + position
Offset; |
| 484 positions[poffset + 1] = (-1 + 2 * yy / gridRes) * positionMult + position
Offset; |
| 485 positions[poffset + 2] = 0; |
| 486 |
| 487 poffset += 3; |
| 488 } |
| 489 } |
| 490 |
| 491 var buf = gl.createBuffer(); |
| 492 gl.bindBuffer(gl.ARRAY_BUFFER, buf); |
| 493 gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); |
| 494 gl.enableVertexAttribArray(positionLocation); |
| 495 gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); |
| 496 objects.push(buf); |
| 497 |
| 498 if (options.colorLocation !== undefined) { |
| 499 var colors = new Float32Array(numVerts * 4); |
| 500 for (var yy = 0; yy <= gridRes; ++yy) { |
| 501 for (var xx = 0; xx <= gridRes; ++xx) { |
| 502 if (options.color !== undefined) { |
| 503 colors[poffset + 0] = options.color[0]; |
| 504 colors[poffset + 1] = options.color[1]; |
| 505 colors[poffset + 2] = options.color[2]; |
| 506 colors[poffset + 3] = options.color[3]; |
| 507 } else { |
| 508 colors[poffset + 0] = xx / gridRes; |
| 509 colors[poffset + 1] = yy / gridRes; |
| 510 colors[poffset + 2] = (xx / gridRes) * (yy / gridRes); |
| 511 colors[poffset + 3] = (yy % 2) * 0.5 + 0.5; |
| 512 } |
| 513 poffset += 4; |
| 514 } |
| 515 } |
| 516 |
| 517 var buf = gl.createBuffer(); |
| 518 gl.bindBuffer(gl.ARRAY_BUFFER, buf); |
| 519 gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); |
| 520 gl.enableVertexAttribArray(options.colorLocation); |
| 521 gl.vertexAttribPointer(options.colorLocation, 4, gl.FLOAT, false, 0, 0); |
| 522 objects.push(buf); |
| 523 } |
| 524 |
| 525 var tbase = 0; |
| 526 for (var yy = 0; yy < gridRes; ++yy) { |
| 527 var index = yy * vertsAcross; |
| 528 for (var xx = 0; xx < gridRes; ++xx) { |
| 529 indices[tbase + 0] = index + 0; |
| 530 indices[tbase + 1] = index + 1; |
| 531 indices[tbase + 2] = index + vertsAcross; |
| 532 indices[tbase + 3] = index + vertsAcross; |
| 533 indices[tbase + 4] = index + 1; |
| 534 indices[tbase + 5] = index + vertsAcross + 1; |
| 535 |
| 536 if (options.flipOddTriangles) { |
| 537 indices[tbase + 4] = index + vertsAcross + 1; |
| 538 indices[tbase + 5] = index + 1; |
| 539 } |
| 540 |
| 541 index += 1; |
| 542 tbase += 6; |
| 543 } |
| 544 } |
| 545 |
| 546 var buf = gl.createBuffer(); |
| 547 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf); |
| 548 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); |
| 549 objects.push(buf); |
| 550 |
| 551 return objects; |
| 552 }; |
| 553 |
| 554 /** |
| 555 * Fills the given texture with a solid color |
| 556 * @param {!WebGLContext} gl The WebGLContext to use. |
| 557 * @param {!WebGLTexture} tex The texture to fill. |
| 558 * @param {number} width The width of the texture to create. |
| 559 * @param {number} height The height of the texture to create. |
| 560 * @param {!Array.<number>} color The color to fill with. A 4 element array |
| 561 * where each element is in the range 0 to 255. |
| 562 * @param {number} opt_level The level of the texture to fill. Default = 0. |
| 563 */ |
| 564 var fillTexture = function(gl, tex, width, height, color, opt_level) { |
| 565 opt_level = opt_level || 0; |
| 566 var numPixels = width * height; |
| 567 var size = numPixels * 4; |
| 568 var buf = new Uint8Array(size); |
| 569 for (var ii = 0; ii < numPixels; ++ii) { |
| 570 var off = ii * 4; |
| 571 buf[off + 0] = color[0]; |
| 572 buf[off + 1] = color[1]; |
| 573 buf[off + 2] = color[2]; |
| 574 buf[off + 3] = color[3]; |
| 575 } |
| 576 gl.bindTexture(gl.TEXTURE_2D, tex); |
| 577 gl.texImage2D( |
| 578 gl.TEXTURE_2D, opt_level, gl.RGBA, width, height, 0, |
| 579 gl.RGBA, gl.UNSIGNED_BYTE, buf); |
| 580 }; |
| 581 |
| 582 /** |
| 583 * Creates a textures and fills it with a solid color |
| 584 * @param {!WebGLContext} gl The WebGLContext to use. |
| 585 * @param {number} width The width of the texture to create. |
| 586 * @param {number} height The height of the texture to create. |
| 587 * @param {!Array.<number>} color The color to fill with. A 4 element array |
| 588 * where each element is in the range 0 to 255. |
| 589 * @return {!WebGLTexture} |
| 590 */ |
| 591 var createColoredTexture = function(gl, width, height, color) { |
| 592 var tex = gl.createTexture(); |
| 593 fillTexture(gl, tex, width, height, color); |
| 594 return tex; |
| 595 }; |
| 596 |
| 597 /** |
| 598 * Draws a previously setup quad. |
| 599 * @param {!WebGLContext} gl The WebGLContext to use. |
| 600 * @param {!Array.<number>} opt_color The color to fill clear with before |
| 601 * drawing. A 4 element array where each element is in the range 0 to |
| 602 * 255. Default [255, 255, 255, 255] |
| 603 */ |
| 604 var drawQuad = function(gl, opt_color) { |
| 605 opt_color = opt_color || [255, 255, 255, 255]; |
| 606 gl.clearColor( |
| 607 opt_color[0] / 255, |
| 608 opt_color[1] / 255, |
| 609 opt_color[2] / 255, |
| 610 opt_color[3] / 255); |
| 611 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| 612 gl.drawArrays(gl.TRIANGLES, 0, 6); |
| 613 }; |
| 614 |
| 615 /** |
| 616 * Draws a previously setup quad. |
| 617 * @param {!WebGLContext} gl The WebGLContext to use. |
| 618 * @param {number} gridRes Resolution of grid. |
| 619 * @param {!Array.<number>} opt_color The color to fill clear with before |
| 620 * drawing. A 4 element array where each element is in the range 0 to |
| 621 * 255. Default [255, 255, 255, 255] |
| 622 */ |
| 623 var drawIndexedQuad = function(gl, gridRes, opt_color) { |
| 624 opt_color = opt_color || [255, 255, 255, 255]; |
| 625 gl.clearColor( |
| 626 opt_color[0] / 255, |
| 627 opt_color[1] / 255, |
| 628 opt_color[2] / 255, |
| 629 opt_color[3] / 255); |
| 630 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| 631 gl.drawElements(gl.TRIANGLES, gridRes * 6, gl.UNSIGNED_SHORT, 0); |
| 632 }; |
| 633 |
| 634 /** |
| 635 * Checks that a portion of a canvas is 1 color. |
| 636 * @param {!WebGLContext} gl The WebGLContext to use. |
| 637 * @param {number} x left corner of region to check. |
| 638 * @param {number} y bottom corner of region to check. |
| 639 * @param {number} width width of region to check. |
| 640 * @param {number} height width of region to check. |
| 641 * @param {!Array.<number>} color The color to fill clear with before drawing. A |
| 642 * 4 element array where each element is in the range 0 to 255. |
| 643 * @param {string} msg Message to associate with success. Eg ("should be red"). |
| 644 * @param {number} errorRange Optional. Acceptable error in |
| 645 * color checking. 0 by default. |
| 646 */ |
| 647 var checkCanvasRect = function(gl, x, y, width, height, color, msg, errorRange)
{ |
| 648 errorRange = errorRange || 0; |
| 649 if (!errorRange.length) { |
| 650 errorRange = [errorRange, errorRange, errorRange, errorRange] |
| 651 } |
| 652 var buf = new Uint8Array(width * height * 4); |
| 653 gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf); |
| 654 for (var i = 0; i < width * height; ++i) { |
| 655 var offset = i * 4; |
| 656 for (var j = 0; j < color.length; ++j) { |
| 657 if (Math.abs(buf[offset + j] - color[j]) > errorRange[j]) { |
| 658 testFailed(msg); |
| 659 var was = buf[offset + 0].toString(); |
| 660 for (j = 1; j < color.length; ++j) { |
| 661 was += "," + buf[offset + j]; |
| 662 } |
| 663 debug('at (' + (i % width) + ', ' + Math.floor(i / width) + |
| 664 ') expected: ' + color + ' was ' + was); |
| 665 return; |
| 666 } |
| 667 } |
| 668 } |
| 669 testPassed(msg); |
| 670 }; |
| 671 |
| 672 /** |
| 673 * Checks that an entire canvas is 1 color. |
| 674 * @param {!WebGLContext} gl The WebGLContext to use. |
| 675 * @param {!Array.<number>} color The color to fill clear with before drawing. A |
| 676 * 4 element array where each element is in the range 0 to 255. |
| 677 * @param {string} msg Message to associate with success. Eg ("should be red"). |
| 678 * @param {number} errorRange Optional. Acceptable error in |
| 679 * color checking. 0 by default. |
| 680 */ |
| 681 var checkCanvas = function(gl, color, msg, errorRange) { |
| 682 checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, color, msg, error
Range); |
| 683 }; |
| 684 |
| 685 /** |
| 686 * Loads a texture, calls callback when finished. |
| 687 * @param {!WebGLContext} gl The WebGLContext to use. |
| 688 * @param {string} url URL of image to load |
| 689 * @param {function(!Image): void} callback Function that gets called after |
| 690 * image has loaded |
| 691 * @return {!WebGLTexture} The created texture. |
| 692 */ |
| 693 var loadTexture = function(gl, url, callback) { |
| 694 var texture = gl.createTexture(); |
| 695 gl.bindTexture(gl.TEXTURE_2D, texture); |
| 696 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
| 697 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
| 698 var image = new Image(); |
| 699 image.onload = function() { |
| 700 gl.bindTexture(gl.TEXTURE_2D, texture); |
| 701 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); |
| 702 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imag
e); |
| 703 callback(image); |
| 704 }; |
| 705 image.src = url; |
| 706 return texture; |
| 707 }; |
| 708 |
| 709 /** |
| 710 * Creates a webgl context. |
| 711 * @param {!Canvas|string} opt_canvas The canvas tag to get |
| 712 * context from. If one is not passed in one will be |
| 713 * created. If it's a string it's assumed to be the id of a |
| 714 * canvas. |
| 715 * @return {!WebGLContext} The created context. |
| 716 */ |
| 717 var create3DContext = function(opt_canvas, opt_attributes) { |
| 718 opt_canvas = opt_canvas || document.createElement("canvas"); |
| 719 if (typeof opt_canvas == 'string') { |
| 720 opt_canvas = document.getElementById(opt_canvas); |
| 721 } |
| 722 var context = null; |
| 723 var names = ["webgl", "experimental-webgl"]; |
| 724 for (var i = 0; i < names.length; ++i) { |
| 725 try { |
| 726 context = opt_canvas.getContext(names[i], opt_attributes); |
| 727 } catch (e) { |
| 728 } |
| 729 if (context) { |
| 730 break; |
| 731 } |
| 732 } |
| 733 if (!context) { |
| 734 testFailed("Unable to fetch WebGL rendering context for Canvas"); |
| 735 } |
| 736 return context; |
| 737 } |
| 738 |
| 739 /** |
| 740 * Gets a GLError value as a string. |
| 741 * @param {!WebGLContext} gl The WebGLContext to use. |
| 742 * @param {number} err The webgl error as retrieved from gl.getError(). |
| 743 * @return {string} the error as a string. |
| 744 */ |
| 745 var getGLErrorAsString = function(gl, err) { |
| 746 if (err === gl.NO_ERROR) { |
| 747 return "NO_ERROR"; |
| 748 } |
| 749 for (var name in gl) { |
| 750 if (gl[name] === err) { |
| 751 return name; |
| 752 } |
| 753 } |
| 754 return err.toString(); |
| 755 }; |
| 756 |
| 757 /** |
| 758 * Wraps a WebGL function with a function that throws an exception if there is |
| 759 * an error. |
| 760 * @param {!WebGLContext} gl The WebGLContext to use. |
| 761 * @param {string} fname Name of function to wrap. |
| 762 * @return {function} The wrapped function. |
| 763 */ |
| 764 var createGLErrorWrapper = function(context, fname) { |
| 765 return function() { |
| 766 var rv = context[fname].apply(context, arguments); |
| 767 var err = context.getError(); |
| 768 if (err != context.NO_ERROR) |
| 769 throw "GL error " + getGLErrorAsString(context, err) + " in " + fname; |
| 770 return rv; |
| 771 }; |
| 772 }; |
| 773 |
| 774 /** |
| 775 * Creates a WebGL context where all functions are wrapped to throw an exception |
| 776 * if there is an error. |
| 777 * @param {!Canvas} canvas The HTML canvas to get a context from. |
| 778 * @return {!Object} The wrapped context. |
| 779 */ |
| 780 function create3DContextWithWrapperThatThrowsOnGLError(canvas) { |
| 781 var context = create3DContext(canvas); |
| 782 var wrap = {}; |
| 783 for (var i in context) { |
| 784 try { |
| 785 if (typeof context[i] == 'function') { |
| 786 wrap[i] = createGLErrorWrapper(context, i); |
| 787 } else { |
| 788 wrap[i] = context[i]; |
| 789 } |
| 790 } catch (e) { |
| 791 error("createContextWrapperThatThrowsOnGLError: Error accessing " + i); |
| 792 } |
| 793 } |
| 794 wrap.getError = function() { |
| 795 return context.getError(); |
| 796 }; |
| 797 return wrap; |
| 798 }; |
| 799 |
| 800 /** |
| 801 * Tests that an evaluated expression generates a specific GL error. |
| 802 * @param {!WebGLContext} gl The WebGLContext to use. |
| 803 * @param {number} glError The expected gl error. |
| 804 * @param {string} evalSTr The string to evaluate. |
| 805 */ |
| 806 var shouldGenerateGLError = function(gl, glError, evalStr) { |
| 807 var exception; |
| 808 try { |
| 809 eval(evalStr); |
| 810 } catch (e) { |
| 811 exception = e; |
| 812 } |
| 813 if (exception) { |
| 814 testFailed(evalStr + " threw exception " + exception); |
| 815 } else { |
| 816 var err = gl.getError(); |
| 817 if (err != glError) { |
| 818 testFailed(evalStr + " expected: " + getGLErrorAsString(gl, glError) + ".
Was " + getGLErrorAsString(gl, err) + "."); |
| 819 } else { |
| 820 testPassed(evalStr + " was expected value: " + getGLErrorAsString(gl, glEr
ror) + "."); |
| 821 } |
| 822 } |
| 823 }; |
| 824 |
| 825 /** |
| 826 * Tests that the first error GL returns is the specified error. |
| 827 * @param {!WebGLContext} gl The WebGLContext to use. |
| 828 * @param {number} glError The expected gl error. |
| 829 * @param {string} opt_msg |
| 830 */ |
| 831 var glErrorShouldBe = function(gl, glError, opt_msg) { |
| 832 opt_msg = opt_msg || ""; |
| 833 var err = gl.getError(); |
| 834 if (err != glError) { |
| 835 testFailed("getError expected: " + getGLErrorAsString(gl, glError) + |
| 836 ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg); |
| 837 } else { |
| 838 testPassed("getError was expected value: " + |
| 839 getGLErrorAsString(gl, glError) + " : " + opt_msg); |
| 840 } |
| 841 }; |
| 842 |
| 843 /** |
| 844 * Links a WebGL program, throws if there are errors. |
| 845 * @param {!WebGLContext} gl The WebGLContext to use. |
| 846 * @param {!WebGLProgram} program The WebGLProgram to link. |
| 847 * @param {function(string): void) opt_errorCallback callback for errors. |
| 848 */ |
| 849 var linkProgram = function(gl, program, opt_errorCallback) { |
| 850 errFn = opt_errorCallback || testFailed; |
| 851 // Link the program |
| 852 gl.linkProgram(program); |
| 853 |
| 854 // Check the link status |
| 855 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); |
| 856 if (!linked) { |
| 857 // something went wrong with the link |
| 858 var error = gl.getProgramInfoLog (program); |
| 859 |
| 860 errFn("Error in program linking:" + error); |
| 861 |
| 862 gl.deleteProgram(program); |
| 863 } |
| 864 }; |
| 865 |
| 866 /** |
| 867 * Loads text from an external file. This function is synchronous. |
| 868 * @param {string} url The url of the external file. |
| 869 * @param {!function(bool, string): void} callback that is sent a bool for |
| 870 * success and the string. |
| 871 */ |
| 872 var loadTextFileAsync = function(url, callback) { |
| 873 log ("loading: " + url); |
| 874 var error = 'loadTextFileSynchronous failed to load url "' + url + '"'; |
| 875 var request; |
| 876 if (window.XMLHttpRequest) { |
| 877 request = new XMLHttpRequest(); |
| 878 if (request.overrideMimeType) { |
| 879 request.overrideMimeType('text/plain'); |
| 880 } |
| 881 } else { |
| 882 throw 'XMLHttpRequest is disabled'; |
| 883 } |
| 884 try { |
| 885 request.open('GET', url, true); |
| 886 request.onreadystatechange = function() { |
| 887 if (request.readyState == 4) { |
| 888 var text = ''; |
| 889 // HTTP reports success with a 200 status. The file protocol reports |
| 890 // success with zero. HTTP does not use zero as a status code (they |
| 891 // start at 100). |
| 892 // https://developer.mozilla.org/En/Using_XMLHttpRequest |
| 893 var success = request.status == 200 || request.status == 0; |
| 894 if (success) { |
| 895 text = request.responseText; |
| 896 } |
| 897 log("loaded: " + url); |
| 898 callback(success, text); |
| 899 } |
| 900 }; |
| 901 request.send(null); |
| 902 } catch (e) { |
| 903 log("failed to load: " + url); |
| 904 callback(false, ''); |
| 905 } |
| 906 }; |
| 907 |
| 908 /** |
| 909 * Recursively loads a file as a list. Each line is parsed for a relative |
| 910 * path. If the file ends in .txt the contents of that file is inserted in |
| 911 * the list. |
| 912 * |
| 913 * @param {string} url The url of the external file. |
| 914 * @param {!function(bool, Array<string>): void} callback that is sent a bool |
| 915 * for success and the array of strings. |
| 916 */ |
| 917 var getFileListAsync = function(url, callback) { |
| 918 var files = []; |
| 919 |
| 920 var getFileListImpl = function(url, callback) { |
| 921 var files = []; |
| 922 if (url.substr(url.length - 4) == '.txt') { |
| 923 loadTextFileAsync(url, function() { |
| 924 return function(success, text) { |
| 925 if (!success) { |
| 926 callback(false, ''); |
| 927 return; |
| 928 } |
| 929 var lines = text.split('\n'); |
| 930 var prefix = ''; |
| 931 var lastSlash = url.lastIndexOf('/'); |
| 932 if (lastSlash >= 0) { |
| 933 prefix = url.substr(0, lastSlash + 1); |
| 934 } |
| 935 var fail = false; |
| 936 var count = 1; |
| 937 var index = 0; |
| 938 for (var ii = 0; ii < lines.length; ++ii) { |
| 939 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 940 if (str.length > 4 && |
| 941 str[0] != '#' && |
| 942 str[0] != ";" && |
| 943 str.substr(0, 2) != "//") { |
| 944 var names = str.split(/ +/); |
| 945 new_url = prefix + str; |
| 946 if (names.length == 1) { |
| 947 new_url = prefix + str; |
| 948 ++count; |
| 949 getFileListImpl(new_url, function(index) { |
| 950 return function(success, new_files) { |
| 951 log("got files: " + new_files.length); |
| 952 if (success) { |
| 953 files[index] = new_files; |
| 954 } |
| 955 finish(success); |
| 956 }; |
| 957 }(index++)); |
| 958 } else { |
| 959 var s = ""; |
| 960 var p = ""; |
| 961 for (var jj = 0; jj < names.length; ++jj) { |
| 962 s += p + prefix + names[jj]; |
| 963 p = " "; |
| 964 } |
| 965 files[index++] = s; |
| 966 } |
| 967 } |
| 968 } |
| 969 finish(true); |
| 970 |
| 971 function finish(success) { |
| 972 if (!success) { |
| 973 fail = true; |
| 974 } |
| 975 --count; |
| 976 log("count: " + count); |
| 977 if (!count) { |
| 978 callback(!fail, files); |
| 979 } |
| 980 } |
| 981 } |
| 982 }()); |
| 983 |
| 984 } else { |
| 985 files.push(url); |
| 986 callback(true, files); |
| 987 } |
| 988 }; |
| 989 |
| 990 getFileListImpl(url, function(success, files) { |
| 991 // flatten |
| 992 var flat = []; |
| 993 flatten(files); |
| 994 function flatten(files) { |
| 995 for (var ii = 0; ii < files.length; ++ii) { |
| 996 var value = files[ii]; |
| 997 if (typeof(value) == "string") { |
| 998 flat.push(value); |
| 999 } else { |
| 1000 flatten(value); |
| 1001 } |
| 1002 } |
| 1003 } |
| 1004 callback(success, flat); |
| 1005 }); |
| 1006 }; |
| 1007 |
| 1008 /** |
| 1009 * Gets a file from a file/URL |
| 1010 * @param {string} file the URL of the file to get. |
| 1011 * @return {string} The contents of the file. |
| 1012 */ |
| 1013 var readFile = function(file) { |
| 1014 var xhr = new XMLHttpRequest(); |
| 1015 xhr.open("GET", file, false); |
| 1016 xhr.send(); |
| 1017 return xhr.responseText.replace(/\r/g, ""); |
| 1018 }; |
| 1019 |
| 1020 var readFileList = function(url) { |
| 1021 var files = []; |
| 1022 if (url.substr(url.length - 4) == '.txt') { |
| 1023 var lines = readFile(url).split('\n'); |
| 1024 var prefix = ''; |
| 1025 var lastSlash = url.lastIndexOf('/'); |
| 1026 if (lastSlash >= 0) { |
| 1027 prefix = url.substr(0, lastSlash + 1); |
| 1028 } |
| 1029 for (var ii = 0; ii < lines.length; ++ii) { |
| 1030 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 1031 if (str.length > 4 && |
| 1032 str[0] != '#' && |
| 1033 str[0] != ";" && |
| 1034 str.substr(0, 2) != "//") { |
| 1035 var names = str.split(/ +/); |
| 1036 if (names.length == 1) { |
| 1037 new_url = prefix + str; |
| 1038 files = files.concat(readFileList(new_url)); |
| 1039 } else { |
| 1040 var s = ""; |
| 1041 var p = ""; |
| 1042 for (var jj = 0; jj < names.length; ++jj) { |
| 1043 s += p + prefix + names[jj]; |
| 1044 p = " "; |
| 1045 } |
| 1046 files.push(s); |
| 1047 } |
| 1048 } |
| 1049 } |
| 1050 } else { |
| 1051 files.push(url); |
| 1052 } |
| 1053 return files; |
| 1054 }; |
| 1055 |
| 1056 /** |
| 1057 * Loads a shader. |
| 1058 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1059 * @param {string} shaderSource The shader source. |
| 1060 * @param {number} shaderType The type of shader. |
| 1061 * @param {function(string): void) opt_errorCallback callback for errors. |
| 1062 * @return {!WebGLShader} The created shader. |
| 1063 */ |
| 1064 var loadShader = function(gl, shaderSource, shaderType, opt_errorCallback) { |
| 1065 var errFn = opt_errorCallback || error; |
| 1066 // Create the shader object |
| 1067 var shader = gl.createShader(shaderType); |
| 1068 if (shader == null) { |
| 1069 errFn("*** Error: unable to create shader '"+shaderSource+"'"); |
| 1070 return null; |
| 1071 } |
| 1072 |
| 1073 // Load the shader source |
| 1074 gl.shaderSource(shader, shaderSource); |
| 1075 var err = gl.getError(); |
| 1076 if (err != gl.NO_ERROR) { |
| 1077 errFn("*** Error loading shader '" + shader + "':" + glEnumToString(gl, err)
); |
| 1078 return null; |
| 1079 } |
| 1080 |
| 1081 // Compile the shader |
| 1082 gl.compileShader(shader); |
| 1083 |
| 1084 // Check the compile status |
| 1085 var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); |
| 1086 if (!compiled) { |
| 1087 // Something went wrong during compilation; get the error |
| 1088 lastError = gl.getShaderInfoLog(shader); |
| 1089 errFn("*** Error compiling shader '" + shader + "':" + lastError); |
| 1090 gl.deleteShader(shader); |
| 1091 return null; |
| 1092 } |
| 1093 |
| 1094 return shader; |
| 1095 } |
| 1096 |
| 1097 /** |
| 1098 * Loads a shader from a URL. |
| 1099 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1100 * @param {file} file The URL of the shader source. |
| 1101 * @param {number} type The type of shader. |
| 1102 * @param {function(string): void) opt_errorCallback callback for errors. |
| 1103 * @return {!WebGLShader} The created shader. |
| 1104 */ |
| 1105 var loadShaderFromFile = function(gl, file, type, opt_errorCallback) { |
| 1106 var shaderSource = readFile(file); |
| 1107 return loadShader(gl, shaderSource, type, opt_errorCallback); |
| 1108 }; |
| 1109 |
| 1110 /** |
| 1111 * Gets the content of script. |
| 1112 */ |
| 1113 var getScript = function(scriptId) { |
| 1114 var shaderScript = document.getElementById(scriptId); |
| 1115 if (!shaderScript) { |
| 1116 throw("*** Error: unknown script element" + scriptId); |
| 1117 } |
| 1118 return shaderScript.text; |
| 1119 }; |
| 1120 |
| 1121 /** |
| 1122 * Loads a shader from a script tag. |
| 1123 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1124 * @param {string} scriptId The id of the script tag. |
| 1125 * @param {number} opt_shaderType The type of shader. If not passed in it will |
| 1126 * be derived from the type of the script tag. |
| 1127 * @param {function(string): void) opt_errorCallback callback for errors. |
| 1128 * @return {!WebGLShader} The created shader. |
| 1129 */ |
| 1130 var loadShaderFromScript = function( |
| 1131 gl, scriptId, opt_shaderType, opt_errorCallback) { |
| 1132 var shaderSource = ""; |
| 1133 var shaderType; |
| 1134 var shaderScript = document.getElementById(scriptId); |
| 1135 if (!shaderScript) { |
| 1136 throw("*** Error: unknown script element " + scriptId); |
| 1137 } |
| 1138 shaderSource = shaderScript.text; |
| 1139 |
| 1140 if (!opt_shaderType) { |
| 1141 if (shaderScript.type == "x-shader/x-vertex") { |
| 1142 shaderType = gl.VERTEX_SHADER; |
| 1143 } else if (shaderScript.type == "x-shader/x-fragment") { |
| 1144 shaderType = gl.FRAGMENT_SHADER; |
| 1145 } else if (shaderType != gl.VERTEX_SHADER && shaderType != gl.FRAGMENT_SHADE
R) { |
| 1146 throw("*** Error: unknown shader type"); |
| 1147 return null; |
| 1148 } |
| 1149 } |
| 1150 |
| 1151 return loadShader( |
| 1152 gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType, |
| 1153 opt_errorCallback); |
| 1154 }; |
| 1155 |
| 1156 var loadStandardProgram = function(gl) { |
| 1157 var program = gl.createProgram(); |
| 1158 gl.attachShader(program, loadStandardVertexShader(gl)); |
| 1159 gl.attachShader(program, loadStandardFragmentShader(gl)); |
| 1160 linkProgram(gl, program); |
| 1161 return program; |
| 1162 }; |
| 1163 |
| 1164 /** |
| 1165 * Loads shaders from files, creates a program, attaches the shaders and links. |
| 1166 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1167 * @param {string} vertexShaderPath The URL of the vertex shader. |
| 1168 * @param {string} fragmentShaderPath The URL of the fragment shader. |
| 1169 * @param {function(string): void) opt_errorCallback callback for errors. |
| 1170 * @return {!WebGLProgram} The created program. |
| 1171 */ |
| 1172 var loadProgramFromFile = function( |
| 1173 gl, vertexShaderPath, fragmentShaderPath, opt_errorCallback) { |
| 1174 var program = gl.createProgram(); |
| 1175 var vs = loadShaderFromFile( |
| 1176 gl, vertexShaderPath, gl.VERTEX_SHADER, opt_errorCallback); |
| 1177 var fs = loadShaderFromFile( |
| 1178 gl, fragmentShaderPath, gl.FRAGMENT_SHADER, opt_errorCallback); |
| 1179 if (vs && fs) { |
| 1180 gl.attachShader(program, vs); |
| 1181 gl.attachShader(program, fs); |
| 1182 linkProgram(gl, program, opt_errorCallback); |
| 1183 } |
| 1184 if (vs) { |
| 1185 gl.deleteShader(vs); |
| 1186 } |
| 1187 if (fs) { |
| 1188 gl.deleteShader(fs); |
| 1189 } |
| 1190 return program; |
| 1191 }; |
| 1192 |
| 1193 /** |
| 1194 * Loads shaders from script tags, creates a program, attaches the shaders and |
| 1195 * links. |
| 1196 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1197 * @param {string} vertexScriptId The id of the script tag that contains the |
| 1198 * vertex shader. |
| 1199 * @param {string} fragmentScriptId The id of the script tag that contains the |
| 1200 * fragment shader. |
| 1201 * @param {function(string): void) opt_errorCallback callback for errors. |
| 1202 * @return {!WebGLProgram} The created program. |
| 1203 */ |
| 1204 var loadProgramFromScript = function loadProgramFromScript( |
| 1205 gl, vertexScriptId, fragmentScriptId, opt_errorCallback) { |
| 1206 var program = gl.createProgram(); |
| 1207 gl.attachShader( |
| 1208 program, |
| 1209 loadShaderFromScript( |
| 1210 gl, vertexScriptId, gl.VERTEX_SHADER, opt_errorCallback)); |
| 1211 gl.attachShader( |
| 1212 program, |
| 1213 loadShaderFromScript( |
| 1214 gl, fragmentScriptId, gl.FRAGMENT_SHADER, opt_errorCallback)); |
| 1215 linkProgram(gl, program, opt_errorCallback); |
| 1216 return program; |
| 1217 }; |
| 1218 |
| 1219 /** |
| 1220 * Loads shaders from source, creates a program, attaches the shaders and |
| 1221 * links. |
| 1222 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1223 * @param {!WebGLShader} vertexShader The vertex shader. |
| 1224 * @param {!WebGLShader} fragmentShader The fragment shader. |
| 1225 * @param {function(string): void) opt_errorCallback callback for errors. |
| 1226 * @return {!WebGLProgram} The created program. |
| 1227 */ |
| 1228 var createProgram = function(gl, vertexShader, fragmentShader, opt_errorCallback
) { |
| 1229 var program = gl.createProgram(); |
| 1230 gl.attachShader(program, vertexShader); |
| 1231 gl.attachShader(program, fragmentShader); |
| 1232 linkProgram(gl, program, opt_errorCallback); |
| 1233 return program; |
| 1234 }; |
| 1235 |
| 1236 /** |
| 1237 * Loads shaders from source, creates a program, attaches the shaders and |
| 1238 * links. |
| 1239 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1240 * @param {string} vertexShader The vertex shader source. |
| 1241 * @param {string} fragmentShader The fragment shader source. |
| 1242 * @param {function(string): void) opt_errorCallback callback for errors. |
| 1243 * @return {!WebGLProgram} The created program. |
| 1244 */ |
| 1245 var loadProgram = function( |
| 1246 gl, vertexShader, fragmentShader, opt_errorCallback) { |
| 1247 var program; |
| 1248 var vs = loadShader( |
| 1249 gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback); |
| 1250 var fs = loadShader( |
| 1251 gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback); |
| 1252 if (vs && fs) { |
| 1253 program = createProgram(gl, vs, fs, opt_errorCallback) |
| 1254 } |
| 1255 if (vs) { |
| 1256 gl.deleteShader(vs); |
| 1257 } |
| 1258 if (fs) { |
| 1259 gl.deleteShader(fs); |
| 1260 } |
| 1261 return program; |
| 1262 }; |
| 1263 |
| 1264 /** |
| 1265 * Loads shaders from source, creates a program, attaches the shaders and |
| 1266 * links but expects error. |
| 1267 * |
| 1268 * GLSL 1.0.17 10.27 effectively says that compileShader can |
| 1269 * always succeed as long as linkProgram fails so we can't |
| 1270 * rely on compileShader failing. This function expects |
| 1271 * one of the shader to fail OR linking to fail. |
| 1272 * |
| 1273 * @param {!WebGLContext} gl The WebGLContext to use. |
| 1274 * @param {string} vertexShaderScriptId The vertex shader. |
| 1275 * @param {string} fragmentShaderScriptId The fragment shader. |
| 1276 * @return {WebGLProgram} The created program. |
| 1277 */ |
| 1278 var loadProgramFromScriptExpectError = function( |
| 1279 gl, vertexShaderScriptId, fragmentShaderScriptId) { |
| 1280 var vertexShader = loadShaderFromScript(gl, vertexShaderScriptId); |
| 1281 if (!vertexShader) { |
| 1282 return null; |
| 1283 } |
| 1284 var fragmentShader = loadShaderFromScript(gl, fragmentShaderScriptId); |
| 1285 if (!fragmentShader) { |
| 1286 return null; |
| 1287 } |
| 1288 var linkSuccess = true; |
| 1289 var program = gl.createProgram(); |
| 1290 gl.attachShader(program, vertexShader); |
| 1291 gl.attachShader(program, fragmentShader); |
| 1292 linkSuccess = true; |
| 1293 linkProgram(gl, program, function() { |
| 1294 linkSuccess = false; |
| 1295 }); |
| 1296 return linkSuccess ? program : null; |
| 1297 }; |
| 1298 |
| 1299 var basePath; |
| 1300 var getBasePath = function() { |
| 1301 if (!basePath) { |
| 1302 var expectedBase = "webgl-test-utils.js"; |
| 1303 var scripts = document.getElementsByTagName('script'); |
| 1304 for (var script, i = 0; script = scripts[i]; i++) { |
| 1305 var src = script.src; |
| 1306 var l = src.length; |
| 1307 if (src.substr(l - expectedBase.length) == expectedBase) { |
| 1308 basePath = src.substr(0, l - expectedBase.length); |
| 1309 } |
| 1310 } |
| 1311 } |
| 1312 return basePath; |
| 1313 }; |
| 1314 |
| 1315 var loadStandardVertexShader = function(gl) { |
| 1316 return loadShaderFromFile( |
| 1317 gl, getBasePath() + "vertexShader.vert", gl.VERTEX_SHADER); |
| 1318 }; |
| 1319 |
| 1320 var loadStandardFragmentShader = function(gl) { |
| 1321 return loadShaderFromFile( |
| 1322 gl, getBasePath() + "fragmentShader.frag", gl.FRAGMENT_SHADER); |
| 1323 }; |
| 1324 |
| 1325 /** |
| 1326 * Loads an image asynchronously. |
| 1327 * @param {string} url URL of image to load. |
| 1328 * @param {!function(!Element): void} callback Function to call |
| 1329 * with loaded image. |
| 1330 */ |
| 1331 var loadImageAsync = function(url, callback) { |
| 1332 var img = document.createElement('img'); |
| 1333 img.onload = function() { |
| 1334 callback(img); |
| 1335 }; |
| 1336 img.src = url; |
| 1337 }; |
| 1338 |
| 1339 /** |
| 1340 * Loads an array of images. |
| 1341 * @param {!Array.<string>} urls URLs of images to load. |
| 1342 * @param {!function(!{string, img}): void} callback. Callback |
| 1343 * that gets passed map of urls to img tags. |
| 1344 */ |
| 1345 var loadImagesAsync = function(urls, callback) { |
| 1346 var count = 1; |
| 1347 var images = { }; |
| 1348 function countDown() { |
| 1349 --count; |
| 1350 if (count == 0) { |
| 1351 callback(images); |
| 1352 } |
| 1353 } |
| 1354 function imageLoaded(url) { |
| 1355 return function(img) { |
| 1356 images[url] = img; |
| 1357 countDown(); |
| 1358 } |
| 1359 } |
| 1360 for (var ii = 0; ii < urls.length; ++ii) { |
| 1361 ++count; |
| 1362 loadImageAsync(urls[ii], imageLoaded(urls[ii])); |
| 1363 } |
| 1364 countDown(); |
| 1365 }; |
| 1366 |
| 1367 var getUrlArguments = function() { |
| 1368 var args = {}; |
| 1369 try { |
| 1370 var s = window.location.href; |
| 1371 var q = s.indexOf("?"); |
| 1372 var e = s.indexOf("#"); |
| 1373 if (e < 0) { |
| 1374 e = s.length; |
| 1375 } |
| 1376 var query = s.substring(q + 1, e); |
| 1377 var pairs = query.split("&"); |
| 1378 for (var ii = 0; ii < pairs.length; ++ii) { |
| 1379 var keyValue = pairs[ii].split("="); |
| 1380 var key = keyValue[0]; |
| 1381 var value = decodeURIComponent(keyValue[1]); |
| 1382 args[key] = value; |
| 1383 } |
| 1384 } catch (e) { |
| 1385 throw "could not parse url"; |
| 1386 } |
| 1387 return args; |
| 1388 }; |
| 1389 |
| 1390 var makeImage = function(canvas) { |
| 1391 var img = document.createElement('img'); |
| 1392 img.src = canvas.toDataURL(); |
| 1393 return img; |
| 1394 }; |
| 1395 |
| 1396 var insertImage = function(element, caption, img) { |
| 1397 var div = document.createElement("div"); |
| 1398 div.appendChild(img); |
| 1399 var label = document.createElement("div"); |
| 1400 label.appendChild(document.createTextNode(caption)); |
| 1401 div.appendChild(label); |
| 1402 element.appendChild(div); |
| 1403 }; |
| 1404 |
| 1405 var addShaderSource = function(element, label, source, opt_url) { |
| 1406 var div = document.createElement("div"); |
| 1407 var s = document.createElement("pre"); |
| 1408 s.className = "shader-source"; |
| 1409 s.style.display = "none"; |
| 1410 var ol = document.createElement("ol"); |
| 1411 //s.appendChild(document.createTextNode(source)); |
| 1412 var lines = source.split("\n"); |
| 1413 for (var ii = 0; ii < lines.length; ++ii) { |
| 1414 var line = lines[ii]; |
| 1415 var li = document.createElement("li"); |
| 1416 li.appendChild(document.createTextNode(line)); |
| 1417 ol.appendChild(li); |
| 1418 } |
| 1419 s.appendChild(ol); |
| 1420 var l = document.createElement("a"); |
| 1421 l.href = "show-shader-source"; |
| 1422 l.appendChild(document.createTextNode(label)); |
| 1423 l.addEventListener('click', function(event) { |
| 1424 if (event.preventDefault) { |
| 1425 event.preventDefault(); |
| 1426 } |
| 1427 s.style.display = (s.style.display == 'none') ? 'block' : 'none'; |
| 1428 return false; |
| 1429 }, false); |
| 1430 div.appendChild(l); |
| 1431 if (opt_url) { |
| 1432 var u = document.createElement("a"); |
| 1433 u.href = opt_url; |
| 1434 div.appendChild(document.createTextNode(" ")); |
| 1435 u.appendChild(document.createTextNode("(" + opt_url + ")")); |
| 1436 div.appendChild(u); |
| 1437 } |
| 1438 div.appendChild(s); |
| 1439 element.appendChild(div); |
| 1440 }; |
| 1441 |
| 1442 // Add your prefix here. |
| 1443 var browserPrefixes = [ |
| 1444 "", |
| 1445 "MOZ_", |
| 1446 "OP_", |
| 1447 "WEBKIT_" |
| 1448 ]; |
| 1449 |
| 1450 /** |
| 1451 * Given an extension name like WEBGL_compressed_texture_s3tc |
| 1452 * returns the name of the supported version extension, like |
| 1453 * WEBKIT_WEBGL_compressed_teture_s3tc |
| 1454 * @param {string} name Name of extension to look for |
| 1455 * @return {string} name of extension found or undefined if not |
| 1456 * found. |
| 1457 */ |
| 1458 var getSupportedExtensionWithKnownPrefixes = function(gl, name) { |
| 1459 var supported = gl.getSupportedExtensions(); |
| 1460 for (var ii = 0; ii < browserPrefixes.length; ++ii) { |
| 1461 var prefixedName = browserPrefixes[ii] + name; |
| 1462 if (supported.indexOf(prefixedName) >= 0) { |
| 1463 return prefixedName; |
| 1464 } |
| 1465 } |
| 1466 }; |
| 1467 |
| 1468 /** |
| 1469 * Given an extension name like WEBGL_compressed_texture_s3tc |
| 1470 * returns the supported version extension, like |
| 1471 * WEBKIT_WEBGL_compressed_teture_s3tc |
| 1472 * @param {string} name Name of extension to look for |
| 1473 * @return {WebGLExtension} The extension or undefined if not |
| 1474 * found. |
| 1475 */ |
| 1476 var getExtensionWithKnownPrefixes = function(gl, name) { |
| 1477 for (var ii = 0; ii < browserPrefixes.length; ++ii) { |
| 1478 var prefixedName = browserPrefixes[ii] + name; |
| 1479 var ext = gl.getExtension(prefixedName); |
| 1480 if (ext) { |
| 1481 return ext; |
| 1482 } |
| 1483 } |
| 1484 }; |
| 1485 |
| 1486 |
| 1487 var replaceRE = /\$\((\w+)\)/g; |
| 1488 |
| 1489 /** |
| 1490 * Replaces strings with property values. |
| 1491 * Given a string like "hello $(first) $(last)" and an object |
| 1492 * like {first:"John", last:"Smith"} will return |
| 1493 * "hello John Smith". |
| 1494 * @param {string} str String to do replacements in |
| 1495 * @param {...} 1 or more objects conaining properties. |
| 1496 */ |
| 1497 var replaceParams = function(str) { |
| 1498 var args = arguments; |
| 1499 return str.replace(replaceRE, function(str, p1, offset, s) { |
| 1500 for (var ii = 1; ii < args.length; ++ii) { |
| 1501 if (args[ii][p1] !== undefined) { |
| 1502 return args[ii][p1]; |
| 1503 } |
| 1504 } |
| 1505 throw "unknown string param '" + p1 + "'"; |
| 1506 }); |
| 1507 }; |
| 1508 |
| 1509 |
| 1510 /** |
| 1511 * Provides requestAnimationFrame in a cross browser way. |
| 1512 */ |
| 1513 var requestAnimFrameImpl_; |
| 1514 |
| 1515 var requestAnimFrame = function(callback, element) { |
| 1516 if (!requestAnimFrameImpl_) { |
| 1517 requestAnimFrameImpl_ = function() { |
| 1518 var functionNames = [ |
| 1519 "requestAnimationFrame", |
| 1520 "webkitRequestAnimationFrame", |
| 1521 "mozRequestAnimationFrame", |
| 1522 "oRequestAnimationFrame", |
| 1523 "msRequestAnimationFrame" |
| 1524 ]; |
| 1525 for (var jj = 0; jj < functionNames.length; ++jj) { |
| 1526 var functionName = functionNames[jj]; |
| 1527 if (window[functionName]) { |
| 1528 return function(name) { |
| 1529 return function(callback, element) { |
| 1530 return window[name].call(window, callback, element); |
| 1531 }; |
| 1532 }(functionName); |
| 1533 } |
| 1534 } |
| 1535 return function(callback, element) { |
| 1536 return window.setTimeout(callback, 1000 / 70); |
| 1537 }; |
| 1538 }(); |
| 1539 } |
| 1540 |
| 1541 return requestAnimFrameImpl_(callback, element); |
| 1542 }; |
| 1543 |
| 1544 /** |
| 1545 * Provides cancelAnimationFrame in a cross browser way. |
| 1546 */ |
| 1547 var cancelAnimFrame = (function() { |
| 1548 return window.cancelAnimationFrame || |
| 1549 window.webkitCancelAnimationFrame || |
| 1550 window.mozCancelAnimationFrame || |
| 1551 window.oCancelAnimationFrame || |
| 1552 window.msCancelAnimationFrame || |
| 1553 window.clearTimeout; |
| 1554 })(); |
| 1555 |
| 1556 var waitFrames = function(frames, callback) { |
| 1557 var countDown = function() { |
| 1558 if (frames == 0) { |
| 1559 callback(); |
| 1560 } else { |
| 1561 --frames; |
| 1562 requestAnimFrame(countDown); |
| 1563 } |
| 1564 }; |
| 1565 countDown(); |
| 1566 }; |
| 1567 |
| 1568 return { |
| 1569 addShaderSource: addShaderSource, |
| 1570 cancelAnimFrame: cancelAnimFrame, |
| 1571 create3DContext: create3DContext, |
| 1572 create3DContextWithWrapperThatThrowsOnGLError: |
| 1573 create3DContextWithWrapperThatThrowsOnGLError, |
| 1574 checkCanvas: checkCanvas, |
| 1575 checkCanvasRect: checkCanvasRect, |
| 1576 createColoredTexture: createColoredTexture, |
| 1577 createProgram: createProgram, |
| 1578 drawQuad: drawQuad, |
| 1579 drawIndexedQuad: drawIndexedQuad, |
| 1580 endsWith: endsWith, |
| 1581 getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes, |
| 1582 getFileListAsync: getFileListAsync, |
| 1583 getLastError: getLastError, |
| 1584 getScript: getScript, |
| 1585 getSupportedExtensionWithKnownPrefixes: getSupportedExtensionWithKnownPrefixes
, |
| 1586 getUrlArguments: getUrlArguments, |
| 1587 glEnumToString: glEnumToString, |
| 1588 glErrorShouldBe: glErrorShouldBe, |
| 1589 fillTexture: fillTexture, |
| 1590 insertImage: insertImage, |
| 1591 loadImageAsync: loadImageAsync, |
| 1592 loadImagesAsync: loadImagesAsync, |
| 1593 loadProgram: loadProgram, |
| 1594 loadProgramFromFile: loadProgramFromFile, |
| 1595 loadProgramFromScript: loadProgramFromScript, |
| 1596 loadProgramFromScriptExpectError: loadProgramFromScriptExpectError, |
| 1597 loadShader: loadShader, |
| 1598 loadShaderFromFile: loadShaderFromFile, |
| 1599 loadShaderFromScript: loadShaderFromScript, |
| 1600 loadStandardProgram: loadStandardProgram, |
| 1601 loadStandardVertexShader: loadStandardVertexShader, |
| 1602 loadStandardFragmentShader: loadStandardFragmentShader, |
| 1603 loadTextFileAsync: loadTextFileAsync, |
| 1604 loadTexture: loadTexture, |
| 1605 log: log, |
| 1606 loggingOff: loggingOff, |
| 1607 makeImage: makeImage, |
| 1608 error: error, |
| 1609 setupColorQuad: setupColorQuad, |
| 1610 setupProgram: setupProgram, |
| 1611 setupQuad: setupQuad, |
| 1612 setupQuadWithOptions: setupQuadWithOptions, |
| 1613 setupSimpleTextureFragmentShader: setupSimpleTextureFragmentShader, |
| 1614 setupSimpleTextureProgram: setupSimpleTextureProgram, |
| 1615 setupSimpleTextureVertexShader: setupSimpleTextureVertexShader, |
| 1616 setupNoTexCoordTextureProgram: setupNoTexCoordTextureProgram, |
| 1617 setupNoTexCoordTextureVertexShader: setupNoTexCoordTextureVertexShader, |
| 1618 setupTexturedQuad: setupTexturedQuad, |
| 1619 setupTexturedQuadWithTexCoords: setupTexturedQuadWithTexCoords, |
| 1620 setupUnitQuad: setupUnitQuad, |
| 1621 setupUnitQuadWithTexCoords: setupUnitQuadWithTexCoords, |
| 1622 startsWith: startsWith, |
| 1623 shouldGenerateGLError: shouldGenerateGLError, |
| 1624 readFile: readFile, |
| 1625 readFileList: readFileList, |
| 1626 replaceParams: replaceParams, |
| 1627 requestAnimFrame: requestAnimFrame, |
| 1628 waitFrames: waitFrames, |
| 1629 |
| 1630 none: false |
| 1631 }; |
| 1632 |
| 1633 }()); |
| 1634 |
| 1635 |
OLD | NEW |