diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts | 962 |
1 files changed, 962 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts new file mode 100644 index 0000000000..a50469db2a --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts @@ -0,0 +1,962 @@ +export const description = ` +Tests for device lost induced via destroy. + - Tests that prior to device destruction, valid APIs do not generate errors (control case). + - After device destruction, runs the same APIs. No expected observable results, so test crash or future failures are the only current failure indicators. +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { + allBindingEntries, + bindingTypeInfo, + kBindableResources, + kBufferUsageKeys, + kBufferUsageInfo, + kBufferUsageCopy, + kBufferUsageCopyInfo, + kCompressedTextureFormats, + kQueryTypes, + kTextureUsageType, + kTextureUsageTypeInfo, + kTextureUsageCopy, + kTextureUsageCopyInfo, + kRegularTextureFormats, + kRenderableColorTextureFormats, + kShaderStageKeys, + kTextureFormatInfo, +} from '../../../../capability_info.js'; +import { CommandBufferMaker, EncoderType } from '../../../../util/command_buffer_maker.js'; +import { + canCopyFromCanvasContext, + createCanvas, + kAllCanvasTypes, + kValidCanvasContextIds, +} from '../../../../util/create_elements.js'; +import { ValidationTest } from '../../validation_test.js'; + +const kCommandValidationStages = ['finish', 'submit']; +type CommandValidationStage = typeof kCommandValidationStages[number]; + +class DeviceDestroyTests extends ValidationTest { + /** + * Expects that `fn` does not produce any errors before the device is destroyed, and then calls + * `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<void> { + this.expectDeviceLost('destroyed'); + + this.expectValidationError(fn, false); + this.device.destroy(); + if (awaitLost) { + const lostInfo = await this.device.lost; + this.expect(lostInfo.reason === 'destroyed'); + } + fn(); + } + + /** + * Expects that encoders can finish and submit the resulting commands before the device is + * destroyed, then repeats the same process after the device is destroyed without any specific + * expectations. + * There are two valid stages: 'finish' and 'submit'. + * 'finish': Tests [encode, finish] and [encoder, destroy, finish] + * 'submit': Tests [encoder, finish, submit] and [encoder, finish, destroy, submit] + */ + async executeCommandsAfterDestroy<T extends EncoderType>( + stage: CommandValidationStage, + awaitLost: boolean, + encoderType: T, + fn: (maker: CommandBufferMaker<T>) => CommandBufferMaker<T> + ): Promise<void> { + this.expectDeviceLost('destroyed'); + + switch (stage) { + case 'finish': { + // Control case + fn(this.createEncoder(encoderType)).validateFinish(true); + // Validation case + const encoder = fn(this.createEncoder(encoderType)); + await this.executeAfterDestroy(() => { + encoder.finish(); + }, awaitLost); + break; + } + case 'submit': { + // Control case + fn(this.createEncoder(encoderType)).validateFinishAndSubmit(true, true); + // Validation case + const commands = fn(this.createEncoder(encoderType)).validateFinish(true); + await this.executeAfterDestroy(() => { + this.queue.submit([commands]); + }, awaitLost); + break; + } + } + } +} + +export const g = makeTestGroup(DeviceDestroyTests); + +g.test('createBuffer') + .desc( + ` +Tests creating buffers on destroyed device. Tests valid combinations of: + - Various usages + - Mapped at creation or not + ` + ) + .params(u => + u + .combine('usageType', kBufferUsageKeys) + .beginSubcases() + .combine('usageCopy', kBufferUsageCopy) + .combine('awaitLost', [true, false]) + .filter(({ usageType, usageCopy }) => { + if (usageType === 'COPY_SRC' || usageType === 'COPY_DST') { + return false; + } + if (usageType === 'MAP_READ') { + return usageCopy === 'COPY_NONE' || usageCopy === 'COPY_DST'; + } + if (usageType === 'MAP_WRITE') { + return usageCopy === 'COPY_NONE' || usageCopy === 'COPY_SRC'; + } + return true; + }) + .combine('mappedAtCreation', [true, false]) + ) + .fn(async t => { + const { awaitLost, usageType, usageCopy, mappedAtCreation } = t.params; + await t.executeAfterDestroy(() => { + t.device.createBuffer({ + size: 16, + usage: kBufferUsageInfo[usageType] | kBufferUsageCopyInfo[usageCopy], + mappedAtCreation, + }); + }, awaitLost); + }); + +g.test('createTexture,2d,uncompressed_format') + .desc( + ` +Tests creating 2d uncompressed textures on destroyed device. Tests valid combinations of: + - Various uncompressed texture formats + - Various usages + ` + ) + .params(u => + u + .combine('format', kRegularTextureFormats) + .beginSubcases() + .combine('usageType', kTextureUsageType) + .combine('usageCopy', kTextureUsageCopy) + .combine('awaitLost', [true, false]) + .filter(({ format, usageType }) => { + const info = kTextureFormatInfo[format]; + return !( + (!info.renderable && usageType === 'render') || + (!info.storage && usageType === 'storage') + ); + }) + ) + .fn(async t => { + const { awaitLost, format, usageType, usageCopy } = t.params; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + await t.executeAfterDestroy(() => { + t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy], + format, + }); + }, awaitLost); + }); + +g.test('createTexture,2d,compressed_format') + .desc( + ` +Tests creating 2d compressed textures on destroyed device. Tests valid combinations of: + - Various compressed texture formats + - Various usages + ` + ) + .params(u => + u + .combine('format', kCompressedTextureFormats) + .beginSubcases() + .combine('usageType', kTextureUsageType) + .combine('usageCopy', kTextureUsageCopy) + .combine('awaitLost', [true, false]) + .filter(({ format, usageType }) => { + const info = kTextureFormatInfo[format]; + return !( + (!info.renderable && usageType === 'render') || + (!info.storage && usageType === 'storage') + ); + }) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature); + }) + .fn(async t => { + const { awaitLost, format, usageType, usageCopy } = t.params; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + await t.executeAfterDestroy(() => { + t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy], + format, + }); + }, awaitLost); + }); + +g.test('createView,2d,uncompressed_format') + .desc( + ` +Tests creating texture views on 2d uncompressed textures from destroyed device. Tests valid combinations of: + - Various uncompressed texture formats + - Various usages + ` + ) + .params(u => + u + .combine('format', kRegularTextureFormats) + .beginSubcases() + .combine('usageType', kTextureUsageType) + .combine('usageCopy', kTextureUsageCopy) + .combine('awaitLost', [true, false]) + .filter(({ format, usageType }) => { + const info = kTextureFormatInfo[format]; + return !( + (!info.renderable && usageType === 'render') || + (!info.storage && usageType === 'storage') + ); + }) + ) + .fn(async t => { + const { awaitLost, format, usageType, usageCopy } = t.params; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + const texture = t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy], + format, + }); + await t.executeAfterDestroy(() => { + texture.createView({ format }); + }, awaitLost); + }); + +g.test('createView,2d,compressed_format') + .desc( + ` +Tests creating texture views on 2d compressed textures from destroyed device. Tests valid combinations of: + - Various compressed texture formats + - Various usages + ` + ) + .params(u => + u + .combine('format', kCompressedTextureFormats) + .beginSubcases() + .combine('usageType', kTextureUsageType) + .combine('usageCopy', kTextureUsageCopy) + .combine('awaitLost', [true, false]) + .filter(({ format, usageType }) => { + const info = kTextureFormatInfo[format]; + return !( + (!info.renderable && usageType === 'render') || + (!info.storage && usageType === 'storage') + ); + }) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature); + }) + .fn(async t => { + const { awaitLost, format, usageType, usageCopy } = t.params; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + const texture = t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy], + format, + }); + await t.executeAfterDestroy(() => { + texture.createView({ format }); + }, awaitLost); + }); + +g.test('createSampler') + .desc( + ` +Tests creating samplers on destroyed device. + ` + ) + .params(u => u.beginSubcases().combine('awaitLost', [true, false])) + .fn(async t => { + const { awaitLost } = t.params; + await t.executeAfterDestroy(() => { + t.device.createSampler(); + }, awaitLost); + }); + +g.test('createBindGroupLayout') + .desc( + ` +Tests creating bind group layouts on destroyed device. Tests valid combinations of: + - Various valid binding entries + - Maximum set of visibility for each binding entry + ` + ) + .params(u => + u.combine('entry', allBindingEntries(false)).beginSubcases().combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { awaitLost, entry } = t.params; + const visibility = bindingTypeInfo(entry).validStages; + await t.executeAfterDestroy(() => { + t.device.createBindGroupLayout({ + entries: [{ binding: 0, visibility, ...entry }], + }); + }, awaitLost); + }); + +g.test('createBindGroup') + .desc( + ` +Tests creating bind group on destroyed device. Tests valid combinations of: + - Various binded resource types + - Various valid binding entries + - Maximum set of visibility for each binding entry + ` + ) + .desc(`A destroyed device should not be able to create any valid bind groups.`) + .params(u => + u + .combine('resourceType', kBindableResources) + .combine('entry', allBindingEntries(false)) + .filter(({ resourceType, entry }) => { + const info = bindingTypeInfo(entry); + switch (info.resource) { + // Either type of sampler may be bound to a filtering sampler binding. + case 'filtSamp': + return resourceType === 'filtSamp' || resourceType === 'nonFiltSamp'; + // But only non-filtering samplers can be used with non-filtering sampler bindings. + case 'nonFiltSamp': + return resourceType === 'nonFiltSamp'; + default: + return info.resource === resourceType; + } + }) + .beginSubcases() + .combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { awaitLost, resourceType, entry } = t.params; + const visibility = bindingTypeInfo(entry).validStages; + const layout = t.device.createBindGroupLayout({ + entries: [{ binding: 0, visibility, ...entry }], + }); + const resource = t.getBindingResource(resourceType); + await t.executeAfterDestroy(() => { + t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] }); + }, awaitLost); + }); + +g.test('createPipelineLayout') + .desc( + ` +Tests creating pipeline layouts on destroyed device. Tests valid combinations of: + - Various bind groups with valid binding entries + - Maximum set of visibility for each binding entry + ` + ) + .params(u => + u.combine('entry', allBindingEntries(false)).beginSubcases().combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { awaitLost, entry } = t.params; + const visibility = bindingTypeInfo(entry).validStages; + const bindGroupLayout = t.device.createBindGroupLayout({ + entries: [{ binding: 0, visibility, ...entry }], + }); + await t.executeAfterDestroy(() => { + t.device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout], + }); + }, awaitLost); + }); + +g.test('createShaderModule') + .desc( + ` +Tests creating shader modules on destroyed device. + - Tests all shader stages: vertex, fragment, compute + ` + ) + .params(u => + u.combine('stage', kShaderStageKeys).beginSubcases().combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { awaitLost, stage } = t.params; + await t.executeAfterDestroy(() => { + t.device.createShaderModule({ code: t.getNoOpShaderCode(stage) }); + }, awaitLost); + }); + +g.test('createComputePipeline') + .desc( + ` +Tests creating compute pipeline on destroyed device. + - Tests with a valid no-op compute shader + ` + ) + .params(u => u.beginSubcases().combine('awaitLost', [true, false])) + .fn(async t => { + const { awaitLost } = t.params; + const cShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('COMPUTE') }); + await t.executeAfterDestroy(() => { + t.device.createComputePipeline({ + layout: 'auto', + compute: { module: cShader, entryPoint: 'main' }, + }); + }, awaitLost); + }); + +g.test('createRenderPipeline') + .desc( + ` +Tests creating render pipeline on destroyed device. + - Tests with valid no-op vertex and fragment shaders + ` + ) + .params(u => u.beginSubcases().combine('awaitLost', [true, false])) + .fn(async t => { + const { awaitLost } = t.params; + const vShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX') }); + const fShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('FRAGMENT') }); + await t.executeAfterDestroy(() => { + t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module: vShader, entryPoint: 'main' }, + fragment: { + module: fShader, + entryPoint: 'main', + targets: [{ format: 'rgba8unorm', writeMask: 0 }], + }, + }); + }, awaitLost); + }); + +g.test('createCommandEncoder') + .desc( + ` +Tests creating command encoders on destroyed device. + ` + ) + .params(u => u.beginSubcases().combine('awaitLost', [true, false])) + .fn(async t => { + const { awaitLost } = t.params; + await t.executeAfterDestroy(() => { + t.device.createCommandEncoder(); + }, awaitLost); + }); + +g.test('createRenderBundleEncoder') + .desc( + ` +Tests creating render bundle encoders on destroyed device. + - Tests various renderable texture color formats + ` + ) + .params(u => + u + .combine('format', kRenderableColorTextureFormats) + .beginSubcases() + .combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { awaitLost, format } = t.params; + await t.executeAfterDestroy(() => { + t.device.createRenderBundleEncoder({ colorFormats: [format] }); + }, awaitLost); + }); + +g.test('createQuerySet') + .desc( + ` +Tests creating query sets on destroyed device. + - Tests various query set types + ` + ) + .params(u => u.combine('type', kQueryTypes).beginSubcases().combine('awaitLost', [true, false])) + .beforeAllSubcases(t => { + const { type } = t.params; + t.selectDeviceForQueryTypeOrSkipTestCase(type); + }) + .fn(async t => { + const { awaitLost, type } = t.params; + await t.executeAfterDestroy(() => { + t.device.createQuerySet({ type, count: 4 }); + }, awaitLost); + }); + +g.test('command,copyBufferToBuffer') + .desc( + ` +Tests copyBufferToBuffer command with various uncompressed formats on destroyed device. + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const kBufferSize = 16; + const src = t.device.createBuffer({ + size: kBufferSize, + usage: GPUBufferUsage.COPY_SRC, + }); + const dst = t.device.createBuffer({ + size: kBufferSize, + usage: GPUBufferUsage.COPY_DST, + }); + await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => { + maker.encoder.copyBufferToBuffer(src, 0, dst, 0, kBufferSize); + return maker; + }); + }); + +g.test('command,copyBufferToTexture') + .desc( + ` +Tests copyBufferToTexture command on destroyed device. + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const format = 'rgba32uint'; + const { bytesPerBlock, blockWidth, blockHeight } = kTextureFormatInfo[format]; + const src = { + buffer: t.device.createBuffer({ + size: bytesPerBlock, + usage: GPUBufferUsage.COPY_SRC, + }), + }; + const dst = { + texture: t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: GPUTextureUsage.COPY_DST, + format, + }), + }; + const copySize = { width: blockWidth, height: blockHeight }; + await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => { + maker.encoder.copyBufferToTexture(src, dst, copySize); + return maker; + }); + }); + +g.test('command,copyTextureToBuffer') + .desc( + ` +Tests copyTextureToBuffer command on destroyed device. + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const format = 'rgba32uint'; + const { bytesPerBlock, blockWidth, blockHeight } = kTextureFormatInfo[format]; + const src = { + texture: t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: GPUTextureUsage.COPY_SRC, + format, + }), + }; + const dst = { + buffer: t.device.createBuffer({ + size: bytesPerBlock, + usage: GPUBufferUsage.COPY_DST, + }), + }; + const copySize = { width: blockWidth, height: blockHeight }; + await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => { + maker.encoder.copyTextureToBuffer(src, dst, copySize); + return maker; + }); + }); + +g.test('command,copyTextureToTexture') + .desc( + ` +Tests copyTextureToTexture command on destroyed device. + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const format = 'rgba32uint'; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + const src = { + texture: t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: GPUTextureUsage.COPY_SRC, + format, + }), + }; + const dst = { + texture: t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: GPUBufferUsage.COPY_DST, + format, + }), + }; + const copySize = { width: blockWidth, height: blockHeight }; + await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => { + maker.encoder.copyTextureToTexture(src, dst, copySize); + return maker; + }); + }); + +g.test('command,clearBuffer') + .desc( + ` +Tests encoding and finishing a clearBuffer command on destroyed device. + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const kBufferSize = 16; + const buffer = t.device.createBuffer({ + size: kBufferSize, + usage: GPUBufferUsage.COPY_SRC, + }); + await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => { + maker.encoder.clearBuffer(buffer, 0, kBufferSize); + return maker; + }); + }); + +g.test('command,writeTimestamp') + .desc( + ` +Tests encoding and finishing a writeTimestamp command on destroyed device. + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u + .combine('type', kQueryTypes) + .beginSubcases() + .combine('stage', kCommandValidationStages) + .combine('awaitLost', [true, false]) + ) + .beforeAllSubcases(t => { + const { type } = t.params; + + // writeTimestamp is only available for devices that enable the 'timestamp-query' feature. + const queryTypes: GPUQueryType[] = ['timestamp']; + if (type !== 'timestamp') { + queryTypes.push(type); + } + + t.selectDeviceForQueryTypeOrSkipTestCase(queryTypes); + }) + .fn(async t => { + const { type, stage, awaitLost } = t.params; + const querySet = t.device.createQuerySet({ type, count: 2 }); + await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => { + maker.encoder.writeTimestamp(querySet, 0); + return maker; + }); + }); + +g.test('command,resolveQuerySet') + .desc( + ` +Tests encoding and finishing a resolveQuerySet command on destroyed device. + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const kQueryCount = 2; + const querySet = t.createQuerySetWithState('valid'); + const destination = t.createBufferWithState('valid', { + size: kQueryCount * 8, + usage: GPUBufferUsage.QUERY_RESOLVE, + }); + await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => { + maker.encoder.resolveQuerySet(querySet, 0, 1, destination, 0); + return maker; + }); + }); + +g.test('command,computePass,dispatch') + .desc( + ` +Tests encoding and dispatching a simple valid compute pass on destroyed device. + - Binds valid pipeline and bindgroups, then dispatches + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const cShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('COMPUTE') }); + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { module: cShader, entryPoint: 'main' }, + }); + await t.executeCommandsAfterDestroy(stage, awaitLost, 'compute pass', maker => { + maker.encoder.setPipeline(pipeline); + maker.encoder.dispatchWorkgroups(1); + return maker; + }); + }); + +g.test('command,renderPass,draw') + .desc( + ` +Tests encoding and finishing a simple valid render pass on destroyed device. + - Binds valid pipeline and bindgroups, then draws + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const vShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX') }); + const fShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('FRAGMENT') }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module: vShader, entryPoint: 'main' }, + fragment: { + module: fShader, + entryPoint: 'main', + targets: [{ format: 'rgba8unorm', writeMask: 0 }], + }, + }); + await t.executeCommandsAfterDestroy(stage, awaitLost, 'render pass', maker => { + maker.encoder.setPipeline(pipeline); + maker.encoder.draw(0); + return maker; + }); + }); + +g.test('command,renderPass,renderBundle') + .desc( + ` +Tests encoding and drawing a render pass including a render bundle on destroyed device. + - Binds valid pipeline and bindgroups, executes render bundle, then draws + - Tests finishing encoding on destroyed device + - Tests submitting command on destroyed device + ` + ) + .params(u => + u.beginSubcases().combine('stage', kCommandValidationStages).combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { stage, awaitLost } = t.params; + const vShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX') }); + const fShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('FRAGMENT') }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module: vShader, entryPoint: 'main' }, + fragment: { + module: fShader, + entryPoint: 'main', + targets: [{ format: 'rgba8unorm', writeMask: 0 }], + }, + }); + await t.executeCommandsAfterDestroy(stage, awaitLost, 'render bundle', maker => { + maker.encoder.setPipeline(pipeline); + maker.encoder.draw(0); + return maker; + }); + }); + +g.test('queue,writeBuffer') + .desc( + ` +Tests writeBuffer on queue on destroyed device. + ` + ) + .params(u => + u.combine('numElements', [4, 8, 16]).beginSubcases().combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { numElements, awaitLost } = t.params; + const buffer = t.device.createBuffer({ + size: numElements, + usage: GPUBufferUsage.COPY_DST, + }); + const data = new Uint8Array(numElements); + await t.executeAfterDestroy(() => { + t.device.queue.writeBuffer(buffer, 0, data); + }, awaitLost); + }); + +g.test('queue,writeTexture,2d,uncompressed_format') + .desc( + ` +Tests writeTexture on queue on destroyed device with uncompressed formats. + ` + ) + .params(u => + u.combine('format', kRegularTextureFormats).beginSubcases().combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { format, awaitLost } = t.params; + const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format]; + const data = new Uint8Array(bytesPerBlock); + const texture = t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: GPUTextureUsage.COPY_DST, + format, + }); + await t.executeAfterDestroy(() => { + t.device.queue.writeTexture( + { texture }, + data, + {}, + { width: blockWidth, height: blockHeight } + ); + }, awaitLost); + }); + +g.test('queue,writeTexture,2d,compressed_format') + .desc( + ` +Tests writeTexture on queue on destroyed device with compressed formats. + ` + ) + .params(u => + u + .combine('format', kCompressedTextureFormats) + .beginSubcases() + .combine('awaitLost', [true, false]) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature); + }) + .fn(async t => { + const { format, awaitLost } = t.params; + const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format]; + const data = new Uint8Array(bytesPerBlock); + const texture = t.device.createTexture({ + size: { width: blockWidth, height: blockHeight }, + usage: GPUTextureUsage.COPY_DST, + format, + }); + await t.executeAfterDestroy(() => { + t.device.queue.writeTexture( + { texture }, + data, + {}, + { width: blockWidth, height: blockHeight } + ); + }, awaitLost); + }); + +g.test('queue,copyExternalImageToTexture,canvas') + .desc( + ` +Tests copyExternalImageToTexture from canvas on queue on destroyed device. + ` + ) + .params(u => + u + .combine('canvasType', kAllCanvasTypes) + .combine('contextType', kValidCanvasContextIds) + .filter(({ contextType }) => { + return canCopyFromCanvasContext(contextType); + }) + .beginSubcases() + .combine('awaitLost', [true, false]) + ) + .fn(async t => { + const { canvasType, contextType, awaitLost } = t.params; + const canvas = createCanvas(t, canvasType, 1, 1); + const texture = t.device.createTexture({ + size: { width: 1, height: 1 }, + format: 'bgra8unorm', + usage: GPUTextureUsage.COPY_DST, + }); + + const ctx = ((canvas as unknown) as HTMLCanvasElement).getContext(contextType); + if (ctx === null) { + t.skip('Failed to get context for canvas element'); + return; + } + t.tryTrackForCleanup(ctx); + + await t.executeAfterDestroy(() => { + t.device.queue.copyExternalImageToTexture( + { source: canvas }, + { texture }, + { width: 1, height: 1 } + ); + }, awaitLost); + }); + +g.test('queue,copyExternalImageToTexture,imageBitmap') + .desc( + ` +Tests copyExternalImageToTexture from canvas on queue on destroyed device. + ` + ) + .params(u => u.beginSubcases().combine('awaitLost', [true, false])) + .fn(async t => { + const { awaitLost } = t.params; + if (typeof createImageBitmap === 'undefined') { + t.skip('Creating ImageBitmaps is not supported.'); + } + const imageBitmap = await createImageBitmap(new ImageData(new Uint8ClampedArray(4), 1, 1)); + + const texture = t.device.createTexture({ + size: { width: 1, height: 1 }, + format: 'bgra8unorm', + usage: GPUTextureUsage.COPY_DST, + }); + + await t.executeAfterDestroy(() => { + t.device.queue.copyExternalImageToTexture( + { source: imageBitmap }, + { texture }, + { width: 1, height: 1 } + ); + }, awaitLost); + }); |