summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/operation_context_helper.ts
diff options
context:
space:
mode:
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.ts334
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;
+ }
+ }
+}