diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts new file mode 100644 index 0000000000..0d56222eed --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts @@ -0,0 +1,587 @@ +export const description = ` +Validation tests to all commands of GPUCommandEncoder, GPUComputePassEncoder, and +GPURenderPassEncoder when the encoder is not finished. +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../common/util/data_tables.js'; +import { unreachable } from '../../../../common/util/util.js'; +import { ValidationTest } from '../validation_test.js'; + +import { beginRenderPassWithQuerySet } from './queries/common.js'; + +class F extends ValidationTest { + createRenderPipelineForTest(): GPURenderPipeline { + return this.device.createRenderPipeline({ + layout: 'auto', + vertex: { + module: this.device.createShaderModule({ + code: ` + @vertex fn main() -> @builtin(position) vec4<f32> { + return vec4<f32>(); + } + `, + }), + entryPoint: 'main', + }, + fragment: { + module: this.device.createShaderModule({ + code: `@fragment fn main() {}`, + }), + entryPoint: 'main', + targets: [{ format: 'rgba8unorm', writeMask: 0 }], + }, + }); + } + + createBindGroupForTest(): GPUBindGroup { + return this.device.createBindGroup({ + entries: [ + { + binding: 0, + resource: this.device.createSampler(), + }, + ], + layout: this.device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + sampler: { type: 'filtering' }, + }, + ], + }), + }); + } +} + +export const g = makeTestGroup(F); + +type EncoderCommands = keyof Omit<GPUCommandEncoder, '__brand' | 'label' | 'finish'>; +const kEncoderCommandInfo: { + readonly [k in EncoderCommands]: {}; +} = { + beginComputePass: {}, + beginRenderPass: {}, + clearBuffer: {}, + copyBufferToBuffer: {}, + copyBufferToTexture: {}, + copyTextureToBuffer: {}, + copyTextureToTexture: {}, + insertDebugMarker: {}, + popDebugGroup: {}, + pushDebugGroup: {}, + writeTimestamp: {}, + resolveQuerySet: {}, +}; +const kEncoderCommands = keysOf(kEncoderCommandInfo); + +type RenderPassEncoderCommands = keyof Omit<GPURenderPassEncoder, '__brand' | 'label' | 'end'>; +const kRenderPassEncoderCommandInfo: { + readonly [k in RenderPassEncoderCommands]: {}; +} = { + draw: {}, + drawIndexed: {}, + drawIndexedIndirect: {}, + drawIndirect: {}, + setIndexBuffer: {}, + setBindGroup: {}, + setVertexBuffer: {}, + setPipeline: {}, + setViewport: {}, + setScissorRect: {}, + setBlendConstant: {}, + setStencilReference: {}, + beginOcclusionQuery: {}, + endOcclusionQuery: {}, + executeBundles: {}, + pushDebugGroup: {}, + popDebugGroup: {}, + insertDebugMarker: {}, +}; +const kRenderPassEncoderCommands = keysOf(kRenderPassEncoderCommandInfo); + +type RenderBundleEncoderCommands = keyof Omit< + GPURenderBundleEncoder, + '__brand' | 'label' | 'finish' +>; +const kRenderBundleEncoderCommandInfo: { + readonly [k in RenderBundleEncoderCommands]: {}; +} = { + draw: {}, + drawIndexed: {}, + drawIndexedIndirect: {}, + drawIndirect: {}, + setPipeline: {}, + setBindGroup: {}, + setIndexBuffer: {}, + setVertexBuffer: {}, + pushDebugGroup: {}, + popDebugGroup: {}, + insertDebugMarker: {}, +}; +const kRenderBundleEncoderCommands = keysOf(kRenderBundleEncoderCommandInfo); + +// MAINTENANCE_TODO: remove the deprecated 'dispatch' and 'dispatchIndirect' here once they're +// removed from `@webgpu/types`. +type ComputePassEncoderCommands = keyof Omit< + GPUComputePassEncoder, + '__brand' | 'label' | 'end' | 'dispatch' | 'dispatchIndirect' +>; +const kComputePassEncoderCommandInfo: { + readonly [k in ComputePassEncoderCommands]: {}; +} = { + setBindGroup: {}, + setPipeline: {}, + dispatchWorkgroups: {}, + dispatchWorkgroupsIndirect: {}, + pushDebugGroup: {}, + popDebugGroup: {}, + insertDebugMarker: {}, +}; +const kComputePassEncoderCommands = keysOf(kComputePassEncoderCommandInfo); + +g.test('non_pass_commands') + .desc( + ` + Test that functions of GPUCommandEncoder generate a validation error if the encoder is already + finished. + ` + ) + .params(u => + u + .combine('command', kEncoderCommands) + .beginSubcases() + .combine('finishBeforeCommand', [false, true]) + ) + .beforeAllSubcases(t => { + switch (t.params.command) { + case 'writeTimestamp': + t.selectDeviceOrSkipTestCase('timestamp-query'); + break; + } + }) + .fn(t => { + const { command, finishBeforeCommand } = t.params; + + const srcBuffer = t.device.createBuffer({ + size: 16, + usage: GPUBufferUsage.COPY_SRC | GPUTextureUsage.COPY_DST, + }); + const dstBuffer = t.device.createBuffer({ + size: 16, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.QUERY_RESOLVE, + }); + + const textureSize = { width: 1, height: 1 }; + const textureFormat = 'rgba8unorm'; + const srcTexture = t.device.createTexture({ + size: textureSize, + format: textureFormat, + usage: GPUTextureUsage.COPY_SRC, + }); + const dstTexture = t.device.createTexture({ + size: textureSize, + format: textureFormat, + usage: GPUTextureUsage.COPY_DST, + }); + + const querySet = t.device.createQuerySet({ + type: command === 'writeTimestamp' ? 'timestamp' : 'occlusion', + count: 1, + }); + + const encoder = t.device.createCommandEncoder(); + + if (finishBeforeCommand) encoder.finish(); + + t.expectValidationError(() => { + switch (command) { + case 'beginComputePass': + { + encoder.beginComputePass(); + } + break; + case 'beginRenderPass': + { + encoder.beginRenderPass({ colorAttachments: [] }); + } + break; + case 'clearBuffer': + { + encoder.clearBuffer(dstBuffer, 0, 16); + } + break; + case 'copyBufferToBuffer': + { + encoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 0); + } + break; + case 'copyBufferToTexture': + { + encoder.copyBufferToTexture( + { buffer: srcBuffer }, + { texture: dstTexture }, + textureSize + ); + } + break; + case 'copyTextureToBuffer': + { + encoder.copyTextureToBuffer( + { texture: srcTexture }, + { buffer: dstBuffer }, + textureSize + ); + } + break; + case 'copyTextureToTexture': + { + encoder.copyTextureToTexture( + { texture: srcTexture }, + { texture: dstTexture }, + textureSize + ); + } + break; + case 'insertDebugMarker': + { + encoder.insertDebugMarker('marker'); + } + break; + case 'pushDebugGroup': + { + encoder.pushDebugGroup('group'); + } + break; + case 'popDebugGroup': + { + encoder.popDebugGroup(); + } + break; + case 'writeTimestamp': + { + encoder.writeTimestamp(querySet, 0); + } + break; + case 'resolveQuerySet': + { + encoder.resolveQuerySet(querySet, 0, 1, dstBuffer, 0); + } + break; + default: + unreachable(); + } + }, finishBeforeCommand); + }); + +g.test('render_pass_commands') + .desc( + ` + Test that functions of GPURenderPassEncoder generate a validation error if the encoder or the + pass is already finished. + + - TODO: Consider testing: nothing before command, end before command, end+finish before command. + ` + ) + .params(u => + u + .combine('command', kRenderPassEncoderCommands) + .beginSubcases() + .combine('finishBeforeCommand', [false, true]) + ) + .fn(t => { + const { command, finishBeforeCommand } = t.params; + + const querySet = t.device.createQuerySet({ type: 'occlusion', count: 1 }); + const encoder = t.device.createCommandEncoder(); + const renderPass = beginRenderPassWithQuerySet(t, encoder, querySet); + + const buffer = t.device.createBuffer({ + size: 12, + usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.VERTEX, + }); + + const pipeline = t.createRenderPipelineForTest(); + + const bindGroup = t.createBindGroupForTest(); + + if (finishBeforeCommand) { + renderPass.end(); + encoder.finish(); + } + + t.expectValidationError(() => { + switch (command) { + case 'draw': + { + renderPass.draw(1); + } + break; + case 'drawIndexed': + { + renderPass.drawIndexed(1); + } + break; + case 'drawIndirect': + { + renderPass.drawIndirect(buffer, 1); + } + break; + case 'setIndexBuffer': + { + renderPass.setIndexBuffer(buffer, 'uint32'); + } + break; + case 'drawIndexedIndirect': + { + renderPass.drawIndexedIndirect(buffer, 0); + } + break; + case 'setBindGroup': + { + renderPass.setBindGroup(0, bindGroup); + } + break; + case 'setVertexBuffer': + { + renderPass.setVertexBuffer(1, buffer); + } + break; + case 'setPipeline': + { + renderPass.setPipeline(pipeline); + } + break; + case 'setViewport': + { + const kNumTestPoints = 8; + const kViewportMinDepth = 0; + const kViewportMaxDepth = 1; + renderPass.setViewport(0, 0, kNumTestPoints, 0, kViewportMinDepth, kViewportMaxDepth); + } + break; + case 'setScissorRect': + { + renderPass.setScissorRect(0, 0, 0, 0); + } + break; + case 'setBlendConstant': + { + renderPass.setBlendConstant({ r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); + } + break; + case 'setStencilReference': + { + renderPass.setStencilReference(0); + } + break; + case 'beginOcclusionQuery': + { + renderPass.beginOcclusionQuery(0); + } + break; + case 'endOcclusionQuery': + { + renderPass.endOcclusionQuery(); + } + break; + case 'executeBundles': + { + renderPass.executeBundles([]); + } + break; + case 'pushDebugGroup': + { + encoder.pushDebugGroup('group'); + } + break; + case 'popDebugGroup': + { + encoder.popDebugGroup(); + } + break; + case 'insertDebugMarker': + { + encoder.insertDebugMarker('marker'); + } + break; + default: + unreachable(); + } + }, finishBeforeCommand); + }); + +g.test('render_bundle_commands') + .desc( + ` + Test that functions of GPURenderBundleEncoder generate a validation error if the encoder or the + pass is already finished. + ` + ) + .params(u => + u + .combine('command', kRenderBundleEncoderCommands) + .beginSubcases() + .combine('finishBeforeCommand', [false, true]) + ) + .fn(t => { + const { command, finishBeforeCommand } = t.params; + + const buffer = t.device.createBuffer({ + size: 12, + usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.VERTEX, + }); + + const pipeline = t.createRenderPipelineForTest(); + + const bindGroup = t.createBindGroupForTest(); + + const bundleEncoder = t.device.createRenderBundleEncoder({ + colorFormats: ['rgba8unorm'], + }); + + if (finishBeforeCommand) { + bundleEncoder.finish(); + } + + t.expectValidationError(() => { + switch (command) { + case 'draw': + { + bundleEncoder.draw(1); + } + break; + case 'drawIndexed': + { + bundleEncoder.drawIndexed(1); + } + break; + case 'drawIndexedIndirect': + { + bundleEncoder.drawIndexedIndirect(buffer, 0); + } + break; + case 'drawIndirect': + { + bundleEncoder.drawIndirect(buffer, 1); + } + break; + case 'setPipeline': + { + bundleEncoder.setPipeline(pipeline); + } + break; + case 'setBindGroup': + { + bundleEncoder.setBindGroup(0, bindGroup); + } + break; + case 'setIndexBuffer': + { + bundleEncoder.setIndexBuffer(buffer, 'uint32'); + } + break; + case 'setVertexBuffer': + { + bundleEncoder.setVertexBuffer(1, buffer); + } + break; + case 'pushDebugGroup': + { + bundleEncoder.pushDebugGroup('group'); + } + break; + case 'popDebugGroup': + { + bundleEncoder.popDebugGroup(); + } + break; + case 'insertDebugMarker': + { + bundleEncoder.insertDebugMarker('marker'); + } + break; + default: + unreachable(); + } + }, finishBeforeCommand); + }); + +g.test('compute_pass_commands') + .desc( + ` + Test that functions of GPUComputePassEncoder generate a validation error if the encoder or the + pass is already finished. + + - TODO: Consider testing: nothing before command, end before command, end+finish before command. + ` + ) + .params(u => + u + .combine('command', kComputePassEncoderCommands) + .beginSubcases() + .combine('finishBeforeCommand', [false, true]) + ) + .fn(t => { + const { command, finishBeforeCommand } = t.params; + + const encoder = t.device.createCommandEncoder(); + const computePass = encoder.beginComputePass(); + + const indirectBuffer = t.device.createBuffer({ + size: 12, + usage: GPUBufferUsage.INDIRECT, + }); + + const computePipeline = t.createNoOpComputePipeline(); + + const bindGroup = t.createBindGroupForTest(); + + if (finishBeforeCommand) { + computePass.end(); + encoder.finish(); + } + + t.expectValidationError(() => { + switch (command) { + case 'setBindGroup': + { + computePass.setBindGroup(0, bindGroup); + } + break; + case 'setPipeline': + { + computePass.setPipeline(computePipeline); + } + break; + case 'dispatchWorkgroups': + { + computePass.dispatchWorkgroups(0); + } + break; + case 'dispatchWorkgroupsIndirect': + { + computePass.dispatchWorkgroupsIndirect(indirectBuffer, 0); + } + break; + case 'pushDebugGroup': + { + computePass.pushDebugGroup('group'); + } + break; + case 'popDebugGroup': + { + computePass.popDebugGroup(); + } + break; + case 'insertDebugMarker': + { + computePass.insertDebugMarker('marker'); + } + break; + default: + unreachable(); + } + }, finishBeforeCommand); + }); |