diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/operation_context_helper.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/operation_context_helper.ts | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/operation_context_helper.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/operation_context_helper.ts new file mode 100644 index 0000000000..f095969e0d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/operation_context_helper.ts @@ -0,0 +1,334 @@ +import { assert, unreachable } from '../../../../common/util/util.js'; +import { EncodableTextureFormat } from '../../../capability_info.js'; +import { GPUTest } from '../../../gpu_test.js'; + +/** + * Boundary between the first operation, and the second operation. + */ +export const kOperationBoundaries = [ + 'queue-op', // Operations are performed in different queue operations (submit, writeTexture). + 'command-buffer', // Operations are in different command buffers. + 'pass', // Operations are in different passes. + 'execute-bundles', // Operations are in different executeBundles(...) calls + 'render-bundle', // Operations are in different render bundles. + 'dispatch', // Operations are in different dispatches. + 'draw', // Operations are in different draws. +] as const; +export type OperationBoundary = typeof kOperationBoundaries[number]; + +/** + * Context a particular operation is permitted in. + * These contexts should be sorted such that the first is the most top-level + * context, and the last is most nested (inside a render bundle, in a render pass, ...). + */ +export const kOperationContexts = [ + 'queue', // Operation occurs on the GPUQueue object + 'command-encoder', // Operation may be encoded in a GPUCommandEncoder. + 'compute-pass-encoder', // Operation may be encoded in a GPUComputePassEncoder. + 'render-pass-encoder', // Operation may be encoded in a GPURenderPassEncoder. + 'render-bundle-encoder', // Operation may be encoded in a GPURenderBundleEncoder. +] as const; +export type OperationContext = typeof kOperationContexts[number]; + +interface BoundaryInfo { + readonly contexts: [OperationContext, OperationContext][]; + // Add fields as needed +} + +function combineContexts( + as: readonly OperationContext[], + bs: readonly OperationContext[] +): [OperationContext, OperationContext][] { + const result: [OperationContext, OperationContext][] = []; + for (const a of as) { + for (const b of bs) { + result.push([a, b]); + } + } + return result; +} + +const queueContexts = combineContexts(kOperationContexts, kOperationContexts); +const commandBufferContexts = combineContexts( + kOperationContexts.filter(c => c !== 'queue'), + kOperationContexts.filter(c => c !== 'queue') +); + +/** + * Mapping of OperationBoundary => to a set of OperationContext pairs. + * The boundary is capable of separating operations in those two contexts. + */ +export const kBoundaryInfo: { + readonly [k in OperationBoundary]: BoundaryInfo; +} = /* prettier-ignore */ { + 'queue-op': { + contexts: queueContexts, + }, + 'command-buffer': { + contexts: commandBufferContexts, + }, + 'pass': { + contexts: [ + ['compute-pass-encoder', 'compute-pass-encoder'], + ['compute-pass-encoder', 'render-pass-encoder'], + ['render-pass-encoder', 'compute-pass-encoder'], + ['render-pass-encoder', 'render-pass-encoder'], + ['render-bundle-encoder', 'render-pass-encoder'], + ['render-pass-encoder', 'render-bundle-encoder'], + ['render-bundle-encoder', 'render-bundle-encoder'], + ], + }, + 'execute-bundles': { + contexts: [ + ['render-bundle-encoder', 'render-bundle-encoder'], + ] + }, + 'render-bundle': { + contexts: [ + ['render-bundle-encoder', 'render-pass-encoder'], + ['render-pass-encoder', 'render-bundle-encoder'], + ['render-bundle-encoder', 'render-bundle-encoder'], + ], + }, + 'dispatch': { + contexts: [ + ['compute-pass-encoder', 'compute-pass-encoder'], + ], + }, + 'draw': { + contexts: [ + ['render-pass-encoder', 'render-pass-encoder'], + ['render-bundle-encoder', 'render-pass-encoder'], + ['render-pass-encoder', 'render-bundle-encoder'], + ], + }, +}; + +export class OperationContextHelper { + // We start at the queue context which is top-level. + protected currentContext: OperationContext = 'queue'; + + // Set based on the current context. + queue: GPUQueue; + commandEncoder?: GPUCommandEncoder; + computePassEncoder?: GPUComputePassEncoder; + renderPassEncoder?: GPURenderPassEncoder; + renderBundleEncoder?: GPURenderBundleEncoder; + + protected t: GPUTest; + protected device: GPUDevice; + + protected commandBuffers: GPUCommandBuffer[] = []; + protected renderBundles: GPURenderBundle[] = []; + + public readonly kTextureSize = [4, 4] as const; + public readonly kTextureFormat: EncodableTextureFormat = 'rgba8unorm'; + + constructor(t: GPUTest) { + this.t = t; + this.device = t.device; + this.queue = t.device.queue; + } + + // Ensure that all encoded commands are finished and submitted. + ensureSubmit() { + this.ensureContext('queue'); + this.flushCommandBuffers(); + } + + private popContext(): GPURenderBundle | GPUCommandBuffer | null { + switch (this.currentContext) { + case 'queue': + unreachable(); + break; + case 'command-encoder': { + assert(this.commandEncoder !== undefined); + const commandBuffer = this.commandEncoder.finish(); + this.commandEncoder = undefined; + this.currentContext = 'queue'; + return commandBuffer; + } + case 'compute-pass-encoder': + assert(this.computePassEncoder !== undefined); + this.computePassEncoder.end(); + this.computePassEncoder = undefined; + this.currentContext = 'command-encoder'; + break; + case 'render-pass-encoder': + assert(this.renderPassEncoder !== undefined); + this.renderPassEncoder.end(); + this.renderPassEncoder = undefined; + this.currentContext = 'command-encoder'; + break; + case 'render-bundle-encoder': { + assert(this.renderBundleEncoder !== undefined); + const renderBundle = this.renderBundleEncoder.finish(); + this.renderBundleEncoder = undefined; + this.currentContext = 'render-pass-encoder'; + return renderBundle; + } + } + return null; + } + + private makeDummyAttachment(): GPURenderPassColorAttachment { + const texture = this.t.trackForCleanup( + this.device.createTexture({ + format: this.kTextureFormat, + size: this.kTextureSize, + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }) + ); + return { + view: texture.createView(), + loadOp: 'load', + storeOp: 'store', + }; + } + + ensureContext(context: OperationContext) { + // Find the common ancestor. So we can transition from currentContext -> context. + const ancestorContext = + kOperationContexts[ + Math.min( + kOperationContexts.indexOf(context), + kOperationContexts.indexOf(this.currentContext) + ) + ]; + + // Pop the context until we're at the common ancestor. + while (this.currentContext !== ancestorContext) { + // About to pop the render pass encoder. Execute any outstanding render bundles. + if (this.currentContext === 'render-pass-encoder') { + this.flushRenderBundles(); + } + + const result = this.popContext(); + if (result) { + if (result instanceof GPURenderBundle) { + this.renderBundles.push(result); + } else { + this.commandBuffers.push(result); + } + } + } + + if (this.currentContext === context) { + return; + } + + switch (context) { + case 'queue': + unreachable(); + break; + case 'command-encoder': + assert(this.currentContext === 'queue'); + this.commandEncoder = this.device.createCommandEncoder(); + break; + case 'compute-pass-encoder': + switch (this.currentContext) { + case 'queue': + this.commandEncoder = this.device.createCommandEncoder(); + // fallthrough + case 'command-encoder': + assert(this.commandEncoder !== undefined); + this.computePassEncoder = this.commandEncoder.beginComputePass(); + break; + case 'compute-pass-encoder': + case 'render-bundle-encoder': + case 'render-pass-encoder': + unreachable(); + } + break; + case 'render-pass-encoder': + switch (this.currentContext) { + case 'queue': + this.commandEncoder = this.device.createCommandEncoder(); + // fallthrough + case 'command-encoder': + assert(this.commandEncoder !== undefined); + this.renderPassEncoder = this.commandEncoder.beginRenderPass({ + colorAttachments: [this.makeDummyAttachment()], + }); + break; + case 'render-pass-encoder': + case 'render-bundle-encoder': + case 'compute-pass-encoder': + unreachable(); + } + break; + case 'render-bundle-encoder': + switch (this.currentContext) { + case 'queue': + this.commandEncoder = this.device.createCommandEncoder(); + // fallthrough + case 'command-encoder': + assert(this.commandEncoder !== undefined); + this.renderPassEncoder = this.commandEncoder.beginRenderPass({ + colorAttachments: [this.makeDummyAttachment()], + }); + // fallthrough + case 'render-pass-encoder': + this.renderBundleEncoder = this.device.createRenderBundleEncoder({ + colorFormats: [this.kTextureFormat], + }); + break; + case 'render-bundle-encoder': + case 'compute-pass-encoder': + unreachable(); + } + break; + } + this.currentContext = context; + } + + private flushRenderBundles() { + assert(this.renderPassEncoder !== undefined); + if (this.renderBundles.length) { + this.renderPassEncoder.executeBundles(this.renderBundles); + this.renderBundles = []; + } + } + + private flushCommandBuffers() { + if (this.commandBuffers.length) { + this.queue.submit(this.commandBuffers); + this.commandBuffers = []; + } + } + + ensureBoundary(boundary: OperationBoundary) { + switch (boundary) { + case 'command-buffer': + this.ensureContext('queue'); + break; + case 'queue-op': + this.ensureContext('queue'); + // Submit any GPUCommandBuffers so the next one is in a separate submit. + this.flushCommandBuffers(); + break; + case 'dispatch': + // Nothing to do to separate dispatches. + assert(this.currentContext === 'compute-pass-encoder'); + break; + case 'draw': + // Nothing to do to separate draws. + assert( + this.currentContext === 'render-pass-encoder' || + this.currentContext === 'render-bundle-encoder' + ); + break; + case 'pass': + this.ensureContext('command-encoder'); + break; + case 'render-bundle': + this.ensureContext('render-pass-encoder'); + break; + case 'execute-bundles': + this.ensureContext('render-pass-encoder'); + // Execute any GPURenderBundles so the next one is in a separate executeBundles. + this.flushRenderBundles(); + break; + } + } +} |