diff options
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html')
-rw-r--r-- | dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html | 1021 |
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> |