webgl-test-utils.js (121698B)
1 /* 2 Copyright (c) 2019 The Khronos Group Inc. 3 Use of this source code is governed by an MIT-style license that can be 4 found in the LICENSE.txt file. 5 */ 6 var WebGLTestUtils = (function() { 7 "use strict"; 8 9 /** 10 * Wrapped logging function. 11 * @param {string} msg The message to log. 12 */ 13 var log = function(msg) { 14 bufferedLogToConsole(msg); 15 }; 16 17 /** 18 * Wrapped logging function. 19 * @param {string} msg The message to log. 20 */ 21 var error = function(msg) { 22 // For the time being, diverting this to window.console.log rather 23 // than window.console.error. If anyone cares enough they can 24 // generalize the mechanism in js-test-pre.js. 25 log(msg); 26 }; 27 28 /** 29 * Turn off all logging. 30 */ 31 var loggingOff = function() { 32 log = function() {}; 33 error = function() {}; 34 }; 35 36 const ENUM_NAME_REGEX = RegExp('[A-Z][A-Z0-9_]*'); 37 const ENUM_NAME_BY_VALUE = {}; 38 const ENUM_NAME_PROTOTYPES = new Map(); 39 40 /** 41 * Converts a WebGL enum to a string. 42 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 43 * @param {number} value The enum value. 44 * @return {string} The enum as a string. 45 */ 46 var glEnumToString = function(glOrExt, value) { 47 if (value === undefined) 48 throw new Error('glEnumToString: `value` must not be undefined'); 49 50 const proto = glOrExt.__proto__; 51 if (!ENUM_NAME_PROTOTYPES.has(proto)) { 52 ENUM_NAME_PROTOTYPES.set(proto, true); 53 54 for (const k in proto) { 55 if (!ENUM_NAME_REGEX.test(k)) continue; 56 57 const v = glOrExt[k]; 58 if (ENUM_NAME_BY_VALUE[v] === undefined) { 59 ENUM_NAME_BY_VALUE[v] = k; 60 } else { 61 ENUM_NAME_BY_VALUE[v] += '/' + k; 62 } 63 } 64 } 65 66 const key = ENUM_NAME_BY_VALUE[value]; 67 if (key !== undefined) return key; 68 69 return "0x" + Number(value).toString(16); 70 }; 71 72 var lastError = ""; 73 74 /** 75 * Returns the last compiler/linker error. 76 * @return {string} The last compiler/linker error. 77 */ 78 var getLastError = function() { 79 return lastError; 80 }; 81 82 /** 83 * Whether a haystack ends with a needle. 84 * @param {string} haystack String to search 85 * @param {string} needle String to search for. 86 * @param {boolean} True if haystack ends with needle. 87 */ 88 var endsWith = function(haystack, needle) { 89 return haystack.substr(haystack.length - needle.length) === needle; 90 }; 91 92 /** 93 * Whether a haystack starts with a needle. 94 * @param {string} haystack String to search 95 * @param {string} needle String to search for. 96 * @param {boolean} True if haystack starts with needle. 97 */ 98 var startsWith = function(haystack, needle) { 99 return haystack.substr(0, needle.length) === needle; 100 }; 101 102 /** 103 * A vertex shader for a single texture. 104 * @type {string} 105 */ 106 var simpleTextureVertexShader = [ 107 'attribute vec4 vPosition;', 108 'attribute vec2 texCoord0;', 109 'varying vec2 texCoord;', 110 'void main() {', 111 ' gl_Position = vPosition;', 112 ' texCoord = texCoord0;', 113 '}'].join('\n'); 114 115 /** 116 * A vertex shader for a single texture. 117 * @type {string} 118 */ 119 var simpleTextureVertexShaderESSL300 = [ 120 '#version 300 es', 121 'layout(location=0) in vec4 vPosition;', 122 'layout(location=1) in vec2 texCoord0;', 123 'out vec2 texCoord;', 124 'void main() {', 125 ' gl_Position = vPosition;', 126 ' texCoord = texCoord0;', 127 '}'].join('\n'); 128 129 /** 130 * A fragment shader for a single texture. 131 * @type {string} 132 */ 133 var simpleTextureFragmentShader = [ 134 'precision mediump float;', 135 'uniform sampler2D tex;', 136 'varying vec2 texCoord;', 137 'void main() {', 138 ' gl_FragData[0] = texture2D(tex, texCoord);', 139 '}'].join('\n'); 140 141 /** 142 * A fragment shader for a single texture. 143 * @type {string} 144 */ 145 var simpleTextureFragmentShaderESSL300 = [ 146 '#version 300 es', 147 'precision highp float;', 148 'uniform highp sampler2D tex;', 149 'in vec2 texCoord;', 150 'out vec4 out_color;', 151 'void main() {', 152 ' out_color = texture(tex, texCoord);', 153 '}'].join('\n'); 154 155 /** 156 * A fragment shader for a single texture with high precision. 157 * @type {string} 158 */ 159 var simpleHighPrecisionTextureFragmentShader = [ 160 'precision highp float;', 161 'uniform highp sampler2D tex;', 162 'varying vec2 texCoord;', 163 'void main() {', 164 ' gl_FragData[0] = texture2D(tex, texCoord);', 165 '}'].join('\n'); 166 167 /** 168 * A fragment shader for a single cube map texture. 169 * @type {string} 170 */ 171 var simpleCubeMapTextureFragmentShader = [ 172 'precision mediump float;', 173 'uniform samplerCube tex;', 174 'uniform highp int face;', 175 'varying vec2 texCoord;', 176 'void main() {', 177 // Transform [0, 1] -> [-1, 1] 178 ' vec2 texC2 = (texCoord * 2.) - 1.;', 179 // Transform 2d tex coord. to each face of TEXTURE_CUBE_MAP coord. 180 ' vec3 texCube = vec3(0., 0., 0.);', 181 ' if (face == 34069) {', // TEXTURE_CUBE_MAP_POSITIVE_X 182 ' texCube = vec3(1., -texC2.y, -texC2.x);', 183 ' } else if (face == 34070) {', // TEXTURE_CUBE_MAP_NEGATIVE_X 184 ' texCube = vec3(-1., -texC2.y, texC2.x);', 185 ' } else if (face == 34071) {', // TEXTURE_CUBE_MAP_POSITIVE_Y 186 ' texCube = vec3(texC2.x, 1., texC2.y);', 187 ' } else if (face == 34072) {', // TEXTURE_CUBE_MAP_NEGATIVE_Y 188 ' texCube = vec3(texC2.x, -1., -texC2.y);', 189 ' } else if (face == 34073) {', // TEXTURE_CUBE_MAP_POSITIVE_Z 190 ' texCube = vec3(texC2.x, -texC2.y, 1.);', 191 ' } else if (face == 34074) {', // TEXTURE_CUBE_MAP_NEGATIVE_Z 192 ' texCube = vec3(-texC2.x, -texC2.y, -1.);', 193 ' }', 194 ' gl_FragData[0] = textureCube(tex, texCube);', 195 '}'].join('\n'); 196 197 /** 198 * A vertex shader for a single texture. 199 * @type {string} 200 */ 201 var noTexCoordTextureVertexShader = [ 202 'attribute vec4 vPosition;', 203 'varying vec2 texCoord;', 204 'void main() {', 205 ' gl_Position = vPosition;', 206 ' texCoord = vPosition.xy * 0.5 + 0.5;', 207 '}'].join('\n'); 208 209 /** 210 * A vertex shader for a uniform color. 211 * @type {string} 212 */ 213 var simpleVertexShader = [ 214 'attribute vec4 vPosition;', 215 'void main() {', 216 ' gl_Position = vPosition;', 217 '}'].join('\n'); 218 219 /** 220 * A vertex shader for a uniform color. 221 * @type {string} 222 */ 223 var simpleVertexShaderESSL300 = [ 224 '#version 300 es', 225 'in vec4 vPosition;', 226 'void main() {', 227 ' gl_Position = vPosition;', 228 '}'].join('\n'); 229 230 /** 231 * A fragment shader for a uniform color. 232 * @type {string} 233 */ 234 var simpleColorFragmentShader = [ 235 'precision mediump float;', 236 'uniform vec4 u_color;', 237 'void main() {', 238 ' gl_FragData[0] = u_color;', 239 '}'].join('\n'); 240 241 /** 242 * A fragment shader for a uniform color. 243 * @type {string} 244 */ 245 var simpleColorFragmentShaderESSL300 = [ 246 '#version 300 es', 247 'precision mediump float;', 248 'out vec4 out_color;', 249 'uniform vec4 u_color;', 250 'void main() {', 251 ' out_color = u_color;', 252 '}'].join('\n'); 253 254 /** 255 * A vertex shader for vertex colors. 256 * @type {string} 257 */ 258 var simpleVertexColorVertexShader = [ 259 'attribute vec4 vPosition;', 260 'attribute vec4 a_color;', 261 'varying vec4 v_color;', 262 'void main() {', 263 ' gl_Position = vPosition;', 264 ' v_color = a_color;', 265 '}'].join('\n'); 266 267 /** 268 * A fragment shader for vertex colors. 269 * @type {string} 270 */ 271 var simpleVertexColorFragmentShader = [ 272 'precision mediump float;', 273 'varying vec4 v_color;', 274 'void main() {', 275 ' gl_FragData[0] = v_color;', 276 '}'].join('\n'); 277 278 /** 279 * Creates a program, attaches shaders, binds attrib locations, links the 280 * program and calls useProgram. 281 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 282 * @param {!Array.<!WebGLShader|string>} shaders The shaders to 283 * attach, or the source, or the id of a script to get 284 * the source from. 285 * @param {!Array.<string>} opt_attribs The attribs names. 286 * @param {!Array.<number>} opt_locations The locations for the attribs. 287 * @param {boolean} opt_logShaders Whether to log shader source. 288 */ 289 var setupProgram = function( 290 gl, shaders, opt_attribs, opt_locations, opt_logShaders) { 291 var realShaders = []; 292 var program = gl.createProgram(); 293 var shaderCount = 0; 294 for (var ii = 0; ii < shaders.length; ++ii) { 295 var shader = shaders[ii]; 296 var shaderType = undefined; 297 if (typeof shader == 'string') { 298 var element = document.getElementById(shader); 299 if (element) { 300 if (element.type != "x-shader/x-vertex" && element.type != "x-shader/x-fragment") 301 shaderType = ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER; 302 shader = loadShaderFromScript(gl, shader, shaderType, undefined, opt_logShaders); 303 } else if (endsWith(shader, ".vert")) { 304 shader = loadShaderFromFile(gl, shader, gl.VERTEX_SHADER, undefined, opt_logShaders); 305 } else if (endsWith(shader, ".frag")) { 306 shader = loadShaderFromFile(gl, shader, gl.FRAGMENT_SHADER, undefined, opt_logShaders); 307 } else { 308 shader = loadShader(gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER, undefined, opt_logShaders); 309 } 310 } else if (opt_logShaders) { 311 throw 'Shader source logging requested but no shader source provided'; 312 } 313 if (shader) { 314 ++shaderCount; 315 gl.attachShader(program, shader); 316 } 317 } 318 if (shaderCount != 2) { 319 error("Error in compiling shader"); 320 return null; 321 } 322 if (opt_attribs) { 323 for (var ii = 0; ii < opt_attribs.length; ++ii) { 324 gl.bindAttribLocation( 325 program, 326 opt_locations ? opt_locations[ii] : ii, 327 opt_attribs[ii]); 328 } 329 } 330 gl.linkProgram(program); 331 332 // Check the link status 333 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); 334 if (!linked) { 335 // something went wrong with the link 336 lastError = gl.getProgramInfoLog (program); 337 error("Error in program linking:" + lastError); 338 339 gl.deleteProgram(program); 340 return null; 341 } 342 343 gl.useProgram(program); 344 return program; 345 }; 346 347 /** 348 * Creates a program, attaches shader, sets up trasnform feedback varyings, 349 * binds attrib locations, links the program and calls useProgram. 350 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 351 * @param {!Array.<!WebGLShader|string>} shaders The shaders to 352 * attach, or the source, or the id of a script to get 353 * the source from. 354 * @param {!Array.<string>} varyings The transform feedback varying names. 355 * @param {number} bufferMode The mode used to capture the varying variables. 356 * @param {!Array.<string>} opt_attribs The attribs names. 357 * @param {!Array.<number>} opt_locations The locations for the attribs. 358 * @param {boolean} opt_logShaders Whether to log shader source. 359 */ 360 var setupTransformFeedbackProgram = function( 361 gl, shaders, varyings, bufferMode, opt_attribs, opt_locations, opt_logShaders, opt_skipCompileStatus) { 362 var realShaders = []; 363 var program = gl.createProgram(); 364 var shaderCount = 0; 365 for (var ii = 0; ii < shaders.length; ++ii) { 366 var shader = shaders[ii]; 367 var shaderType = undefined; 368 if (typeof shader == 'string') { 369 var element = document.getElementById(shader); 370 if (element) { 371 if (element.type != "x-shader/x-vertex" && element.type != "x-shader/x-fragment") 372 shaderType = ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER; 373 shader = loadShaderFromScript(gl, shader, shaderType, undefined, opt_logShaders, opt_skipCompileStatus); 374 } else if (endsWith(shader, ".vert")) { 375 shader = loadShaderFromFile(gl, shader, gl.VERTEX_SHADER, undefined, opt_logShaders, opt_skipCompileStatus); 376 } else if (endsWith(shader, ".frag")) { 377 shader = loadShaderFromFile(gl, shader, gl.FRAGMENT_SHADER, undefined, opt_logShaders, opt_skipCompileStatus); 378 } else { 379 shader = loadShader(gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER, undefined, opt_logShaders, undefined, undefined, opt_skipCompileStatus); 380 } 381 } else if (opt_logShaders) { 382 throw 'Shader source logging requested but no shader source provided'; 383 } 384 if (shader) { 385 ++shaderCount; 386 gl.attachShader(program, shader); 387 } 388 } 389 if (shaderCount != 2) { 390 error("Error in compiling shader"); 391 return null; 392 } 393 394 if (opt_attribs) { 395 for (var ii = 0; ii < opt_attribs.length; ++ii) { 396 gl.bindAttribLocation( 397 program, 398 opt_locations ? opt_locations[ii] : ii, 399 opt_attribs[ii]); 400 } 401 } 402 403 gl.transformFeedbackVaryings(program, varyings, bufferMode); 404 405 gl.linkProgram(program); 406 407 // Check the link status 408 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); 409 if (!linked) { 410 // something went wrong with the link 411 lastError = gl.getProgramInfoLog (program); 412 error("Error in program linking:" + lastError); 413 414 gl.deleteProgram(program); 415 return null; 416 } 417 418 gl.useProgram(program); 419 return program; 420 }; 421 422 /** 423 * Creates a simple texture program. 424 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 425 * @return {WebGLProgram} 426 */ 427 var setupNoTexCoordTextureProgram = function(gl) { 428 return setupProgram(gl, 429 [noTexCoordTextureVertexShader, simpleTextureFragmentShader], 430 ['vPosition'], 431 [0]); 432 }; 433 434 /** 435 * Creates a simple texture program. 436 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 437 * @param {number} opt_positionLocation The attrib location for position. 438 * @param {number} opt_texcoordLocation The attrib location for texture coords. 439 * @param {string} opt_fragmentShaderOverride The alternative fragment shader to use. 440 * @return {WebGLProgram} 441 */ 442 var setupSimpleTextureProgram = function( 443 gl, opt_positionLocation, opt_texcoordLocation, opt_fragmentShaderOverride) { 444 opt_positionLocation = opt_positionLocation || 0; 445 opt_texcoordLocation = opt_texcoordLocation || 1; 446 opt_fragmentShaderOverride = opt_fragmentShaderOverride || simpleTextureFragmentShader; 447 return setupProgram(gl, 448 [simpleTextureVertexShader, opt_fragmentShaderOverride], 449 ['vPosition', 'texCoord0'], 450 [opt_positionLocation, opt_texcoordLocation]); 451 }; 452 453 /** 454 * Creates a simple texture program using glsl version 300. 455 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 456 * @param {number} opt_positionLocation The attrib location for position. 457 * @param {number} opt_texcoordLocation The attrib location for texture coords. 458 * @param {string} opt_fragmentShaderOverride The alternative fragment shader to use. 459 * @return {WebGLProgram} 460 */ 461 var setupSimpleTextureProgramESSL300 = function( 462 gl, opt_positionLocation, opt_texcoordLocation, opt_fragmentShaderOverride) { 463 opt_positionLocation = opt_positionLocation || 0; 464 opt_texcoordLocation = opt_texcoordLocation || 1; 465 opt_fragmentShaderOverride = opt_fragmentShaderOverride || simpleTextureFragmentShaderESSL300; 466 return setupProgram(gl, 467 [simpleTextureVertexShaderESSL300, opt_fragmentShaderOverride], 468 ['vPosition', 'texCoord0'], 469 [opt_positionLocation, opt_texcoordLocation]); 470 }; 471 472 /** 473 * Creates a simple cube map texture program. 474 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 475 * @param {number} opt_positionLocation The attrib location for position. 476 * @param {number} opt_texcoordLocation The attrib location for texture coords. 477 * @return {WebGLProgram} 478 */ 479 var setupSimpleCubeMapTextureProgram = function( 480 gl, opt_positionLocation, opt_texcoordLocation) { 481 opt_positionLocation = opt_positionLocation || 0; 482 opt_texcoordLocation = opt_texcoordLocation || 1; 483 return setupProgram(gl, 484 [simpleTextureVertexShader, simpleCubeMapTextureFragmentShader], 485 ['vPosition', 'texCoord0'], 486 [opt_positionLocation, opt_texcoordLocation]); 487 }; 488 489 /** 490 * Creates a simple vertex color program. 491 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 492 * @param {number} opt_positionLocation The attrib location for position. 493 * @param {number} opt_vertexColorLocation The attrib location 494 * for vertex colors. 495 * @return {WebGLProgram} 496 */ 497 var setupSimpleVertexColorProgram = function( 498 gl, opt_positionLocation, opt_vertexColorLocation) { 499 opt_positionLocation = opt_positionLocation || 0; 500 opt_vertexColorLocation = opt_vertexColorLocation || 1; 501 return setupProgram(gl, 502 [simpleVertexColorVertexShader, simpleVertexColorFragmentShader], 503 ['vPosition', 'a_color'], 504 [opt_positionLocation, opt_vertexColorLocation]); 505 }; 506 507 /** 508 * Creates a simple color program. 509 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 510 * @param {number} opt_positionLocation The attrib location for position. 511 * @return {WebGLProgram} 512 */ 513 var setupSimpleColorProgram = function(gl, opt_positionLocation) { 514 opt_positionLocation = opt_positionLocation || 0; 515 return setupProgram(gl, 516 [simpleVertexShader, simpleColorFragmentShader], 517 ['vPosition'], 518 [opt_positionLocation]); 519 }; 520 521 /** 522 * Creates buffers for a textured unit quad and attaches them to vertex attribs. 523 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 524 * @param {number} opt_positionLocation The attrib location for position. 525 * @param {number} opt_texcoordLocation The attrib location for texture coords. 526 * @param {!Object} various options. See setupQuad for details. 527 * @return {!Array.<WebGLBuffer>} The buffer objects that were 528 * created. 529 */ 530 var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation, options) { 531 return setupQuadWithTexCoords(gl, [ 0.0, 0.0 ], [ 1.0, 1.0 ], 532 opt_positionLocation, opt_texcoordLocation, 533 options); 534 }; 535 536 /** 537 * Creates buffers for a textured quad with specified lower left 538 * and upper right texture coordinates, and attaches them to vertex 539 * attribs. 540 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 541 * @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the lower left corner. 542 * @param {!Array.<number>} upperRightTexCoords The texture coordinates for the upper right corner. 543 * @param {number} opt_positionLocation The attrib location for position. 544 * @param {number} opt_texcoordLocation The attrib location for texture coords. 545 * @param {!Object} various options. See setupQuad for details. 546 * @return {!Array.<WebGLBuffer>} The buffer objects that were 547 * created. 548 */ 549 var setupQuadWithTexCoords = function( 550 gl, lowerLeftTexCoords, upperRightTexCoords, 551 opt_positionLocation, opt_texcoordLocation, options) { 552 var defaultOptions = { 553 positionLocation: opt_positionLocation || 0, 554 texcoordLocation: opt_texcoordLocation || 1, 555 lowerLeftTexCoords: lowerLeftTexCoords, 556 upperRightTexCoords: upperRightTexCoords 557 }; 558 if (options) { 559 for (var prop in options) { 560 defaultOptions[prop] = options[prop] 561 } 562 } 563 return setupQuad(gl, defaultOptions); 564 }; 565 566 /** 567 * Makes a quad with various options. 568 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 569 * @param {!Object} options 570 * 571 * scale: scale to multiply unit quad values by. default 1.0. 572 * positionLocation: attribute location for position. 573 * texcoordLocation: attribute location for texcoords. 574 * If this does not exist no texture coords are created. 575 * lowerLeftTexCoords: an array of 2 values for the 576 * lowerLeftTexCoords. 577 * upperRightTexCoords: an array of 2 values for the 578 * upperRightTexCoords. 579 */ 580 var setupQuad = function(gl, options) { 581 var positionLocation = options.positionLocation || 0; 582 var scale = options.scale || 1; 583 584 var objects = []; 585 586 var vertexObject = gl.createBuffer(); 587 gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); 588 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 589 1.0 * scale , 1.0 * scale, 590 -1.0 * scale , 1.0 * scale, 591 -1.0 * scale , -1.0 * scale, 592 1.0 * scale , 1.0 * scale, 593 -1.0 * scale , -1.0 * scale, 594 1.0 * scale , -1.0 * scale]), gl.STATIC_DRAW); 595 gl.enableVertexAttribArray(positionLocation); 596 gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); 597 objects.push(vertexObject); 598 599 if (options.texcoordLocation !== undefined) { 600 var llx = options.lowerLeftTexCoords[0]; 601 var lly = options.lowerLeftTexCoords[1]; 602 var urx = options.upperRightTexCoords[0]; 603 var ury = options.upperRightTexCoords[1]; 604 605 vertexObject = gl.createBuffer(); 606 gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); 607 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 608 urx, ury, 609 llx, ury, 610 llx, lly, 611 urx, ury, 612 llx, lly, 613 urx, lly]), gl.STATIC_DRAW); 614 gl.enableVertexAttribArray(options.texcoordLocation); 615 gl.vertexAttribPointer(options.texcoordLocation, 2, gl.FLOAT, false, 0, 0); 616 objects.push(vertexObject); 617 } 618 619 return objects; 620 }; 621 622 /** 623 * Creates a program and buffers for rendering a textured quad. 624 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 625 * @param {number} opt_positionLocation The attrib location for 626 * position. Default = 0. 627 * @param {number} opt_texcoordLocation The attrib location for 628 * texture coords. Default = 1. 629 * @param {!Object} various options defined by setupQuad, plus an option 630 fragmentShaderOverride to specify a custom fragment shader. 631 * @return {!WebGLProgram} 632 */ 633 var setupTexturedQuad = function( 634 gl, opt_positionLocation, opt_texcoordLocation, options) { 635 var program = setupSimpleTextureProgram( 636 gl, opt_positionLocation, opt_texcoordLocation, options && options.fragmentShaderOverride); 637 setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation, options); 638 return program; 639 }; 640 641 /** 642 * Creates a program and buffers for rendering a color quad. 643 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 644 * @param {number} opt_positionLocation The attrib location for position. 645 * @param {!Object} various options. See setupQuad for details. 646 * @return {!WebGLProgram} 647 */ 648 var setupColorQuad = function(gl, opt_positionLocation, options) { 649 opt_positionLocation = opt_positionLocation || 0; 650 var program = setupSimpleColorProgram(gl, opt_positionLocation); 651 setupUnitQuad(gl, opt_positionLocation, 0, options); 652 return program; 653 }; 654 655 /** 656 * Creates a program and buffers for rendering a textured quad with 657 * specified lower left and upper right texture coordinates. 658 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 659 * @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the lower left corner. 660 * @param {!Array.<number>} upperRightTexCoords The texture coordinates for the upper right corner. 661 * @param {number} opt_positionLocation The attrib location for position. 662 * @param {number} opt_texcoordLocation The attrib location for texture coords. 663 * @return {!WebGLProgram} 664 */ 665 var setupTexturedQuadWithTexCoords = function( 666 gl, lowerLeftTexCoords, upperRightTexCoords, 667 opt_positionLocation, opt_texcoordLocation) { 668 var program = setupSimpleTextureProgram( 669 gl, opt_positionLocation, opt_texcoordLocation); 670 setupQuadWithTexCoords(gl, lowerLeftTexCoords, upperRightTexCoords, 671 opt_positionLocation, opt_texcoordLocation); 672 return program; 673 }; 674 675 /** 676 * Creates a program and buffers for rendering a textured quad with 677 * a cube map texture. 678 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 679 * @param {number} opt_positionLocation The attrib location for 680 * position. Default = 0. 681 * @param {number} opt_texcoordLocation The attrib location for 682 * texture coords. Default = 1. 683 * @return {!WebGLProgram} 684 */ 685 var setupTexturedQuadWithCubeMap = function( 686 gl, opt_positionLocation, opt_texcoordLocation) { 687 var program = setupSimpleCubeMapTextureProgram( 688 gl, opt_positionLocation, opt_texcoordLocation); 689 setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation, undefined); 690 return program; 691 }; 692 693 /** 694 * Creates a unit quad with only positions of a given resolution. 695 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 696 * @param {number} gridRes The resolution of the mesh grid, 697 * expressed in the number of quads across and down. 698 * @param {number} opt_positionLocation The attrib location for position. 699 */ 700 var setupIndexedQuad = function ( 701 gl, gridRes, opt_positionLocation, opt_flipOddTriangles) { 702 return setupIndexedQuadWithOptions(gl, 703 { gridRes: gridRes, 704 positionLocation: opt_positionLocation, 705 flipOddTriangles: opt_flipOddTriangles 706 }); 707 }; 708 709 /** 710 * Creates a quad with various options. 711 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 712 * @param {!Object} options The options. See below. 713 * @return {!Array.<WebGLBuffer>} The created buffers. 714 * [positions, <colors>, indices] 715 * 716 * Options: 717 * gridRes: number of quads across and down grid. 718 * positionLocation: attrib location for position 719 * flipOddTriangles: reverse order of vertices of every other 720 * triangle 721 * positionOffset: offset added to each vertex 722 * positionMult: multipier for each vertex 723 * colorLocation: attrib location for vertex colors. If 724 * undefined no vertex colors will be created. 725 */ 726 var setupIndexedQuadWithOptions = function (gl, options) { 727 var positionLocation = options.positionLocation || 0; 728 var objects = []; 729 730 var gridRes = options.gridRes || 1; 731 var positionOffset = options.positionOffset || 0; 732 var positionMult = options.positionMult || 1; 733 var vertsAcross = gridRes + 1; 734 var numVerts = vertsAcross * vertsAcross; 735 var positions = new Float32Array(numVerts * 3); 736 var indices = new Uint16Array(6 * gridRes * gridRes); 737 var poffset = 0; 738 739 for (var yy = 0; yy <= gridRes; ++yy) { 740 for (var xx = 0; xx <= gridRes; ++xx) { 741 positions[poffset + 0] = (-1 + 2 * xx / gridRes) * positionMult + positionOffset; 742 positions[poffset + 1] = (-1 + 2 * yy / gridRes) * positionMult + positionOffset; 743 positions[poffset + 2] = 0; 744 745 poffset += 3; 746 } 747 } 748 749 var buf = gl.createBuffer(); 750 gl.bindBuffer(gl.ARRAY_BUFFER, buf); 751 gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); 752 gl.enableVertexAttribArray(positionLocation); 753 gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); 754 objects.push(buf); 755 756 if (options.colorLocation !== undefined) { 757 var colors = new Float32Array(numVerts * 4); 758 poffset = 0; 759 for (var yy = 0; yy <= gridRes; ++yy) { 760 for (var xx = 0; xx <= gridRes; ++xx) { 761 if (options.color !== undefined) { 762 colors[poffset + 0] = options.color[0]; 763 colors[poffset + 1] = options.color[1]; 764 colors[poffset + 2] = options.color[2]; 765 colors[poffset + 3] = options.color[3]; 766 } else { 767 colors[poffset + 0] = xx / gridRes; 768 colors[poffset + 1] = yy / gridRes; 769 colors[poffset + 2] = (xx / gridRes) * (yy / gridRes); 770 colors[poffset + 3] = (yy % 2) * 0.5 + 0.5; 771 } 772 poffset += 4; 773 } 774 } 775 776 buf = gl.createBuffer(); 777 gl.bindBuffer(gl.ARRAY_BUFFER, buf); 778 gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); 779 gl.enableVertexAttribArray(options.colorLocation); 780 gl.vertexAttribPointer(options.colorLocation, 4, gl.FLOAT, false, 0, 0); 781 objects.push(buf); 782 } 783 784 var tbase = 0; 785 for (var yy = 0; yy < gridRes; ++yy) { 786 var index = yy * vertsAcross; 787 for (var xx = 0; xx < gridRes; ++xx) { 788 indices[tbase + 0] = index + 0; 789 indices[tbase + 1] = index + 1; 790 indices[tbase + 2] = index + vertsAcross; 791 indices[tbase + 3] = index + vertsAcross; 792 indices[tbase + 4] = index + 1; 793 indices[tbase + 5] = index + vertsAcross + 1; 794 795 if (options.flipOddTriangles) { 796 indices[tbase + 4] = index + vertsAcross + 1; 797 indices[tbase + 5] = index + 1; 798 } 799 800 index += 1; 801 tbase += 6; 802 } 803 } 804 805 buf = gl.createBuffer(); 806 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf); 807 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); 808 objects.push(buf); 809 810 return objects; 811 }; 812 813 /** 814 * Returns the constructor for a typed array that corresponds to the given 815 * WebGL type. 816 * @param {!WebGLRenderingContext} gl A WebGLRenderingContext. 817 * @param {number} type The WebGL type (eg, gl.UNSIGNED_BYTE) 818 * @return {!Constructor} The typed array constructor that 819 * corresponds to the given type. 820 */ 821 var glTypeToTypedArrayType = function(gl, type) { 822 switch (type) { 823 case gl.BYTE: 824 return window.Int8Array; 825 case gl.UNSIGNED_BYTE: 826 return window.Uint8Array; 827 case gl.SHORT: 828 return window.Int16Array; 829 case gl.UNSIGNED_SHORT: 830 case gl.UNSIGNED_SHORT_5_6_5: 831 case gl.UNSIGNED_SHORT_4_4_4_4: 832 case gl.UNSIGNED_SHORT_5_5_5_1: 833 return window.Uint16Array; 834 case gl.INT: 835 return window.Int32Array; 836 case gl.UNSIGNED_INT: 837 case gl.UNSIGNED_INT_5_9_9_9_REV: 838 case gl.UNSIGNED_INT_10F_11F_11F_REV: 839 case gl.UNSIGNED_INT_2_10_10_10_REV: 840 case gl.UNSIGNED_INT_24_8: 841 return window.Uint32Array; 842 case gl.HALF_FLOAT: 843 case 0x8D61: // HALF_FLOAT_OES 844 return window.Uint16Array; 845 case gl.FLOAT: 846 return window.Float32Array; 847 default: 848 throw 'unknown gl type ' + glEnumToString(gl, type); 849 } 850 }; 851 852 /** 853 * Returns the number of bytes per component for a given WebGL type. 854 * @param {!WebGLRenderingContext} gl A WebGLRenderingContext. 855 * @param {GLenum} type The WebGL type (eg, gl.UNSIGNED_BYTE) 856 * @return {number} The number of bytes per component. 857 */ 858 var getBytesPerComponent = function(gl, type) { 859 switch (type) { 860 case gl.BYTE: 861 case gl.UNSIGNED_BYTE: 862 return 1; 863 case gl.SHORT: 864 case gl.UNSIGNED_SHORT: 865 case gl.UNSIGNED_SHORT_5_6_5: 866 case gl.UNSIGNED_SHORT_4_4_4_4: 867 case gl.UNSIGNED_SHORT_5_5_5_1: 868 case gl.HALF_FLOAT: 869 case 0x8D61: // HALF_FLOAT_OES 870 return 2; 871 case gl.INT: 872 case gl.UNSIGNED_INT: 873 case gl.UNSIGNED_INT_5_9_9_9_REV: 874 case gl.UNSIGNED_INT_10F_11F_11F_REV: 875 case gl.UNSIGNED_INT_2_10_10_10_REV: 876 case gl.UNSIGNED_INT_24_8: 877 case gl.FLOAT: 878 return 4; 879 default: 880 throw 'unknown gl type ' + glEnumToString(gl, type); 881 } 882 }; 883 884 /** 885 * Returns the number of typed array elements per pixel for a given WebGL 886 * format/type combination. The corresponding typed array type can be determined 887 * by calling glTypeToTypedArrayType. 888 * @param {!WebGLRenderingContext} gl A WebGLRenderingContext. 889 * @param {GLenum} format The WebGL format (eg, gl.RGBA) 890 * @param {GLenum} type The WebGL type (eg, gl.UNSIGNED_BYTE) 891 * @return {number} The number of typed array elements per pixel. 892 */ 893 var getTypedArrayElementsPerPixel = function(gl, format, type) { 894 switch (type) { 895 case gl.UNSIGNED_SHORT_5_6_5: 896 case gl.UNSIGNED_SHORT_4_4_4_4: 897 case gl.UNSIGNED_SHORT_5_5_5_1: 898 return 1; 899 case gl.UNSIGNED_BYTE: 900 break; 901 default: 902 throw 'not a gl type for color information ' + glEnumToString(gl, type); 903 } 904 905 switch (format) { 906 case gl.RGBA: 907 return 4; 908 case gl.RGB: 909 return 3; 910 case gl.LUMINANCE_ALPHA: 911 return 2; 912 case gl.LUMINANCE: 913 case gl.ALPHA: 914 return 1; 915 default: 916 throw 'unknown gl format ' + glEnumToString(gl, format); 917 } 918 }; 919 920 /** 921 * Fills the given texture with a solid color. 922 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 923 * @param {!WebGLTexture} tex The texture to fill. 924 * @param {number} width The width of the texture to create. 925 * @param {number} height The height of the texture to create. 926 * @param {!Array.<number>} color The color to fill with. 927 * where each element is in the range 0 to 255. 928 * @param {number} opt_level The level of the texture to fill. Default = 0. 929 * @param {number} opt_format The format for the texture. 930 * @param {number} opt_internalFormat The internal format for the texture. 931 */ 932 var fillTexture = function(gl, tex, width, height, color, opt_level, opt_format, opt_type, opt_internalFormat) { 933 opt_level = opt_level || 0; 934 opt_format = opt_format || gl.RGBA; 935 opt_type = opt_type || gl.UNSIGNED_BYTE; 936 opt_internalFormat = opt_internalFormat || opt_format; 937 var pack = gl.getParameter(gl.UNPACK_ALIGNMENT); 938 var numComponents = color.length; 939 var bytesPerComponent = getBytesPerComponent(gl, opt_type); 940 var rowSize = numComponents * width * bytesPerComponent; 941 // See equation 3.10 in ES 2.0 spec and equation 3.13 in ES 3.0 spec for paddedRowLength calculation. 942 // k is paddedRowLength. 943 // n is numComponents. 944 // l is width. 945 // a is pack. 946 // s is bytesPerComponent. 947 var paddedRowLength; 948 if (bytesPerComponent >= pack) 949 paddedRowLength = numComponents * width; 950 else 951 paddedRowLength = Math.floor((rowSize + pack - 1) / pack) * pack / bytesPerComponent; 952 var size = width * numComponents + (height - 1) * paddedRowLength; 953 var buf = new (glTypeToTypedArrayType(gl, opt_type))(size); 954 for (var yy = 0; yy < height; ++yy) { 955 var off = yy * paddedRowLength; 956 for (var xx = 0; xx < width; ++xx) { 957 for (var jj = 0; jj < numComponents; ++jj) { 958 buf[off++] = color[jj]; 959 } 960 } 961 } 962 gl.bindTexture(gl.TEXTURE_2D, tex); 963 gl.texImage2D( 964 gl.TEXTURE_2D, opt_level, opt_internalFormat, width, height, 0, 965 opt_format, opt_type, buf); 966 }; 967 968 /** 969 * Creates a texture and fills it with a solid color. 970 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 971 * @param {number} width The width of the texture to create. 972 * @param {number} height The height of the texture to create. 973 * @param {!Array.<number>} color The color to fill with. A 4 element array 974 * where each element is in the range 0 to 255. 975 * @return {!WebGLTexture} 976 */ 977 var createColoredTexture = function(gl, width, height, color) { 978 var tex = gl.createTexture(); 979 fillTexture(gl, tex, width, height, color); 980 return tex; 981 }; 982 983 var ubyteToFloat = function(c) { 984 return c / 255; 985 }; 986 987 var ubyteColorToFloatColor = function(color) { 988 var floatColor = []; 989 for (var ii = 0; ii < color.length; ++ii) { 990 floatColor[ii] = ubyteToFloat(color[ii]); 991 } 992 return floatColor; 993 }; 994 995 /** 996 * Sets the "u_color" uniform of the current program to color. 997 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 998 * @param {!Array.<number>} color 4 element array of 0-1 color 999 * components. 1000 */ 1001 var setFloatDrawColor = function(gl, color) { 1002 var program = gl.getParameter(gl.CURRENT_PROGRAM); 1003 var colorLocation = gl.getUniformLocation(program, "u_color"); 1004 gl.uniform4fv(colorLocation, color); 1005 }; 1006 1007 /** 1008 * Sets the "u_color" uniform of the current program to color. 1009 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1010 * @param {!Array.<number>} color 4 element array of 0-255 color 1011 * components. 1012 */ 1013 var setUByteDrawColor = function(gl, color) { 1014 setFloatDrawColor(gl, ubyteColorToFloatColor(color)); 1015 }; 1016 1017 /** 1018 * Draws a previously setup quad in the given color. 1019 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1020 * @param {!Array.<number>} color The color to draw with. A 4 1021 * element array where each element is in the range 0 to 1022 * 1. 1023 */ 1024 var drawFloatColorQuad = function(gl, color) { 1025 var program = gl.getParameter(gl.CURRENT_PROGRAM); 1026 var colorLocation = gl.getUniformLocation(program, "u_color"); 1027 gl.uniform4fv(colorLocation, color); 1028 gl.drawArrays(gl.TRIANGLES, 0, 6); 1029 }; 1030 1031 1032 /** 1033 * Draws a previously setup quad in the given color. 1034 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1035 * @param {!Array.<number>} color The color to draw with. A 4 1036 * element array where each element is in the range 0 to 1037 * 255. 1038 */ 1039 var drawUByteColorQuad = function(gl, color) { 1040 drawFloatColorQuad(gl, ubyteColorToFloatColor(color)); 1041 }; 1042 1043 /** 1044 * Draws a previously setupUnitQuad. 1045 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1046 */ 1047 var drawUnitQuad = function(gl) { 1048 gl.drawArrays(gl.TRIANGLES, 0, 6); 1049 }; 1050 1051 var dummySetProgramAndDrawNothing = function(gl) { 1052 if (!gl._wtuDummyProgram) { 1053 gl._wtuDummyProgram = setupProgram(gl, [ 1054 "void main() { gl_Position = vec4(0.0); }", 1055 "void main() { gl_FragColor = vec4(0.0); }" 1056 ], [], []); 1057 } 1058 gl.useProgram(gl._wtuDummyProgram); 1059 gl.drawArrays(gl.TRIANGLES, 0, 3); 1060 }; 1061 1062 /** 1063 * Clears then Draws a previously setupUnitQuad. 1064 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1065 * @param {!Array.<number>} opt_color The color to fill clear with before 1066 * drawing. A 4 element array where each element is in the range 0 to 1067 * 255. Default [255, 255, 255, 255] 1068 */ 1069 var clearAndDrawUnitQuad = function(gl, opt_color) { 1070 opt_color = opt_color || [255, 255, 255, 255]; 1071 gl.clearColor( 1072 opt_color[0] / 255, 1073 opt_color[1] / 255, 1074 opt_color[2] / 255, 1075 opt_color[3] / 255); 1076 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 1077 drawUnitQuad(gl); 1078 }; 1079 1080 /** 1081 * Draws a quad previously setup with setupIndexedQuad. 1082 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1083 * @param {number} gridRes Resolution of grid. 1084 */ 1085 var drawIndexedQuad = function(gl, gridRes) { 1086 gl.drawElements(gl.TRIANGLES, gridRes * gridRes * 6, gl.UNSIGNED_SHORT, 0); 1087 }; 1088 1089 /** 1090 * Draws a previously setupIndexedQuad 1091 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1092 * @param {number} gridRes Resolution of grid. 1093 * @param {!Array.<number>} opt_color The color to fill clear with before 1094 * drawing. A 4 element array where each element is in the range 0 to 1095 * 255. Default [255, 255, 255, 255] 1096 */ 1097 var clearAndDrawIndexedQuad = function(gl, gridRes, opt_color) { 1098 opt_color = opt_color || [255, 255, 255, 255]; 1099 gl.clearColor( 1100 opt_color[0] / 255, 1101 opt_color[1] / 255, 1102 opt_color[2] / 255, 1103 opt_color[3] / 255); 1104 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 1105 drawIndexedQuad(gl, gridRes); 1106 }; 1107 1108 /** 1109 * Clips a range to min, max 1110 * (Eg. clipToRange(-5,7,0,20) would return {value:0,extent:2} 1111 * @param {number} value start of range 1112 * @param {number} extent extent of range 1113 * @param {number} min min. 1114 * @param {number} max max. 1115 * @return {!{value:number,extent:number}} The clipped value. 1116 */ 1117 var clipToRange = function(value, extent, min, max) { 1118 if (value < min) { 1119 extent -= min - value; 1120 value = min; 1121 } 1122 var end = value + extent; 1123 if (end > max) { 1124 extent -= end - max; 1125 } 1126 if (extent < 0) { 1127 value = max; 1128 extent = 0; 1129 } 1130 return {value:value, extent: extent}; 1131 }; 1132 1133 /** 1134 * Determines if the passed context is an instance of a WebGLRenderingContext 1135 * or later variant (like WebGL2RenderingContext) 1136 * @param {CanvasRenderingContext} ctx The context to check. 1137 */ 1138 var isWebGLContext = function(ctx) { 1139 if (ctx instanceof WebGLRenderingContext) 1140 return true; 1141 1142 if ('WebGL2RenderingContext' in window && ctx instanceof WebGL2RenderingContext) 1143 return true; 1144 1145 return false; 1146 }; 1147 1148 /** 1149 * Creates a check rect is used by checkCanvasRects. 1150 * @param {number} x left corner of region to check. 1151 * @param {number} y bottom corner of region to check in case of checking from 1152 * a GL context or top corner in case of checking from a 2D context. 1153 * @param {number} width width of region to check. 1154 * @param {number} height width of region to check. 1155 * @param {!Array.<number>} color The color expected. A 4 element array where 1156 * each element is in the range 0 to 255. 1157 * @param {string} opt_msg Message to associate with success. Eg 1158 * ("should be red"). 1159 * @param {number} opt_errorRange Optional. Acceptable error in 1160 * color checking. 0 by default. 1161 */ 1162 var makeCheckRect = function(x, y, width, height, color, msg, errorRange) { 1163 var rect = { 1164 'x': x, 'y': y, 1165 'width': width, 'height': height, 1166 'color': color, 'msg': msg, 1167 'errorRange': errorRange, 1168 1169 'checkRect': function (buf, l, b, w) { 1170 for (var px = (x - l) ; px < (x + width - l) ; ++px) { 1171 for (var py = (y - b) ; py < (y + height - b) ; ++py) { 1172 var offset = (py * w + px) * 4; 1173 for (var j = 0; j < color.length; ++j) { 1174 if (Math.abs(buf[offset + j] - color[j]) > errorRange) { 1175 testFailed(msg); 1176 var was = buf[offset + 0].toString(); 1177 for (j = 1; j < color.length; ++j) { 1178 was += "," + buf[offset + j]; 1179 } 1180 debug('at (' + px + ', ' + py + 1181 ') expected: ' + color + ' was ' + was); 1182 return; 1183 } 1184 } 1185 } 1186 } 1187 testPassed(msg); 1188 } 1189 } 1190 return rect; 1191 }; 1192 1193 /** 1194 * Checks that a portions of a canvas or the currently attached framebuffer is 1 color. 1195 * @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The 1196 * WebGLRenderingContext or 2D context to use. 1197 * @param {!Array.<checkRect>} array of rects to check for matching color. 1198 */ 1199 var checkCanvasRects = function(gl, rects) { 1200 if (rects.length > 0) { 1201 var left = rects[0].x; 1202 var right = rects[0].x + rects[1].width; 1203 var bottom = rects[0].y; 1204 var top = rects[0].y + rects[0].height; 1205 for (var i = 1; i < rects.length; ++i) { 1206 left = Math.min(left, rects[i].x); 1207 right = Math.max(right, rects[i].x + rects[i].width); 1208 bottom = Math.min(bottom, rects[i].y); 1209 top = Math.max(top, rects[i].y + rects[i].height); 1210 } 1211 var width = right - left; 1212 var height = top - bottom; 1213 var buf = new Uint8Array(width * height * 4); 1214 gl.readPixels(left, bottom, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf); 1215 for (var i = 0; i < rects.length; ++i) { 1216 rects[i].checkRect(buf, left, bottom, width); 1217 } 1218 } 1219 }; 1220 1221 /** 1222 * Checks that a portion of a canvas or the currently attached framebuffer is 1 color. 1223 * @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The 1224 * WebGLRenderingContext or 2D context to use. 1225 * @param {number} x left corner of region to check. 1226 * @param {number} y bottom corner of region to check in case of checking from 1227 * a GL context or top corner in case of checking from a 2D context. 1228 * @param {number} width width of region to check. 1229 * @param {number} height width of region to check. 1230 * @param {!Array.<number>} color The color expected. A 4 element array where 1231 * each element is in the range 0 to 255. 1232 * @param {number} opt_errorRange Optional. Acceptable error in 1233 * color checking. 0 by default. 1234 * @param {!function()} sameFn Function to call if all pixels 1235 * are the same as color. 1236 * @param {!function()} differentFn Function to call if a pixel 1237 * is different than color 1238 * @param {!function()} logFn Function to call for logging. 1239 * @param {TypedArray} opt_readBackBuf optional buffer to read back into. 1240 * Typically passed to either reuse buffer, or support readbacks from 1241 * floating-point/norm16 framebuffers. 1242 * @param {GLenum} opt_readBackType optional read back type, defaulting to 1243 * gl.UNSIGNED_BYTE. Can be used to support readback from floating-point 1244 * /norm16 framebuffers. 1245 * @param {GLenum} opt_readBackFormat optional read back format, defaulting to 1246 * gl.RGBA. Can be used to support readback from norm16 1247 * framebuffers. 1248 */ 1249 var checkCanvasRectColor = function(gl, x, y, width, height, color, opt_errorRange, sameFn, differentFn, logFn, opt_readBackBuf, opt_readBackType, opt_readBackFormat) { 1250 if (isWebGLContext(gl) && !gl.getParameter(gl.FRAMEBUFFER_BINDING)) { 1251 // We're reading the backbuffer so clip. 1252 var xr = clipToRange(x, width, 0, gl.canvas.width); 1253 var yr = clipToRange(y, height, 0, gl.canvas.height); 1254 if (!xr.extent || !yr.extent) { 1255 logFn("checking rect: effective width or height is zero"); 1256 sameFn(); 1257 return; 1258 } 1259 x = xr.value; 1260 y = yr.value; 1261 width = xr.extent; 1262 height = yr.extent; 1263 } 1264 var errorRange = opt_errorRange || 0; 1265 if (!errorRange.length) { 1266 errorRange = [errorRange, errorRange, errorRange, errorRange] 1267 } 1268 var buf; 1269 if (isWebGLContext(gl)) { 1270 buf = opt_readBackBuf ? opt_readBackBuf : new Uint8Array(width * height * 4); 1271 var readBackType = opt_readBackType ? opt_readBackType : gl.UNSIGNED_BYTE; 1272 var readBackFormat = opt_readBackFormat ? opt_readBackFormat : gl.RGBA; 1273 gl.readPixels(x, y, width, height, readBackFormat, readBackType, buf); 1274 } else { 1275 buf = gl.getImageData(x, y, width, height).data; 1276 } 1277 for (var i = 0; i < width * height; ++i) { 1278 var offset = i * 4; 1279 for (var j = 0; j < color.length; ++j) { 1280 if (Math.abs(buf[offset + j] - color[j]) > errorRange[j]) { 1281 var was = buf[offset + 0].toString(); 1282 for (j = 1; j < color.length; ++j) { 1283 was += "," + buf[offset + j]; 1284 } 1285 differentFn('at (' + (x + (i % width)) + ', ' + (y + Math.floor(i / width)) + 1286 ') expected: ' + color + ' was ' + was, buf); 1287 return; 1288 } 1289 } 1290 } 1291 sameFn(); 1292 }; 1293 1294 /** 1295 * Checks that a portion of a canvas or the currently attached framebuffer is 1 color. 1296 * @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The 1297 * WebGLRenderingContext or 2D context to use. 1298 * @param {number} x left corner of region to check. 1299 * @param {number} y bottom corner of region to check in case of checking from 1300 * a GL context or top corner in case of checking from a 2D context. 1301 * @param {number} width width of region to check. 1302 * @param {number} height width of region to check. 1303 * @param {!Array.<number>} color The color expected. A 4 element array where 1304 * each element is in the range 0 to 255. 1305 * @param {string} opt_msg Message to associate with success or failure. Eg 1306 * ("should be red"). 1307 * @param {number} opt_errorRange Optional. Acceptable error in 1308 * color checking. 0 by default. 1309 * @param {TypedArray} opt_readBackBuf optional buffer to read back into. 1310 * Typically passed to either reuse buffer, or support readbacks from 1311 * floating-point/norm16 framebuffers. 1312 * @param {GLenum} opt_readBackType optional read back type, defaulting to 1313 * gl.UNSIGNED_BYTE. Can be used to support readback from floating-point 1314 * /norm16 framebuffers. 1315 * @param {GLenum} opt_readBackFormat optional read back format, defaulting to 1316 * gl.RGBA. Can be used to support readback from floating-point 1317 * /norm16 framebuffers. 1318 */ 1319 var checkCanvasRect = function(gl, x, y, width, height, color, opt_msg, opt_errorRange, opt_readBackBuf, opt_readBackType, opt_readBackFormat) { 1320 checkCanvasRectColor( 1321 gl, x, y, width, height, color, opt_errorRange, 1322 function() { 1323 var msg = opt_msg; 1324 if (msg === undefined) 1325 msg = "should be " + color.toString(); 1326 testPassed(msg); 1327 }, 1328 function(differentMsg) { 1329 var msg = opt_msg; 1330 if (msg === undefined) 1331 msg = "should be " + color.toString(); 1332 testFailed(msg + "\n" + differentMsg); 1333 }, 1334 debug, 1335 opt_readBackBuf, 1336 opt_readBackType, 1337 opt_readBackFormat); 1338 }; 1339 1340 /** 1341 * Checks that an entire canvas or the currently attached framebuffer is 1 color. 1342 * @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The 1343 * WebGLRenderingContext or 2D context to use. 1344 * @param {!Array.<number>} color The color expected. A 4 element array where 1345 * each element is in the range 0 to 255. 1346 * @param {string} msg Message to associate with success. Eg ("should be red"). 1347 * @param {number} errorRange Optional. Acceptable error in 1348 * color checking. 0 by default. 1349 */ 1350 var checkCanvas = function(gl, color, msg, errorRange) { 1351 checkCanvasRect(gl, 0, 0, gl.canvas.width, gl.canvas.height, color, msg, errorRange); 1352 }; 1353 1354 /** 1355 * Checks a rectangular area both inside the area and outside 1356 * the area. 1357 * @param {!WebGLRenderingContext|CanvasRenderingContext2D} gl The 1358 * WebGLRenderingContext or 2D context to use. 1359 * @param {number} x left corner of region to check. 1360 * @param {number} y bottom corner of region to check in case of checking from 1361 * a GL context or top corner in case of checking from a 2D context. 1362 * @param {number} width width of region to check. 1363 * @param {number} height width of region to check. 1364 * @param {!Array.<number>} innerColor The color expected inside 1365 * the area. A 4 element array where each element is in the 1366 * range 0 to 255. 1367 * @param {!Array.<number>} outerColor The color expected 1368 * outside. A 4 element array where each element is in the 1369 * range 0 to 255. 1370 * @param {!number} opt_edgeSize: The number of pixels to skip 1371 * around the edges of the area. Defaut 0. 1372 * @param {!{width:number, height:number}} opt_outerDimensions 1373 * The outer dimensions. Default the size of gl.canvas. 1374 */ 1375 var checkAreaInAndOut = function(gl, x, y, width, height, innerColor, outerColor, opt_edgeSize, opt_outerDimensions) { 1376 var outerDimensions = opt_outerDimensions || { width: gl.canvas.width, height: gl.canvas.height }; 1377 var edgeSize = opt_edgeSize || 0; 1378 checkCanvasRect(gl, x + edgeSize, y + edgeSize, width - edgeSize * 2, height - edgeSize * 2, innerColor); 1379 checkCanvasRect(gl, 0, 0, x - edgeSize, outerDimensions.height, outerColor); 1380 checkCanvasRect(gl, x + width + edgeSize, 0, outerDimensions.width - x - width - edgeSize, outerDimensions.height, outerColor); 1381 checkCanvasRect(gl, 0, 0, outerDimensions.width, y - edgeSize, outerColor); 1382 checkCanvasRect(gl, 0, y + height + edgeSize, outerDimensions.width, outerDimensions.height - y - height - edgeSize, outerColor); 1383 }; 1384 1385 /** 1386 * Checks that an entire buffer matches the floating point values provided. 1387 * (WebGL 2.0 only) 1388 * @param {!WebGL2RenderingContext} gl The WebGL2RenderingContext to use. 1389 * @param {number} target The buffer target to bind to. 1390 * @param {!Array.<number>} expected The values expected. 1391 * @param {string} opt_msg Optional. Message to associate with success. Eg ("should be red"). 1392 * @param {number} opt_errorRange Optional. Acceptable error in value checking. 0.001 by default. 1393 */ 1394 var checkFloatBuffer = function(gl, target, expected, opt_msg, opt_errorRange) { 1395 if (opt_msg === undefined) 1396 opt_msg = "buffer should match expected values"; 1397 1398 if (opt_errorRange === undefined) 1399 opt_errorRange = 0.001; 1400 1401 var floatArray = new Float32Array(expected.length); 1402 gl.getBufferSubData(target, 0, floatArray); 1403 1404 for (var i = 0; i < expected.length; i++) { 1405 if (Math.abs(floatArray[i] - expected[i]) > opt_errorRange) { 1406 testFailed(opt_msg); 1407 debug('at [' + i + '] expected: ' + expected[i] + ' was ' + floatArray[i]); 1408 return; 1409 } 1410 } 1411 testPassed(opt_msg); 1412 }; 1413 1414 /** 1415 * Loads a texture, calls callback when finished. 1416 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1417 * @param {string} url URL of image to load 1418 * @param {function(!Image): void} callback Function that gets called after 1419 * image has loaded 1420 * @return {!WebGLTexture} The created texture. 1421 */ 1422 var loadTexture = function(gl, url, callback) { 1423 var texture = gl.createTexture(); 1424 gl.bindTexture(gl.TEXTURE_2D, texture); 1425 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 1426 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 1427 var image = new Image(); 1428 image.onload = function() { 1429 gl.bindTexture(gl.TEXTURE_2D, texture); 1430 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 1431 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 1432 callback(image); 1433 }; 1434 image.src = url; 1435 return texture; 1436 }; 1437 1438 /** 1439 * Checks whether the bound texture has expected dimensions. One corner pixel 1440 * of the texture will be changed as a side effect. 1441 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1442 * @param {!WebGLTexture} texture The texture to check. 1443 * @param {number} width Expected width. 1444 * @param {number} height Expected height. 1445 * @param {GLenum} opt_format The texture's format. Defaults to RGBA. 1446 * @param {GLenum} opt_type The texture's type. Defaults to UNSIGNED_BYTE. 1447 */ 1448 var checkTextureSize = function(gl, width, height, opt_format, opt_type) { 1449 opt_format = opt_format || gl.RGBA; 1450 opt_type = opt_type || gl.UNSIGNED_BYTE; 1451 1452 var numElements = getTypedArrayElementsPerPixel(gl, opt_format, opt_type); 1453 var buf = new (glTypeToTypedArrayType(gl, opt_type))(numElements); 1454 1455 var errors = 0; 1456 gl.texSubImage2D(gl.TEXTURE_2D, 0, width - 1, height - 1, 1, 1, opt_format, opt_type, buf); 1457 if (gl.getError() != gl.NO_ERROR) { 1458 testFailed("Texture was smaller than the expected size " + width + "x" + height); 1459 ++errors; 1460 } 1461 gl.texSubImage2D(gl.TEXTURE_2D, 0, width - 1, height, 1, 1, opt_format, opt_type, buf); 1462 if (gl.getError() == gl.NO_ERROR) { 1463 testFailed("Texture was taller than " + height); 1464 ++errors; 1465 } 1466 gl.texSubImage2D(gl.TEXTURE_2D, 0, width, height - 1, 1, 1, opt_format, opt_type, buf); 1467 if (gl.getError() == gl.NO_ERROR) { 1468 testFailed("Texture was wider than " + width); 1469 ++errors; 1470 } 1471 if (errors == 0) { 1472 testPassed("Texture had the expected size " + width + "x" + height); 1473 } 1474 }; 1475 1476 /** 1477 * Makes a shallow copy of an object. 1478 * @param {!Object} src Object to copy 1479 * @return {!Object} The copy of src. 1480 */ 1481 var shallowCopyObject = function(src) { 1482 var dst = {}; 1483 for (var attr in src) { 1484 if (src.hasOwnProperty(attr)) { 1485 dst[attr] = src[attr]; 1486 } 1487 } 1488 return dst; 1489 }; 1490 1491 /** 1492 * Checks if an attribute exists on an object case insensitive. 1493 * @param {!Object} obj Object to check 1494 * @param {string} attr Name of attribute to look for. 1495 * @return {string?} The name of the attribute if it exists, 1496 * undefined if not. 1497 */ 1498 var hasAttributeCaseInsensitive = function(obj, attr) { 1499 var lower = attr.toLowerCase(); 1500 for (var key in obj) { 1501 if (obj.hasOwnProperty(key) && key.toLowerCase() == lower) { 1502 return key; 1503 } 1504 } 1505 }; 1506 1507 /** 1508 * Returns a map of URL querystring options 1509 * @return {Object?} Object containing all the values in the URL querystring 1510 */ 1511 var getUrlOptions = (function() { 1512 var _urlOptionsParsed = false; 1513 var _urlOptions = {}; 1514 return function() { 1515 if (!_urlOptionsParsed) { 1516 var s = window.location.href; 1517 var q = s.indexOf("?"); 1518 var e = s.indexOf("#"); 1519 if (e < 0) { 1520 e = s.length; 1521 } 1522 var query = s.substring(q + 1, e); 1523 var pairs = query.split("&"); 1524 for (var ii = 0; ii < pairs.length; ++ii) { 1525 var keyValue = pairs[ii].split("="); 1526 var key = keyValue[0]; 1527 var value = decodeURIComponent(keyValue[1]); 1528 _urlOptions[key] = value; 1529 } 1530 _urlOptionsParsed = true; 1531 } 1532 1533 return _urlOptions; 1534 } 1535 })(); 1536 1537 var default3DContextVersion = 1; 1538 1539 /** 1540 * Set the default context version for create3DContext. 1541 * Initially the default version is 1. 1542 * @param {number} Default version of WebGL contexts. 1543 */ 1544 var setDefault3DContextVersion = function(version) { 1545 default3DContextVersion = version; 1546 }; 1547 1548 /** 1549 * Get the default contex version for create3DContext. 1550 * First it looks at the URI option |webglVersion|. If it does not exist, 1551 * then look at the global default3DContextVersion variable. 1552 */ 1553 var getDefault3DContextVersion = function() { 1554 return parseInt(getUrlOptions().webglVersion, 10) || default3DContextVersion; 1555 }; 1556 1557 /** 1558 * Creates a webgl context. 1559 * @param {!Canvas|string} opt_canvas The canvas tag to get 1560 * context from. If one is not passed in one will be 1561 * created. If it's a string it's assumed to be the id of a 1562 * canvas. 1563 * @param {Object} opt_attributes Context attributes. 1564 * @param {!number} opt_version Version of WebGL context to create. 1565 * The default version can be set by calling setDefault3DContextVersion. 1566 * @return {!WebGLRenderingContext} The created context. 1567 */ 1568 var create3DContext = function(opt_canvas, opt_attributes, opt_version) { 1569 if (window.initTestingHarness) { 1570 window.initTestingHarness(); 1571 } 1572 var attributes = shallowCopyObject(opt_attributes || {}); 1573 if (!hasAttributeCaseInsensitive(attributes, "antialias")) { 1574 attributes.antialias = false; 1575 } 1576 1577 const parseString = v => v; 1578 const parseBoolean = v => v.toLowerCase().startsWith('t') || parseFloat(v) > 0; 1579 const params = new URLSearchParams(window.location.search); 1580 for (const [key, parseFn] of Object.entries({ 1581 alpha: parseBoolean, 1582 antialias: parseBoolean, 1583 depth: parseBoolean, 1584 desynchronized: parseBoolean, 1585 failIfMajorPerformanceCaveat: parseBoolean, 1586 powerPreference: parseString, 1587 premultipliedAlpha: parseBoolean, 1588 preserveDrawingBuffer: parseBoolean, 1589 stencil: parseBoolean, 1590 })) { 1591 const value = params.get(key); 1592 if (value) { 1593 const v = parseFn(value); 1594 attributes[key] = v; 1595 debug(`setting context attribute: ${key} = ${v}`); 1596 } 1597 } 1598 1599 if (!opt_version) { 1600 opt_version = getDefault3DContextVersion(); 1601 } 1602 opt_canvas = opt_canvas || document.createElement("canvas"); 1603 if (typeof opt_canvas == 'string') { 1604 opt_canvas = document.getElementById(opt_canvas); 1605 } 1606 var context = null; 1607 1608 var names; 1609 switch (opt_version) { 1610 case 2: 1611 names = ["webgl2"]; break; 1612 default: 1613 names = ["webgl", "experimental-webgl"]; break; 1614 } 1615 1616 for (var i = 0; i < names.length; ++i) { 1617 try { 1618 context = opt_canvas.getContext(names[i], attributes); 1619 } catch (e) { 1620 } 1621 if (context) { 1622 break; 1623 } 1624 } 1625 if (!context) { 1626 testFailed("Unable to fetch WebGL rendering context for Canvas"); 1627 } else { 1628 if (!window._wtu_contexts) { 1629 window._wtu_contexts = [] 1630 } 1631 window._wtu_contexts.push(context); 1632 } 1633 1634 if (params.get('showRenderer')) { 1635 const ext = context.getExtension('WEBGL_debug_renderer_info'); 1636 debug(`RENDERER: ${context.getParameter(ext ? ext.UNMASKED_RENDERER_WEBGL : context.RENDERER)}`); 1637 } 1638 1639 return context; 1640 }; 1641 1642 /** 1643 * Indicates whether the given context is WebGL 2.0 or greater. 1644 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1645 * @return {boolean} True if the given context is WebGL 2.0 or greater. 1646 */ 1647 var isWebGL2 = function(gl) { 1648 // Duck typing is used so that the conformance suite can be run 1649 // against libraries emulating WebGL 1.0 on top of WebGL 2.0. 1650 return !!gl.drawArraysInstanced; 1651 }; 1652 1653 /** 1654 * Defines the exception type for a GL error. 1655 * @constructor 1656 * @param {string} message The error message. 1657 * @param {number} error GL error code 1658 */ 1659 function GLErrorException (message, error) { 1660 this.message = message; 1661 this.name = "GLErrorException"; 1662 this.error = error; 1663 }; 1664 1665 /** 1666 * Wraps a WebGL function with a function that throws an exception if there is 1667 * an error. 1668 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1669 * @param {string} fname Name of function to wrap. 1670 * @return {function()} The wrapped function. 1671 */ 1672 var createGLErrorWrapper = function(context, fname) { 1673 return function() { 1674 var rv = context[fname].apply(context, arguments); 1675 var err = context.getError(); 1676 if (err != context.NO_ERROR) { 1677 var msg = "GL error " + glEnumToString(context, err) + " in " + fname; 1678 throw new GLErrorException(msg, err); 1679 } 1680 return rv; 1681 }; 1682 }; 1683 1684 /** 1685 * Creates a WebGL context where all functions are wrapped to throw an exception 1686 * if there is an error. 1687 * @param {!Canvas} canvas The HTML canvas to get a context from. 1688 * @param {Object} opt_attributes Context attributes. 1689 * @param {!number} opt_version Version of WebGL context to create 1690 * @return {!Object} The wrapped context. 1691 */ 1692 function create3DContextWithWrapperThatThrowsOnGLError(canvas, opt_attributes, opt_version) { 1693 var context = create3DContext(canvas, opt_attributes, opt_version); 1694 var wrap = {}; 1695 for (var i in context) { 1696 try { 1697 if (typeof context[i] == 'function') { 1698 wrap[i] = createGLErrorWrapper(context, i); 1699 } else { 1700 wrap[i] = context[i]; 1701 } 1702 } catch (e) { 1703 error("createContextWrapperThatThrowsOnGLError: Error accessing " + i); 1704 } 1705 } 1706 wrap.getError = function() { 1707 return context.getError(); 1708 }; 1709 return wrap; 1710 }; 1711 1712 /** 1713 * Tests that an evaluated expression generates a specific GL error. 1714 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1715 * @param {number|Array.<number>} glErrors The expected gl error or an array of expected errors. 1716 * @param {string} evalStr The string to evaluate. 1717 */ 1718 var shouldGenerateGLError = function(gl, glErrors, evalStr, opt_msg) { 1719 var exception; 1720 try { 1721 eval(evalStr); 1722 } catch (e) { 1723 exception = e; 1724 } 1725 if (exception) { 1726 testFailed(evalStr + " threw exception " + exception); 1727 return -1; 1728 } else { 1729 if (!opt_msg) { 1730 opt_msg = "after evaluating: " + evalStr; 1731 } 1732 return glErrorShouldBe(gl, glErrors, opt_msg); 1733 } 1734 }; 1735 1736 /** 1737 * Tests that an evaluated expression does not generate a GL error. 1738 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1739 * @param {string} evalStr The string to evaluate. 1740 */ 1741 var failIfGLError = function(gl, evalStr) { 1742 var exception; 1743 try { 1744 eval(evalStr); 1745 } catch (e) { 1746 exception = e; 1747 } 1748 if (exception) { 1749 testFailed(evalStr + " threw exception " + exception); 1750 } else { 1751 glErrorShouldBeImpl(gl, gl.NO_ERROR, false, "after evaluating: " + evalStr); 1752 } 1753 }; 1754 1755 /** 1756 * Tests that the first error GL returns is the specified error. 1757 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1758 * @param {number|Array.<number>} glErrors The expected gl error or an array of expected errors. 1759 * @param {string} opt_msg Optional additional message. 1760 */ 1761 var glErrorShouldBe = function(gl, glErrors, opt_msg) { 1762 return glErrorShouldBeImpl(gl, glErrors, true, opt_msg); 1763 }; 1764 1765 const glErrorAssert = function(gl, glErrors, opt_msg) { 1766 return glErrorShouldBeImpl(gl, glErrors, false, opt_msg); 1767 }; 1768 1769 /** 1770 * Tests that the given framebuffer has a specific status 1771 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1772 * @param {number|Array.<number>} glStatuses The expected gl 1773 * status or an array of expected statuses. 1774 * @param {string} opt_msg Optional additional message. 1775 */ 1776 var framebufferStatusShouldBe = function(gl, target, glStatuses, opt_msg) { 1777 if (!glStatuses.length) { 1778 glStatuses = [glStatuses]; 1779 } 1780 opt_msg = opt_msg || ""; 1781 const status = gl.checkFramebufferStatus(target); 1782 const ndx = glStatuses.indexOf(status); 1783 const expected = glStatuses.map((status) => { 1784 return glEnumToString(gl, status); 1785 }).join(' or '); 1786 if (ndx < 0) { 1787 let msg = "checkFramebufferStatus expected" + ((glStatuses.length > 1) ? " one of: " : ": ") + 1788 expected + ". Was " + glEnumToString(gl, status); 1789 if (opt_msg) { 1790 msg += ": " + opt_msg; 1791 } 1792 testFailed(msg); 1793 return false; 1794 } 1795 let msg = `checkFramebufferStatus was ${glEnumToString(gl, status)}`; 1796 if (glStatuses.length > 1) { 1797 msg += `, one of: ${expected}`; 1798 } 1799 if (opt_msg) { 1800 msg += ": " + opt_msg; 1801 } 1802 testPassed(msg); 1803 return [status]; 1804 } 1805 1806 /** 1807 * Tests that the first error GL returns is the specified error. Allows suppression of successes. 1808 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1809 * @param {number|Array.<number>} glErrors The expected gl error or an array of expected errors. 1810 * @param {boolean} reportSuccesses Whether to report successes as passes, or to silently pass. 1811 * @param {string} opt_msg Optional additional message. 1812 */ 1813 var glErrorShouldBeImpl = function(gl, glErrors, reportSuccesses, opt_msg) { 1814 if (!glErrors.length) { 1815 glErrors = [glErrors]; 1816 } 1817 opt_msg = opt_msg || ""; 1818 1819 const fnErrStr = function(errVal) { 1820 if (errVal == 0) return "NO_ERROR"; 1821 return glEnumToString(gl, errVal); 1822 }; 1823 1824 var err = gl.getError(); 1825 var ndx = glErrors.indexOf(err); 1826 var errStrs = []; 1827 for (var ii = 0; ii < glErrors.length; ++ii) { 1828 errStrs.push(fnErrStr(glErrors[ii])); 1829 } 1830 var expected = errStrs.join(" or "); 1831 if (ndx < 0) { 1832 var msg = "getError expected" + ((glErrors.length > 1) ? " one of: " : ": "); 1833 testFailed(msg + expected + ". Was " + fnErrStr(err) + " : " + opt_msg); 1834 } else if (reportSuccesses) { 1835 var msg = "getError was " + ((glErrors.length > 1) ? "one of: " : "expected value: "); 1836 testPassed(msg + expected + " : " + opt_msg); 1837 } 1838 return err; 1839 }; 1840 1841 /** 1842 * Tests that a function throws or not. 1843 * @param {!WebGLContext} gl The WebGLContext to use. 1844 * @param throwType Type of thrown error (e.g. TypeError), or false. 1845 * @param {string} info Info on what's being tested 1846 * @param {function} func The func to test. 1847 */ 1848 var shouldThrow = function(gl, throwType, info, func) { 1849 while (gl.getError()) {} 1850 1851 var shouldThrow = (throwType != false); 1852 1853 try { 1854 func(); 1855 1856 if (shouldThrow) { 1857 testFailed("Should throw a " + throwType.name + ": " + info); 1858 } else { 1859 testPassed("Should not have thrown: " + info); 1860 } 1861 } catch (e) { 1862 if (shouldThrow) { 1863 if (e instanceof throwType) { 1864 testPassed("Should throw a " + throwType.name + ": " + info); 1865 } else { 1866 testFailed("Should throw a " + throwType.name + ", threw " + e.name + ": " + info); 1867 } 1868 } else { 1869 testFailed("Should not have thrown: " + info); 1870 } 1871 1872 if (gl.getError()) { 1873 testFailed("Should not generate an error when throwing: " + info); 1874 } 1875 } 1876 1877 while (gl.getError()) {} 1878 }; 1879 1880 /** 1881 * Links a WebGL program, throws if there are errors. 1882 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 1883 * @param {!WebGLProgram} program The WebGLProgram to link. 1884 * @param {function(string): void} opt_errorCallback callback for errors. 1885 */ 1886 var linkProgram = function(gl, program, opt_errorCallback) { 1887 var errFn = opt_errorCallback || testFailed; 1888 // Link the program 1889 gl.linkProgram(program); 1890 1891 // Check the link status 1892 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); 1893 if (!linked) { 1894 // something went wrong with the link 1895 var error = gl.getProgramInfoLog (program); 1896 1897 errFn("Error in program linking:" + error); 1898 1899 gl.deleteProgram(program); 1900 } 1901 }; 1902 1903 /** 1904 * Loads text from an external file. This function is asynchronous. 1905 * @param {string} url The url of the external file. 1906 * @param {!function(bool, string): void} callback that is sent a bool for 1907 * success and the string. 1908 */ 1909 var loadTextFileAsync = function(url, callback) { 1910 log ("loading: " + url); 1911 var error = 'loadTextFileAsync failed to load url "' + url + '"'; 1912 var request; 1913 if (window.XMLHttpRequest) { 1914 request = new XMLHttpRequest(); 1915 if (request.overrideMimeType) { 1916 request.overrideMimeType('text/plain'); 1917 } 1918 } else { 1919 throw 'XMLHttpRequest is disabled'; 1920 } 1921 try { 1922 request.open('GET', url, true); 1923 request.onreadystatechange = function() { 1924 if (request.readyState == 4) { 1925 var text = ''; 1926 // HTTP reports success with a 200 status. The file protocol reports 1927 // success with zero. HTTP does not use zero as a status code (they 1928 // start at 100). 1929 // https://developer.mozilla.org/En/Using_XMLHttpRequest 1930 var success = request.status == 200 || request.status == 0; 1931 if (success) { 1932 text = request.responseText; 1933 log("completed load request: " + url); 1934 } else { 1935 log("loading " + url + " resulted in unexpected status: " + request.status + " " + request.statusText); 1936 } 1937 callback(success, text); 1938 } 1939 }; 1940 request.onerror = function(errorEvent) { 1941 log("error occurred loading " + url); 1942 callback(false, ''); 1943 }; 1944 request.send(null); 1945 } catch (err) { 1946 log("failed to load: " + url + " with exception " + err.message); 1947 callback(false, ''); 1948 } 1949 }; 1950 1951 /** 1952 * Recursively loads a file as a list. Each line is parsed for a relative 1953 * path. If the file ends in .txt the contents of that file is inserted in 1954 * the list. 1955 * 1956 * @param {string} url The url of the external file. 1957 * @param {!function(bool, Array<string>): void} callback that is sent a bool 1958 * for success and the array of strings. 1959 */ 1960 var getFileListAsync = function(url, callback) { 1961 var files = []; 1962 1963 var getFileListImpl = function(url, callback) { 1964 var files = []; 1965 if (url.substr(url.length - 4) == '.txt') { 1966 loadTextFileAsync(url, function() { 1967 return function(success, text) { 1968 if (!success) { 1969 callback(false, ''); 1970 return; 1971 } 1972 var lines = text.split('\n'); 1973 var prefix = ''; 1974 var lastSlash = url.lastIndexOf('/'); 1975 if (lastSlash >= 0) { 1976 prefix = url.substr(0, lastSlash + 1); 1977 } 1978 var fail = false; 1979 var count = 1; 1980 var index = 0; 1981 for (var ii = 0; ii < lines.length; ++ii) { 1982 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 1983 if (str.length > 4 && 1984 str[0] != '#' && 1985 str[0] != ";" && 1986 str.substr(0, 2) != "//") { 1987 var names = str.split(/ +/); 1988 var new_url = prefix + str; 1989 if (names.length == 1) { 1990 new_url = prefix + str; 1991 ++count; 1992 getFileListImpl(new_url, function(index) { 1993 return function(success, new_files) { 1994 log("got files: " + new_files.length); 1995 if (success) { 1996 files[index] = new_files; 1997 } 1998 finish(success); 1999 }; 2000 }(index++)); 2001 } else { 2002 var s = ""; 2003 var p = ""; 2004 for (var jj = 0; jj < names.length; ++jj) { 2005 s += p + prefix + names[jj]; 2006 p = " "; 2007 } 2008 files[index++] = s; 2009 } 2010 } 2011 } 2012 finish(true); 2013 2014 function finish(success) { 2015 if (!success) { 2016 fail = true; 2017 } 2018 --count; 2019 log("count: " + count); 2020 if (!count) { 2021 callback(!fail, files); 2022 } 2023 } 2024 } 2025 }()); 2026 2027 } else { 2028 files.push(url); 2029 callback(true, files); 2030 } 2031 }; 2032 2033 getFileListImpl(url, function(success, files) { 2034 // flatten 2035 var flat = []; 2036 flatten(files); 2037 function flatten(files) { 2038 for (var ii = 0; ii < files.length; ++ii) { 2039 var value = files[ii]; 2040 if (typeof(value) == "string") { 2041 flat.push(value); 2042 } else { 2043 flatten(value); 2044 } 2045 } 2046 } 2047 callback(success, flat); 2048 }); 2049 }; 2050 2051 /** 2052 * Gets a file from a file/URL. 2053 * @param {string} file the URL of the file to get. 2054 * @return {string} The contents of the file. 2055 */ 2056 var readFile = function(file) { 2057 var xhr = new XMLHttpRequest(); 2058 xhr.open("GET", file, false); 2059 xhr.overrideMimeType("text/plain"); 2060 xhr.send(); 2061 return xhr.responseText.replace(/\r/g, ""); 2062 }; 2063 2064 var readFileList = function(url) { 2065 var files = []; 2066 if (url.substr(url.length - 4) == '.txt') { 2067 var lines = readFile(url).split('\n'); 2068 var prefix = ''; 2069 var lastSlash = url.lastIndexOf('/'); 2070 if (lastSlash >= 0) { 2071 prefix = url.substr(0, lastSlash + 1); 2072 } 2073 for (var ii = 0; ii < lines.length; ++ii) { 2074 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 2075 if (str.length > 4 && 2076 str[0] != '#' && 2077 str[0] != ";" && 2078 str.substr(0, 2) != "//") { 2079 var names = str.split(/ +/); 2080 if (names.length == 1) { 2081 var new_url = prefix + str; 2082 files = files.concat(readFileList(new_url)); 2083 } else { 2084 var s = ""; 2085 var p = ""; 2086 for (var jj = 0; jj < names.length; ++jj) { 2087 s += p + prefix + names[jj]; 2088 p = " "; 2089 } 2090 files.push(s); 2091 } 2092 } 2093 } 2094 } else { 2095 files.push(url); 2096 } 2097 return files; 2098 }; 2099 2100 /** 2101 * Loads a shader. 2102 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2103 * @param {string} shaderSource The shader source. 2104 * @param {number} shaderType The type of shader. 2105 * @param {function(string): void} opt_errorCallback callback for errors. 2106 * @param {boolean} opt_logShaders Whether to log shader source. 2107 * @param {string} opt_shaderLabel Label that identifies the shader source in 2108 * the log. 2109 * @param {string} opt_url URL from where the shader source was loaded from. 2110 * If opt_logShaders is set, then a link to the source file will also be 2111 * added. 2112 * @param {boolean} Skip compilation status check. Default = false. 2113 * @return {!WebGLShader} The created shader. 2114 */ 2115 var loadShader = function( 2116 gl, shaderSource, shaderType, opt_errorCallback, opt_logShaders, 2117 opt_shaderLabel, opt_url, opt_skipCompileStatus) { 2118 var errFn = opt_errorCallback || error; 2119 // Create the shader object 2120 var shader = gl.createShader(shaderType); 2121 if (shader == null) { 2122 errFn("*** Error: unable to create shader '"+shaderSource+"'"); 2123 return null; 2124 } 2125 2126 // Load the shader source 2127 gl.shaderSource(shader, shaderSource); 2128 2129 // Compile the shader 2130 gl.compileShader(shader); 2131 2132 if (opt_logShaders) { 2133 var label = shaderType == gl.VERTEX_SHADER ? 'vertex shader' : 'fragment_shader'; 2134 if (opt_shaderLabel) { 2135 label = opt_shaderLabel + ' ' + label; 2136 } 2137 addShaderSources( 2138 gl, document.getElementById('console'), label, shader, shaderSource, opt_url); 2139 } 2140 2141 // Check the compile status 2142 if (!opt_skipCompileStatus) { 2143 var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 2144 if (!compiled) { 2145 // Something went wrong during compilation; get the error 2146 lastError = gl.getShaderInfoLog(shader); 2147 errFn("*** Error compiling " + glEnumToString(gl, shaderType) + " '" + shader + "':" + lastError); 2148 gl.deleteShader(shader); 2149 return null; 2150 } 2151 } 2152 2153 return shader; 2154 } 2155 2156 /** 2157 * Loads a shader from a URL. 2158 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2159 * @param {file} file The URL of the shader source. 2160 * @param {number} type The type of shader. 2161 * @param {function(string): void} opt_errorCallback callback for errors. 2162 * @param {boolean} opt_logShaders Whether to log shader source. 2163 * @param {boolean} Skip compilation status check. Default = false. 2164 * @return {!WebGLShader} The created shader. 2165 */ 2166 var loadShaderFromFile = function( 2167 gl, file, type, opt_errorCallback, opt_logShaders, opt_skipCompileStatus) { 2168 var shaderSource = readFile(file); 2169 return loadShader(gl, shaderSource, type, opt_errorCallback, 2170 opt_logShaders, undefined, file, opt_skipCompileStatus); 2171 }; 2172 2173 var loadShaderFromFileAsync = function( 2174 gl, file, type, opt_errorCallback, opt_logShaders, opt_skipCompileStatus, callback) { 2175 loadTextFileAsync(file, function(gl, type, opt_errorCallback, opt_logShaders, file, opt_skipCompileStatus){ 2176 return function(success, shaderSource) { 2177 if (success) { 2178 var shader = loadShader(gl, shaderSource, type, opt_errorCallback, 2179 opt_logShaders, undefined, file, opt_skipCompileStatus); 2180 callback(true, shader); 2181 } else { 2182 callback(false, null); 2183 } 2184 } 2185 }(gl, type, opt_errorCallback, opt_logShaders, file, opt_skipCompileStatus)); 2186 }; 2187 2188 /** 2189 * Gets the content of script. 2190 * @param {string} scriptId The id of the script tag. 2191 * @return {string} The content of the script. 2192 */ 2193 var getScript = function(scriptId) { 2194 var shaderScript = document.getElementById(scriptId); 2195 if (!shaderScript) { 2196 throw("*** Error: unknown script element " + scriptId); 2197 } 2198 return shaderScript.text; 2199 }; 2200 2201 /** 2202 * Loads a shader from a script tag. 2203 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2204 * @param {string} scriptId The id of the script tag. 2205 * @param {number} opt_shaderType The type of shader. If not passed in it will 2206 * be derived from the type of the script tag. 2207 * @param {function(string): void} opt_errorCallback callback for errors. 2208 * @param {boolean} opt_logShaders Whether to log shader source. 2209 * @param {boolean} Skip compilation status check. Default = false. 2210 * @return {!WebGLShader} The created shader. 2211 */ 2212 var loadShaderFromScript = function( 2213 gl, scriptId, opt_shaderType, opt_errorCallback, opt_logShaders, opt_skipCompileStatus) { 2214 var shaderSource = ""; 2215 var shaderScript = document.getElementById(scriptId); 2216 if (!shaderScript) { 2217 throw("*** Error: unknown script element " + scriptId); 2218 } 2219 shaderSource = shaderScript.text.trim(); 2220 2221 if (!opt_shaderType) { 2222 if (shaderScript.type == "x-shader/x-vertex") { 2223 opt_shaderType = gl.VERTEX_SHADER; 2224 } else if (shaderScript.type == "x-shader/x-fragment") { 2225 opt_shaderType = gl.FRAGMENT_SHADER; 2226 } else { 2227 throw("*** Error: unknown shader type"); 2228 return null; 2229 } 2230 } 2231 2232 return loadShader(gl, shaderSource, opt_shaderType, opt_errorCallback, 2233 opt_logShaders, undefined, undefined, opt_skipCompileStatus); 2234 }; 2235 2236 var loadStandardProgram = function(gl) { 2237 var program = gl.createProgram(); 2238 gl.attachShader(program, loadStandardVertexShader(gl)); 2239 gl.attachShader(program, loadStandardFragmentShader(gl)); 2240 gl.bindAttribLocation(program, 0, "a_vertex"); 2241 gl.bindAttribLocation(program, 1, "a_normal"); 2242 linkProgram(gl, program); 2243 return program; 2244 }; 2245 2246 var loadStandardProgramAsync = function(gl, callback) { 2247 loadStandardVertexShaderAsync(gl, function(gl) { 2248 return function(success, vs) { 2249 if (success) { 2250 loadStandardFragmentShaderAsync(gl, function(vs) { 2251 return function(success, fs) { 2252 if (success) { 2253 var program = gl.createProgram(); 2254 gl.attachShader(program, vs); 2255 gl.attachShader(program, fs); 2256 gl.bindAttribLocation(program, 0, "a_vertex"); 2257 gl.bindAttribLocation(program, 1, "a_normal"); 2258 linkProgram(gl, program); 2259 callback(true, program); 2260 } else { 2261 callback(false, null); 2262 } 2263 }; 2264 }(vs)); 2265 } else { 2266 callback(false, null); 2267 } 2268 }; 2269 }(gl)); 2270 }; 2271 2272 /** 2273 * Loads shaders from files, creates a program, attaches the shaders and links. 2274 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2275 * @param {string} vertexShaderPath The URL of the vertex shader. 2276 * @param {string} fragmentShaderPath The URL of the fragment shader. 2277 * @param {function(string): void} opt_errorCallback callback for errors. 2278 * @return {!WebGLProgram} The created program. 2279 */ 2280 var loadProgramFromFile = function( 2281 gl, vertexShaderPath, fragmentShaderPath, opt_errorCallback) { 2282 var program = gl.createProgram(); 2283 var vs = loadShaderFromFile( 2284 gl, vertexShaderPath, gl.VERTEX_SHADER, opt_errorCallback); 2285 var fs = loadShaderFromFile( 2286 gl, fragmentShaderPath, gl.FRAGMENT_SHADER, opt_errorCallback); 2287 if (vs && fs) { 2288 gl.attachShader(program, vs); 2289 gl.attachShader(program, fs); 2290 linkProgram(gl, program, opt_errorCallback); 2291 } 2292 if (vs) { 2293 gl.deleteShader(vs); 2294 } 2295 if (fs) { 2296 gl.deleteShader(fs); 2297 } 2298 return program; 2299 }; 2300 2301 /** 2302 * Loads shaders from script tags, creates a program, attaches the shaders and 2303 * links. 2304 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2305 * @param {string} vertexScriptId The id of the script tag that contains the 2306 * vertex shader. 2307 * @param {string} fragmentScriptId The id of the script tag that contains the 2308 * fragment shader. 2309 * @param {function(string): void} opt_errorCallback callback for errors. 2310 * @return {!WebGLProgram} The created program. 2311 */ 2312 var loadProgramFromScript = function loadProgramFromScript( 2313 gl, vertexScriptId, fragmentScriptId, opt_errorCallback) { 2314 var program = gl.createProgram(); 2315 gl.attachShader( 2316 program, 2317 loadShaderFromScript( 2318 gl, vertexScriptId, gl.VERTEX_SHADER, opt_errorCallback)); 2319 gl.attachShader( 2320 program, 2321 loadShaderFromScript( 2322 gl, fragmentScriptId, gl.FRAGMENT_SHADER, opt_errorCallback)); 2323 linkProgram(gl, program, opt_errorCallback); 2324 return program; 2325 }; 2326 2327 /** 2328 * Loads shaders from source, creates a program, attaches the shaders and 2329 * links. 2330 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2331 * @param {!WebGLShader} vertexShader The vertex shader. 2332 * @param {!WebGLShader} fragmentShader The fragment shader. 2333 * @param {function(string): void} opt_errorCallback callback for errors. 2334 * @return {!WebGLProgram} The created program. 2335 */ 2336 var createProgram = function(gl, vertexShader, fragmentShader, opt_errorCallback) { 2337 var program = gl.createProgram(); 2338 gl.attachShader(program, vertexShader); 2339 gl.attachShader(program, fragmentShader); 2340 linkProgram(gl, program, opt_errorCallback); 2341 return program; 2342 }; 2343 2344 /** 2345 * Loads shaders from source, creates a program, attaches the shaders and 2346 * links. 2347 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2348 * @param {string} vertexShader The vertex shader source. 2349 * @param {string} fragmentShader The fragment shader source. 2350 * @param {function(string): void} opt_errorCallback callback for errors. 2351 * @param {boolean} opt_logShaders Whether to log shader source. 2352 * @return {!WebGLProgram} The created program. 2353 */ 2354 var loadProgram = function( 2355 gl, vertexShader, fragmentShader, opt_errorCallback, opt_logShaders) { 2356 var program; 2357 var vs = loadShader( 2358 gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback, opt_logShaders); 2359 var fs = loadShader( 2360 gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback, opt_logShaders); 2361 if (vs && fs) { 2362 program = createProgram(gl, vs, fs, opt_errorCallback) 2363 } 2364 if (vs) { 2365 gl.deleteShader(vs); 2366 } 2367 if (fs) { 2368 gl.deleteShader(fs); 2369 } 2370 return program; 2371 }; 2372 2373 /** 2374 * Loads shaders from source, creates a program, attaches the shaders and 2375 * links but expects error. 2376 * 2377 * GLSL 1.0.17 10.27 effectively says that compileShader can 2378 * always succeed as long as linkProgram fails so we can't 2379 * rely on compileShader failing. This function expects 2380 * one of the shader to fail OR linking to fail. 2381 * 2382 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2383 * @param {string} vertexShaderScriptId The vertex shader. 2384 * @param {string} fragmentShaderScriptId The fragment shader. 2385 * @return {WebGLProgram} The created program. 2386 */ 2387 var loadProgramFromScriptExpectError = function( 2388 gl, vertexShaderScriptId, fragmentShaderScriptId) { 2389 var vertexShader = loadShaderFromScript(gl, vertexShaderScriptId); 2390 if (!vertexShader) { 2391 return null; 2392 } 2393 var fragmentShader = loadShaderFromScript(gl, fragmentShaderScriptId); 2394 if (!fragmentShader) { 2395 return null; 2396 } 2397 var linkSuccess = true; 2398 var program = gl.createProgram(); 2399 gl.attachShader(program, vertexShader); 2400 gl.attachShader(program, fragmentShader); 2401 linkSuccess = true; 2402 linkProgram(gl, program, function() { 2403 linkSuccess = false; 2404 }); 2405 return linkSuccess ? program : null; 2406 }; 2407 2408 2409 var getActiveMap = function(gl, program, typeInfo) { 2410 var numVariables = gl.getProgramParameter(program, gl[typeInfo.param]); 2411 var variables = {}; 2412 for (var ii = 0; ii < numVariables; ++ii) { 2413 var info = gl[typeInfo.activeFn](program, ii); 2414 variables[info.name] = { 2415 name: info.name, 2416 size: info.size, 2417 type: info.type, 2418 location: gl[typeInfo.locFn](program, info.name) 2419 }; 2420 } 2421 return variables; 2422 }; 2423 2424 /** 2425 * Returns a map of attrib names to info about those 2426 * attribs. 2427 * 2428 * eg: 2429 * { "attrib1Name": 2430 * { 2431 * name: "attrib1Name", 2432 * size: 1, 2433 * type: gl.FLOAT_MAT2, 2434 * location: 0 2435 * }, 2436 * "attrib2Name[0]": 2437 * { 2438 * name: "attrib2Name[0]", 2439 * size: 4, 2440 * type: gl.FLOAT, 2441 * location: 1 2442 * }, 2443 * } 2444 * 2445 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2446 * @param {WebGLProgram} The program to query for attribs. 2447 * @return the map. 2448 */ 2449 var getAttribMap = function(gl, program) { 2450 return getActiveMap(gl, program, { 2451 param: "ACTIVE_ATTRIBUTES", 2452 activeFn: "getActiveAttrib", 2453 locFn: "getAttribLocation" 2454 }); 2455 }; 2456 2457 /** 2458 * Returns a map of uniform names to info about those uniforms. 2459 * 2460 * eg: 2461 * { "uniform1Name": 2462 * { 2463 * name: "uniform1Name", 2464 * size: 1, 2465 * type: gl.FLOAT_MAT2, 2466 * location: WebGLUniformLocation 2467 * }, 2468 * "uniform2Name[0]": 2469 * { 2470 * name: "uniform2Name[0]", 2471 * size: 4, 2472 * type: gl.FLOAT, 2473 * location: WebGLUniformLocation 2474 * }, 2475 * } 2476 * 2477 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use. 2478 * @param {WebGLProgram} The program to query for uniforms. 2479 * @return the map. 2480 */ 2481 var getUniformMap = function(gl, program) { 2482 return getActiveMap(gl, program, { 2483 param: "ACTIVE_UNIFORMS", 2484 activeFn: "getActiveUniform", 2485 locFn: "getUniformLocation" 2486 }); 2487 }; 2488 2489 var basePath; 2490 var getResourcePath = function() { 2491 if (!basePath) { 2492 var expectedBase = "js/webgl-test-utils.js"; 2493 var scripts = document.getElementsByTagName('script'); 2494 for (var script, i = 0; script = scripts[i]; i++) { 2495 var src = script.src; 2496 var l = src.length; 2497 if (src.substr(l - expectedBase.length) == expectedBase) { 2498 basePath = src.substr(0, l - expectedBase.length); 2499 } 2500 } 2501 } 2502 return basePath + "resources/"; 2503 }; 2504 2505 var loadStandardVertexShader = function(gl) { 2506 return loadShaderFromFile( 2507 gl, getResourcePath() + "vertexShader.vert", gl.VERTEX_SHADER); 2508 }; 2509 var loadStandardVertexShaderAsync = function(gl, callback) { 2510 loadShaderFromFileAsync(gl, getResourcePath() + "vertexShader.vert", gl.VERTEX_SHADER, undefined, undefined, undefined, callback); 2511 }; 2512 2513 var loadStandardFragmentShader = function(gl) { 2514 return loadShaderFromFile( 2515 gl, getResourcePath() + "fragmentShader.frag", gl.FRAGMENT_SHADER); 2516 }; 2517 var loadStandardFragmentShaderAsync = function(gl, callback) { 2518 loadShaderFromFileAsync(gl, getResourcePath() + "fragmentShader.frag", gl.FRAGMENT_SHADER, undefined, undefined, undefined, callback); 2519 }; 2520 2521 var loadUniformBlockProgram = function(gl) { 2522 var program = gl.createProgram(); 2523 gl.attachShader(program, loadUniformBlockVertexShader(gl)); 2524 gl.attachShader(program, loadUniformBlockFragmentShader(gl)); 2525 gl.bindAttribLocation(program, 0, "a_vertex"); 2526 gl.bindAttribLocation(program, 1, "a_normal"); 2527 linkProgram(gl, program); 2528 return program; 2529 }; 2530 2531 var loadUniformBlockVertexShader = function(gl) { 2532 return loadShaderFromFile( 2533 gl, getResourcePath() + "uniformBlockShader.vert", gl.VERTEX_SHADER); 2534 }; 2535 2536 var loadUniformBlockFragmentShader = function(gl) { 2537 return loadShaderFromFile( 2538 gl, getResourcePath() + "uniformBlockShader.frag", gl.FRAGMENT_SHADER); 2539 }; 2540 2541 /** 2542 * Loads an image asynchronously. 2543 * @param {string} url URL of image to load. 2544 * @param {!function(!Element): void} callback Function to call 2545 * with loaded image. 2546 */ 2547 var loadImageAsync = function(url, callback) { 2548 var img = document.createElement('img'); 2549 img.onload = function() { 2550 callback(img); 2551 }; 2552 img.src = url; 2553 }; 2554 2555 /** 2556 * Loads an array of images. 2557 * @param {!Array.<string>} urls URLs of images to load. 2558 * @param {!function(!{string, img}): void} callback Callback 2559 * that gets passed map of urls to img tags. 2560 */ 2561 var loadImagesAsync = function(urls, callback) { 2562 var count = 1; 2563 var images = { }; 2564 function countDown() { 2565 --count; 2566 if (count == 0) { 2567 log("loadImagesAsync: all images loaded"); 2568 callback(images); 2569 } 2570 } 2571 function imageLoaded(url) { 2572 return function(img) { 2573 images[url] = img; 2574 log("loadImagesAsync: loaded " + url); 2575 countDown(); 2576 } 2577 } 2578 for (var ii = 0; ii < urls.length; ++ii) { 2579 ++count; 2580 loadImageAsync(urls[ii], imageLoaded(urls[ii])); 2581 } 2582 countDown(); 2583 }; 2584 2585 /** 2586 * Returns a map of key=value values from url. 2587 * @return {!Object.<string, number>} map of keys to values. 2588 */ 2589 var getUrlArguments = function() { 2590 var args = {}; 2591 try { 2592 var s = window.location.href; 2593 var q = s.indexOf("?"); 2594 var e = s.indexOf("#"); 2595 if (e < 0) { 2596 e = s.length; 2597 } 2598 var query = s.substring(q + 1, e); 2599 var pairs = query.split("&"); 2600 for (var ii = 0; ii < pairs.length; ++ii) { 2601 var keyValue = pairs[ii].split("="); 2602 var key = keyValue[0]; 2603 var value = decodeURIComponent(keyValue[1]); 2604 args[key] = value; 2605 } 2606 } catch (e) { 2607 throw "could not parse url"; 2608 } 2609 return args; 2610 }; 2611 2612 /** 2613 * Makes an image from a src. 2614 * @param {string} src Image source URL. 2615 * @param {function()} onload Callback to call when the image has finised loading. 2616 * @param {function()} onerror Callback to call when an error occurs. 2617 * @return {!Image} The created image. 2618 */ 2619 var makeImage = function(src, onload, onerror) { 2620 var img = document.createElement('img'); 2621 if (onload) { 2622 img.onload = onload; 2623 } 2624 if (onerror) { 2625 img.onerror = onerror; 2626 } else { 2627 img.onerror = function() { 2628 log("WARNING: creating image failed; src: " + this.src); 2629 }; 2630 } 2631 if (src) { 2632 img.src = src; 2633 } 2634 return img; 2635 } 2636 2637 /** 2638 * Makes an image element from a canvas. 2639 * @param {!HTMLCanvas} canvas Canvas to make image from. 2640 * @param {function()} onload Callback to call when the image has finised loading. 2641 * @param {string} imageFormat Image format to be passed to toDataUrl(). 2642 * @return {!Image} The created image. 2643 */ 2644 var makeImageFromCanvas = function(canvas, onload, imageFormat) { 2645 return makeImage(canvas.toDataURL(imageFormat), onload); 2646 }; 2647 2648 /** 2649 * Makes a video element from a src. 2650 * @param {string} src Video source URL. 2651 * @param {function()} onerror Callback to call when an error occurs. 2652 * @return {!Video} The created video. 2653 */ 2654 var makeVideo = function(src, onerror) { 2655 var vid = document.createElement('video'); 2656 vid.muted = true; 2657 if (onerror) { 2658 vid.onerror = onerror; 2659 } else { 2660 vid.onerror = function() { 2661 log("WARNING: creating video failed; src: " + this.src); 2662 }; 2663 } 2664 if (src) { 2665 vid.src = src; 2666 } 2667 return vid; 2668 } 2669 2670 /** 2671 * Inserts an image with a caption into 'element'. 2672 * @param {!HTMLElement} element Element to append image to. 2673 * @param {string} caption caption to associate with image. 2674 * @param {!Image} img image to insert. 2675 */ 2676 var insertImage = function(element, caption, img) { 2677 var div = document.createElement("div"); 2678 var label = document.createElement("div"); 2679 label.appendChild(document.createTextNode(caption)); 2680 div.appendChild(label); 2681 div.appendChild(img); 2682 element.appendChild(div); 2683 }; 2684 2685 /** 2686 * Inserts a 'label' that when clicked expands to the pre formatted text 2687 * supplied by 'source'. 2688 * @param {!HTMLElement} element element to append label to. 2689 * @param {string} label label for anchor. 2690 * @param {string} source preformatted text to expand to. 2691 * @param {string} opt_url URL of source. If provided a link to the source file 2692 * will also be added. 2693 */ 2694 var addShaderSource = function(element, label, source, opt_url) { 2695 var div = document.createElement("div"); 2696 var s = document.createElement("pre"); 2697 s.className = "shader-source"; 2698 s.style.display = "none"; 2699 var ol = document.createElement("ol"); 2700 //s.appendChild(document.createTextNode(source)); 2701 var lines = source.split("\n"); 2702 for (var ii = 0; ii < lines.length; ++ii) { 2703 var line = lines[ii]; 2704 var li = document.createElement("li"); 2705 li.appendChild(document.createTextNode(line)); 2706 ol.appendChild(li); 2707 } 2708 s.appendChild(ol); 2709 var l = document.createElement("a"); 2710 l.href = "show-shader-source"; 2711 l.appendChild(document.createTextNode(label)); 2712 l.addEventListener('click', function(event) { 2713 if (event.preventDefault) { 2714 event.preventDefault(); 2715 } 2716 s.style.display = (s.style.display == 'none') ? 'block' : 'none'; 2717 return false; 2718 }, false); 2719 div.appendChild(l); 2720 if (opt_url) { 2721 var u = document.createElement("a"); 2722 u.href = opt_url; 2723 div.appendChild(document.createTextNode(" ")); 2724 u.appendChild(document.createTextNode("(" + opt_url + ")")); 2725 div.appendChild(u); 2726 } 2727 div.appendChild(s); 2728 element.appendChild(div); 2729 }; 2730 2731 /** 2732 * Inserts labels that when clicked expand to show the original source of the 2733 * shader and also translated source of the shader, if that is available. 2734 * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. 2735 * @param {!HTMLElement} element element to append label to. 2736 * @param {string} label label for anchor. 2737 * @param {WebGLShader} shader Shader to show the sources for. 2738 * @param {string} shaderSource Original shader source. 2739 * @param {string} opt_url URL of source. If provided a link to the source file 2740 * will also be added. 2741 */ 2742 var addShaderSources = function( 2743 gl, element, label, shader, shaderSource, opt_url) { 2744 addShaderSource(element, label, shaderSource, opt_url); 2745 2746 var debugShaders = gl.getExtension('WEBGL_debug_shaders'); 2747 if (debugShaders && shader) { 2748 var translatedSource = debugShaders.getTranslatedShaderSource(shader); 2749 if (translatedSource != '') { 2750 addShaderSource(element, label + ' translated for driver', translatedSource); 2751 } 2752 } 2753 }; 2754 2755 /** 2756 * Sends shader information to the server to be dumped into text files 2757 * when tests are run from within the test-runner harness. 2758 * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. 2759 * @param {string} url URL of current. 2760 * @param {string} passMsg Test description. 2761 * @param {object} vInfo Object containing vertex shader information. 2762 * @param {object} fInfo Object containing fragment shader information. 2763 */ 2764 var dumpShadersInfo = function(gl, url, passMsg, vInfo, fInfo) { 2765 var shaderInfo = {}; 2766 shaderInfo.url = url; 2767 shaderInfo.testDescription = passMsg; 2768 shaderInfo.vLabel = vInfo.label; 2769 shaderInfo.vShouldCompile = vInfo.shaderSuccess; 2770 shaderInfo.vSource = vInfo.source; 2771 shaderInfo.fLabel = fInfo.label; 2772 shaderInfo.fShouldCompile = fInfo.shaderSuccess; 2773 shaderInfo.fSource = fInfo.source; 2774 shaderInfo.vTranslatedSource = null; 2775 shaderInfo.fTranslatedSource = null; 2776 var debugShaders = gl.getExtension('WEBGL_debug_shaders'); 2777 if (debugShaders) { 2778 if (vInfo.shader) 2779 shaderInfo.vTranslatedSource = debugShaders.getTranslatedShaderSource(vInfo.shader); 2780 if (fInfo.shader) 2781 shaderInfo.fTranslatedSource = debugShaders.getTranslatedShaderSource(fInfo.shader); 2782 } 2783 2784 var dumpShaderInfoRequest = new XMLHttpRequest(); 2785 dumpShaderInfoRequest.open('POST', "/dumpShaderInfo", true); 2786 dumpShaderInfoRequest.setRequestHeader("Content-Type", "text/plain"); 2787 dumpShaderInfoRequest.send(JSON.stringify(shaderInfo)); 2788 }; 2789 2790 // Add your prefix here. 2791 var browserPrefixes = [ 2792 "", 2793 "MOZ_", 2794 "OP_", 2795 "WEBKIT_" 2796 ]; 2797 2798 /** 2799 * Given an extension name like WEBGL_compressed_texture_s3tc 2800 * returns the name of the supported version extension, like 2801 * WEBKIT_WEBGL_compressed_teture_s3tc 2802 * @param {string} name Name of extension to look for. 2803 * @return {string} name of extension found or undefined if not 2804 * found. 2805 */ 2806 var getSupportedExtensionWithKnownPrefixes = function(gl, name) { 2807 var supported = gl.getSupportedExtensions(); 2808 for (var ii = 0; ii < browserPrefixes.length; ++ii) { 2809 var prefixedName = browserPrefixes[ii] + name; 2810 if (supported.indexOf(prefixedName) >= 0) { 2811 return prefixedName; 2812 } 2813 } 2814 }; 2815 2816 /** 2817 * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. 2818 * @param {string} name Name of extension to look for. 2819 * @param {boolean} extensionEnabled True if the extension was enabled successfully via gl.getExtension(). 2820 */ 2821 var runExtensionSupportedTest = function(gl, name, extensionEnabled) { 2822 var prefixedName = getSupportedExtensionWithKnownPrefixes(gl, name); 2823 if (prefixedName !== undefined) { 2824 if (extensionEnabled) { 2825 testPassed(name + " listed as supported and getExtension succeeded"); 2826 } else { 2827 testFailed(name + " listed as supported but getExtension failed"); 2828 } 2829 } else { 2830 if (extensionEnabled) { 2831 testFailed(name + " not listed as supported but getExtension succeeded"); 2832 } else { 2833 testPassed(name + " not listed as supported and getExtension failed -- this is legal"); 2834 } 2835 } 2836 } 2837 2838 /** 2839 * Given an extension name like WEBGL_compressed_texture_s3tc 2840 * returns the supported version extension, like 2841 * WEBKIT_WEBGL_compressed_teture_s3tc 2842 * @param {string} name Name of extension to look for. 2843 * @return {WebGLExtension} The extension or undefined if not 2844 * found. 2845 */ 2846 var getExtensionWithKnownPrefixes = function(gl, name) { 2847 for (var ii = 0; ii < browserPrefixes.length; ++ii) { 2848 var prefixedName = browserPrefixes[ii] + name; 2849 var ext = gl.getExtension(prefixedName); 2850 if (ext) { 2851 return ext; 2852 } 2853 } 2854 }; 2855 2856 /** 2857 * Returns possible prefixed versions of an extension's name. 2858 * @param {string} name Name of extension. May already include a prefix. 2859 * @return {Array.<string>} Variations of the extension name with known 2860 * browser prefixes. 2861 */ 2862 var getExtensionPrefixedNames = function(name) { 2863 var unprefix = function(name) { 2864 for (var ii = 0; ii < browserPrefixes.length; ++ii) { 2865 if (browserPrefixes[ii].length > 0 && 2866 name.substring(0, browserPrefixes[ii].length).toLowerCase() === 2867 browserPrefixes[ii].toLowerCase()) { 2868 return name.substring(browserPrefixes[ii].length); 2869 } 2870 } 2871 return name; 2872 } 2873 2874 var unprefixed = unprefix(name); 2875 2876 var variations = []; 2877 for (var ii = 0; ii < browserPrefixes.length; ++ii) { 2878 variations.push(browserPrefixes[ii] + unprefixed); 2879 } 2880 2881 return variations; 2882 }; 2883 2884 var replaceRE = /\$\((\w+)\)/g; 2885 2886 /** 2887 * Replaces strings with property values. 2888 * Given a string like "hello $(first) $(last)" and an object 2889 * like {first:"John", last:"Smith"} will return 2890 * "hello John Smith". 2891 * @param {string} str String to do replacements in. 2892 * @param {...} 1 or more objects containing properties. 2893 */ 2894 var replaceParams = function(str) { 2895 var args = arguments; 2896 return str.replace(replaceRE, function(str, p1, offset, s) { 2897 for (var ii = 1; ii < args.length; ++ii) { 2898 if (args[ii][p1] !== undefined) { 2899 return args[ii][p1]; 2900 } 2901 } 2902 throw "unknown string param '" + p1 + "'"; 2903 }); 2904 }; 2905 2906 var upperCaseFirstLetter = function(str) { 2907 return str.substring(0, 1).toUpperCase() + str.substring(1); 2908 }; 2909 2910 /** 2911 * Gets a prefixed property. For example, 2912 * 2913 * var fn = getPrefixedProperty( 2914 * window, 2915 * "requestAnimationFrame"); 2916 * 2917 * Will return either: 2918 * "window.requestAnimationFrame", 2919 * "window.oRequestAnimationFrame", 2920 * "window.msRequestAnimationFrame", 2921 * "window.mozRequestAnimationFrame", 2922 * "window.webKitRequestAnimationFrame", 2923 * undefined 2924 * 2925 * the non-prefixed function is tried first. 2926 */ 2927 var propertyPrefixes = ["", "moz", "ms", "o", "webkit"]; 2928 var getPrefixedProperty = function(obj, propertyName) { 2929 for (var ii = 0; ii < propertyPrefixes.length; ++ii) { 2930 var prefix = propertyPrefixes[ii]; 2931 var name = prefix + propertyName; 2932 log(name); 2933 var property = obj[name]; 2934 if (property) { 2935 return property; 2936 } 2937 if (ii == 0) { 2938 propertyName = upperCaseFirstLetter(propertyName); 2939 } 2940 } 2941 return undefined; 2942 }; 2943 2944 var _requestAnimFrame; 2945 2946 /** 2947 * Provides requestAnimationFrame in a cross browser way. 2948 */ 2949 var requestAnimFrame = function(callback) { 2950 if (!_requestAnimFrame) { 2951 _requestAnimFrame = getPrefixedProperty(window, "requestAnimationFrame") || 2952 function(callback, element) { 2953 return window.setTimeout(callback, 1000 / 70); 2954 }; 2955 } 2956 _requestAnimFrame.call(window, callback); 2957 }; 2958 2959 var _cancelAnimFrame; 2960 2961 /** 2962 * Provides cancelAnimationFrame in a cross browser way. 2963 */ 2964 var cancelAnimFrame = function(request) { 2965 if (!_cancelAnimFrame) { 2966 _cancelAnimFrame = getPrefixedProperty(window, "cancelAnimationFrame") || 2967 window.clearTimeout; 2968 } 2969 _cancelAnimFrame.call(window, request); 2970 }; 2971 2972 /** 2973 * Provides requestFullScreen in a cross browser way. 2974 */ 2975 var requestFullScreen = function(element) { 2976 var fn = getPrefixedProperty(element, "requestFullScreen"); 2977 if (fn) { 2978 fn.call(element); 2979 } 2980 }; 2981 2982 /** 2983 * Provides cancelFullScreen in a cross browser way. 2984 */ 2985 var cancelFullScreen = function() { 2986 var fn = getPrefixedProperty(document, "cancelFullScreen"); 2987 if (fn) { 2988 fn.call(document); 2989 } 2990 }; 2991 2992 var fullScreenStateName; 2993 (function() { 2994 var fullScreenStateNames = [ 2995 "isFullScreen", 2996 "fullScreen" 2997 ]; 2998 for (var ii = 0; ii < fullScreenStateNames.length; ++ii) { 2999 var propertyName = fullScreenStateNames[ii]; 3000 for (var jj = 0; jj < propertyPrefixes.length; ++jj) { 3001 var prefix = propertyPrefixes[jj]; 3002 if (prefix.length) { 3003 propertyName = upperCaseFirstLetter(propertyName); 3004 fullScreenStateName = prefix + propertyName; 3005 if (document[fullScreenStateName] !== undefined) { 3006 return; 3007 } 3008 } 3009 } 3010 fullScreenStateName = undefined; 3011 } 3012 }()); 3013 3014 /** 3015 * @return {boolean} True if fullscreen mode is active. 3016 */ 3017 var getFullScreenState = function() { 3018 log("fullscreenstatename:" + fullScreenStateName); 3019 log(document[fullScreenStateName]); 3020 return document[fullScreenStateName]; 3021 }; 3022 3023 /** 3024 * @param {!HTMLElement} element The element to go fullscreen. 3025 * @param {!function(boolean)} callback A function that will be called 3026 * when entering/exiting fullscreen. It is passed true if 3027 * entering fullscreen, false if exiting. 3028 */ 3029 var onFullScreenChange = function(element, callback) { 3030 propertyPrefixes.forEach(function(prefix) { 3031 var eventName = prefix + "fullscreenchange"; 3032 log("addevent: " + eventName); 3033 document.addEventListener(eventName, function(event) { 3034 log("event: " + eventName); 3035 callback(getFullScreenState()); 3036 }); 3037 }); 3038 }; 3039 3040 /** 3041 * @param {!string} buttonId The id of the button that will toggle fullscreen 3042 * mode. 3043 * @param {!string} fullscreenId The id of the element to go fullscreen. 3044 * @param {!function(boolean)} callback A function that will be called 3045 * when entering/exiting fullscreen. It is passed true if 3046 * entering fullscreen, false if exiting. 3047 * @return {boolean} True if fullscreen mode is supported. 3048 */ 3049 var setupFullscreen = function(buttonId, fullscreenId, callback) { 3050 if (!fullScreenStateName) { 3051 return false; 3052 } 3053 3054 var fullscreenElement = document.getElementById(fullscreenId); 3055 onFullScreenChange(fullscreenElement, callback); 3056 3057 var toggleFullScreen = function(event) { 3058 if (getFullScreenState()) { 3059 cancelFullScreen(fullscreenElement); 3060 } else { 3061 requestFullScreen(fullscreenElement); 3062 } 3063 event.preventDefault(); 3064 return false; 3065 }; 3066 3067 var buttonElement = document.getElementById(buttonId); 3068 buttonElement.addEventListener('click', toggleFullScreen); 3069 3070 return true; 3071 }; 3072 3073 /** 3074 * Waits for the browser to composite the web page. 3075 * @param {function()} callback A function to call after compositing has taken 3076 * place. 3077 */ 3078 var waitForComposite = function(callback) { 3079 var frames = 5; 3080 var countDown = function() { 3081 if (frames == 0) { 3082 // TODO(kbr): unify with js-test-pre.js and enable these with 3083 // verbose logging. 3084 // log("waitForComposite: callback"); 3085 callback(); 3086 } else { 3087 // log("waitForComposite: countdown(" + frames + ")"); 3088 --frames; 3089 requestAnimFrame.call(window, countDown); 3090 } 3091 }; 3092 countDown(); 3093 }; 3094 3095 var setZeroTimeout = (function() { 3096 // See https://dbaron.org/log/20100309-faster-timeouts 3097 3098 var timeouts = []; 3099 var messageName = "zero-timeout-message"; 3100 3101 // Like setTimeout, but only takes a function argument. There's 3102 // no time argument (always zero) and no arguments (you have to 3103 // use a closure). 3104 function setZeroTimeout(fn) { 3105 timeouts.push(fn); 3106 window.postMessage(messageName, "*"); 3107 } 3108 3109 function handleMessage(event) { 3110 if (event.source == window && event.data == messageName) { 3111 event.stopPropagation(); 3112 if (timeouts.length > 0) { 3113 var fn = timeouts.shift(); 3114 fn(); 3115 } 3116 } 3117 } 3118 3119 window.addEventListener("message", handleMessage, true); 3120 3121 return setZeroTimeout; 3122 })(); 3123 3124 function dispatchPromise(fn) { 3125 return new Promise((fn_resolve, fn_reject) => { 3126 setZeroTimeout(() => { 3127 let val; 3128 if (fn) { 3129 val = fn(); 3130 } 3131 fn_resolve(val); 3132 }); 3133 }); 3134 } 3135 3136 /** 3137 * Runs an array of functions, yielding to the browser between each step. 3138 * If you want to know when all the steps are finished add a last step. 3139 * @param {!Array.<function(): void>} steps Array of functions. 3140 */ 3141 var runSteps = function(steps) { 3142 if (!steps.length) { 3143 return; 3144 } 3145 3146 // copy steps so they can't be modifed. 3147 var stepsToRun = steps.slice(); 3148 var currentStep = 0; 3149 var runNextStep = function() { 3150 stepsToRun[currentStep++](); 3151 if (currentStep < stepsToRun.length) { 3152 setTimeout(runNextStep, 1); 3153 } 3154 }; 3155 runNextStep(); 3156 }; 3157 3158 /** 3159 * Starts playing a video and waits for it to be consumable. 3160 * @param {!HTMLVideoElement} video An HTML5 Video element. 3161 * @param {!function(!HTMLVideoElement): void} callback Function to call when 3162 * video is ready. 3163 */ 3164 async function startPlayingAndWaitForVideo(video, callback) { 3165 if (video.error) { 3166 testFailed('Video failed to load: ' + video.error); 3167 return; 3168 } 3169 3170 video.loop = true; 3171 video.muted = true; 3172 // See whether setting the preload flag de-flakes video-related tests. 3173 video.preload = 'auto'; 3174 3175 try { 3176 await video.play(); 3177 } catch (e) { 3178 testFailed('video.play failed: ' + e); 3179 return; 3180 } 3181 3182 if (video.requestVideoFrameCallback) { 3183 await new Promise(go => video.requestVideoFrameCallback(go)); 3184 } 3185 3186 callback(video); 3187 } 3188 3189 var getHost = function(url) { 3190 url = url.replace("\\", "/"); 3191 var pos = url.indexOf("://"); 3192 if (pos >= 0) { 3193 url = url.substr(pos + 3); 3194 } 3195 var parts = url.split('/'); 3196 return parts[0]; 3197 } 3198 3199 // This function returns the last 2 words of the domain of a URL 3200 // This is probably not the correct check but it will do for now. 3201 var getBaseDomain = function(host) { 3202 var parts = host.split(":"); 3203 var hostname = parts[0]; 3204 var port = parts[1] || "80"; 3205 parts = hostname.split("."); 3206 if(parts.length < 2) 3207 return hostname + ":" + port; 3208 var tld = parts[parts.length-1]; 3209 var domain = parts[parts.length-2]; 3210 return domain + "." + tld + ":" + port; 3211 } 3212 3213 var runningOnLocalhost = function() { 3214 let hostname = window.location.hostname; 3215 return hostname == "localhost" || 3216 hostname == "127.0.0.1" || 3217 hostname == "::1"; 3218 } 3219 3220 var getLocalCrossOrigin = function() { 3221 var domain; 3222 if (window.location.host.indexOf("localhost") != -1) { 3223 // TODO(kbr): figure out whether to use an IPv6 loopback address. 3224 domain = "127.0.0.1"; 3225 } else { 3226 domain = "localhost"; 3227 } 3228 3229 var port = window.location.port || "80"; 3230 return window.location.protocol + "//" + domain + ":" + port 3231 } 3232 3233 var getRelativePath = function(path) { 3234 var relparts = window.location.pathname.split("/"); 3235 relparts.pop(); // Pop off filename 3236 var pathparts = path.split("/"); 3237 3238 var i; 3239 for (i = 0; i < pathparts.length; ++i) { 3240 switch (pathparts[i]) { 3241 case "": break; 3242 case ".": break; 3243 case "..": 3244 relparts.pop(); 3245 break; 3246 default: 3247 relparts.push(pathparts[i]); 3248 break; 3249 } 3250 } 3251 3252 return relparts.join("/"); 3253 } 3254 3255 async function loadCrossOriginImage(img, webUrl, localUrl) { 3256 if (runningOnLocalhost()) { 3257 img.src = getLocalCrossOrigin() + getRelativePath(localUrl); 3258 console.log('[loadCrossOriginImage]', ' trying', img.src); 3259 await img.decode(); 3260 return; 3261 } 3262 3263 try { 3264 img.src = getUrlOptions().imgUrl || webUrl; 3265 console.log('[loadCrossOriginImage]', 'trying', img.src); 3266 await img.decode(); 3267 return; 3268 } catch {} 3269 3270 throw 'createCrossOriginImage failed'; 3271 } 3272 3273 /** 3274 * Convert sRGB color to linear color. 3275 * @param {!Array.<number>} color The color to be converted. 3276 * The array has 4 elements, for example [R, G, B, A]. 3277 * where each element is in the range 0 to 255. 3278 * @return {!Array.<number>} color The color to be converted. 3279 * The array has 4 elements, for example [R, G, B, A]. 3280 * where each element is in the range 0 to 255. 3281 */ 3282 var sRGBToLinear = function(color) { 3283 return [sRGBChannelToLinear(color[0]), 3284 sRGBChannelToLinear(color[1]), 3285 sRGBChannelToLinear(color[2]), 3286 color[3]] 3287 } 3288 3289 /** 3290 * Convert linear color to sRGB color. 3291 * @param {!Array.<number>} color The color to be converted. 3292 * The array has 4 elements, for example [R, G, B, A]. 3293 * where each element is in the range 0 to 255. 3294 * @return {!Array.<number>} color The color to be converted. 3295 * The array has 4 elements, for example [R, G, B, A]. 3296 * where each element is in the range 0 to 255. 3297 */ 3298 var linearToSRGB = function(color) { 3299 return [linearChannelToSRGB(color[0]), 3300 linearChannelToSRGB(color[1]), 3301 linearChannelToSRGB(color[2]), 3302 color[3]] 3303 } 3304 3305 function sRGBChannelToLinear(value) { 3306 value = value / 255; 3307 if (value <= 0.04045) 3308 value = value / 12.92; 3309 else 3310 value = Math.pow((value + 0.055) / 1.055, 2.4); 3311 return Math.trunc(value * 255 + 0.5); 3312 } 3313 3314 function linearChannelToSRGB(value) { 3315 value = value / 255; 3316 if (value <= 0.0) { 3317 value = 0.0; 3318 } else if (value < 0.0031308) { 3319 value = value * 12.92; 3320 } else if (value < 1) { 3321 value = Math.pow(value, 0.41666) * 1.055 - 0.055; 3322 } else { 3323 value = 1.0; 3324 } 3325 return Math.trunc(value * 255 + 0.5); 3326 } 3327 3328 /** 3329 * Return the named color in the specified color space. 3330 * @param {string} colorName The name of the color to convert. 3331 * Supported color names are: 3332 * 'Red', which is the CSS color color('srgb' 1 0 0 1) 3333 * 'Green', which is the CSS color color('srgb' 0 1 0 1) 3334 * @param {string} colorSpace The color space to convert to. Supported 3335 color spaces are: 3336 * null, which is treated as sRGB 3337 * 'srgb' 3338 * 'display-p3'. 3339 * Documentation on the formulas for color conversion between 3340 * spaces can be found at 3341 https://www.w3.org/TR/css-color-4/#predefined-to-predefined 3342 * @return {!Array.<number>} color The color in the specified color 3343 * space as an 8-bit RGBA array with unpremultiplied alpha. 3344 */ 3345 var namedColorInColorSpace = function(colorName, colorSpace) { 3346 var result; 3347 switch (colorSpace) { 3348 case undefined: 3349 case 'srgb': 3350 switch(colorName) { 3351 case 'Red': 3352 return [255, 0, 0, 255]; 3353 case 'Green': 3354 return [0, 255, 0, 255]; 3355 break; 3356 default: 3357 throw 'unexpected color name: ' + colorName; 3358 }; 3359 break; 3360 case 'display-p3': 3361 switch(colorName) { 3362 case 'Red': 3363 return [234, 51, 35, 255]; 3364 break; 3365 case 'Green': 3366 return [117, 251, 76, 255]; 3367 break; 3368 default: 3369 throw 'unexpected color name: ' + colorName; 3370 } 3371 break; 3372 default: 3373 throw 'unexpected color space: ' + colorSpace; 3374 } 3375 } 3376 3377 /** 3378 * Return the named color as it would be sampled with the specified 3379 * internal format 3380 * @param {!Array.<number>} color The color as an 8-bit RGBA array. 3381 * @param {string} internalformat The internal format. 3382 * @return {!Array.<number>} color The color, as it would be sampled by 3383 * the specified internal format, as an 8-bit RGBA array. 3384 */ 3385 var colorAsSampledWithInternalFormat = function(color, internalFormat) { 3386 switch (internalFormat) { 3387 case 'ALPHA': 3388 return [0, 0, 0, color[3]]; 3389 case 'LUMINANCE': 3390 return [color[0], color[0], color[0], 255]; 3391 case 'LUMINANCE_ALPHA': 3392 return [color[0], color[0], color[0], color[3]]; 3393 case 'SRGB8': 3394 case 'SRGB8_ALPHA8': 3395 return [sRGBChannelToLinear(color[0]), 3396 sRGBChannelToLinear(color[1]), 3397 sRGBChannelToLinear(color[2]), 3398 color[3]]; 3399 case 'R16F': 3400 case 'R32F': 3401 case 'R8': 3402 case 'R8UI': 3403 case 'RED': 3404 case 'RED_INTEGER': 3405 return [color[0], 0, 0, 0]; 3406 case 'RG': 3407 case 'RG16F': 3408 case 'RG32F': 3409 case 'RG8': 3410 case 'RG8UI': 3411 case 'RG_INTEGER': 3412 return [color[0], color[1], 0, 0]; 3413 break; 3414 default: 3415 break; 3416 } 3417 return color; 3418 } 3419 3420 function comparePixels(cmp, ref, tolerance, diff) { 3421 if (cmp.length != ref.length) { 3422 testFailed("invalid pixel size."); 3423 } 3424 3425 var count = 0; 3426 for (var i = 0; i < cmp.length; i++) { 3427 if (diff) { 3428 diff[i * 4] = 0; 3429 diff[i * 4 + 1] = 255; 3430 diff[i * 4 + 2] = 0; 3431 diff[i * 4 + 3] = 255; 3432 } 3433 if (Math.abs(cmp[i * 4] - ref[i * 4]) > tolerance || 3434 Math.abs(cmp[i * 4 + 1] - ref[i * 4 + 1]) > tolerance || 3435 Math.abs(cmp[i * 4 + 2] - ref[i * 4 + 2]) > tolerance || 3436 Math.abs(cmp[i * 4 + 3] - ref[i * 4 + 3]) > tolerance) { 3437 if (count < 10) { 3438 testFailed("Pixel " + i + ": expected (" + 3439 [ref[i * 4], ref[i * 4 + 1], ref[i * 4 + 2], ref[i * 4 + 3]] + "), got (" + 3440 [cmp[i * 4], cmp[i * 4 + 1], cmp[i * 4 + 2], cmp[i * 4 + 3]] + ")"); 3441 } 3442 count++; 3443 if (diff) { 3444 diff[i * 4] = 255; 3445 diff[i * 4 + 1] = 0; 3446 } 3447 } 3448 } 3449 3450 return count; 3451 } 3452 3453 function destroyContext(gl) { 3454 const ext = gl.getExtension('WEBGL_lose_context'); 3455 if (ext) { 3456 ext.loseContext(); 3457 } 3458 gl.canvas.width = 1; 3459 gl.canvas.height = 1; 3460 } 3461 3462 function destroyAllContexts() { 3463 if (!window._wtu_contexts) 3464 return; 3465 for (const x of window._wtu_contexts) { 3466 destroyContext(x); 3467 } 3468 window._wtu_contexts = []; 3469 } 3470 3471 function displayImageDiff(cmp, ref, diff, width, height) { 3472 var div = document.createElement("div"); 3473 3474 var cmpImg = createImageFromPixel(cmp, width, height); 3475 var refImg = createImageFromPixel(ref, width, height); 3476 var diffImg = createImageFromPixel(diff, width, height); 3477 wtu.insertImage(div, "Reference", refImg); 3478 wtu.insertImage(div, "Result", cmpImg); 3479 wtu.insertImage(div, "Difference", diffImg); 3480 3481 var console = document.getElementById("console"); 3482 console.appendChild(div); 3483 } 3484 3485 function createImageFromPixel(buf, width, height) { 3486 var canvas = document.createElement("canvas"); 3487 canvas.width = width; 3488 canvas.height = height; 3489 var ctx = canvas.getContext("2d"); 3490 var imgData = ctx.getImageData(0, 0, width, height); 3491 3492 for (var i = 0; i < buf.length; i++) 3493 imgData.data[i] = buf[i]; 3494 ctx.putImageData(imgData, 0, 0); 3495 var img = wtu.makeImageFromCanvas(canvas); 3496 return img; 3497 } 3498 3499 async function awaitTimeout(ms) { 3500 await new Promise(res => { 3501 setTimeout(() => { 3502 res(); 3503 }, ms); 3504 }); 3505 } 3506 3507 async function awaitOrTimeout(promise, opt_timeout_ms) { 3508 async function throwOnTimeout(ms) { 3509 await awaitTimeout(ms); 3510 throw 'timeout'; 3511 } 3512 3513 let timeout_ms = opt_timeout_ms; 3514 if (timeout_ms === undefined) 3515 timeout_ms = 5000; 3516 3517 await Promise.race([promise, throwOnTimeout(timeout_ms)]); 3518 } 3519 3520 var API = { 3521 addShaderSource: addShaderSource, 3522 addShaderSources: addShaderSources, 3523 cancelAnimFrame: cancelAnimFrame, 3524 create3DContext: create3DContext, 3525 GLErrorException: GLErrorException, 3526 create3DContextWithWrapperThatThrowsOnGLError: create3DContextWithWrapperThatThrowsOnGLError, 3527 checkAreaInAndOut: checkAreaInAndOut, 3528 checkCanvas: checkCanvas, 3529 checkCanvasRect: checkCanvasRect, 3530 checkCanvasRectColor: checkCanvasRectColor, 3531 checkCanvasRects: checkCanvasRects, 3532 checkFloatBuffer: checkFloatBuffer, 3533 checkTextureSize: checkTextureSize, 3534 clipToRange: clipToRange, 3535 createColoredTexture: createColoredTexture, 3536 createProgram: createProgram, 3537 clearAndDrawUnitQuad: clearAndDrawUnitQuad, 3538 clearAndDrawIndexedQuad: clearAndDrawIndexedQuad, 3539 comparePixels: comparePixels, 3540 destroyAllContexts: destroyAllContexts, 3541 destroyContext: destroyContext, 3542 dispatchPromise: dispatchPromise, 3543 displayImageDiff: displayImageDiff, 3544 drawUnitQuad: drawUnitQuad, 3545 drawIndexedQuad: drawIndexedQuad, 3546 drawUByteColorQuad: drawUByteColorQuad, 3547 drawFloatColorQuad: drawFloatColorQuad, 3548 dummySetProgramAndDrawNothing: dummySetProgramAndDrawNothing, 3549 dumpShadersInfo: dumpShadersInfo, 3550 endsWith: endsWith, 3551 failIfGLError: failIfGLError, 3552 fillTexture: fillTexture, 3553 framebufferStatusShouldBe: framebufferStatusShouldBe, 3554 getBytesPerComponent: getBytesPerComponent, 3555 getDefault3DContextVersion: getDefault3DContextVersion, 3556 getExtensionPrefixedNames: getExtensionPrefixedNames, 3557 getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes, 3558 getFileListAsync: getFileListAsync, 3559 getLastError: getLastError, 3560 getPrefixedProperty: getPrefixedProperty, 3561 getScript: getScript, 3562 getSupportedExtensionWithKnownPrefixes: getSupportedExtensionWithKnownPrefixes, 3563 getTypedArrayElementsPerPixel: getTypedArrayElementsPerPixel, 3564 getUrlArguments: getUrlArguments, 3565 getUrlOptions: getUrlOptions, 3566 getAttribMap: getAttribMap, 3567 getUniformMap: getUniformMap, 3568 glEnumToString: glEnumToString, 3569 glErrorAssert: glErrorAssert, 3570 glErrorShouldBe: glErrorShouldBe, 3571 glTypeToTypedArrayType: glTypeToTypedArrayType, 3572 hasAttributeCaseInsensitive: hasAttributeCaseInsensitive, 3573 insertImage: insertImage, 3574 isWebGL2: isWebGL2, 3575 linkProgram: linkProgram, 3576 loadCrossOriginImage: loadCrossOriginImage, 3577 loadImageAsync: loadImageAsync, 3578 loadImagesAsync: loadImagesAsync, 3579 loadProgram: loadProgram, 3580 loadProgramFromFile: loadProgramFromFile, 3581 loadProgramFromScript: loadProgramFromScript, 3582 loadProgramFromScriptExpectError: loadProgramFromScriptExpectError, 3583 loadShader: loadShader, 3584 loadShaderFromFile: loadShaderFromFile, 3585 loadShaderFromScript: loadShaderFromScript, 3586 loadStandardProgram: loadStandardProgram, 3587 loadStandardProgramAsync: loadStandardProgramAsync, 3588 loadStandardVertexShader: loadStandardVertexShader, 3589 loadStandardVertexShaderAsync: loadStandardVertexShaderAsync, 3590 loadStandardFragmentShader: loadStandardFragmentShader, 3591 loadStandardFragmentShaderAsync: loadStandardFragmentShaderAsync, 3592 loadUniformBlockProgram: loadUniformBlockProgram, 3593 loadUniformBlockVertexShader: loadUniformBlockVertexShader, 3594 loadUniformBlockFragmentShader: loadUniformBlockFragmentShader, 3595 loadTextFileAsync: loadTextFileAsync, 3596 loadTexture: loadTexture, 3597 log: log, 3598 loggingOff: loggingOff, 3599 makeCheckRect: makeCheckRect, 3600 makeImage: makeImage, 3601 makeImageFromCanvas: makeImageFromCanvas, 3602 makeVideo: makeVideo, 3603 error: error, 3604 runExtensionSupportedTest: runExtensionSupportedTest, 3605 shallowCopyObject: shallowCopyObject, 3606 setDefault3DContextVersion: setDefault3DContextVersion, 3607 setupColorQuad: setupColorQuad, 3608 setupProgram: setupProgram, 3609 setupTransformFeedbackProgram: setupTransformFeedbackProgram, 3610 setupQuad: setupQuad, 3611 setupQuadWithTexCoords: setupQuadWithTexCoords, 3612 setupIndexedQuad: setupIndexedQuad, 3613 setupIndexedQuadWithOptions: setupIndexedQuadWithOptions, 3614 setupSimpleColorProgram: setupSimpleColorProgram, 3615 setupSimpleTextureProgram: setupSimpleTextureProgram, 3616 setupSimpleTextureProgramESSL300: setupSimpleTextureProgramESSL300, 3617 setupSimpleCubeMapTextureProgram: setupSimpleCubeMapTextureProgram, 3618 setupSimpleVertexColorProgram: setupSimpleVertexColorProgram, 3619 setupNoTexCoordTextureProgram: setupNoTexCoordTextureProgram, 3620 setupTexturedQuad: setupTexturedQuad, 3621 setupTexturedQuadWithTexCoords: setupTexturedQuadWithTexCoords, 3622 setupTexturedQuadWithCubeMap: setupTexturedQuadWithCubeMap, 3623 setupUnitQuad: setupUnitQuad, 3624 setFloatDrawColor: setFloatDrawColor, 3625 setUByteDrawColor: setUByteDrawColor, 3626 startPlayingAndWaitForVideo: startPlayingAndWaitForVideo, 3627 startsWith: startsWith, 3628 shouldGenerateGLError: shouldGenerateGLError, 3629 shouldThrow: shouldThrow, 3630 readFile: readFile, 3631 readFileList: readFileList, 3632 replaceParams: replaceParams, 3633 requestAnimFrame: requestAnimFrame, 3634 runSteps: runSteps, 3635 waitForComposite: waitForComposite, 3636 3637 // fullscreen api 3638 setupFullscreen: setupFullscreen, 3639 3640 // color converter API 3641 namedColorInColorSpace: namedColorInColorSpace, 3642 colorAsSampledWithInternalFormat: colorAsSampledWithInternalFormat, 3643 3644 // sRGB converter api 3645 sRGBToLinear: sRGBToLinear, 3646 linearToSRGB: linearToSRGB, 3647 3648 getHost: getHost, 3649 getBaseDomain: getBaseDomain, 3650 runningOnLocalhost: runningOnLocalhost, 3651 getLocalCrossOrigin: getLocalCrossOrigin, 3652 getRelativePath: getRelativePath, 3653 awaitOrTimeout: awaitOrTimeout, 3654 awaitTimeout: awaitTimeout, 3655 3656 none: false 3657 }; 3658 3659 Object.defineProperties(API, { 3660 noTexCoordTextureVertexShader: { value: noTexCoordTextureVertexShader, writable: false }, 3661 simpleTextureVertexShader: { value: simpleTextureVertexShader, writable: false }, 3662 simpleTextureVertexShaderESSL300: { value: simpleTextureVertexShaderESSL300, writable: false }, 3663 simpleColorFragmentShader: { value: simpleColorFragmentShader, writable: false }, 3664 simpleColorFragmentShaderESSL300: { value: simpleColorFragmentShaderESSL300, writable: false }, 3665 simpleVertexShader: { value: simpleVertexShader, writable: false }, 3666 simpleVertexShaderESSL300: { value: simpleVertexShaderESSL300, writable: false }, 3667 simpleTextureFragmentShader: { value: simpleTextureFragmentShader, writable: false }, 3668 simpleTextureFragmentShaderESSL300: { value: simpleTextureFragmentShaderESSL300, writable: false }, 3669 simpleHighPrecisionTextureFragmentShader: { value: simpleHighPrecisionTextureFragmentShader, writable: false }, 3670 simpleCubeMapTextureFragmentShader: { value: simpleCubeMapTextureFragmentShader, writable: false }, 3671 simpleVertexColorFragmentShader: { value: simpleVertexColorFragmentShader, writable: false }, 3672 simpleVertexColorVertexShader: { value: simpleVertexColorVertexShader, writable: false } 3673 }); 3674 3675 return API; 3676 3677 }());