diff --git a/src/webgpu/api/validation/encoding/cmds/debug.spec.ts b/src/webgpu/api/validation/encoding/cmds/debug.spec.ts index 850a93d61e38..d88772a8b3c8 100644 --- a/src/webgpu/api/validation/encoding/cmds/debug.spec.ts +++ b/src/webgpu/api/validation/encoding/cmds/debug.spec.ts @@ -13,87 +13,10 @@ Test Coverage: import { poptions, params } from '../../../../../common/framework/params_builder.js'; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; -import { assert } from '../../../../../common/framework/util/util.js'; -import { ValidationTest } from './../../validation_test.js'; +import { ValidationTest, kEncoderTypes } from './../../validation_test.js'; -type Encoder = GPUCommandEncoder | GPUProgrammablePassEncoder; -const kEncoderTypes = ['non-pass', 'compute pass', 'render pass', 'render bundle'] as const; -type EncoderType = typeof kEncoderTypes[number]; - -class F extends ValidationTest { - private commandEncoder: GPUCommandEncoder | undefined = undefined; - - makeAttachmentTexture(): GPUTexture { - return this.device.createTexture({ - format: 'rgba8unorm', - size: { width: 16, height: 16, depth: 1 }, - usage: GPUTextureUsage.OUTPUT_ATTACHMENT, - }); - } - - createEncoder(encoderType: EncoderType): Encoder { - assert(this.commandEncoder === undefined); - switch (encoderType) { - case 'non-pass': - return this.device.createCommandEncoder({}); - case 'render bundle': - return this.device.createRenderBundleEncoder({ colorFormats: ['rgba8unorm'] }); - case 'compute pass': - this.commandEncoder = this.device.createCommandEncoder({}); - return this.commandEncoder.beginComputePass({}); - case 'render pass': - this.commandEncoder = this.device.createCommandEncoder({}); - return this.commandEncoder.beginRenderPass({ - colorAttachments: [ - { - attachment: this.makeAttachmentTexture().createView(), - loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, - }, - ], - }); - } - } - - finishEncoder(encoder: Encoder, encoderType: EncoderType) { - let commandBuffer: GPUCommandBuffer | undefined = undefined; - switch (encoderType) { - case 'non-pass': { - commandBuffer = (encoder as GPUCommandEncoder).finish({}); - break; - } - case 'render bundle': { - const bundle = (encoder as GPURenderBundleEncoder).finish({}); - const commandEncoder = this.device.createCommandEncoder({}); - const pass = commandEncoder.beginRenderPass({ - colorAttachments: [ - { - attachment: this.makeAttachmentTexture().createView(), - loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, - }, - ], - }); - pass.executeBundles([bundle]); - pass.endPass(); - commandBuffer = commandEncoder.finish({}); - break; - } - case 'compute pass': - case 'render pass': { - assert(this.commandEncoder !== undefined); - (encoder as GPUComputePassEncoder | GPURenderPassEncoder).endPass(); - commandBuffer = this.commandEncoder?.finish(); - this.commandEncoder = undefined; - break; - } - } - if (commandBuffer !== undefined) { - this.queue.submit([commandBuffer]); - } - } -} - -export const g = makeTestGroup(F); +export const g = makeTestGroup(ValidationTest); g.test('debug_group_balanced') .params( @@ -103,7 +26,7 @@ g.test('debug_group_balanced') .combine(poptions('popCount', [0, 1, 2])) ) .fn(t => { - const encoder = t.createEncoder(t.params.encoderType); + const { encoder, finish } = t.createEncoder(t.params.encoderType); for (let i = 0; i < t.params.pushCount; ++i) { encoder.pushDebugGroup(`${i}`); } @@ -112,7 +35,8 @@ g.test('debug_group_balanced') } const shouldError = t.params.popCount !== t.params.pushCount; t.expectValidationError(() => { - t.finishEncoder(encoder, t.params.encoderType); + const commandBuffer = finish(); + t.queue.submit([commandBuffer]); }, shouldError); }); @@ -123,10 +47,11 @@ g.test('debug_group') .combine(poptions('label', ['', 'group'])) ) .fn(t => { - const encoder = t.createEncoder(t.params.encoderType); + const { encoder, finish } = t.createEncoder(t.params.encoderType); encoder.pushDebugGroup(t.params.label); encoder.popDebugGroup(); - t.finishEncoder(encoder, t.params.encoderType); + const commandBuffer = finish(); + t.queue.submit([commandBuffer]); }); g.test('debug_marker') @@ -136,7 +61,8 @@ g.test('debug_marker') .combine(poptions('label', ['', 'marker'])) ) .fn(t => { - const encoder = t.createEncoder(t.params.encoderType); - encoder.insertDebugMarker(t.params.label); - t.finishEncoder(encoder, t.params.encoderType); + const maker = t.createEncoder(t.params.encoderType); + maker.encoder.insertDebugMarker(t.params.label); + const commandBuffer = maker.finish(); + t.queue.submit([commandBuffer]); }); diff --git a/src/webgpu/api/validation/validation_test.ts b/src/webgpu/api/validation/validation_test.ts index a5357b450ce8..20a2bbf9aca3 100644 --- a/src/webgpu/api/validation/validation_test.ts +++ b/src/webgpu/api/validation/validation_test.ts @@ -2,6 +2,15 @@ import { unreachable } from '../../../common/framework/util/util.js'; import { BindableResource } from '../../capability_info.js'; import { GPUTest } from '../../gpu_test.js'; +type Encoder = GPUCommandEncoder | GPUProgrammablePassEncoder | GPURenderBundleEncoder; +export const kEncoderTypes = ['non-pass', 'compute pass', 'render pass', 'render bundle'] as const; +type EncoderType = typeof kEncoderTypes[number]; + +interface CommandBufferMaker { + readonly encoder: E; + finish(): GPUCommandBuffer; +} + export class ValidationTest extends GPUTest { createTextureWithState( state: 'valid' | 'invalid' | 'destroyed', @@ -181,6 +190,84 @@ export class ValidationTest extends GPUTest { }); } + createEncoder(encoderType: 'non-pass'): CommandBufferMaker; + createEncoder(encoderType: 'render pass'): CommandBufferMaker; + createEncoder(encoderType: 'compute pass'): CommandBufferMaker; + createEncoder(encoderType: 'render bundle'): CommandBufferMaker; + createEncoder( + encoderType: 'render pass' | 'render bundle' + ): CommandBufferMaker; + createEncoder( + encoderType: 'compute pass' | 'render pass' | 'render bundle' + ): CommandBufferMaker; + createEncoder(encoderType: EncoderType): CommandBufferMaker; + createEncoder(encoderType: EncoderType): CommandBufferMaker { + const colorFormat = 'rgba8unorm'; + switch (encoderType) { + case 'non-pass': { + const encoder = this.device.createCommandEncoder(); + return { + encoder, + + finish: () => { + return encoder.finish(); + }, + }; + } + case 'render bundle': { + const device = this.device; + const encoder = device.createRenderBundleEncoder({ + colorFormats: [colorFormat], + }); + const pass = this.createEncoder('render pass'); + return { + encoder, + finish: () => { + const bundle = encoder.finish(); + pass.encoder.executeBundles([bundle]); + return pass.finish(); + }, + }; + } + case 'compute pass': { + const commandEncoder = this.device.createCommandEncoder(); + const encoder = commandEncoder.beginComputePass(); + return { + encoder, + finish: () => { + encoder.endPass(); + return commandEncoder.finish(); + }, + }; + } + case 'render pass': { + const commandEncoder = this.device.createCommandEncoder(); + const attachment = this.device + .createTexture({ + format: colorFormat, + size: { width: 16, height: 16, depth: 1 }, + usage: GPUTextureUsage.OUTPUT_ATTACHMENT, + }) + .createView(); + const encoder = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + attachment, + loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, + }, + ], + }); + return { + encoder, + finish: () => { + encoder.endPass(); + return commandEncoder.finish(); + }, + }; + } + } + } + expectValidationError(fn: Function, shouldError: boolean = true): void { // If no error is expected, we let the scope surrounding the test catch it. if (shouldError === false) {