export const description = `
Validation tests for indexed draws accessing the index buffer.
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js';
class F extends AllFeaturesMaxLimitsGPUTest {
createIndexBuffer(indexData: Iterable<number>): GPUBuffer {
return this.makeBufferWithContents(new Uint32Array(indexData), GPUBufferUsage.INDEX);
}
createRenderPipeline(): GPURenderPipeline {
return this.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: this.device.createShaderModule({
code: `
@vertex fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}`,
}),
entryPoint: 'main',
},
fragment: {
module: this.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: 'rgba8unorm' }],
},
primitive: {
topology: 'triangle-strip',
stripIndexFormat: 'uint32',
},
});
}
beginRenderPass(encoder: GPUCommandEncoder) {
const colorAttachment = this.createTextureTracked({
format: 'rgba8unorm',
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
return encoder.beginRenderPass({
colorAttachments: [
{
view: colorAttachment.createView(),
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
});
}
drawIndexed(
indexBuffer: GPUBuffer,
indexCount: number,
instanceCount: number,
firstIndex: number,
baseVertex: number,
firstInstance: number,
isSuccess: boolean
) {
const pipeline = this.createRenderPipeline();
const encoder = this.device.createCommandEncoder();
const pass = this.beginRenderPass(encoder);
pass.setPipeline(pipeline);
pass.setIndexBuffer(indexBuffer, 'uint32');
pass.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
pass.end();
if (isSuccess) {
this.device.queue.submit([encoder.finish()]);
} else {
this.expectValidationError(() => {
encoder.finish();
});
}
}
}
export const g = makeTestGroup(F);
g.test('out_of_bounds')
.desc(
`Test drawing with out of bound index access to make sure encoder validation catch the
following indexCount and firstIndex OOB conditions
- either is within bound but indexCount + firstIndex is out of bound
- only firstIndex is out of bound
- only indexCount is out of bound
- firstIndex much larger than indexCount
- indexCount much larger than firstIndex
- max uint32 value for both to make sure the sum doesn't overflow
- max uint32 indexCount and small firstIndex
- max uint32 firstIndex and small indexCount
Together with normal and large instanceCount`
)
.params(
u =>
u
.combineWithParams([
{ indexCount: 6, firstIndex: 0 },
{ indexCount: 5, firstIndex: 1 },
{ indexCount: 1, firstIndex: 5 },
{ indexCount: 0, firstIndex: 6 },
{ indexCount: 0, firstIndex: 7 },
{ indexCount: 7, firstIndex: 0 },
{ indexCount: 6, firstIndex: 1 },
{ indexCount: 1, firstIndex: 6 },
{ indexCount: 6, firstIndex: 10000 },
{ indexCount: 10000, firstIndex: 0 },
{ indexCount: 0xffffffff, firstIndex: 0xffffffff },
{ indexCount: 0xffffffff, firstIndex: 2 },
{ indexCount: 2, firstIndex: 0xffffffff },
] as const)
.combine('instanceCount', [1, 10000])
)
.fn(t => {
const { indexCount, firstIndex, instanceCount } = t.params;
const indexBuffer = t.createIndexBuffer([0, 1, 2, 3, 1, 2]);
const isSuccess: boolean = indexCount + firstIndex <= 6;
t.drawIndexed(indexBuffer, indexCount, instanceCount, firstIndex, 0, 0, isSuccess);
});
g.test('out_of_bounds_zero_sized_index_buffer')
.desc(
`Test drawing with an empty index buffer to make sure the encoder validation catch the
following indexCount and firstIndex conditions
- indexCount + firstIndex is out of bound
- indexCount is 0 but firstIndex is out of bound
- only indexCount is out of bound
- both are 0s (not out of bound) but index buffer size is 0
Together with normal and large instanceCount`
)
.params(
u =>
u
.combineWithParams([
{ indexCount: 3, firstIndex: 1 },
{ indexCount: 0, firstIndex: 1 },
{ indexCount: 3, firstIndex: 0 },
{ indexCount: 0, firstIndex: 0 },
] as const)
.combine('instanceCount', [1, 10000])
)
.fn(t => {
const { indexCount, firstIndex, instanceCount } = t.params;
const indexBuffer = t.createIndexBuffer([]);
const isSuccess: boolean = indexCount + firstIndex <= 0;
t.drawIndexed(indexBuffer, indexCount, instanceCount, firstIndex, 0, 0, isSuccess);
});