export const description = `
Validation tests for drawIndirect/drawIndexedIndirect on render pass and render bundle.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUConst } from '../../../../../constants.js';
import { kResourceStates, AllFeaturesMaxLimitsGPUTest } from '../../../../../gpu_test.js';
import * as vtu from '../../../validation_test_utils.js';
import { kRenderEncodeTypeParams } from './render.js';
const kIndirectDrawTestParams = kRenderEncodeTypeParams.combine('indexed', [true, false] as const);
class F extends AllFeaturesMaxLimitsGPUTest {
makeIndexBuffer(): GPUBuffer {
return this.createBufferTracked({
size: 16,
usage: GPUBufferUsage.INDEX,
});
}
}
export const g = makeTestGroup(F);
g.test('indirect_buffer_state')
.desc(
`
Tests indirect buffer must be valid.
`
)
.paramsSubcasesOnly(kIndirectDrawTestParams.combine('state', kResourceStates))
.fn(t => {
const { encoderType, indexed, state } = t.params;
const pipeline = vtu.createNoOpRenderPipeline(t);
const indirectBuffer = vtu.createBufferWithState(t, state, {
size: 256,
usage: GPUBufferUsage.INDIRECT,
});
const { encoder, validateFinishAndSubmitGivenState } = t.createEncoder(encoderType);
encoder.setPipeline(pipeline);
if (indexed) {
const indexBuffer = t.makeIndexBuffer();
encoder.setIndexBuffer(indexBuffer, 'uint32');
encoder.drawIndexedIndirect(indirectBuffer, 0);
} else {
encoder.drawIndirect(indirectBuffer, 0);
}
validateFinishAndSubmitGivenState(state);
});
g.test('indirect_buffer,device_mismatch')
.desc(
'Tests draw(Indexed)Indirect cannot be called with an indirect buffer created from another device'
)
.paramsSubcasesOnly(kIndirectDrawTestParams.combine('mismatched', [true, false]))
.beforeAllSubcases(t => t.usesMismatchedDevice())
.fn(t => {
const { encoderType, indexed, mismatched } = t.params;
const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
const indirectBuffer = t.trackForCleanup(
sourceDevice.createBuffer({
size: 256,
usage: GPUBufferUsage.INDIRECT,
})
);
const { encoder, validateFinish } = t.createEncoder(encoderType);
encoder.setPipeline(vtu.createNoOpRenderPipeline(t));
if (indexed) {
encoder.setIndexBuffer(t.makeIndexBuffer(), 'uint32');
encoder.drawIndexedIndirect(indirectBuffer, 0);
} else {
encoder.drawIndirect(indirectBuffer, 0);
}
validateFinish(!mismatched);
});
g.test('indirect_buffer_usage')
.desc(
`
Tests indirect buffer must have 'Indirect' usage.
`
)
.paramsSubcasesOnly(
kIndirectDrawTestParams.combine('usage', [
GPUConst.BufferUsage.INDIRECT,
GPUConst.BufferUsage.COPY_DST,
GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.INDIRECT,
] as const)
)
.fn(t => {
const { encoderType, indexed, usage } = t.params;
const indirectBuffer = t.createBufferTracked({
size: 256,
usage,
});
const { encoder, validateFinish } = t.createEncoder(encoderType);
encoder.setPipeline(vtu.createNoOpRenderPipeline(t));
if (indexed) {
const indexBuffer = t.makeIndexBuffer();
encoder.setIndexBuffer(indexBuffer, 'uint32');
encoder.drawIndexedIndirect(indirectBuffer, 0);
} else {
encoder.drawIndirect(indirectBuffer, 0);
}
validateFinish((usage & GPUBufferUsage.INDIRECT) !== 0);
});
g.test('indirect_offset_alignment')
.desc(
`
Tests indirect offset must be a multiple of 4.
`
)
.paramsSubcasesOnly(kIndirectDrawTestParams.combine('indirectOffset', [0, 2, 4] as const))
.fn(t => {
const { encoderType, indexed, indirectOffset } = t.params;
const pipeline = vtu.createNoOpRenderPipeline(t);
const indirectBuffer = t.createBufferTracked({
size: 256,
usage: GPUBufferUsage.INDIRECT,
});
const { encoder, validateFinish } = t.createEncoder(encoderType);
encoder.setPipeline(pipeline);
if (indexed) {
const indexBuffer = t.makeIndexBuffer();
encoder.setIndexBuffer(indexBuffer, 'uint32');
encoder.drawIndexedIndirect(indirectBuffer, indirectOffset);
} else {
encoder.drawIndirect(indirectBuffer, indirectOffset);
}
validateFinish(indirectOffset % 4 === 0);
});
g.test('indirect_offset_oob')
.desc(
`
Tests indirect draw calls with various indirect offsets and buffer sizes.
- (offset, b.size) is
- (0, 0)
- (0, min size) (control case)
- (0, min size + 1) (control case)
- (0, min size - 1)
- (0, min size - min alignment)
- (min alignment, min size + min alignment)
- (min alignment, min size + min alignment - 1)
- (min alignment / 2, min size + min alignment)
- (min alignment +/- 1, min size + min alignment)
- (min size, min size)
- (min size + min alignment, min size)
- min size = indirect draw parameters size
- x =(drawIndirect, drawIndexedIndirect)
`
)
.paramsSubcasesOnly(
kIndirectDrawTestParams.expandWithParams(p => {
const indirectParamsSize = p.indexed ? 20 : 16;
return [
{ indirectOffset: 0, bufferSize: 0, _valid: false },
{ indirectOffset: 0, bufferSize: indirectParamsSize, _valid: true },
{ indirectOffset: 0, bufferSize: indirectParamsSize + 1, _valid: true },
{ indirectOffset: 0, bufferSize: indirectParamsSize - 1, _valid: false },
{ indirectOffset: 0, bufferSize: indirectParamsSize - 4, _valid: false },
{ indirectOffset: 4, bufferSize: indirectParamsSize + 4, _valid: true },
{ indirectOffset: 4, bufferSize: indirectParamsSize + 3, _valid: false },
{ indirectOffset: 2, bufferSize: indirectParamsSize + 4, _valid: false },
{ indirectOffset: 3, bufferSize: indirectParamsSize + 4, _valid: false },
{ indirectOffset: 5, bufferSize: indirectParamsSize + 4, _valid: false },
{ indirectOffset: indirectParamsSize, bufferSize: indirectParamsSize, _valid: false },
{ indirectOffset: indirectParamsSize + 4, bufferSize: indirectParamsSize, _valid: false },
] as const;
})
)
.fn(t => {
const { encoderType, indexed, indirectOffset, bufferSize, _valid } = t.params;
const pipeline = vtu.createNoOpRenderPipeline(t);
const indirectBuffer = t.createBufferTracked({
size: bufferSize,
usage: GPUBufferUsage.INDIRECT,
});
const { encoder, validateFinish } = t.createEncoder(encoderType);
encoder.setPipeline(pipeline);
if (indexed) {
const indexBuffer = t.makeIndexBuffer();
encoder.setIndexBuffer(indexBuffer, 'uint32');
encoder.drawIndexedIndirect(indirectBuffer, indirectOffset);
} else {
encoder.drawIndirect(indirectBuffer, indirectOffset);
}
validateFinish(_valid);
});