blob: eb5d82467562fa09d4376175ed2694b920742713 [file] [log] [blame]
export const description = `
Validation tests for the map-state of mappable buffers used in submitted command buffers.
Tests every operation that has a dependency on a buffer
- writeBuffer
- copyB2B {src,dst}
- copyB2T
- copyT2B
Test those operations against buffers in the following states:
- Unmapped
- In the process of mapping
- mapped
- mapped with a mapped range queried
- unmapped after mapping
- mapped at creation
Also tests every order of operations combination of mapping operations and command recording
operations to ensure the mapping state is only considered when a command buffer is submitted.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
class F extends AllFeaturesMaxLimitsGPUTest {
async runBufferDependencyTest(usage: number, callback: Function): Promise<void> {
const bufferDesc = {
size: 8,
usage,
mappedAtCreation: false,
};
const mapMode = usage & GPUBufferUsage.MAP_READ ? GPUMapMode.READ : GPUMapMode.WRITE;
// Create a mappable buffer, and one that will remain unmapped for comparison.
const mappableBuffer = this.createBufferTracked(bufferDesc);
const unmappedBuffer = this.createBufferTracked(bufferDesc);
// Run the given operation before the buffer is mapped. Should succeed.
callback(mappableBuffer);
// Map the buffer
const mapPromise = mappableBuffer.mapAsync(mapMode);
// Run the given operation while the buffer is in the process of mapping. Should fail.
this.expectValidationError(() => {
callback(mappableBuffer);
});
// Run on a different, unmapped buffer. Should succeed.
callback(unmappedBuffer);
await mapPromise;
// Run the given operation when the buffer is finished mapping with no getMappedRange. Should fail.
this.expectValidationError(() => {
callback(mappableBuffer);
});
// Run on a different, unmapped buffer. Should succeed.
callback(unmappedBuffer);
// Run the given operation when the buffer is mapped with getMappedRange. Should fail.
mappableBuffer.getMappedRange();
this.expectValidationError(() => {
callback(mappableBuffer);
});
// Unmap the buffer and run the operation. Should succeed.
mappableBuffer.unmap();
callback(mappableBuffer);
// Create a buffer that's mappedAtCreation.
bufferDesc.mappedAtCreation = true;
const mappedBuffer = this.createBufferTracked(bufferDesc);
// Run the operation with the mappedAtCreation buffer. Should fail.
this.expectValidationError(() => {
callback(mappedBuffer);
});
// Run on a different, unmapped buffer. Should succeed.
callback(unmappedBuffer);
// Unmap the mappedAtCreation buffer and run the operation. Should succeed.
mappedBuffer.unmap();
callback(mappedBuffer);
}
}
export const g = makeTestGroup(F);
g.test('writeBuffer')
.desc(`Test that an outstanding mapping will prevent writeBuffer calls.`)
.fn(async t => {
const data = new Uint32Array([42]);
await t.runBufferDependencyTest(
GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
(buffer: GPUBuffer) => {
t.queue.writeBuffer(buffer, 0, data);
}
);
});
g.test('copyBufferToBuffer')
.desc(
`
Test that an outstanding mapping will prevent copyBufferToTexture commands from submitting,
both when used as the source and destination.`
)
.fn(async t => {
const sourceBuffer = t.createBufferTracked({
size: 8,
usage: GPUBufferUsage.COPY_SRC,
});
const destBuffer = t.createBufferTracked({
size: 8,
usage: GPUBufferUsage.COPY_DST,
});
await t.runBufferDependencyTest(
GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
(buffer: GPUBuffer) => {
const commandEncoder = t.device.createCommandEncoder();
commandEncoder.copyBufferToBuffer(buffer, 0, destBuffer, 0, 4);
t.queue.submit([commandEncoder.finish()]);
}
);
await t.runBufferDependencyTest(
GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
(buffer: GPUBuffer) => {
const commandEncoder = t.device.createCommandEncoder();
commandEncoder.copyBufferToBuffer(sourceBuffer, 0, buffer, 0, 4);
t.queue.submit([commandEncoder.finish()]);
}
);
});
g.test('copyBufferToTexture')
.desc(
`Test that an outstanding mapping will prevent copyBufferToTexture commands from submitting.`
)
.fn(async t => {
const size = { width: 1, height: 1 };
const texture = t.createTextureTracked({
size,
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_DST,
});
await t.runBufferDependencyTest(
GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
(buffer: GPUBuffer) => {
const commandEncoder = t.device.createCommandEncoder();
commandEncoder.copyBufferToTexture({ buffer }, { texture }, size);
t.queue.submit([commandEncoder.finish()]);
}
);
});
g.test('copyTextureToBuffer')
.desc(
`Test that an outstanding mapping will prevent copyTextureToBuffer commands from submitting.`
)
.fn(async t => {
const size = { width: 1, height: 1 };
const texture = t.createTextureTracked({
size,
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_SRC,
});
await t.runBufferDependencyTest(
GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
(buffer: GPUBuffer) => {
const commandEncoder = t.device.createCommandEncoder();
commandEncoder.copyTextureToBuffer({ texture }, { buffer }, size);
t.queue.submit([commandEncoder.finish()]);
}
);
});
g.test('map_command_recording_order')
.desc(
`
Test that the order of mapping a buffer relative to when commands are recorded that use it
does not matter, as long as the buffer is unmapped when the commands are submitted.
`
)
.paramsSubcasesOnly([
{
order: ['record', 'map', 'unmap', 'finish', 'submit'],
mappedAtCreation: false,
_shouldError: false,
},
{
order: ['record', 'map', 'finish', 'unmap', 'submit'],
mappedAtCreation: false,
_shouldError: false,
},
{
order: ['record', 'finish', 'map', 'unmap', 'submit'],
mappedAtCreation: false,
_shouldError: false,
},
{
order: ['map', 'record', 'unmap', 'finish', 'submit'],
mappedAtCreation: false,
_shouldError: false,
},
{
order: ['map', 'record', 'finish', 'unmap', 'submit'],
mappedAtCreation: false,
_shouldError: false,
},
{
order: ['map', 'record', 'finish', 'submit', 'unmap'],
mappedAtCreation: false,
_shouldError: true,
},
{
order: ['record', 'map', 'finish', 'submit', 'unmap'],
mappedAtCreation: false,
_shouldError: true,
},
{
order: ['record', 'finish', 'map', 'submit', 'unmap'],
mappedAtCreation: false,
_shouldError: true,
},
{ order: ['record', 'unmap', 'finish', 'submit'], mappedAtCreation: true, _shouldError: false },
{ order: ['record', 'finish', 'unmap', 'submit'], mappedAtCreation: true, _shouldError: false },
{ order: ['record', 'finish', 'submit', 'unmap'], mappedAtCreation: true, _shouldError: true },
] as const)
.fn(async t => {
const { order, mappedAtCreation, _shouldError: shouldError } = t.params;
const buffer = t.createBufferTracked({
size: 4,
usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
mappedAtCreation,
});
const targetBuffer = t.createBufferTracked({
size: 4,
usage: GPUBufferUsage.COPY_DST,
});
const commandEncoder = t.device.createCommandEncoder();
let commandBuffer: GPUCommandBuffer;
const steps = {
record: () => {
commandEncoder.copyBufferToBuffer(buffer, 0, targetBuffer, 0, 4);
},
map: async () => {
await buffer.mapAsync(GPUMapMode.WRITE);
},
unmap: () => {
buffer.unmap();
},
finish: () => {
commandBuffer = commandEncoder.finish();
},
submit: () => {
t.expectValidationError(() => {
t.queue.submit([commandBuffer]);
}, shouldError);
},
};
for (const op of order) {
await steps[op]();
}
});