Change some format x format combinations to feature combinations

Signficantly reduces the number of testcases in favor of subcases
diff --git a/src/webgpu/api/validation/attachment_compatibility.spec.ts b/src/webgpu/api/validation/attachment_compatibility.spec.ts
index e5d25c9..48c8bc6 100644
--- a/src/webgpu/api/validation/attachment_compatibility.spec.ts
+++ b/src/webgpu/api/validation/attachment_compatibility.spec.ts
@@ -11,6 +11,8 @@
   kTextureSampleCounts,
   kMaxColorAttachments,
   kTextureFormatInfo,
+  getFeaturesForFormats,
+  filterFormatsByFeature,
 } from '../../capability_info.js';
 
 import { ValidationTest } from './validation_test.js';
@@ -78,6 +80,11 @@
   ...kUnsizedDepthStencilFormats,
 ] as const;
 
+const kFeaturesForDepthStencilAttachmentFormats = getFeaturesForFormats([
+  ...kSizedDepthStencilFormats,
+  ...kUnsizedDepthStencilFormats,
+]);
+
 class F extends ValidationTest {
   createAttachmentTextureView(format: GPUTextureFormat, sampleCount?: number) {
     return this.device
@@ -258,12 +265,19 @@
   .desc('Test that the depth attachment format in render passes and bundles must match.')
   .params(u =>
     u //
-      .combine('passFormat', kDepthStencilAttachmentFormats)
-      .combine('bundleFormat', kDepthStencilAttachmentFormats)
+      .combine('passFeature', kFeaturesForDepthStencilAttachmentFormats)
+      .combine('bundleFeature', kFeaturesForDepthStencilAttachmentFormats)
+      .beginSubcases()
+      .expand('passFormat', ({ passFeature }) =>
+        filterFormatsByFeature(passFeature, kDepthStencilAttachmentFormats)
+      )
+      .expand('bundleFormat', ({ bundleFeature }) =>
+        filterFormatsByFeature(bundleFeature, kDepthStencilAttachmentFormats)
+      )
   )
   .before(async t => {
-    const { passFormat, bundleFormat } = t.params;
-    await t.selectDeviceForTextureFormatOrSkipTestCase([passFormat, bundleFormat]);
+    const { passFeature, bundleFeature } = t.params;
+    await t.selectDeviceOrSkipTestCase([passFeature, bundleFeature]);
   })
   .fn(async t => {
     const { passFormat, bundleFormat } = t.params;
@@ -407,12 +421,19 @@
   .params(u =>
     u
       .combine('encoderType', ['render pass', 'render bundle'] as const)
-      .combine('encoderFormat', kDepthStencilAttachmentFormats)
-      .combine('pipelineFormat', kDepthStencilAttachmentFormats)
+      .combine('encoderFormatFeature', kFeaturesForDepthStencilAttachmentFormats)
+      .combine('pipelineFormatFeature', kFeaturesForDepthStencilAttachmentFormats)
+      .beginSubcases()
+      .expand('encoderFormat', ({ encoderFormatFeature }) =>
+        filterFormatsByFeature(encoderFormatFeature, kDepthStencilAttachmentFormats)
+      )
+      .expand('pipelineFormat', ({ pipelineFormatFeature }) =>
+        filterFormatsByFeature(pipelineFormatFeature, kDepthStencilAttachmentFormats)
+      )
   )
   .before(async t => {
-    const { encoderFormat, pipelineFormat } = t.params;
-    await t.selectDeviceForTextureFormatOrSkipTestCase([encoderFormat, pipelineFormat]);
+    const { encoderFormatFeature, pipelineFormatFeature } = t.params;
+    await t.selectDeviceOrSkipTestCase([encoderFormatFeature, pipelineFormatFeature]);
   })
   .fn(async t => {
     const { encoderType, encoderFormat, pipelineFormat } = t.params;
diff --git a/src/webgpu/api/validation/createTexture.spec.ts b/src/webgpu/api/validation/createTexture.spec.ts
index 8481e8a..087d8b6 100644
--- a/src/webgpu/api/validation/createTexture.spec.ts
+++ b/src/webgpu/api/validation/createTexture.spec.ts
@@ -11,9 +11,11 @@
   kTextureUsages,
   kUncompressedTextureFormats,
   kRegularTextureFormats,
+  kFeaturesForFormats,
   textureDimensionAndFormatCompatible,
   kLimitInfo,
   viewCompatible,
+  filterFormatsByFeature,
 } from '../../capability_info.js';
 import { GPUConst } from '../../constants.js';
 import { maxMipLevelCount } from '../../util/texture/base.js';
@@ -771,10 +773,21 @@
   .desc(
     `Test creating a texture with viewFormats list for all {texture format}x{view format}. Only compatible view formats should be valid.`
   )
-  .params(u => u.combine('format', kTextureFormats).combine('viewFormat', kTextureFormats))
+  .params(u =>
+    u
+      .combine('formatFeature', kFeaturesForFormats)
+      .combine('viewFormatFeature', kFeaturesForFormats)
+      .beginSubcases()
+      .expand('format', ({ formatFeature }) =>
+        filterFormatsByFeature(formatFeature, kTextureFormats)
+      )
+      .expand('viewFormat', ({ viewFormatFeature }) =>
+        filterFormatsByFeature(viewFormatFeature, kTextureFormats)
+      )
+  )
   .before(async t => {
-    const { format, viewFormat } = t.params;
-    await t.selectDeviceForTextureFormatOrSkipTestCase([format, viewFormat]);
+    const { formatFeature, viewFormatFeature } = t.params;
+    await t.selectDeviceOrSkipTestCase([formatFeature, viewFormatFeature]);
   })
   .fn(async t => {
     const { format, viewFormat } = t.params;
diff --git a/src/webgpu/api/validation/createView.spec.ts b/src/webgpu/api/validation/createView.spec.ts
index b764789..0604efb 100644
--- a/src/webgpu/api/validation/createView.spec.ts
+++ b/src/webgpu/api/validation/createView.spec.ts
@@ -9,7 +9,9 @@
   kTextureFormatInfo,
   kTextureFormats,
   kTextureViewDimensions,
+  kFeaturesForFormats,
   viewCompatible,
+  filterFormatsByFeature,
 } from '../../capability_info.js';
 import { kResourceStates } from '../../gpu_test.js';
 import {
@@ -31,14 +33,20 @@
   )
   .params(u =>
     u
-      .combine('textureFormat', kTextureFormats)
-      .combine('viewFormat', [undefined, ...kTextureFormats])
+      .combine('textureFormatFeature', kFeaturesForFormats)
+      .combine('viewFormatFeature', kFeaturesForFormats)
       .beginSubcases()
+      .expand('textureFormat', ({ textureFormatFeature }) =>
+        filterFormatsByFeature(textureFormatFeature, kTextureFormats)
+      )
+      .expand('viewFormat', ({ viewFormatFeature }) =>
+        filterFormatsByFeature(viewFormatFeature, [undefined, ...kTextureFormats])
+      )
       .combine('useViewFormatList', [false, true])
   )
   .before(async t => {
-    const { textureFormat, viewFormat } = t.params;
-    await t.selectDeviceForTextureFormatOrSkipTestCase([textureFormat, viewFormat]);
+    const { textureFormatFeature, viewFormatFeature } = t.params;
+    await t.selectDeviceOrSkipTestCase([textureFormatFeature, viewFormatFeature]);
   })
   .fn(async t => {
     const { textureFormat, viewFormat, useViewFormatList } = t.params;
diff --git a/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts b/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
index 8e3e3cd..588d6d7 100644
--- a/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
+++ b/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
@@ -11,6 +11,8 @@
   kTextureUsages,
   textureDimensionAndFormatCompatible,
   kTextureDimensions,
+  kFeaturesForFormats,
+  filterFormatsByFeature,
 } from '../../../../capability_info.js';
 import { kResourceStates } from '../../../../gpu_test.js';
 import { align, lcm } from '../../../../util/math.js';
@@ -350,15 +352,20 @@
 `
   )
   .params(u =>
-    u //
-      .combine('srcFormat', kTextureFormats)
-      .combine('dstFormat', kTextureFormats)
+    u
+      .combine('srcFormatFeature', kFeaturesForFormats)
+      .combine('dstFormatFeature', kFeaturesForFormats)
+      .beginSubcases()
+      .expand('srcFormat', ({ srcFormatFeature }) =>
+        filterFormatsByFeature(srcFormatFeature, kTextureFormats)
+      )
+      .expand('dstFormat', ({ dstFormatFeature }) =>
+        filterFormatsByFeature(dstFormatFeature, kTextureFormats)
+      )
   )
   .before(async t => {
-    const { srcFormat, dstFormat } = t.params;
-    const srcFormatInfo = kTextureFormatInfo[srcFormat];
-    const dstFormatInfo = kTextureFormatInfo[dstFormat];
-    await t.selectDeviceOrSkipTestCase([srcFormatInfo.feature, dstFormatInfo.feature]);
+    const { srcFormatFeature, dstFormatFeature } = t.params;
+    await t.selectDeviceOrSkipTestCase([srcFormatFeature, dstFormatFeature]);
   })
   .fn(async t => {
     const { srcFormat, dstFormat } = t.params;
diff --git a/src/webgpu/capability_info.ts b/src/webgpu/capability_info.ts
index bd1ed80..b0bbc64 100644
--- a/src/webgpu/capability_info.ts
+++ b/src/webgpu/capability_info.ts
@@ -1039,3 +1039,18 @@
 export function viewCompatible(a: GPUTextureFormat, b: GPUTextureFormat): boolean {
   return a === b || a + '-srgb' === b || b + '-srgb' === a;
 }
+
+export function getFeaturesForFormats<T>(
+  formats: readonly (T & (GPUTextureFormat | undefined))[]
+): readonly (GPUFeatureName | undefined)[] {
+  return Array.from(new Set(formats.map(f => (f ? kTextureFormatInfo[f].feature : undefined))));
+}
+
+export function filterFormatsByFeature<T>(
+  feature: GPUFeatureName | undefined,
+  formats: readonly (T & (GPUTextureFormat | undefined))[]
+): readonly (T & (GPUTextureFormat | undefined))[] {
+  return formats.filter(f => f === undefined || kTextureFormatInfo[f].feature === feature);
+}
+
+export const kFeaturesForFormats = getFeaturesForFormats(kTextureFormats);