blob: 292beeded6df0ce6bad92118b3956975f94aa7ba [file] [log] [blame]
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(
/* prettier-ignore */ new Uint32Array([
0, 1, 2, // The bottom left triangle
1, 2, 3, // The top right triangle
]),
GPUBufferUsage.INDEX
);
}
makeVertexBuffer(isIndexed: boolean): GPUBuffer {
/* prettier-ignore */
const vertices = isIndexed
? [
-1.0, -1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0,
]
: [
// The bottom left triangle
-1.0, 1.0,
1.0, -1.0,
-1.0, -1.0,
// The top right triangle
-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) {
// draw args that will draw the left bottom triangle (expected call)
indirectBuffer[o] = 3; // indexCount
indirectBuffer[o + 1] = 1; // instanceCount
indirectBuffer[o + 2] = 0; // firstIndex
indirectBuffer[o + 3] = 0; // baseVertex
indirectBuffer[o + 4] = 0; // firstInstance
// draw args that will draw both triangles
indirectBuffer[o + 5] = 6; // indexCount
indirectBuffer[o + 6] = 1; // instanceCount
indirectBuffer[o + 7] = 0; // firstIndex
indirectBuffer[o + 8] = 0; // baseVertex
indirectBuffer[o + 9] = 0; // firstInstance
if (o >= parametersSize) {
// draw args that will draw the right top triangle
indirectBuffer[o - 5] = 3; // indexCount
indirectBuffer[o - 4] = 1; // instanceCount
indirectBuffer[o - 3] = 3; // firstIndex
indirectBuffer[o - 2] = 0; // baseVertex
indirectBuffer[o - 1] = 0; // firstInstance
}
if (o >= parametersSize * 2) {
// draw args that will draw nothing
indirectBuffer[0] = 0; // indexCount
indirectBuffer[1] = 0; // instanceCount
indirectBuffer[2] = 0; // firstIndex
indirectBuffer[3] = 0; // baseVertex
indirectBuffer[4] = 0; // firstInstance
}
} else {
// draw args that will draw the left bottom triangle (expected call)
indirectBuffer[o] = 3; // vertexCount
indirectBuffer[o + 1] = 1; // instanceCount
indirectBuffer[o + 2] = 0; // firstVertex
indirectBuffer[o + 3] = 0; // firstInstance
// draw args that will draw both triangles
indirectBuffer[o + 4] = 6; // vertexCount
indirectBuffer[o + 5] = 1; // instanceCount
indirectBuffer[o + 6] = 0; // firstVertex
indirectBuffer[o + 7] = 0; // firstInstance
if (o >= parametersSize) {
// draw args that will draw the right top triangle
indirectBuffer[o - 4] = 3; // vertexCount
indirectBuffer[o - 3] = 1; // instanceCount
indirectBuffer[o - 2] = 3; // firstVertex
indirectBuffer[o - 1] = 0; // firstInstance
}
if (o >= parametersSize * 2) {
// draw args that will draw nothing
indirectBuffer[0] = 0; // vertexCount
indirectBuffer[1] = 0; // instanceCount
indirectBuffer[2] = 0; // firstVertex
indirectBuffer[3] = 0; // firstInstance
}
}
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 }, [
// The bottom left area is filled
{ coord: { x: 0, y: 1 }, exp: filled },
// The top right area is not filled
{ coord: { x: 1, y: 0 }, exp: notFilled },
]);
});