Source code
Revision control
Copy as Markdown
Other Tools
<!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
// 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>