| 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](); |
| } |
| }); |