export const description = ` Tests using a destroyed texture on a queue. `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { unreachable } from '../../../../../common/util/util.js'; import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js'; export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest); g.test('writeTexture') .desc( ` Tests that using a destroyed texture in writeTexture fails. - x= {destroyed, not destroyed (control case)} ` ) .paramsSubcasesOnly(u => u.combine('destroyed', [false, true] as const)) .fn(t => { const { destroyed } = t.params; const texture = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', usage: GPUTextureUsage.COPY_DST, }); if (destroyed) { texture.destroy(); } t.expectValidationError( () => t.queue.writeTexture({ texture }, new Uint8Array(4), { bytesPerRow: 4 }, [1, 1, 1]), destroyed ); }); g.test('copyTextureToTexture') .desc( ` Tests that using a destroyed texture in copyTextureToTexture fails. - x= {not destroyed (control case), src destroyed, dst destroyed} ` ) .paramsSubcasesOnly(u => u.combine('destroyed', ['none', 'src', 'dst', 'both'] as const)) .fn(t => { const src = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', usage: GPUTextureUsage.COPY_SRC, }); const dst = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', usage: GPUTextureUsage.COPY_DST, }); const encoder = t.device.createCommandEncoder(); encoder.copyTextureToTexture({ texture: src }, { texture: dst }, [1, 1, 1]); const commandBuffer = encoder.finish(); let shouldError = true; switch (t.params.destroyed) { case 'none': shouldError = false; break; case 'src': src.destroy(); break; case 'dst': dst.destroy(); break; case 'both': src.destroy(); dst.destroy(); break; } t.expectValidationError(() => { t.queue.submit([commandBuffer]); }, shouldError); }); g.test('copyBufferToTexture') .desc( ` Tests that using a destroyed texture in copyBufferToTexture fails. - x= {not destroyed (control case), dst destroyed} ` ) .paramsSubcasesOnly(u => u.combine('destroyed', [false, true] as const)) .fn(t => { const { destroyed } = t.params; const buffer = t.createBufferTracked({ size: 4, usage: GPUBufferUsage.COPY_SRC }); const texture = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', usage: GPUTextureUsage.COPY_DST, }); const encoder = t.device.createCommandEncoder(); encoder.copyBufferToTexture({ buffer }, { texture }, [1, 1, 1]); const commandBuffer = encoder.finish(); if (destroyed) { texture.destroy(); } t.expectValidationError(() => { t.queue.submit([commandBuffer]); }, destroyed); }); g.test('copyTextureToBuffer') .desc( ` Tests that using a destroyed texture in copyTextureToBuffer fails. - x= {not destroyed (control case), src destroyed} ` ) .paramsSubcasesOnly(u => u.combine('destroyed', [false, true] as const)) .fn(t => { const { destroyed } = t.params; const texture = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', usage: GPUTextureUsage.COPY_SRC, }); const buffer = t.createBufferTracked({ size: 4, usage: GPUBufferUsage.COPY_DST }); const encoder = t.device.createCommandEncoder(); encoder.copyTextureToBuffer({ texture }, { buffer }, [1, 1, 1]); const commandBuffer = encoder.finish(); if (destroyed) { texture.destroy(); } t.expectValidationError(() => { t.queue.submit([commandBuffer]); }, destroyed); }); g.test('setBindGroup') .desc( ` Tests that using a destroyed texture referenced by a bindGroup set with setBindGroup fails - x= {not destroyed (control case), destroyed} ` ) .paramsSubcasesOnly(u => u .combine('destroyed', [false, true] as const) .combine('encoderType', ['compute pass', 'render pass', 'render bundle'] as const) .combine('bindingType', ['texture', 'storageTexture'] as const) ) .fn(t => { const { destroyed, encoderType, bindingType } = t.params; const { device } = t; const texture = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING, }); const layout = device.createBindGroupLayout({ entries: [ { binding: 0, visibility: GPUShaderStage.COMPUTE, [bindingType]: { format: texture.format, }, }, ], }); const bindGroup = device.createBindGroup({ layout, entries: [{ binding: 0, resource: texture.createView() }], }); const { encoder, finish } = t.createEncoder(encoderType); encoder.setBindGroup(0, bindGroup); const commandBuffer = finish(); if (destroyed) { texture.destroy(); } t.expectValidationError(() => { t.queue.submit([commandBuffer]); }, destroyed); }); g.test('beginRenderPass') .desc( ` Tests that using a destroyed texture referenced by a render pass fails - x= {not destroyed (control case), colorAttachment destroyed, depthAttachment destroyed, resolveTarget destroyed} ` ) .paramsSubcasesOnly(u => u.combine('textureToDestroy', [ 'none', 'colorAttachment', 'resolveAttachment', 'depthStencilAttachment', ]) ) .fn(t => { const { textureToDestroy } = t.params; const { device } = t; const colorAttachment = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', sampleCount: 4, usage: GPUTextureUsage.RENDER_ATTACHMENT, }); const resolveAttachment = t.createTextureTracked({ size: [1, 1, 1], format: 'rgba8unorm', usage: GPUTextureUsage.RENDER_ATTACHMENT, }); const depthStencilAttachment = t.createTextureTracked({ size: [1, 1, 1], format: 'depth32float', sampleCount: 4, usage: GPUTextureUsage.RENDER_ATTACHMENT, }); const encoder = device.createCommandEncoder(); const pass = encoder.beginRenderPass({ colorAttachments: [ { view: colorAttachment.createView(), resolveTarget: resolveAttachment.createView(), loadOp: 'clear', storeOp: 'store', }, ], depthStencilAttachment: { view: depthStencilAttachment.createView(), depthClearValue: 0, depthLoadOp: 'clear', depthStoreOp: 'store', }, }); pass.end(); const commandBuffer = encoder.finish(); switch (textureToDestroy) { case 'none': break; case 'colorAttachment': colorAttachment.destroy(); break; case 'resolveAttachment': resolveAttachment.destroy(); break; case 'depthStencilAttachment': depthStencilAttachment.destroy(); break; default: unreachable(); } const shouldError = textureToDestroy !== 'none'; t.expectValidationError(() => { t.queue.submit([commandBuffer]); }, shouldError); });