summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts275
1 files changed, 275 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts
new file mode 100644
index 0000000000..e6ee87f797
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts
@@ -0,0 +1,275 @@
+interface Resource {
+ readonly buffer?: GPUBufferBindingLayout;
+ readonly sampler?: GPUSamplerBindingLayout;
+ readonly texture?: GPUTextureBindingLayout;
+ readonly storageTexture?: GPUStorageTextureBindingLayout;
+ readonly externalTexture?: GPUExternalTextureBindingLayout;
+ readonly code: string;
+ readonly staticUse?: string;
+}
+
+/**
+ * Returns an array of possible resources
+ */
+function generateResources(): Resource[] {
+ const resources: Resource[] = [
+ // Buffers
+ {
+ buffer: { type: 'uniform' },
+ code: `var<uniform> res : array<vec4u, 16>`,
+ staticUse: `res[0]`,
+ },
+ {
+ buffer: { type: 'storage' },
+ code: `var<storage, read_write> res : array<vec4u>`,
+ staticUse: `res[0]`,
+ },
+ {
+ buffer: { type: 'read-only-storage' },
+ code: `var<storage> res : array<vec4u>`,
+ staticUse: `res[0]`,
+ },
+
+ // Samplers
+ {
+ sampler: { type: 'filtering' },
+ code: `var res : sampler`,
+ },
+ {
+ sampler: { type: 'non-filtering' },
+ code: `var res : sampler`,
+ },
+ {
+ sampler: { type: 'comparison' },
+ code: `var res : sampler_comparison`,
+ },
+ // Multisampled textures
+ {
+ texture: { sampleType: 'depth', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_depth_multisampled_2d`,
+ },
+ {
+ texture: { sampleType: 'unfilterable-float', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<f32>`,
+ },
+ {
+ texture: { sampleType: 'sint', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<i32>`,
+ },
+ {
+ texture: { sampleType: 'uint', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<u32>`,
+ },
+ ];
+
+ // Sampled textures
+ const sampleDims: GPUTextureViewDimension[] = [
+ '1d',
+ '2d',
+ '2d-array',
+ '3d',
+ 'cube',
+ 'cube-array',
+ ];
+ const sampleTypes: GPUTextureSampleType[] = ['float', 'unfilterable-float', 'sint', 'uint'];
+ const sampleWGSL = ['f32', 'f32', 'i32', 'u32'];
+ for (const dim of sampleDims) {
+ let i = 0;
+ for (const type of sampleTypes) {
+ resources.push({
+ texture: { sampleType: type, viewDimension: dim, multisampled: false },
+ code: `var res : texture_${dim.replace('-', '_')}<${sampleWGSL[i++]}>`,
+ });
+ }
+ }
+
+ // Depth textures
+ const depthDims: GPUTextureViewDimension[] = ['2d', '2d-array', 'cube', 'cube-array'];
+ for (const dim of depthDims) {
+ resources.push({
+ texture: { sampleType: 'depth', viewDimension: dim, multisampled: false },
+ code: `var res : texture_depth_${dim.replace('-', '_')}`,
+ });
+ }
+
+ // Storage textures
+ // Only cover r32uint, r32sint, and r32float here for ease of testing.
+ const storageDims: GPUTextureViewDimension[] = ['1d', '2d', '2d-array', '3d'];
+ const formats: GPUTextureFormat[] = ['r32float', 'r32sint', 'r32uint'];
+ const accesses: GPUStorageTextureAccess[] = ['write-only', 'read-only', 'read-write'];
+ for (const dim of storageDims) {
+ for (const format of formats) {
+ for (const access of accesses) {
+ resources.push({
+ storageTexture: { access, format, viewDimension: dim },
+ code: `var res : texture_storage_${dim.replace('-', '_')}<${format},${access
+ .replace('-only', '')
+ .replace('-', '_')}>`,
+ });
+ }
+ }
+ }
+
+ return resources;
+}
+
+/**
+ * Returns a string suitable as a Record key.
+ */
+function resourceKey(res: Resource): string {
+ if (res.buffer) {
+ return `${res.buffer.type}_buffer`;
+ }
+ if (res.sampler) {
+ return `${res.sampler.type}_sampler`;
+ }
+ if (res.texture) {
+ return `texture_${res.texture.sampleType}_${res.texture.viewDimension}_${res.texture.multisampled}`;
+ }
+ if (res.storageTexture) {
+ return `storage_texture_${res.storageTexture.viewDimension}_${res.storageTexture.format}_${res.storageTexture.access}`;
+ }
+ if (res.externalTexture) {
+ return `external_texture`;
+ }
+ return ``;
+}
+
+/**
+ * Resource array converted to a Record for nicer test parameterization names.
+ */
+export const kAPIResources: Record<string, Resource> = Object.fromEntries(
+ generateResources().map(x => [resourceKey(x), x])
+) as Record<string, Resource>;
+
+/**
+ * Generates a shader of the specified stage using the specified resource at binding (0,0).
+ */
+export function getWGSLShaderForResource(stage: string, resource: Resource): string {
+ let code = `@group(0) @binding(0) ${resource.code};\n`;
+
+ code += `@${stage}`;
+ if (stage === 'compute') {
+ code += `@workgroup_size(1)`;
+ }
+
+ let retTy = '';
+ let retVal = '';
+ if (stage === 'vertex') {
+ retTy = ' -> @builtin(position) vec4f';
+ retVal = 'return vec4f();';
+ } else if (stage === 'fragment') {
+ retTy = ' -> @location(0) vec4f';
+ retVal = 'return vec4f();';
+ }
+ code += `
+fn main() ${retTy} {
+ _ = ${resource.staticUse ?? 'res'};
+ ${retVal}
+}
+`;
+
+ return code;
+}
+
+/**
+ * Generates a bind group layout for for the given resource at binding 0.
+ */
+export function getAPIBindGroupLayoutForResource(
+ device: GPUDevice,
+ stage: GPUShaderStageFlags,
+ resource: Resource
+): GPUBindGroupLayout {
+ const entry: GPUBindGroupLayoutEntry = {
+ binding: 0,
+ visibility: stage,
+ };
+ if (resource.buffer) {
+ entry.buffer = resource.buffer;
+ }
+ if (resource.sampler) {
+ entry.sampler = resource.sampler;
+ }
+ if (resource.texture) {
+ entry.texture = resource.texture;
+ }
+ if (resource.storageTexture) {
+ entry.storageTexture = resource.storageTexture;
+ }
+ if (resource.externalTexture) {
+ entry.externalTexture = resource.externalTexture;
+ }
+
+ const entries: GPUBindGroupLayoutEntry[] = [entry];
+ return device.createBindGroupLayout({ entries });
+}
+
+/**
+ * Returns true if the sample types are compatible.
+ */
+function doSampleTypesMatch(api: GPUTextureSampleType, wgsl: GPUTextureSampleType): boolean {
+ if (api === 'float' || api === 'unfilterable-float') {
+ return wgsl === 'float' || wgsl === 'unfilterable-float';
+ }
+ return api === wgsl;
+}
+
+/**
+ * Returns true if the access modes are compatible.
+ */
+function doAccessesMatch(api: GPUStorageTextureAccess, wgsl: GPUStorageTextureAccess): boolean {
+ if (api === 'read-write') {
+ return wgsl === 'read-write' || wgsl === 'write-only';
+ }
+ return api === wgsl;
+}
+
+/**
+ * Returns true if the resources are compatible.
+ */
+export function doResourcesMatch(api: Resource, wgsl: Resource): boolean {
+ if (api.buffer) {
+ if (!wgsl.buffer) {
+ return false;
+ }
+ return api.buffer.type === wgsl.buffer.type;
+ }
+ if (api.sampler) {
+ if (!wgsl.sampler) {
+ return false;
+ }
+ return (
+ api.sampler.type === wgsl.sampler.type ||
+ (api.sampler.type !== 'comparison' && wgsl.sampler.type !== 'comparison')
+ );
+ }
+ if (api.texture) {
+ if (!wgsl.texture) {
+ return false;
+ }
+ const aType = api.texture.sampleType as GPUTextureSampleType;
+ const wType = wgsl.texture.sampleType as GPUTextureSampleType;
+ return (
+ doSampleTypesMatch(aType, wType) &&
+ api.texture.viewDimension === wgsl.texture.viewDimension &&
+ api.texture.multisampled === wgsl.texture.multisampled
+ );
+ }
+ if (api.storageTexture) {
+ if (!wgsl.storageTexture) {
+ return false;
+ }
+ const aAccess = api.storageTexture.access as GPUStorageTextureAccess;
+ const wAccess = wgsl.storageTexture.access as GPUStorageTextureAccess;
+ return (
+ doAccessesMatch(aAccess, wAccess) &&
+ api.storageTexture.format === wgsl.storageTexture.format &&
+ api.storageTexture.viewDimension === wgsl.storageTexture.viewDimension
+ );
+ }
+ if (api.externalTexture) {
+ return wgsl.externalTexture !== undefined;
+ }
+
+ return false;
+}