export const description = `
Tests for the indirect-specific aspects of drawIndirect/drawIndexedIndirect.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import {
kDrawIndirectParametersSize,
kDrawIndexedIndirectParametersSize,
} from '../../../capability_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import * as ttu from '../../../texture_test_utils.js';
const filled = new Uint8Array([0, 255, 0, 255]);
const notFilled = new Uint8Array([0, 0, 0, 0]);
const kRenderTargetFormat = 'rgba8unorm';
class F extends AllFeaturesMaxLimitsGPUTest {
makeIndexBuffer(): GPUBuffer {
return this.makeBufferWithContents(
new Uint32Array([
0, 1, 2,
1, 2, 3,
]),
GPUBufferUsage.INDEX
);
}
makeVertexBuffer(isIndexed: boolean): GPUBuffer {
const vertices = isIndexed
? [
-1.0, -1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0,
]
: [
-1.0, 1.0,
1.0, -1.0,
-1.0, -1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0,
];
return this.makeBufferWithContents(new Float32Array(vertices), GPUBufferUsage.VERTEX);
}
makeIndirectBuffer(isIndexed: boolean, indirectOffset: number): GPUBuffer {
const o = indirectOffset / Uint32Array.BYTES_PER_ELEMENT;
const parametersSize = isIndexed
? kDrawIndexedIndirectParametersSize
: kDrawIndirectParametersSize;
const arraySize = o + parametersSize * 2;
const indirectBuffer = [...Array(arraySize)].map(() => Math.floor(Math.random() * 100));
if (isIndexed) {
indirectBuffer[o] = 3;
indirectBuffer[o + 1] = 1;
indirectBuffer[o + 2] = 0;
indirectBuffer[o + 3] = 0;
indirectBuffer[o + 4] = 0;
indirectBuffer[o + 5] = 6;
indirectBuffer[o + 6] = 1;
indirectBuffer[o + 7] = 0;
indirectBuffer[o + 8] = 0;
indirectBuffer[o + 9] = 0;
if (o >= parametersSize) {
indirectBuffer[o - 5] = 3;
indirectBuffer[o - 4] = 1;
indirectBuffer[o - 3] = 3;
indirectBuffer[o - 2] = 0;
indirectBuffer[o - 1] = 0;
}
if (o >= parametersSize * 2) {
indirectBuffer[0] = 0;
indirectBuffer[1] = 0;
indirectBuffer[2] = 0;
indirectBuffer[3] = 0;
indirectBuffer[4] = 0;
}
} else {
indirectBuffer[o] = 3;
indirectBuffer[o + 1] = 1;
indirectBuffer[o + 2] = 0;
indirectBuffer[o + 3] = 0;
indirectBuffer[o + 4] = 6;
indirectBuffer[o + 5] = 1;
indirectBuffer[o + 6] = 0;
indirectBuffer[o + 7] = 0;
if (o >= parametersSize) {
indirectBuffer[o - 4] = 3;
indirectBuffer[o - 3] = 1;
indirectBuffer[o - 2] = 3;
indirectBuffer[o - 1] = 0;
}
if (o >= parametersSize * 2) {
indirectBuffer[0] = 0;
indirectBuffer[1] = 0;
indirectBuffer[2] = 0;
indirectBuffer[3] = 0;
}
}
return this.makeBufferWithContents(new Uint32Array(indirectBuffer), GPUBufferUsage.INDIRECT);
}
}
export const g = makeTestGroup(F);
g.test('basics')
.desc(
`Test that the indirect draw parameters are tightly packed for drawIndirect and drawIndexedIndirect.
An indirectBuffer is created based on indirectOffset. The actual draw args being used indicated by the
indirectOffset is going to draw a left bottom triangle.
While the remaining indirectBuffer is populated with random numbers or draw args
that draw right top triangle, both, or nothing which will fail the color check.
The test will check render target to see if only the left bottom area is filled,
meaning the expected draw args is uploaded correctly by the indirectBuffer and indirectOffset.
Params:
- draw{Indirect, IndexedIndirect}
- indirectOffset= {0, 4, k * sizeof(args struct), k * sizeof(args struct) + 4}
`
)
.params(u =>
u
.combine('isIndexed', [true, false])
.beginSubcases()
.expand('indirectOffset', p => {
const indirectDrawParametersSize = p.isIndexed
? kDrawIndexedIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT
: kDrawIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT;
return [
0,
Uint32Array.BYTES_PER_ELEMENT,
1 * indirectDrawParametersSize,
1 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT,
3 * indirectDrawParametersSize,
3 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT,
99 * indirectDrawParametersSize,
99 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT,
] as const;
})
)
.fn(t => {
const { isIndexed, indirectOffset } = t.params;
const vertexBuffer = t.makeVertexBuffer(isIndexed);
const indirectBuffer = t.makeIndirectBuffer(isIndexed, indirectOffset);
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: `@vertex fn main(@location(0) pos : vec2<f32>) -> @builtin(position) vec4<f32> {
return vec4<f32>(pos, 0.0, 1.0);
}`,
}),
entryPoint: 'main',
buffers: [
{
attributes: [
{
shaderLocation: 0,
format: 'float32x2',
offset: 0,
},
],
arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT,
},
],
},
fragment: {
module: t.device.createShaderModule({
code: `@fragment fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
}`,
}),
entryPoint: 'main',
targets: [
{
format: kRenderTargetFormat,
},
],
},
});
const renderTarget = t.createTextureTracked({
size: [4, 4],
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
format: kRenderTargetFormat,
});
const commandEncoder = t.device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
clearValue: [0, 0, 0, 0],
loadOp: 'clear',
storeOp: 'store',
},
],
});
renderPass.setPipeline(pipeline);
renderPass.setVertexBuffer(0, vertexBuffer, 0);
if (isIndexed) {
renderPass.setIndexBuffer(t.makeIndexBuffer(), 'uint32', 0);
renderPass.drawIndexedIndirect(indirectBuffer, indirectOffset);
} else {
renderPass.drawIndirect(indirectBuffer, indirectOffset);
}
renderPass.end();
t.queue.submit([commandEncoder.finish()]);
ttu.expectSinglePixelComparisonsAreOkInTexture(t, { texture: renderTarget }, [
{ coord: { x: 0, y: 1 }, exp: filled },
{ coord: { x: 1, y: 0 }, exp: notFilled },
]);
});