| <!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> |
| </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(); |
| } |
| } |
| |
| runTest(); |
| |
| const successfullyParsed = true; |
| </script> |
| <script src="../../js/js-test-post.js"></script> |
| </body> |
| </html> |