tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }());