summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html1021
1 files changed, 1021 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html
new file mode 100644
index 0000000000..37f3e23762
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html
@@ -0,0 +1,1021 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL ANGLE_base_vertex_base_instance 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="vshaderBaseInstanceWithoutExt" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ color = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_Position = vec4(vPosition * 2.0 - 1.0, gl_BaseInstance, 1);
+}
+</script>
+<!-- Check gl_InstanceID starts at 0 regardless of gl_BaseInstance -->
+<script id="vshaderInstanceIDCheck" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ if (gl_InstanceID == 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>
+<script id="vshaderBaseVertexWithoutExt" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ color = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_Position = vec4(vPosition * 2.0 - 1.0, gl_BaseVertex, 1);
+}
+</script>
+<script id="vshaderWithExt" type="x-shader/x-vertex">#version 300 es
+#extension GL_ANGLE_base_vertex_base_instance : require
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ color = vec4(0, 1, 0, 1);
+ gl_Position = vec4(vPosition * 2.0 - 1.0, 0, 1);
+}
+</script>
+<!-- Check gl_VertexID starts at gl_BaseVertex -->
+<script id="vshaderVertexIDCheck" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ if (gl_VertexID >= 3) {
+ color = vec4(0, 1, 0, 1);
+ } else {
+ color = vec4(1, 0, 0, 1);
+ }
+ gl_Position = vec4(vPosition * 2.0 - 1.0, 0, 1);
+}
+</script>
+<script id="vshaderSimple" type="x-shader/x-vertex">#version 300 es
+ layout(location = 0) in vec2 vPosition;
+ layout(location = 1) in float vInstance;
+ out vec4 color;
+ void main()
+ {
+ if (vInstance <= 0.0) {
+ color = vec4(1.0, 0.0, 0.0, 1.0);
+ } else if (vInstance <= 1.0) {
+ color = vec4(0.0, 1.0, 0.0, 1.0);
+ } else if (vInstance <= 2.0) {
+ color = vec4(0.0, 0.0, 1.0, 1.0);
+ } else {
+ color = vec4(0.0, 0.0, 0.0, 1.0);
+ }
+
+ gl_Position = vec4(vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
+ }
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+ precision mediump float;
+ in vec4 color;
+ out vec4 oColor;
+ void main() {
+ oColor = 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 WEBGL_[multi]_draw_basevertex_base_instance extension, if it is available.");
+
+const wtu = WebGLTestUtils;
+const canvas = document.getElementById("canvas");
+canvas.style.backgroundColor = '#000';
+canvas.style.imageRendering = 'pixelated'; // Because Chrome doesn't support crisp-edges.
+canvas.style.imageRendering = 'crisp-edges';
+const attribs = {
+ antialias: false,
+};
+const gl = wtu.create3DContext(canvas, attribs, 2);
+
+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) ];
+const bufferUsageSet = [ gl.STATIC_DRAW, gl.DYNAMIC_DRAW ];
+
+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 = [];
+let verticesData = [];
+let nonIndexedVerticesData = [];
+const instanceIDsData = Array.from(Array(x_count).keys());
+const is = new Uint16Array([0, 1, 2, 0, 2, 3]);
+// Rects in the same column are within a vertex array, testing gl_VertexID, gl_BaseVertex
+// Rects in the same row are drawn by instancing, testing gl_InstanceID, gl_BaseInstance
+for (let y = 0; y < y_count; ++y) {
+ // v3 ---- v2
+ // | |
+ // | |
+ // v0 ---- v1
+
+ // Get only one column of quad vertices as our geometry
+ // Rely on BaseInstance to duplicate on x axis
+ const vs = getQuadVertices(0, y);
+
+ for (let i = 0; i < vs.length; ++i) {
+ verticesData = verticesData.concat(vs[i]);
+ }
+
+ for (let i = 0; i < is.length; ++i) {
+ nonIndexedVerticesData = nonIndexedVerticesData.concat(vs[is[i]]);
+ }
+}
+
+// Build the indicesData used by drawElements*
+for (let i = 0; i < y_count; ++i) {
+ let oi = 6 * i;
+ let ov = 4 * i;
+ for (let j = 0; j < is.length; ++j) {
+ indicesData[oi + j] = is[j] + ov;
+ }
+}
+
+const indices = new Uint16Array(indicesData);
+const vertices = new Float32Array(verticesData);
+const nonIndexedVertices = new Float32Array(nonIndexedVerticesData);
+const instanceIDs = new Float32Array(instanceIDsData);
+
+const indexBuffer = gl.createBuffer();
+const vertexBuffer = gl.createBuffer();
+const nonIndexedVertexBuffer = gl.createBuffer();
+const instanceIDBuffer = gl.createBuffer();
+
+const drawArraysDrawCount = x_count / 2;
+let drawArraysParams = {
+ drawCount: drawArraysDrawCount,
+ firsts: new Uint32Array(drawArraysDrawCount).fill(0),
+ counts: new Uint32Array(drawArraysDrawCount).fill(y_count * 6),
+ instances: new Uint32Array(drawArraysDrawCount).fill(2),
+ baseInstances: new Uint32Array(drawArraysDrawCount)
+};
+
+for (let i = 0; i < x_count / 2; ++i) {
+ drawArraysParams.baseInstances[i] = i * 2;
+}
+
+const drawElementsDrawCount = x_count * y_count / 2;
+let drawElementsParams = {
+ drawCount: drawElementsDrawCount,
+ offsets: new Uint32Array(drawElementsDrawCount).fill(0),
+ counts: new Uint32Array(drawElementsDrawCount).fill(6),
+ instances: new Uint32Array(drawElementsDrawCount).fill(2),
+ baseVertices: new Uint32Array(drawElementsDrawCount),
+ baseInstances: new Uint32Array(drawElementsDrawCount)
+};
+
+let b = 0;
+for (let v = 0; v < y_count; ++v) {
+ for (let i = 0; i < x_count; i+=2) {
+ drawElementsParams.baseVertices[b] = v * 4;
+ drawElementsParams.baseInstances[b] = i;
+ ++b;
+ }
+}
+
+function setupGeneralBuffers(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, nonIndexedVertexBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, nonIndexedVertices, bufferUsage);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceIDBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, instanceIDs, bufferUsage);
+}
+
+// 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');
+ }
+ }
+
+ doTest('WEBGL_draw_instanced_base_vertex_base_instance', false);
+ doTest('WEBGL_multi_draw_instanced_base_vertex_base_instance', true);
+
+ testGlslBuiltins();
+}
+
+// -
+
+function* range(n) {
+ for (let i = 0; i < n; i++) {
+ yield i;
+ }
+}
+
+function crossCombine(...args) {
+ function crossCombine2(listA, listB) {
+ const listC = [];
+ for (const a of listA) {
+ for (const b of listB) {
+ const c = Object.assign({}, a, b);
+ listC.push(c);
+ }
+ }
+ return listC;
+ }
+
+ let res = [{}];
+ while (args.length) {
+ const next = args.shift();
+ next[0].defined;
+ res = crossCombine2(res, next);
+ }
+ return res;
+}
+
+// -
+
+const PASSTHROUGH_FRAG_SRC = `\
+#version 300 es
+precision mediump float;
+in vec4 v_color;
+out vec4 o_color;
+
+void main() {
+ o_color = v_color;
+}
+`;
+
+function testGlslBuiltins() {
+ const EXT = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance');
+
+ const vertid_prog = (() => {
+ const vert_src = `\
+#version 300 es
+#line 405
+layout(location = 0) in int a_vertex_id; // Same as gl_VertexID
+out vec4 v_color;
+
+void main() {
+ gl_Position = vec4(0,0,0,1);
+ gl_PointSize = 1.0;
+ v_color = vec4(float(gl_VertexID), float(a_vertex_id),0,0);
+ v_color /= 255.0;
+}
+`;
+ const prog = wtu.setupProgram(gl, [vert_src, PASSTHROUGH_FRAG_SRC],
+ undefined, undefined, /*logShaders*/ true);
+ expectTrue(!!prog, `make_vertid_prog failed`);
+ return prog;
+ })();
+
+ const instid_prog = (() => {
+ const vert_src = `\
+#version 300 es
+#line 425
+layout(location = 0) in int a_vertex_id; // Same as gl_VertexID
+layout(location = 1) in int a_instance_div1; // Same as base_instance+gl_InstanceID
+layout(location = 2) in int a_instance_div2; // Same as base_instance+floor(gl_InstanceID/2)
+layout(location = 3) in int a_instance_div3; // Same as base_instance+floor(gl_InstanceID/3)
+out vec4 v_color;
+
+void main() {
+ gl_Position = vec4(0,0,0,1);
+ gl_PointSize = 1.0;
+ v_color = vec4(float(gl_InstanceID), float(a_instance_div1),
+ float(a_instance_div2), float(a_instance_div3));
+ v_color /= 255.0;
+}
+`;
+ const prog = wtu.setupProgram(gl, [vert_src, PASSTHROUGH_FRAG_SRC],
+ undefined, undefined, /*logShaders*/ true);
+ expectTrue(!!prog, `make_instid_prog failed`);
+ return prog;
+ })();
+
+ const COUNT_UP_DATA = new Int32Array(1000);
+ for (const i in COUNT_UP_DATA) {
+ COUNT_UP_DATA[i] = i;
+ }
+
+ const vertex_id_buf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertex_id_buf);
+ gl.bufferData(gl.ARRAY_BUFFER, COUNT_UP_DATA, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribIPointer(0, 1, gl.INT, 0, 0);
+
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribIPointer(1, 1, gl.INT, 0, 0);
+ gl.vertexAttribDivisor(1, 1);
+
+ gl.enableVertexAttribArray(2);
+ gl.vertexAttribIPointer(2, 1, gl.INT, 0, 0);
+ gl.vertexAttribDivisor(2, 2);
+
+ gl.enableVertexAttribArray(3);
+ gl.vertexAttribIPointer(3, 1, gl.INT, 0, 0);
+ gl.vertexAttribDivisor(3, 3);
+
+ const index_buf = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buf);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, COUNT_UP_DATA, gl.STATIC_DRAW);
+
+ gl.canvas.width = gl.canvas.height = 1;
+ gl.canvas.style.width = gl.canvas.style.height = '1em';
+ gl.viewport(0, 0, 1, 1);
+
+ const expect_pixel = (() => {
+ const was = new Uint8Array(4);
+ return (desc, subtest, expected) => {
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, was);
+ if (!areArraysEqual(was, expected)) {
+ testFailed(`${subtest}: Expected [${expected}], was [${was}]. desc: ${JSON.stringify(desc)}`);
+ } else {
+ debug(`${subtest}: Was [${was}] as expected.`);
+ }
+ };
+ })();
+
+ // Common setup complete
+ // -
+ // Create testcases
+
+ const DRAW_FUNC_COMBINER = [{
+ name: 'drawArraysInstanced',
+ draw: desc => {
+ if (desc.base_vert) return false;
+ if (desc.base_inst) return false;
+ gl.drawArraysInstanced(gl[desc.mode], desc.first_vert,
+ desc.vert_count, desc.inst_count);
+ return true;
+ },
+ }, {
+ name: 'drawElementsInstanced',
+ draw: desc => {
+ if (desc.base_vert) return false;
+ if (desc.base_inst) return false;
+ gl.drawElementsInstanced(gl[desc.mode], desc.vert_count,
+ gl.UNSIGNED_INT, 4*desc.first_vert, desc.inst_count);
+ return true;
+ },
+ }, {
+ name: 'drawArraysInstancedBaseInstanceWEBGL',
+ draw: desc => {
+ if (desc.base_vert) return false;
+ if (!EXT) return false;
+ EXT.drawArraysInstancedBaseInstanceWEBGL(gl[desc.mode],
+ desc.first_vert, desc.vert_count, desc.inst_count,
+ desc.base_inst);
+ return true;
+ },
+ }, {
+ name: 'drawElementsInstancedBaseVertexBaseInstanceWEBGL',
+ draw: desc => {
+ if (!EXT) return false;
+ EXT.drawElementsInstancedBaseVertexBaseInstanceWEBGL(
+ gl[desc.mode], desc.vert_count, gl.UNSIGNED_INT, 4*desc.first_vert,
+ desc.inst_count, desc.base_vert, desc.base_inst);
+ return true;
+ },
+ }];
+
+ // -
+
+ function make_key_combiner(key, vals) {
+ const ret = [];
+ for (const v of vals) {
+ const cur = {};
+ cur[key] = v;
+ ret.push(cur);
+ }
+ return ret;
+ }
+
+ const TEST_DESCS = crossCombine(
+ DRAW_FUNC_COMBINER,
+ make_key_combiner('base_vert', [0,1,2]),
+ make_key_combiner('vert_count', [0,1,2]),
+ make_key_combiner('base_inst', [0,1,2]),
+ make_key_combiner('inst_count', range(10)),
+ make_key_combiner('first_vert', [0,1,2]),
+ );
+ console.log('TEST_DESCS', TEST_DESCS);
+
+ // -
+ // Run testcases
+
+ gl.disable(gl.DEPTH_TEST);
+ gl.disable(gl.STENCIL_TEST);
+ gl.disable(gl.BLEND);
+
+ for (const desc of TEST_DESCS) {
+ gl.disable(gl.SCISSOR_TEST);
+ gl.clearBufferfv(gl.COLOR, 0, [1,0,0,1]);
+
+ // From OpenGL ES 3.2 spec section 10.5
+ // https://www.khronos.org/registry/OpenGL/specs/es/3.2/es_spec_3.2.pdf
+ // The index of any element transferred to the GL by DrawArraysOneInstance
+ // is referred to as its vertex ID, and may be read by a vertex shader as gl_VertexID.
+ // The vertex ID of the ith element transferred is first + i.
+ const last_gl_vert_id = desc.base_vert + desc.first_vert + desc.vert_count - 1;
+ const last_vert_id = last_gl_vert_id;
+ const last_inst_id = desc.inst_count - 1;
+ const last_inst_div1 = desc.base_inst + last_inst_id;
+ const last_inst_div2 = desc.base_inst + Math.floor(last_inst_id / 2);
+ const last_inst_div3 = desc.base_inst + Math.floor(last_inst_id / 3);
+
+ gl.useProgram(vertid_prog);
+ if (!desc.draw(desc)) continue;
+ debug('\ndesc: ' + JSON.stringify(desc));
+
+ wtu.glErrorAssert(gl, 0);
+ if (!desc.vert_count || !desc.inst_count) {
+ expect_pixel(desc, 'vertid_prog', [255, 0, 0, 255]);
+ continue;
+ }
+
+ expect_pixel(desc, 'vertid_prog', [last_gl_vert_id, last_vert_id, 0, 0]);
+
+ gl.useProgram(instid_prog);
+ desc.draw(desc);
+ expect_pixel(desc, 'instid_prog', [last_inst_id, last_inst_div1, last_inst_div2, last_inst_div3]);
+ }
+}
+
+// -
+
+function doTest(extensionName, multiDraw) {
+ const ext = gl.getExtension(extensionName);
+ if (!runSupportedTest(extensionName, ext)) {
+ return;
+ }
+
+ function getShaderSource(countX, countY, config) {
+ const vs = [
+ '#version 300 es',
+ config.isMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '',
+ '#define kCountX ' + countX.toString(),
+ '#define kCountY ' + countY.toString(),
+ 'layout(location = 0) in vec2 vPosition;',
+ 'layout(location = 1) in float vInstanceID;',
+ 'out vec4 color;',
+ 'void main()',
+ '{',
+ ' const float xStep = 1.0 / float(kCountX);',
+ ' const float yStep = 1.0 / float(kCountY);',
+ ' float xID = vInstanceID;',
+ ' float xColor = 1.0 - xStep * xID;',
+ ' float yID = floor(float(gl_VertexID) / ' + (config.isDrawArrays ? '6.0' : '4.0') + ' + 0.01);',
+ ' color = vec4(xColor, 1.0 - yStep * yID, 1.0',
+ ' , 1.0);',
+ ' mat3 transform = mat3(1.0);',
+ ' transform[2][0] = xID * xStep;',
+ ' gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1.0);',
+ '}'
+ ].join('\n');
+
+ const fs = document.getElementById('fshader').text.trim();
+
+ return [vs, fs];
+ }
+
+ 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 ]), bufferUsage);
+
+ const instanceBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0, 1, 2 ]), bufferUsage);
+
+ const program = wtu.setupProgram(gl, ['vshaderSimple', 'fshader'], ['vPosition, vInstanceID'], [0, 1], true);
+ expectTrue(program != null, "can compile simple program");
+
+ function setupInstanced() {
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(1, 1);
+ }
+
+ setupInstanced();
+
+ 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.ARRAY_BUFFER, vertexBuffer);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+ }
+
+ function makeDrawValidationCheck(drawFunc, setup) {
+ if (!drawFunc) {
+ return function() {};
+ }
+ return function(f_args, expect, msg) {
+ setup();
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ drawFunc.apply(ext, f_args);
+ wtu.glErrorShouldBe(gl, expect, drawFunc.name + " " + msg);
+ gl.disableVertexAttribArray(0);
+ }
+ }
+
+ if (!multiDraw) {
+ const checkDrawArraysInstancedBaseInstance = makeDrawValidationCheck(
+ ext.drawArraysInstancedBaseInstanceWEBGL, setupDrawArrays);
+ const checkDrawElementsInstancedBaseVertexBaseInstance = makeDrawValidationCheck(
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL, setupDrawElements);
+ checkDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, 0, 3, 1, 1],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 0, 0],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+
+ checkDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, 0, 3, 1, 3],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with baseInstance leading to out of bounds"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 2, 0],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with baseVertex leading to out of bounds"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 0, 3],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with baseInstance leading to out of bounds"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 2, 3],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with both baseVertex and baseInstance leading to out of bounds"
+ );
+ } else {
+ const checkMultiDrawArraysInstancedBaseInstance = makeDrawValidationCheck(
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL, setupDrawArrays);
+ const checkMultiDrawElementsInstancedBaseVertexBaseInstance = makeDrawValidationCheck(
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL, setupDrawElements);
+
+ // Check that drawing a single triangle works
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 0, 1],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, 1],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [3], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION], "with baseInstance leads to out of bounds"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [2], 0, [0], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION], "with baseVertex leads to out of bounds"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [3], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION], "with baseInstance leads to out of bounds"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [2], 0, [3], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with both baseVertex and baseInstance lead to out of bounds"
+ );
+
+ // Zero drawcount permitted
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 0, 0],
+ gl.NO_ERROR, "with drawcount == 0"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, 0],
+ gl.NO_ERROR, "with drawcount == 0"
+ );
+
+ // Check negative drawcount
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 0, -1],
+ gl.INVALID_VALUE, "with drawcount < 0"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, -1],
+ gl.INVALID_VALUE, "with drawcount < 0"
+ );
+
+ // Check offsets greater than array length
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 1, [3], 0, [1], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with firstsStart >= firstsList.length"
+ );
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 1, [1], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with countsStart >= countsList.length"
+ );
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 1, [0], 0, 1],
+ gl.INVALID_OPERATION, "with instanceCountsStart >= instanceCountsList.length"
+ );
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 1, 1],
+ gl.INVALID_OPERATION, "with baseInstancesStart >= baseInstancesList.length"
+ );
+
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 1, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with countsStart >= countsList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 1, [1], 0, [0], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with offsetsStart >= offsetsList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 1, [0], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with instanceCountsStart >= instanceCountsList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 1, [0], 0, 1],
+ gl.INVALID_OPERATION, "with baseVerticesStart >= baseVerticesList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 1, 1],
+ gl.INVALID_OPERATION, "with baseInstancesStart >= baseInstancesList.length"
+ );
+ }
+ }
+
+ function runShaderTests(bufferUsage) {
+ let badProgram;
+
+ badProgram = wtu.setupProgram(gl, ["vshaderBaseInstanceWithoutExt", "fshader"]);
+ expectTrue(!badProgram, "cannot compile program with gl_BaseInstance but no extension directive");
+ badProgram = wtu.setupProgram(gl, ["vshaderBaseVertexWithoutExt", "fshader"]);
+ expectTrue(!badProgram, "cannot compile program with gl_BaseVertex but no extension directive");
+
+ badProgram = wtu.setupProgram(gl, ["vshaderWithExt", "fshader"]);
+ expectTrue(!badProgram, "cannot compile program with #extension GL_ANGLE_base_vertex_base_instance");
+
+ const x = Math.floor(width * 0.4);
+ const y = Math.floor(height * 0.4);
+ const xSize = Math.floor(width * 0.2);
+ const ySize = Math.floor(height * 0.2);
+
+ // gl_InstanceID
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0, 1,0, 0.5,1, 0,1, 0.5,0, 1,1 ]), bufferUsage);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ const instanceIDProgram = wtu.setupProgram(gl, ["vshaderInstanceIDCheck", "fshader"], ["vPosition"], [0]);
+ expectTrue(instanceIDProgram !== null, "can compile program with gl_InstanceID");
+ gl.useProgram(instanceIDProgram);
+
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ if (!multiDraw) {
+ ext.drawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, 0, 6, 1, 5);
+ } else {
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, [0], 0, [6], 0, [1], 0, [5], 0, 1);
+ }
+
+ wtu.checkCanvasRect(gl, x, y, xSize, ySize, [0, 255, 0, 255], "gl_InstanceID should always starts from 0");
+
+ // gl_VertexID
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0, 1,0, 0.5,1, 0,1, 0.5,0, 1,1, 0,0, 1,0, 0.5,1, 0,1 ]), bufferUsage);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([0, 1, 2, 3, 4, 5]), bufferUsage);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ const vertexIDProgram = wtu.setupProgram(gl, ["vshaderVertexIDCheck", "fshader"], ["vPosition"], [0]);
+ expectTrue(vertexIDProgram !== null, "can compile program with gl_VertexID");
+ gl.useProgram(vertexIDProgram);
+
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ if (!multiDraw) {
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 1, 3, 0);
+ } else {
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, [6], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [3], 0, [0], 0, 1);
+ }
+
+ wtu.checkCanvasRect(gl, x, y, xSize, ySize, [0, 255, 0, 255], "gl_VertexID should always starts from 0");
+ }
+
+ function runPixelTests() {
+
+ function checkResult(config) {
+ const rects = [];
+ const expected = [
+ [255, 0, 0, 255],
+ [0, 255, 0, 255],
+ [0, 0, 255, 255],
+ ];
+ const msg = config.drawFunc.name + (
+ config.useBaseVertexBuiltin ? ' gl_BaseVertex' : ''
+ ) + (
+ config.useBaseInstanceBuiltin ? ' gl_BaseInstance' : ' InstanceIDArray'
+ );
+ 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);
+
+ rects.push(wtu.makeCheckRect(
+ center_x - Math.floor(pixelCheckSize[0] / 2),
+ center_y - Math.floor(pixelCheckSize[1] / 2),
+ pixelCheckSize[0],
+ pixelCheckSize[1],
+ [
+ 256.0 * (1.0 - x / x_count),
+ 256.0 * (1.0 - y / y_count),
+ (!config.isDrawArrays && config.useBaseVertexBuiltin) ? 256.0 * (1.0 - y / y_count) : 255.0,
+ 255.0
+ ],
+ msg + ' (' + x + ',' + y + ')', 1.0
+ ));
+ }
+ }
+ wtu.checkCanvasRects(gl, rects);
+ }
+
+ // Draw functions variations
+
+ function drawArraysInstancedBaseInstance() {
+ const countPerDraw = y_count * 6;
+ for (let x = 0; x < x_count; x += 2) {
+ ext.drawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, 0, countPerDraw, 2, x);
+ }
+ }
+
+ function multiDrawArraysInstancedBaseInstance() {
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, drawArraysParams.firsts, 0, drawArraysParams.counts, 0, drawArraysParams.instances, 0, drawArraysParams.baseInstances, 0, drawArraysParams.drawCount);
+ }
+
+ function drawElementsInstancedBaseVertexBaseInstance() {
+ const countPerDraw = 6;
+ for (let v = 0; v < y_count; ++v) {
+ for (let x = 0; x < x_count; x += 2) {
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, countPerDraw, gl.UNSIGNED_SHORT, 0, 2, v * 4, x);
+ }
+ }
+ }
+
+ function multiDrawElementsInstancedBaseVertexBaseInstance() {
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, drawElementsParams.counts, 0, gl.UNSIGNED_SHORT, drawElementsParams.offsets, 0, drawElementsParams.instances, 0, drawElementsParams.baseVertices, 0, drawElementsParams.baseInstances, 0, drawElementsParams.drawCount);
+ }
+
+ function checkDraw(config) {
+ const program = wtu.setupProgram(
+ gl,
+ getShaderSource(x_count, y_count, config),
+ !config.useBaseInstanceBuiltin ? ['vPosition'] : ['vPosition', 'vInstanceID']
+ );
+
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ if (config.isDrawArrays) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, nonIndexedVertexBuffer);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+ } else {
+ 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);
+ }
+
+ if (!config.useBaseInstanceBuiltin) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceIDBuffer);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(1, 1);
+ }
+
+ config.drawFunc();
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+
+ checkResult(config);
+ }
+
+ checkDraw({
+ drawFunc: multiDraw ? multiDrawArraysInstancedBaseInstance : drawArraysInstancedBaseInstance,
+ isDrawArrays: true,
+ isMultiDraw: multiDraw,
+ useBaseVertexBuiltin: false,
+ useBaseInstanceBuiltin: false
+ });
+
+ checkDraw({
+ drawFunc: multiDraw ? multiDrawElementsInstancedBaseVertexBaseInstance : drawElementsInstancedBaseVertexBaseInstance,
+ isDrawArrays: false,
+ isMultiDraw: multiDraw,
+ useBaseVertexBuiltin: false,
+ useBaseInstanceBuiltin: false
+ });
+ }
+
+ for (let i = 0; i < bufferUsageSet.length; i++) {
+ let bufferUsage = bufferUsageSet[i];
+ debug("Testing with BufferUsage = " + bufferUsage);
+ setupGeneralBuffers(bufferUsage);
+ runValidationTests(bufferUsage);
+ runShaderTests(bufferUsage);
+ runPixelTests();
+ }
+}
+
+
+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 drawArraysInstancedBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_draw_instanced_base_vertex_base_instance';
+ }
+
+ ext.drawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, 0, 6, 1, 0);
+ }
+
+ function drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_draw_instanced_base_vertex_base_instance';
+ }
+
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 1, 0, 0);
+ }
+
+ function multiDrawArraysInstancedBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_multi_draw_instanced_base_vertex_base_instance';
+ }
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, [0], 0, [6], 0, [1], 0, [0], 0, 1);
+ }
+
+ function multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_multi_draw_instanced_base_vertex_base_instance';
+ }
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(
+ gl.TRIANGLES,
+ [6], 0, // counts
+ gl.UNSIGNED_BYTE,
+ [0], 0, // offsets
+ [1], 0, // instances
+ [0], 0, // baseVerts
+ [0], 0, // baseInstances
+ 1, // drawCount
+ );
+ }
+
+ await testFn(drawArrays); // sanity check
+ await testFn(drawElements); // sanity check
+ await testFn(drawArraysInstanced); // sanity check
+ await testFn(drawElementsInstanced); // 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_draw_instanced_base_vertex_base_instance')) {
+ await testFn(drawArraysInstancedBaseInstanceWEBGL);
+ await testFn(drawElementsInstancedBaseVertexBaseInstanceWEBGL);
+ }
+ if (gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance')) {
+ await testFn(multiDrawArraysInstancedBaseInstanceWEBGL);
+ await testFn(multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL);
+ }
+}
+
+async function runCompositingTests() {
+ const compositingTestFn = createCompositingTestFn({
+ webglVersion: 2,
+ shadersFn(gl) {
+ const vs = `\
+ #version 300 es
+ layout(location = 0) in vec4 position;
+ void main() {
+ gl_Position = position;
+ }
+ `;
+ const fs = `\
+ #version 300 es
+ precision highp float;
+ out vec4 fragColor;
+ void main() {
+ fragColor = vec4(1, 0, 0, 1);
+ }
+ `;
+ return [vs, fs];
+ },
+ });
+ await runDrawTests(compositingTestFn);
+}
+
+async function runInvalidAttribTests(gl) {
+ const invalidAttribTestFn = createInvalidAttribTestFn(gl);
+ await runDrawTests(invalidAttribTestFn);
+}
+
+async function main() {
+ runTest();
+ await runInvalidAttribTests(gl);
+ await runCompositingTests();
+ finishTest();
+}
+main();
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>