summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts267
1 files changed, 267 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
new file mode 100644
index 0000000000..bf36500057
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
@@ -0,0 +1,267 @@
+import {
+ kTextureFormatInfo,
+ SizedTextureFormat,
+ DepthStencilFormat,
+ depthStencilFormatCopyableAspects,
+} from '../../../capability_info.js';
+import { align } from '../../../util/math.js';
+import { ImageCopyType } from '../../../util/texture/layout.js';
+import { ValidationTest } from '../validation_test.js';
+
+export class ImageCopyTest extends ValidationTest {
+ testRun(
+ textureCopyView: GPUImageCopyTexture,
+ textureDataLayout: GPUImageDataLayout,
+ size: GPUExtent3D,
+ {
+ method,
+ dataSize,
+ success,
+ submit = false,
+ }: {
+ method: ImageCopyType;
+ dataSize: number;
+ success: boolean;
+ /** If submit is true, the validation error is expected to come from the submit and encoding
+ * should succeed. */
+ submit?: boolean;
+ }
+ ): void {
+ switch (method) {
+ case 'WriteTexture': {
+ const data = new Uint8Array(dataSize);
+
+ this.expectValidationError(() => {
+ this.device.queue.writeTexture(textureCopyView, data, textureDataLayout, size);
+ }, !success);
+
+ break;
+ }
+ case 'CopyB2T': {
+ const buffer = this.device.createBuffer({
+ size: dataSize,
+ usage: GPUBufferUsage.COPY_SRC,
+ });
+ this.trackForCleanup(buffer);
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, textureCopyView, size);
+
+ if (submit) {
+ const cmd = encoder.finish();
+ this.expectValidationError(() => {
+ this.device.queue.submit([cmd]);
+ }, !success);
+ } else {
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
+
+ break;
+ }
+ case 'CopyT2B': {
+ const buffer = this.device.createBuffer({
+ size: dataSize,
+ usage: GPUBufferUsage.COPY_DST,
+ });
+ this.trackForCleanup(buffer);
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(textureCopyView, { buffer, ...textureDataLayout }, size);
+
+ if (submit) {
+ const cmd = encoder.finish();
+ this.expectValidationError(() => {
+ this.device.queue.submit([cmd]);
+ }, !success);
+ } else {
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Creates a texture when all that is needed is an aligned texture given the format and desired
+ * dimensions/origin. The resultant texture guarantees that a copy with the same size and origin
+ * should be possible.
+ */
+ createAlignedTexture(
+ format: SizedTextureFormat,
+ size: Required<GPUExtent3DDict> = {
+ width: 1,
+ height: 1,
+ depthOrArrayLayers: 1,
+ },
+ origin: Required<GPUOrigin3DDict> = { x: 0, y: 0, z: 0 },
+ dimension: Required<GPUTextureDimension> = '2d'
+ ): GPUTexture {
+ const info = kTextureFormatInfo[format];
+ const alignedSize = {
+ width: align(Math.max(1, size.width + origin.x), info.blockWidth),
+ height: align(Math.max(1, size.height + origin.y), info.blockHeight),
+ depthOrArrayLayers: Math.max(1, size.depthOrArrayLayers + origin.z),
+ };
+ return this.device.createTexture({
+ size: alignedSize,
+ dimension,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+ }
+
+ testBuffer(
+ buffer: GPUBuffer,
+ texture: GPUTexture,
+ textureDataLayout: GPUImageDataLayout,
+ size: GPUExtent3D,
+ {
+ method,
+ dataSize,
+ success,
+ submit = true,
+ }: {
+ method: ImageCopyType;
+ dataSize: number;
+ success: boolean;
+ /** If submit is true, the validation error is expected to come from the submit and encoding
+ * should succeed. */
+ submit?: boolean;
+ }
+ ): void {
+ switch (method) {
+ case 'WriteTexture': {
+ const data = new Uint8Array(dataSize);
+
+ this.expectValidationError(() => {
+ this.device.queue.writeTexture({ texture }, data, textureDataLayout, size);
+ }, !success);
+
+ break;
+ }
+ case 'CopyB2T': {
+ const { encoder, validateFinish, validateFinishAndSubmit } = this.createEncoder('non-pass');
+ encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, { texture }, size);
+
+ if (submit) {
+ // validation error is expected to come from the submit and encoding should succeed
+ validateFinishAndSubmit(true, success);
+ } else {
+ // validation error is expected to come from the encoding
+ validateFinish(success);
+ }
+
+ break;
+ }
+ case 'CopyT2B': {
+ const { encoder, validateFinish, validateFinishAndSubmit } = this.createEncoder('non-pass');
+ encoder.copyTextureToBuffer({ texture }, { buffer, ...textureDataLayout }, size);
+
+ if (submit) {
+ // validation error is expected to come from the submit and encoding should succeed
+ validateFinishAndSubmit(true, success);
+ } else {
+ // validation error is expected to come from the encoding
+ validateFinish(success);
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+// For testing divisibility by a number we test all the values returned by this function:
+function valuesToTestDivisibilityBy(number: number): Iterable<number> {
+ const values = [];
+ for (let i = 0; i <= 2 * number; ++i) {
+ values.push(i);
+ }
+ values.push(3 * number);
+ return values;
+}
+
+interface WithFormat {
+ format: SizedTextureFormat;
+}
+
+interface WithFormatAndCoordinate extends WithFormat {
+ coordinateToTest: keyof GPUOrigin3DDict | keyof GPUExtent3DDict;
+}
+
+interface WithFormatAndMethod extends WithFormat {
+ method: ImageCopyType;
+}
+
+// This is a helper function used for expanding test parameters for offset alignment, by spec
+export function texelBlockAlignmentTestExpanderForOffset({ format }: WithFormat) {
+ const info = kTextureFormatInfo[format];
+ if (info.depth || info.stencil) {
+ return valuesToTestDivisibilityBy(4);
+ }
+
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].bytesPerBlock);
+}
+
+// This is a helper function used for expanding test parameters for texel block alignment tests on rowsPerImage
+export function texelBlockAlignmentTestExpanderForRowsPerImage({ format }: WithFormat) {
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].blockHeight);
+}
+
+// This is a helper function used for expanding test parameters for texel block alignment tests on origin and size
+export function texelBlockAlignmentTestExpanderForValueToCoordinate({
+ format,
+ coordinateToTest,
+}: WithFormatAndCoordinate) {
+ switch (coordinateToTest) {
+ case 'x':
+ case 'width':
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].blockWidth);
+
+ case 'y':
+ case 'height':
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].blockHeight);
+
+ case 'z':
+ case 'depthOrArrayLayers':
+ return valuesToTestDivisibilityBy(1);
+ }
+}
+
+// This is a helper function used for filtering test parameters
+export function formatCopyableWithMethod({ format, method }: WithFormatAndMethod): boolean {
+ const info = kTextureFormatInfo[format];
+ if (info.depth || info.stencil) {
+ const supportedAspects: readonly GPUTextureAspect[] = depthStencilFormatCopyableAspects(
+ method,
+ format as DepthStencilFormat
+ );
+ return supportedAspects.length > 0;
+ }
+ if (method === 'CopyT2B') {
+ return info.copySrc;
+ } else {
+ return info.copyDst;
+ }
+}
+
+// This is a helper function used for filtering test parameters
+export function getACopyableAspectWithMethod({
+ format,
+ method,
+}: WithFormatAndMethod): GPUTextureAspect {
+ const info = kTextureFormatInfo[format];
+ if (info.depth || info.stencil) {
+ const supportedAspects: readonly GPUTextureAspect[] = depthStencilFormatCopyableAspects(
+ method,
+ format as DepthStencilFormat
+ );
+ return supportedAspects[0];
+ }
+ return 'all' as GPUTextureAspect;
+}