<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL ANGLE_multi_draw Conformance Tests</title>
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/desktop-gl-constants.js"></script>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
<script src="../../js/tests/compositing-test.js"></script>
<script src="../../js/tests/invalid-vertex-attrib-test.js"></script>
</head>
<body>
<script id="vshaderIllegalDrawID" type="x-shader/x-vertex">
attribute vec2 vPosition;
varying vec4 color;
void main()
{
  color = vec4(1.0, 0.0, 0.0, 1.0);
  gl_Position = vec4(vPosition * 2.0 - 1.0, gl_DrawID, 1);
}
</script>
<script id="vshaderDrawIDZero" type="x-shader/x-vertex">
#extension GL_ANGLE_multi_draw : require
attribute vec2 vPosition;
varying vec4 color;
void main()
{
  if (gl_DrawID == 0) {
    color = vec4(0, 1, 0, 1);
  } else {
    color = vec4(1, 0, 0, 1);
  }
  gl_Position = vec4(vPosition * 2.0 - 1.0, 0, 1);
}
</script>
<!-- The behavior of the shaders below is described in runPixelTests() -->
<script id="vshaderWithDrawID" type="x-shader/x-vertex">
#extension GL_ANGLE_multi_draw : require
attribute vec2 vPosition;
attribute float vInstance;
varying vec4 color;
void main()
{
  // color_id = (gl_DrawID / 2) % 3
  float quad_id = float(gl_DrawID / 2);
  float color_id = quad_id - (3.0 * floor(quad_id / 3.0));
  if (color_id < 0.5) {
    color = vec4(1, 0, 0, 1);
  } else if (color_id < 1.5) {
    color = vec4(0, 1, 0, 1);
  } else {
    color = vec4(0, 0, 1, 1);
  }
  mat3 transform = mat3(1.0);
  // vInstance starts at 1.0 on instanced calls
  if (vInstance >= 1.0) {
    transform[0][0] = 0.5;
    transform[1][1] = 0.5;
  }
  if (vInstance == 1.0) {
  } else if (vInstance == 2.0) {
      transform[2][0] = 0.5;
  } else if (vInstance == 3.0) {
      transform[2][1] = 0.5;
  } else if (vInstance == 4.0) {
      transform[2][0] = 0.5;
      transform[2][1] = 0.5;
  }
  gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
}
</script>
<script id="vshaderEmulatedDrawID" type="x-shader/x-vertex">
uniform int drawID;
attribute vec2 vPosition;
attribute float vInstance;
varying vec4 color;
void main()
{
  float quad_id = float(drawID / 2);
  float color_id = quad_id - (3.0 * floor(quad_id / 3.0));
  if (color_id == 0.0) {
    color = vec4(1, 0, 0, 1);
  } else if (color_id == 1.0) {
    color = vec4(0, 1, 0, 1);
  } else {
    color = vec4(0, 0, 1, 1);
  }
  mat3 transform = mat3(1.0);
  // vInstance starts at 1.0 on instanced calls
  if (vInstance >= 1.0) {
    transform[0][0] = 0.5;
    transform[1][1] = 0.5;
  }
  if (vInstance == 1.0) {
  } else if (vInstance == 2.0) {
      transform[2][0] = 0.5;
  } else if (vInstance == 3.0) {
      transform[2][1] = 0.5;
  } else if (vInstance == 4.0) {
      transform[2][0] = 0.5;
      transform[2][1] = 0.5;
  }
  gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
}
</script>
<script id="vshaderNoDrawID" type="x-shader/x-vertex">
attribute vec2 vPosition;
attribute float vInstance;
varying vec4 color;
void main()
{
  color = vec4(1.0, 0.0, 0.0, 1.0);
  mat3 transform = mat3(1.0);
  // vInstance starts at 1.0 on instanced calls
  if (vInstance >= 1.0) {
    transform[0][0] = 0.5;
    transform[1][1] = 0.5;
  }
  if (vInstance == 1.0) {
  } else if (vInstance == 2.0) {
      transform[2][0] = 0.5;
  } else if (vInstance == 3.0) {
      transform[2][1] = 0.5;
  } else if (vInstance == 4.0) {
      transform[2][0] = 0.5;
      transform[2][1] = 0.5;
  }
  gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 color;
void main() {
  gl_FragColor = color;
}
</script>
<div id="description"></div>
<canvas id="canvas" width="128" height="128"> </canvas>
<div id="console"></div>

<script>
"use strict";
description("This test verifies the functionality of the ANGLE_multi_draw extension, if it is available.");

const wtu = WebGLTestUtils;
const canvas = document.getElementById("canvas");
const gl = wtu.create3DContext(canvas);
const instancedExt = gl && gl.getExtension('ANGLE_instanced_arrays');
const bufferUsageSet = [ gl.STATIC_DRAW, gl.DYNAMIC_DRAW ];

// Check if the extension is either both enabled and supported or
// not enabled and not supported.
function runSupportedTest(extensionName, extensionEnabled) {
  const supported = gl.getSupportedExtensions();
  if (supported.indexOf(extensionName) >= 0) {
    if (extensionEnabled) {
      testPassed(extensionName + ' listed as supported and getExtension succeeded');
      return true;
    } else {
      testFailed(extensionName + ' listed as supported but getExtension failed');
    }
  } else {
    if (extensionEnabled) {
      testFailed(extensionName + ' not listed as supported but getExtension succeeded');
    } else {
      testPassed(extensionName + ' not listed as supported and getExtension failed -- this is legal');
    }
  }
  return false;
}

function runTest() {
  if (!gl) {
    return function() {
      testFailed('WebGL context does not exist');
    }
  }

  const extensionName = 'WEBGL_multi_draw';
  const ext = gl.getExtension(extensionName);
  if (!runSupportedTest(extensionName, ext)) {
    return;
  }

  doTest(ext, false);
  doTest(ext, true);
}

function doTest(ext, instanced) {

  function runValidationTests(bufferUsage) {
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0.2,0.2, 0.8,0.2, 0.5,0.8 ]), bufferUsage);

    const indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([ 0, 1, 2, 0]), bufferUsage);

    const instanceBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0, 1, 2, 3 ]), bufferUsage);

    const program = wtu.setupProgram(gl, ["vshaderNoDrawID", "fshader"], ["vPosition", "vInstance"], [0, 1]);
    expectTrue(program != null, "can compile simple program");

    function setupDrawArrays() {
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      gl.enableVertexAttribArray(0);
      gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
    }

    function setupDrawElements() {
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      gl.enableVertexAttribArray(0);
      gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
    }

    function setupInstanced() {
      gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
      gl.enableVertexAttribArray(1);
      gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
      if (wtu.getDefault3DContextVersion() < 2) {
        instancedExt.vertexAttribDivisorANGLE(1, 1);
      } else {
        gl.vertexAttribDivisor(1, 1);
      }
    }

    function setupDrawArraysInstanced() {
      setupDrawArrays();
      setupInstanced();
    }

    function setupDrawElementsInstanced() {
      setupDrawElements();
      setupInstanced();
    }

    // Wrap a draw call in a function to setup the draw call, execute,
    // and check errors.
    // The `drawFunc` is one of the extension entrypoints being tested. It may
    // be undefined if that entrypoint is not supported on the context
    function makeDrawCheck(drawFunc, setup) {
      if (!drawFunc) {
        return function() {};
      }
      return function(f_args, expect, msg) {
        setup();
        drawFunc.apply(ext, f_args);
        wtu.glErrorShouldBe(gl, expect, drawFunc.name + " " + msg);
        gl.disableVertexAttribArray(0);
        gl.disableVertexAttribArray(1);
      }
    }

    const checkMultiDrawArrays = makeDrawCheck(
        ext.multiDrawArraysWEBGL, setupDrawArrays);
    const checkMultiDrawElements = makeDrawCheck(
        ext.multiDrawElementsWEBGL, setupDrawElements);
    const checkMultiDrawArraysInstanced = makeDrawCheck(
        ext.multiDrawArraysInstancedWEBGL, setupDrawArraysInstanced);
    const checkMultiDrawElementsInstanced = makeDrawCheck(
        ext.multiDrawElementsInstancedWEBGL, setupDrawElementsInstanced);

    gl.useProgram(program);

    // Check that drawing a single triangle works
    if (!instanced) {
      checkMultiDrawArrays(
        [gl.TRIANGLES, [0], 0, [3], 0, 1],
        gl.NO_ERROR, "with gl.TRIANGLES");
      checkMultiDrawElements(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, 1],
        gl.NO_ERROR, "with gl.TRIANGLES");
    } else {
      checkMultiDrawElementsInstanced(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, 1],
        gl.NO_ERROR, "with gl.TRIANGLES");
      checkMultiDrawArraysInstanced(
        [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, 1],
        gl.NO_ERROR, "with gl.TRIANGLES");
    }

    // Zero drawcount permitted
    if (!instanced) {
      checkMultiDrawArrays(
        [gl.TRIANGLES, [0], 0, [3], 0, 0],
        gl.NO_ERROR, "with drawcount == 0");
      checkMultiDrawElements(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, 0],
        gl.NO_ERROR, "with drawcount == 0");
    } else {
      checkMultiDrawElementsInstanced(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, 0],
        gl.NO_ERROR, "with drawcount == 0");
      checkMultiDrawArraysInstanced(
        [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, 0],
        gl.NO_ERROR, "with drawcount == 0");
    }

    // Check negative drawcount
    if (!instanced) {
      checkMultiDrawArrays(
        [gl.TRIANGLES, [0], 0, [3], 0, -1],
        gl.INVALID_VALUE, "with drawcount < 0");
      checkMultiDrawElements(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, -1],
        gl.INVALID_VALUE, "with drawcount < 0");
    } else {
      checkMultiDrawElementsInstanced(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, -1],
        gl.INVALID_VALUE, "with drawcount < 0");
      checkMultiDrawArraysInstanced(
        [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, -1],
        gl.INVALID_VALUE, "with drawcount < 0");
    }

    // Check offsets greater than array length
    if (!instanced) {
      checkMultiDrawArrays(
        [gl.TRIANGLES, [0], 1, [3], 0, 1],
        gl.INVALID_OPERATION, "with firstsStart >= firstsList.length");
      checkMultiDrawArrays(
        [gl.TRIANGLES, [0], 0, [3], 1, 1],
        gl.INVALID_OPERATION, "with countsStart >= countsList.length");

      checkMultiDrawElements(
        [gl.TRIANGLES, [3], 1, gl.UNSIGNED_BYTE, [0], 0, 1],
        gl.INVALID_OPERATION, "with countsStart >= countsList.length");
      checkMultiDrawElements(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 1, 1],
        gl.INVALID_OPERATION, "with offsetsStart >= offsetsList.length");
    } else {
      checkMultiDrawArraysInstanced(
        [gl.TRIANGLES, [0], 1, [3], 0, [1], 0, 1],
        gl.INVALID_OPERATION, "with firstsStart >= firstsList.length");
      checkMultiDrawArraysInstanced(
        [gl.TRIANGLES, [0], 0, [3], 1, [1], 0, 1],
        gl.INVALID_OPERATION, "with countsStart >= countsList.length");
      checkMultiDrawArraysInstanced(
        [gl.TRIANGLES, [0], 0, [3], 0, [1], 1, 1],
        gl.INVALID_OPERATION, "with instanceCountsStart >= instanceCountsList.length");

      checkMultiDrawElementsInstanced(
        [gl.TRIANGLES, [3], 1, gl.UNSIGNED_BYTE, [0], 0, [1], 0, 1],
        gl.INVALID_OPERATION, "with countsStart >= countsList.length");
      checkMultiDrawElementsInstanced(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 1, [1], 0, 1],
        gl.INVALID_OPERATION, "with offsetsStart >= offsetsList.length");
      checkMultiDrawElementsInstanced(
        [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 1, 1],
        gl.INVALID_OPERATION, "with instanceCountsStart >= instanceCountsList.length");
    }
  }

  function runShaderTests(bufferUsage) {
    const illegalProgram = wtu.setupProgram(gl, ["vshaderIllegalDrawID", "fshader"], ["vPosition"], [0]);
    expectTrue(illegalProgram == null, "cannot compile program with gl_DrawID but no extension directive");

    const drawIDProgram = wtu.setupProgram(gl, ["vshaderDrawIDZero", "fshader"], ["vPosition"], [0]);
    wtu.setupProgram(gl, ["vshaderDrawIDZero", "fshader"], ["vPosition"], [0]);
    expectTrue(drawIDProgram !== null, "can compile program with gl_DrawID");
    gl.useProgram(drawIDProgram);
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0, 1,0, 0,1, 0,1, 1,0, 1,1 ]), bufferUsage);
    gl.enableVertexAttribArray(0);
    gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    wtu.checkCanvas(gl, [0, 255, 0, 255], "gl_DrawID is 0 for non-Multi* draw calls", 0);
  }

  function runPixelTests(bufferUsage, useSharedArrayBuffer) {
    // An array of quads is tiled across the screen.
    // gl_DrawID is checked by using it to select the color of the draw.
    // Instanced entrypoints are tested here scaling and then instancing the
    // array of quads over four quadrants on the screen.

    // These tests also include "manyDraw" tests which emulate a multiDraw with
    // a Javascript for-loop and gl_DrawID with a uniform constiable. They are
    // included to ensure the test is written correctly.

    const width = gl.canvas.width;
    const height = gl.canvas.height;
    const x_count = 8;
    const y_count = 8;
    const quad_count = x_count * y_count;
    const tri_count = quad_count * 2;
    const tileSize = [ 1/x_count, 1/y_count ];
    const tilePixelSize = [ Math.floor(width / x_count), Math.floor(height / y_count) ];
    const quadRadius = [ 0.25 * tileSize[0], 0.25 * tileSize[1] ];
    const pixelCheckSize = [ Math.floor(quadRadius[0] * width), Math.floor(quadRadius[1] * height) ];

    function getTileCenter(x, y) {
      return [ tileSize[0] * (0.5 + x), tileSize[1] * (0.5 + y) ];
    }

    function getQuadVertices(x, y) {
      const center = getTileCenter(x, y);
      return [
        [center[0] - quadRadius[0], center[1] - quadRadius[1], 0],
        [center[0] + quadRadius[0], center[1] - quadRadius[1], 0],
        [center[0] + quadRadius[0], center[1] + quadRadius[1], 0],
        [center[0] - quadRadius[0], center[1] + quadRadius[1], 0],
      ]
    }

    const indicesData = [];
    const verticesData = [];
    const nonIndexedVerticesData = [];
    {
      const is = new Uint16Array([0, 1, 2, 0, 2, 3]);
      for (let y = 0; y < y_count; ++y) {
        for (let x = 0; x < x_count; ++x) {
          const quadIndex = y * x_count + x;
          const starting_index = 4 * quadIndex;
          const vs = getQuadVertices(x, y);
          for (let i = 0; i < is.length; ++i) {
            indicesData.push(starting_index + is[i]);
          }
          for (let i = 0; i < vs.length; ++i) {
            for (let v = 0; v < vs[i].length; ++v) verticesData.push(vs[i][v]);
          }
          for (let i = 0; i < is.length; ++i) {
            for (let v = 0; v < vs[is[i]].length; ++v) nonIndexedVerticesData.push(vs[is[i]][v]);
          }
        }
      }
    }

    const indices = new Uint16Array(indicesData);
    const vertices = new Float32Array(verticesData);
    const nonIndexedVertices = new Float32Array(nonIndexedVerticesData);

    const indexBuffer = gl.createBuffer();
    const vertexBuffer = gl.createBuffer();
    const nonIndexedVertexBuffer = gl.createBuffer();
    const instanceBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, nonIndexedVertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, nonIndexedVertices, bufferUsage);

    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, bufferUsage);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, bufferUsage);

    gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 2, 3, 4]), bufferUsage);

    function checkResult(config, msg) {
      const rects = [];
      const expected = [
        [255, 0, 0, 255],
        [0, 255, 0, 255],
        [0, 0, 255, 255],
      ];
      for (let y = 0; y < y_count; ++y) {
        for (let x = 0; x < x_count; ++x) {
          const center_x = x * tilePixelSize[0] + Math.floor(tilePixelSize[0] / 2);
          const center_y = y * tilePixelSize[1] + Math.floor(tilePixelSize[1] / 2);
          const quadID = y * x_count + x;
          const colorID = config.drawID ? quadID % 3 : 0;
          if (config.instanced) {
            rects.push(wtu.makeCheckRect(
                center_x / 2 - Math.floor(pixelCheckSize[0] / 4),
                center_y / 2 - Math.floor(pixelCheckSize[1] / 4),
                pixelCheckSize[0] / 2,
                pixelCheckSize[1] / 2,
                expected[colorID],
                msg + " (" + x + "," + y + ")", 0));
            rects.push(wtu.makeCheckRect(
                center_x / 2 - Math.floor(pixelCheckSize[0] / 4) + width / 2,
                center_y / 2 - Math.floor(pixelCheckSize[1] / 4),
                pixelCheckSize[0] / 2,
                pixelCheckSize[1] / 2,
                expected[colorID],
                msg + " (" + x + "," + y + ")", 0));
            rects.push(wtu.makeCheckRect(
                center_x / 2 - Math.floor(pixelCheckSize[0] / 4),
                center_y / 2 - Math.floor(pixelCheckSize[1] / 4) + height / 2,
                pixelCheckSize[0] / 2,
                pixelCheckSize[1] / 2,
                expected[colorID],
                msg + " (" + x + "," + y + ")", 0));
            rects.push(wtu.makeCheckRect(
                center_x / 2 - Math.floor(pixelCheckSize[0] / 4) + width / 2,
                center_y / 2 - Math.floor(pixelCheckSize[1] / 4) + height / 2,
                pixelCheckSize[0] / 2,
                pixelCheckSize[1] / 2,
                expected[colorID],
                msg + " (" + x + "," + y + ")", 0));
          } else {
            rects.push(wtu.makeCheckRect(
                center_x - Math.floor(pixelCheckSize[0] / 2),
                center_y - Math.floor(pixelCheckSize[1] / 2),
                pixelCheckSize[0],
                pixelCheckSize[1],
                expected[colorID],
                msg + " (" + x + "," + y + ")", 0));
          }
        }
      }
      wtu.checkCanvasRects(gl, rects);
    }

    function newIntArray(count) {
      if (!useSharedArrayBuffer) {
        return new Int32Array(count);
      }
      let sab = new SharedArrayBuffer(count * Int32Array.BYTES_PER_ELEMENT);
      return new Int32Array(sab);
    }

    const firsts = newIntArray(tri_count);
    const counts = newIntArray(tri_count);
    const offsets = newIntArray(tri_count);
    const instances = newIntArray(tri_count);

    for (let i = 0; i < firsts.length; ++i) firsts[i] = i * 3;
    counts.fill(3);
    for (let i = 0; i < offsets.length; ++i) offsets[i] = i * 3 * 2;
    instances.fill(4);

    const firstsOffset = 47;
    const countsOffset = firstsOffset + firsts.length;
    const offsetsOffset = countsOffset + counts.length;
    const instancesOffset = offsetsOffset + instances.length;

    const buffer = newIntArray(firstsOffset + firsts.length + counts.length + offsets.length + instances.length);
    buffer.set(firsts, firstsOffset);
    buffer.set(counts, countsOffset);
    buffer.set(offsets, offsetsOffset);
    buffer.set(instances, instancesOffset);

    let drawIDLocation;

    const multiDrawArrays = function() {
      ext.multiDrawArraysWEBGL(gl.TRIANGLES, firsts, 0, counts, 0, tri_count);
    }

    const multiDrawArraysWithNonzeroOffsets = function() {
      ext.multiDrawArraysWEBGL(gl.TRIANGLES, buffer, firstsOffset, buffer, countsOffset, tri_count);
    }

    const multiDrawElements = function() {
      ext.multiDrawElementsWEBGL(gl.TRIANGLES, counts, 0, gl.UNSIGNED_SHORT, offsets, 0, tri_count);
    }

    const multiDrawElementsWithNonzeroOffsets = function() {
      ext.multiDrawElementsWEBGL(gl.TRIANGLES, buffer, countsOffset, gl.UNSIGNED_SHORT, buffer, offsetsOffset, tri_count);
    }

    const multiDrawArraysInstanced = function() {
      ext.multiDrawArraysInstancedWEBGL(gl.TRIANGLES, firsts, 0, counts, 0, instances, 0, tri_count);
    }

    const multiDrawArraysInstancedWithNonzeroOffsets = function() {
      ext.multiDrawArraysInstancedWEBGL(gl.TRIANGLES, buffer, firstsOffset, buffer, countsOffset, buffer, instancesOffset, tri_count);
    }

    const multiDrawElementsInstanced = function() {
      ext.multiDrawElementsInstancedWEBGL(gl.TRIANGLES, counts, 0, gl.UNSIGNED_SHORT, offsets, 0, instances, 0, tri_count);
    }

    const multiDrawElementsInstancedWithNonzeroOffsets = function() {
      ext.multiDrawElementsInstancedWEBGL(gl.TRIANGLES, buffer, countsOffset, gl.UNSIGNED_SHORT, buffer, offsetsOffset, buffer, instancesOffset, tri_count);
    }

    const manyDrawArrays = function() {
      for (let i = 0; i < tri_count; ++i) {
        gl.drawArrays(gl.TRIANGLES, firsts[i], counts[i]);
      }
    }

    const manyDrawElements = function() {
      for (let i = 0; i < tri_count; ++i) {
        gl.drawElements(gl.TRIANGLES, counts[i], gl.UNSIGNED_SHORT, offsets[i]);
      }
    }

    const manyDrawArraysEmulateDrawID = function() {
      for (let i = 0; i < tri_count; ++i) {
        gl.uniform1i(drawIDLocation, i);
        gl.drawArrays(gl.TRIANGLES, firsts[i], counts[i]);
      }
    }

    const manyDrawElementsEmulateDrawID = function() {
      for (let i = 0; i < tri_count; ++i) {
        gl.uniform1i(drawIDLocation, i);
        gl.drawElements(gl.TRIANGLES, counts[i], gl.UNSIGNED_SHORT, offsets[i]);
      }
    }

    function drawArraysInstanced() {
      if (wtu.getDefault3DContextVersion() < 2) {
        instancedExt.drawArraysInstancedANGLE.apply(instancedExt, arguments);
      } else {
        gl.drawArraysInstanced.apply(gl, arguments);
      }
    }

    function drawElementsInstanced() {
      if (wtu.getDefault3DContextVersion() < 2) {
        instancedExt.drawElementsInstancedANGLE.apply(instancedExt, arguments);
      } else {
        gl.drawElementsInstanced.apply(gl, arguments);
      }
    }

    function vertexAttribDivisor(attrib, divisor) {
      if (wtu.getDefault3DContextVersion() < 2) {
        instancedExt.vertexAttribDivisorANGLE(attrib, divisor);
      } else {
        gl.vertexAttribDivisor(attrib, divisor);
      }
    }

    const manyDrawArraysInstanced = function() {
      for (let i = 0; i < tri_count; ++i) {
        drawArraysInstanced(gl.TRIANGLES, firsts[i], counts[i], 4);
      }
    }

    const manyDrawElementsInstanced = function() {
      for (let i = 0; i < tri_count; ++i) {
        drawElementsInstanced(gl.TRIANGLES, counts[i], gl.UNSIGNED_SHORT, offsets[i], 4);
      }
    }

    const manyDrawArraysInstancedEmulateDrawID = function() {
      for (let i = 0; i < tri_count; ++i) {
        gl.uniform1i(drawIDLocation, i);
        drawArraysInstanced(gl.TRIANGLES, firsts[i], counts[i], 4);
      }
    }

    const manyDrawElementsInstancedEmulateDrawID = function() {
      for (let i = 0; i < tri_count; ++i) {
        gl.uniform1i(drawIDLocation, i);
        drawElementsInstanced(gl.TRIANGLES, counts[i], gl.UNSIGNED_SHORT, offsets[i], 4);
      }
    }

    function checkDraw(config) {
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

      if (config.indexed) {
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.enableVertexAttribArray(0);
        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
      } else {
        gl.bindBuffer(gl.ARRAY_BUFFER, nonIndexedVertexBuffer);
        gl.enableVertexAttribArray(0);
        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
      }

      if (config.instanced) {
        gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
        gl.enableVertexAttribArray(1);
        gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
        vertexAttribDivisor(1, 1);
      }

      config.drawFunc();
      wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
      checkResult(config, config.drawFunc.name + (
        config.instanced ? ' instanced' : ''
      ) + (
        config.drawID ? ' with gl_DrawID' : ''
      ) + (
        useSharedArrayBuffer ? ' and SharedArrayBuffer' : ''
      ));

      gl.disableVertexAttribArray(0);
      gl.disableVertexAttribArray(1);
    }

    const noDrawIDProgram = wtu.setupProgram(gl, ["vshaderNoDrawID", "fshader"], ["vPosition", "vInstance"], [0, 1]);
    expectTrue(noDrawIDProgram != null, "can compile simple program");
    if (noDrawIDProgram) {
      gl.useProgram(noDrawIDProgram);

      if (!instanced) {
        checkDraw({
          drawFunc: multiDrawArrays,
          drawID: false,
        });
        checkDraw({
          drawFunc: multiDrawArraysWithNonzeroOffsets,
          drawID: false,
        });
        checkDraw({
          drawFunc: multiDrawElements,
          indexed: true,
          drawID: false,
        });
        checkDraw({
          drawFunc: multiDrawElementsWithNonzeroOffsets,
          indexed: true,
          drawID: false,
        });
        checkDraw({
          drawFunc: manyDrawArrays,
          drawID: false,
        });
        checkDraw({
          drawFunc: manyDrawElements,
          indexed: true,
          drawID: false,
        });
      } else {
        checkDraw({
          drawFunc: multiDrawArraysInstanced,
          drawID: false,
          instanced: true,
        });
        checkDraw({
          drawFunc: multiDrawArraysInstancedWithNonzeroOffsets,
          drawID: false,
          instanced: true,
        });
        checkDraw({
          drawFunc: multiDrawElementsInstanced,
          indexed: true,
          drawID: false,
          instanced: true,
        });
        checkDraw({
          drawFunc: multiDrawElementsInstancedWithNonzeroOffsets,
          indexed: true,
          drawID: false,
          instanced: true,
        });
        checkDraw({
          drawFunc: manyDrawArraysInstanced,
          drawID: false,
          instanced: true,
        });
        checkDraw({
          drawFunc: manyDrawElementsInstanced,
          indexed: true,
          drawID: false,
          instanced: true,
        });
      }
    }

    const withDrawIDProgram = wtu.setupProgram(gl, ["vshaderWithDrawID", "fshader"], ["vPosition", "vInstance"], [0, 1]);
    expectTrue(withDrawIDProgram != null, "can compile program with ANGLE_multi_draw");
    if (withDrawIDProgram) {
      gl.useProgram(withDrawIDProgram);

      if (!instanced) {
        checkDraw({
          drawFunc: multiDrawArrays,
          drawID: true,
        });
        checkDraw({
          drawFunc: multiDrawArraysWithNonzeroOffsets,
          drawID: true,
        });
        checkDraw({
          drawFunc: multiDrawElements,
          indexed: true,
          drawID: true,
        });
        checkDraw({
          drawFunc: multiDrawElementsWithNonzeroOffsets,
          indexed: true,
          drawID: true,
        });
      } else {
        checkDraw({
          drawFunc: multiDrawArraysInstanced,
          drawID: true,
          instanced: true,
        });
        checkDraw({
          drawFunc: multiDrawArraysInstancedWithNonzeroOffsets,
          drawID: true,
          instanced: true,
        });
        checkDraw({
          drawFunc: multiDrawElementsInstanced,
          indexed: true,
          drawID: true,
          instanced: true,
        });
        checkDraw({
          drawFunc: multiDrawElementsInstancedWithNonzeroOffsets,
          indexed: true,
          drawID: true,
          instanced: true,
        });
      }
    }

    const emulatedDrawIDProgram = wtu.setupProgram(gl, ["vshaderEmulatedDrawID", "fshader"], ["vPosition", "vInstance"], [0, 1]);
    expectTrue(emulatedDrawIDProgram != null, "can compile program to emulate gl_DrawID");
    drawIDLocation = gl.getUniformLocation(emulatedDrawIDProgram, "drawID");
    if (emulatedDrawIDProgram) {
      gl.useProgram(emulatedDrawIDProgram);

      if (!instanced) {
        checkDraw({
          drawFunc: manyDrawArraysEmulateDrawID,
          drawID: true,
        });
        checkDraw({
          drawFunc: manyDrawElementsEmulateDrawID,
          indexed: true,
          drawID: true,
        });
      } else {
        checkDraw({
          drawFunc: manyDrawArraysInstancedEmulateDrawID,
          drawID: true,
          instanced: true,
        });
        checkDraw({
          drawFunc: manyDrawElementsInstancedEmulateDrawID,
          indexed: true,
          drawID: true,
          instanced: true,
        });
      }
    }
  }

  for (let i = 0; i < bufferUsageSet.length; i++) {
    let bufferUsage = bufferUsageSet[i];
    debug("Testing with BufferUsage = " + bufferUsage);
    runValidationTests(bufferUsage);
    runShaderTests(bufferUsage);
    runPixelTests(bufferUsage, false);
  }

  // Run a subset of the pixel tests with SharedArrayBuffer if supported.
  if (window.SharedArrayBuffer) {
    runPixelTests(bufferUsageSet[0], true);
  }
}

function waitForComposite() {
  debug('wait for composite');
  return new Promise(resolve => wtu.waitForComposite(resolve));
}

async function testPreserveDrawingBufferFalse(gl, drawFn) {
  debug('');
  debug('test preserveDrawingBuffer: false');

  if (drawFn(gl)) {
    debug('skipped: extension does not exist');
    return;
  }

  wtu.checkCanvasRect(gl, 0, 0, 20, 20, [255, 0, 0, 255],
      "canvas should be red");

  // enable scissor here, before compositing, to make sure it's correctly
  // ignored and restored
  gl.scissor(0, 10, 10, 10);
  gl.enable(gl.SCISSOR_TEST);

  await waitForComposite();

  // scissor was set earlier
  gl.clearColor(0, 0, 1, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  wtu.checkCanvasRect(gl, 0, 10, 10, 10, [0, 0, 255, 255],
      "cleared corner should be blue, stencil should be preserved");
  wtu.checkCanvasRect(gl, 0, 0, 10, 10, [0, 0, 0, 0],
      "remainder of buffer should be cleared");

  gl.disable(gl.SCISSOR_TEST);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}

async function testPreserveDrawingBufferTrue(gl, drawFn) {
  debug('');
  debug('test preserveDrawingBuffer: true');
  if (drawFn(gl)) {
    debug('skipped: extension does not exist');
    return;
  }

  wtu.checkCanvasRect(gl, 0, 0, 20, 20, [255, 0, 0, 255],
      "canvas should be red");

  await waitForComposite();

  wtu.checkCanvasRect(gl, 0, 0, 20, 20, [255, 0, 0, 255],
      "canvas should be red");
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}

async function doCompositingTests([glPreserveDrawingBufferFalse, glPreserveDrawingBufferTrue], drawFn) {
  debug('');
  debug(drawFn.name);

  await testPreserveDrawingBufferFalse(glPreserveDrawingBufferFalse, drawFn);
  await testPreserveDrawingBufferTrue(glPreserveDrawingBufferTrue, drawFn);
}

async function runDrawTests(testFn) {
  function drawArrays(gl) {
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }

  function drawElements(gl) {
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
  }

  function drawArraysInstanced(gl) {
    gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 1);
  }

  function drawElementsInstanced(gl) {
    gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 1);
  }

  function multiDrawArraysWEBGL(gl) {
    const ext = gl.getExtension('WEBGL_multi_draw');
    if (!ext) {
      throw 'Should not have run this test without WEBGL_multi_draw';
    }

    ext.multiDrawArraysWEBGL(
        gl.TRIANGLES,
        [0], 0, // firsts
        [6], 0, // counts
        1,      // drawCount
    );
  }

  function multiDrawElementsWEBGL(gl) {
    const ext = gl.getExtension('WEBGL_multi_draw');
    if (!ext) {
      throw 'Should not have run this test without WEBGL_multi_draw';
    }

    ext.multiDrawElementsWEBGL(
        gl.TRIANGLES,
        [6], 0,   // counts
        gl.UNSIGNED_BYTE,
        [0], 0,   // offsets
        1,        // drawCount
    );
  }

  function multiDrawArraysInstancedWEBGL(gl) {
    const ext = gl.getExtension('WEBGL_multi_draw');
    if (!ext) {
      throw 'Should not have run this test without WEBGL_multi_draw';
    }
    ext.multiDrawArraysInstancedWEBGL(
        gl.TRIANGLES,
        [0], 0, // firsts
        [6], 0, // counts
        [1], 0, // instances
        1,      // drawCount
    );
  }

  function multiDrawElementsInstancedWEBGL(gl) {
    const ext = gl.getExtension('WEBGL_multi_draw');
    if (!ext) {
      throw 'Should not have run this test without WEBGL_multi_draw';
    }
    ext.multiDrawElementsInstancedWEBGL(
        gl.TRIANGLES,
        [6], 0, // counts
        gl.UNSIGNED_BYTE,
        [0], 0, // offsets
        [1], 0, // instances
        1,      // drawCount
    );
  }

  await testFn(drawArrays);             // sanity check
  await testFn(drawElements);           // sanity check

  // It's only legal to call testFn if the extension is supported,
  // since the invalid vertex attrib tests, in particular, expect the
  // draw function to have an effect.
  if (gl.getExtension('WEBGL_multi_draw')) {
    await testFn(multiDrawArraysWEBGL);
    await testFn(multiDrawElementsWEBGL);
    await testFn(multiDrawArraysInstancedWEBGL);
    await testFn(multiDrawElementsInstancedWEBGL);
  }
}

async function runCompositingTests() {
  const compositingTestFn = createCompositingTestFn({
    webglVersion: 1,
    shadersFn(gl) {
      const vs = `\
      //#extension GL_ANGLE_multi_draw : enable
      attribute vec4 position;
      void main() {
        gl_Position = position;
      }
      `;
      const fs = `\
      precision mediump float;
      void main() {
        gl_FragColor = vec4(1, 0, 0, 1);
      }
      `;
      return [vs, fs];
    },
  });
  await runDrawTests(compositingTestFn);
}

async function runInvalidAttribTests(gl) {
  const invalidAttribTestFn = createInvalidAttribTestFn(gl);
  await runDrawTests(invalidAttribTestFn);
}

function testSideEffects() {
  debug("")
  debug("Testing that ANGLE_instanced_arrays is implicitly enabled")
  const canvas = document.createElement("canvas");
  const gl = wtu.create3DContext(canvas, undefined, 1);
  if (!gl) {
    testFailed('WebGL context creation failed');
    return;
  }
  gl.enableVertexAttribArray(0);
  const VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE = 0x88FE;
  gl.getVertexAttrib(0, VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE);
  wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "divisor enum unknown");
  const ext = gl.getExtension("WEBGL_multi_draw");
  if (ext) {
    debug("WEBGL_multi_draw enabled");
    gl.getVertexAttrib(0, VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "divisor enum known");
  }
}

async function main() {
  runTest();
  testSideEffects();
  await runInvalidAttribTests(gl);
  await runCompositingTests();
  finishTest();
}
main();

var successfullyParsed = true;
</script>
</body>
</html>
