diff --git a/src/webgpu/api/operation/buffers/map_detach.spec.ts b/src/webgpu/api/operation/buffers/map_detach.spec.ts index c7699007c845..5112b15c9de7 100644 --- a/src/webgpu/api/operation/buffers/map_detach.spec.ts +++ b/src/webgpu/api/operation/buffers/map_detach.spec.ts @@ -62,10 +62,13 @@ g.test('while_mapped') if (mapMode !== undefined) { if (mappedAtCreation) { + t.expect(buffer.mapState === 'mapped'); buffer.unmap(); } + t.expect(buffer.mapState === 'unmapped'); await buffer.mapAsync(mapMode); } + t.expect(buffer.mapState === 'mapped'); const arrayBuffer = buffer.getMappedRange(); const view = new Uint8Array(arrayBuffer); @@ -78,4 +81,5 @@ g.test('while_mapped') t.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached'); t.expect(view.byteLength === 0, 'ArrayBufferView should be detached'); + t.expect(buffer.mapState === 'unmapped'); }); diff --git a/src/webgpu/api/validation/state/device_lost/destroy.spec.ts b/src/webgpu/api/validation/state/device_lost/destroy.spec.ts index 065e842a111f..b268b40a55be 100644 --- a/src/webgpu/api/validation/state/device_lost/destroy.spec.ts +++ b/src/webgpu/api/validation/state/device_lost/destroy.spec.ts @@ -21,6 +21,7 @@ import { kTextureUsageCopyInfo, kShaderStageKeys, } from '../../../../capability_info.js'; +import { GPUConst } from '../../../../constants.js'; import { getBlockInfoForColorTextureFormat, kCompressedTextureFormats, @@ -52,16 +53,15 @@ class DeviceDestroyTests extends AllFeaturesMaxLimitsValidationTest { * `fn` after the device is destroyed without any specific expectation. If `awaitLost` is true, we * also wait for device.lost to resolve before executing `fn` in the destroy case. */ - async executeAfterDestroy(fn: () => void, awaitLost: boolean): Promise { + async executeAfterDestroy(fn: () => void | Promise, awaitLost: boolean): Promise { this.expectDeviceLost('destroyed'); - this.expectValidationError(fn, false); this.device.destroy(); if (awaitLost) { const lostInfo = await this.device.lost; this.expect(lostInfo.reason === 'destroyed'); } - fn(); + await fn(); } /** @@ -146,6 +146,93 @@ Tests creating buffers on destroyed device. Tests valid combinations of: }, awaitLost); }); +g.test('mapping,mappedAtCreation') + .desc( + ` +Tests behavior of mappedAtCreation buffers when destroying the device (multiple times). +- Various usages +- Wait for .lost or not + ` + ) + .params(u => + u + .combine('usage', [ + GPUConst.BufferUsage.MAP_READ, + GPUConst.BufferUsage.MAP_WRITE, + GPUConst.BufferUsage.COPY_SRC, + ]) + .combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { awaitLost, usage } = t.params; + + const b1 = t.createBufferTracked({ size: 16, usage, mappedAtCreation: true }); + t.expect(b1.mapState === 'mapped', 'b1 before destroy 1'); + + await t.executeAfterDestroy(() => { + // Destroy should have unmapped everything. + t.expect(b1.mapState === 'unmapped', 'b1 after destroy 1'); + // But unmap just in case, to reset state before continuing. + b1.unmap(); + + // mappedAtCreation should still work. + const b2 = t.createBufferTracked({ size: 16, usage, mappedAtCreation: true }); + t.expect(b2.mapState === 'mapped', 'b2 at creation after destroy 1'); + + // Destroying again should unmap. + t.device.destroy(); + t.expect(b2.mapState === 'unmapped', 'b2 after destroy 2'); + }, awaitLost); + }); + +g.test('mapping,mapAsync') + .desc( + ` +Tests behavior of mapAsync'd buffers when destroying the device. +- Various usages +- Wait for .lost or not + ` + ) + .params(u => + u + .combine('usage', [ + GPUConst.BufferUsage.MAP_READ, + GPUConst.BufferUsage.MAP_WRITE, + GPUConst.BufferUsage.COPY_SRC, + ]) + .combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { awaitLost, usage } = t.params; + + const b = t.createBufferTracked({ size: 16, usage }); + const mode = + usage === GPUBufferUsage.MAP_READ + ? GPUMapMode.READ + : usage === GPUBufferUsage.MAP_WRITE + ? GPUMapMode.WRITE + : 0; + if (mode) { + await b.mapAsync(mode); + t.expect(b.mapState === 'mapped', 'bAsync before destroy 1'); + } else { + t.expect(b.mapState === 'unmapped', 'bAsync before destroy 1'); + } + + await t.executeAfterDestroy(async () => { + // Destroy should have unmapped everything. + t.expect(b.mapState === 'unmapped', 'bAsync after destroy 1'); + // But unmap just in case, to reset state before continuing. + b.unmap(); + + // Check that mapping after destroy fails with AbortError. + const mapPromise = b.mapAsync(mode); + t.shouldReject('AbortError', mapPromise); + await mapPromise.catch(() => {}); + t.expect(b.mapState === 'unmapped', 'bAsync after mapAsync after destroy 1'); + }, awaitLost); + }); + g.test('createTexture,2d,uncompressed_format') .desc( `