text: /*
  ** Copyright (c) 2012 The Khronos Group Inc.
  **
  ** Permission is hereby granted, free of charge, to any person obtaining a
  ** copy of this software and/or associated documentation files (the
  ** "Materials"), to deal in the Materials without restriction, including
  ** without limitation the rights to use, copy, modify, merge, publish,
  ** distribute, sublicense, and/or sell copies of the Materials, and to
  ** permit persons to whom the Materials are furnished to do so, subject to
  ** the following conditions:
  **
  ** The above copyright notice and this permission notice shall be included
  ** in all copies or substantial portions of the Materials.
  **
  ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
  */
  NoOverOptimizeOnUniformArrayTester = (function(){
  
  var vshader = [
      "attribute vec4 a_position;",
      "void main()",
      "{",
      "    gl_Position = a_position;",
      "}"
  ].join('\n');
  
  var fshader_max = [
      "precision mediump float;",
      "uniform vec4 colora[$(maxUniformVectors)];",
      "void main()",
      "{",
      "    gl_FragColor = vec4(colora[$(usedUniformVector)]);",
      "}"
  ].join('\n');
  
  var fshader_max_ab_ab = [
      "precision mediump float;",
      "uniform vec4 $(decl1);",
      "uniform vec4 $(decl2);",
      "void main()",
      "{",
      "gl_FragColor = vec4($(usage1) + $(usage2));",
      "}"
  ].join('\n');
  
  // MaxInt32 is 2^32-1. We need +1 of that to test overflow conditions
  var MaxInt32PlusOne = 4294967296;
  
  function setupTests(gl) {
      var tests = [];
      var maxUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
  
      // This test is to test drivers the have bugs related to optimizing
      // an array of uniforms when only 1 of those uniforms is used.
      tests.push({
          desc: "using last element",
          maxUniformVectors: maxUniformVectors,
          usedUniformVector: maxUniformVectors - 1,
          shader: "fshader-max",
          color: [0, 1, 0, 1],
          arrayName: "colora",
          extraName: "colorb",
      });
      tests.push({
          desc: "using first element",
          maxUniformVectors: maxUniformVectors,
          usedUniformVector: 0,
          shader: "fshader-max",
          color: [0, 1, 0, 1],
          arrayName: "colora",
          extraName: "colorb",
      });
  
      // Generate test shaders. We're trying to force the driver to
      // overflow from 1 array into the next if it optimizes. So for example if it was C
      //
      //   int big[4];
      //   int little[1];
      //   big[5] = 124;
      //
      // Would end up setting little[0] instead of big. Some drivers optimize
      // where if you only use say 'big[3]' it will actually only allocate just 1 element
      // for big.
      //
      // But, some drivers have a bug where the fact that they optimized big to 1 element
      // does not get passed down to glUniform so when setting the uniform 'big[3]' they
      // overwrite memory.
      //
      // If the driver crashes, yea. We found a bug. We can blacklist the driver.
      // Otherwise we try various combinations so that setting 'little[0]' first
      // and then setting all elements of 'big' we hope it will overwrite 'little[0]'
      // which will show the bug and again we can blacklist the driver.
      //
      // We don't know how the driver will order, in memory, the various uniforms
      // or for that matter we don't even know if they will be contiguous in memory
      // but to hopefully expose any bugs we try various combinations.
      //
      //    It could be the compiler orders uniforms alphabetically.
      //    It could be it orders them in order of declaration.
      //    It could be it orders them in order of usage.
      //
      // We also test using only first element of big or just the last element of big.
      //
      for (var nameOrder = 0; nameOrder < 2; ++nameOrder) {
          var name1 = nameOrder ? "colora" : "colorb";
          var name2 = nameOrder ? "colorb" : "colora";
          for (var last = 0; last < 2; ++last) {
              var usedUniformVector = last ? maxUniformVectors - 2 : 0;
              for (var declOrder = 0; declOrder < 2; ++declOrder) {
                  var bigName    = declOrder ? name1 : name2;
                  var littleName = declOrder ? name2 : name1;
                  var decl1 = bigName + "[" + (maxUniformVectors - 1) + "]";
                  var decl2 = littleName + "[1]";
                  if (declOrder) {
                      var t = decl1;
                      decl1 = decl2;
                      decl2 = t;
                  }
                  for (var usageOrder = 0; usageOrder < 2; ++usageOrder) {
                      var usage1 = bigName + "[" + usedUniformVector + "]";
                      var usage2 = littleName + "[0]";
                      if (usageOrder) {
                          var t = usage1;
                          usage1 = usage2;
                          usage2 = t;
                      }
                      var fSrc = wtu.replaceParams(fshader_max_ab_ab, {
                          decl1: decl1,
                          decl2: decl2,
                          usage1: usage1,
                          usage2: usage2,
                      });
                      var desc = "testing: " + name1 + ":" + name2 + " using " + (last ? "last" : "first") +
                          " creating uniforms " + decl1 + " " + decl2 + " and accessing " + usage1 + " " + usage2;
                      tests.push({
                          desc: desc,
                          maxUniformVectors: maxUniformVectors - 1,
                          usedUniformVector: usedUniformVector,
                          source: fSrc,
                          color: [0, 0, 0, 1],
                          arrayName: bigName,
                          extraName: littleName,
                      });
                  }
              }
          }
      }
      return tests;
  };
  
  function testUniformOptimizationIssues(test) {
      debug("");
      debug(test.desc);
      var fshader = test.source;
      if (!fshader) {
          fshader = wtu.replaceParams(fshader_max, test);
      }
  
      var consoleElem = document.getElementById("console");
      wtu.addShaderSource(
          consoleElem, "vertex shader", vshader);
      wtu.addShaderSource(
          consoleElem, "fragment shader", fshader);
  
      var program = wtu.loadProgram(gl, vshader, fshader);
      gl.useProgram(program);
  
      var colorbLocation = gl.getUniformLocation(program, test.extraName + "[0]");
      if (colorbLocation) {
          gl.uniform4fv(colorbLocation, [0, 1, 0, 0]);
      }
  
      // Ensure that requesting an array uniform past MaxInt32PlusOne returns no uniform
      var nameMaxInt32PlusOne = test.arrayName + "[" + (test.usedUniformVector + MaxInt32PlusOne) + "]";
      assertMsg(gl.getUniformLocation(program, nameMaxInt32PlusOne) === null,
          "Requesting " + nameMaxInt32PlusOne + " uniform should return a null uniform location");
  
      // Set just the used uniform
      var name = test.arrayName + "[" + test.usedUniformVector + "]";
      var uniformLocation = gl.getUniformLocation(program, name);
      gl.uniform4fv(uniformLocation, test.color);
      wtu.setupIndexedQuad(gl, 1);
      wtu.clearAndDrawIndexedQuad(gl, 1);
      wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
  
      // Set all the unused uniforms
      var locations = [];
      var allRequiredUniformLocationsQueryable = true;
      for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
          var name = test.arrayName + "[" + ii + "]";
          var uniformLocation = gl.getUniformLocation(program, name);
          locations.push(uniformLocation);
          if (ii == test.usedUniformVector) {
              continue;
          }
          // Locations > usedUnformVector may not exist.
          // Locations <= usedUniformVector MUST exist.
          if (ii <= test.usedUniformVector && (uniformLocation === undefined || uniformLocation === null)) {
              allRequiredUniformLocationsQueryable = false;
          }
          gl.uniform4fv(uniformLocation, [1, 0, 0, 1]);
      }
      if (allRequiredUniformLocationsQueryable) {
          testPassed("allRequiredUniformLocationsQueryable is true.");
      }
      else {
          testFailed("allRequiredUniformLocationsQueryable should be true. Was false.");
      }
      var positionLoc = gl.getAttribLocation(program, "a_position");
      wtu.setupIndexedQuad(gl, 1, positionLoc);
      wtu.clearAndDrawIndexedQuad(gl, 1);
      wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
  
      // Check we can read & write each uniform.
      // Note: uniforms past test.usedUniformVector might not exist.
      for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
          gl.uniform4fv(locations[ii], [ii + 4, ii + 2, ii + 3, ii + 1]);
      }
  
      var kEpsilon = 0.01;
      var isSame = function(v1, v2) {
          return Math.abs(v1 - v2) < kEpsilon;
      };
  
      for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
          var location = locations[ii];
          if (location) {
              var value = gl.getUniform(program, locations[ii]);
              if (!isSame(value[0], ii + 4) ||
                  !isSame(value[1], ii + 2) ||
                  !isSame(value[2], ii + 3) ||
                  !isSame(value[3], ii + 1)) {
                  testFailed("location: " + ii + " was not correct value");
                  break;
              }
          }
      }
  }
  
  function runOneTest(gl, test) {
      testUniformOptimizationIssues(test);
  };
  
  function runTests(gl, tests) {
      debug("");
      debug("Test drivers don't over optimize unused array elements");
  
      for (var ii = 0; ii < tests.length; ++ii) {
          runOneTest(gl, tests[ii]);
      }
  };
  
  return {
      setupTests : setupTests,
      runTests : runTests
  };
  
  }());
  