From b72bd61ed6e3452941294349ebe9d1869e56a442 Mon Sep 17 00:00:00 2001 From: Greggman Date: Tue, 31 Dec 2024 10:44:51 +0900 Subject: [PATCH] Compat: refactor resource_usages tests for 0 storage buffer/textures (#4126) --- .../buffer/in_pass_encoder.spec.ts | 118 +++++++++++++++++- .../buffer/in_pass_misc.spec.ts | 31 ++++- .../texture/in_render_common.spec.ts | 17 ++- .../texture/in_render_misc.spec.ts | 23 +++- 4 files changed, 180 insertions(+), 9 deletions(-) diff --git a/src/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.ts b/src/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.ts index 2e56bc52ee4c..911ec6b5a8bd 100644 --- a/src/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.ts +++ b/src/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.ts @@ -4,6 +4,7 @@ Buffer Usages Validation Tests in Render Pass and Compute Pass. import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { assert, unreachable } from '../../../../../common/util/util.js'; +import { GPUTestBase, MaxLimitsTestMixin } from '../../../../gpu_test.js'; import { ValidationTest } from '../../validation_test.js'; const kBoundBufferSize = 256; @@ -27,6 +28,10 @@ export const kAllBufferUsages: BufferUsage[] = [ 'indexedIndirect', ]; +function resourceVisibilityToVisibility(resourceVisibility: 'compute' | 'fragment') { + return resourceVisibility === 'compute' ? GPUShaderStage.COMPUTE : GPUShaderStage.FRAGMENT; +} + export class BufferResourceUsageTest extends ValidationTest { createBindGroupLayoutForTest( type: 'uniform' | 'storage' | 'read-only-storage', @@ -34,8 +39,7 @@ export class BufferResourceUsageTest extends ValidationTest { ): GPUBindGroupLayout { const bindGroupLayoutEntry: GPUBindGroupLayoutEntry = { binding: 0, - visibility: - resourceVisibility === 'compute' ? GPUShaderStage.COMPUTE : GPUShaderStage.FRAGMENT, + visibility: resourceVisibilityToVisibility(resourceVisibility), buffer: { type, }, @@ -138,7 +142,45 @@ function IsBufferUsageInBindGroup(bufferUsage: BufferUsage): boolean { } } -export const g = makeTestGroup(BufferResourceUsageTest); +function skipIfStorageBuffersNotAvailableInStages( + t: GPUTestBase, + visibility: number, + numRequired: number +) { + if (t.isCompatibility) { + t.skipIf( + (visibility & GPUShaderStage.FRAGMENT) !== 0 && + !(t.device.limits.maxStorageBuffersInFragmentStage! >= numRequired), + `maxStorageBuffersInFragmentStage${t.device.limits.maxStorageBuffersInFragmentStage} < ${numRequired}` + ); + t.skipIf( + (visibility & GPUShaderStage.VERTEX) !== 0 && + !(t.device.limits.maxStorageBuffersInVertexStage! >= numRequired), + `maxStorageBuffersInVertexStage${t.device.limits.maxStorageBuffersInVertexStage} < ${numRequired}` + ); + } +} + +/** + * Skips test if usage is a storage buffer and there are not numRequired + * storage buffers supported for the given visibility. + */ +export function skipIfStorageBuffersUsedAndNotAvailableInStages( + t: GPUTestBase, + usage: BufferUsage | 'copy-src' | 'copy-dst', + visibility: 'fragment' | 'compute', + numRequired: number +) { + if (usage === 'storage' || usage === 'read-only-storage') { + skipIfStorageBuffersNotAvailableInStages( + t, + resourceVisibilityToVisibility(visibility), + numRequired + ); + } +} + +export const g = makeTestGroup(MaxLimitsTestMixin(BufferResourceUsageTest)); g.test('subresources,buffer_usage_in_one_compute_pass_with_no_dispatch') .desc( @@ -158,6 +200,19 @@ bindGroup, dynamicOffsets), do not contribute directly to a usage scope.` ) .fn(t => { const { usage0, usage1, visibility0, visibility1, hasOverlap } = t.params; + const numStorageBuffersNeededInFragmentStage = 1; + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage0, + visibility0, + numStorageBuffersNeededInFragmentStage + ); + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage1, + visibility1, + numStorageBuffersNeededInFragmentStage + ); const buffer = t.createBufferWithState('valid', { size: kBoundBufferSize * 2, @@ -255,6 +310,19 @@ have tests covered (https://github.com/gpuweb/cts/issues/2232) visibility1, hasOverlap, } = t.params; + const numStorageBuffersNeededInFragmentStage = 1; + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage0, + visibility0, + numStorageBuffersNeededInFragmentStage + ); + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage1, + visibility1, + numStorageBuffersNeededInFragmentStage + ); const buffer = t.createBufferWithState('valid', { size: kBoundBufferSize * 2, @@ -476,6 +544,20 @@ there is no draw call in the render pass. .fn(t => { const { usage0, usage1, hasOverlap, visibility0, visibility1 } = t.params; + const numStorageBuffersNeededInFragmentStage = 1; + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage0, + visibility0, + numStorageBuffersNeededInFragmentStage + ); + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage1, + visibility1, + numStorageBuffersNeededInFragmentStage + ); + const UseBufferOnRenderPassEncoder = ( buffer: GPUBuffer, offset: number, @@ -627,6 +709,21 @@ have tests covered (https://github.com/gpuweb/cts/issues/2232) visibility1, hasOverlap, } = t.params; + + const numStorageBuffersNeededInFragmentStage = 1; + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage0, + visibility0, + numStorageBuffersNeededInFragmentStage + ); + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage1, + visibility1, + numStorageBuffersNeededInFragmentStage + ); + const buffer = t.createBufferWithState('valid', { size: kBoundBufferSize * 2, usage: @@ -849,6 +946,21 @@ different render pass encoders belong to different usage scopes.` GPUBufferUsage.INDEX | GPUBufferUsage.INDIRECT, }); + + const numStorageBuffersNeededInFragmentStage = 1; + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage0, + 'fragment', + numStorageBuffersNeededInFragmentStage + ); + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage1, + 'fragment', + numStorageBuffersNeededInFragmentStage + ); + const UseBufferOnRenderPassEncoderInDrawCall = ( offset: number, usage: BufferUsage, diff --git a/src/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.ts b/src/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.ts index 9aa1a375370f..45f949567bf9 100644 --- a/src/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.ts +++ b/src/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.ts @@ -4,10 +4,16 @@ Test other buffer usage validation rules that are not tests in ./in_pass_encoder import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { unreachable } from '../../../../../common/util/util.js'; +import { MaxLimitsTestMixin } from '../../../../gpu_test.js'; -import { BufferUsage, BufferResourceUsageTest, kAllBufferUsages } from './in_pass_encoder.spec.js'; +import { + BufferUsage, + BufferResourceUsageTest, + kAllBufferUsages, + skipIfStorageBuffersUsedAndNotAvailableInStages, +} from './in_pass_encoder.spec.js'; -export const g = makeTestGroup(BufferResourceUsageTest); +export const g = makeTestGroup(MaxLimitsTestMixin(BufferResourceUsageTest)); const kBufferSize = 256; @@ -95,6 +101,9 @@ still contribute directly to the usage scope of the draw call.` .fn(t => { const { usage0, usage1 } = t.params; + skipIfStorageBuffersUsedAndNotAvailableInStages(t, usage0, 'fragment', 1); + skipIfStorageBuffersUsedAndNotAvailableInStages(t, usage1, 'fragment', 1); + const kUsages = GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE | @@ -247,7 +256,7 @@ g.test('subresources,buffer_usages_in_copy_and_pass') 'indirect', 'indexedIndirect', ] as const) - .combine('pass', ['render', 'compute']) + .combine('pass', ['render', 'compute'] as const) .unless(({ usage0, usage1, pass }) => { const IsCopy = (usage: BufferUsage | 'copy-src' | 'copy-dst') => { return usage === 'copy-src' || usage === 'copy-dst'; @@ -282,6 +291,19 @@ g.test('subresources,buffer_usages_in_copy_and_pass') .fn(t => { const { usage0, usage1, pass } = t.params; + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage0, + pass === 'render' ? 'fragment' : 'compute', + 1 + ); + skipIfStorageBuffersUsedAndNotAvailableInStages( + t, + usage1, + pass === 'render' ? 'fragment' : 'compute', + 1 + ); + const kUsages = GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | @@ -328,15 +350,16 @@ g.test('subresources,buffer_usages_in_copy_and_pass') case 'uniform': case 'storage': case 'read-only-storage': { - const bindGroup = t.createBindGroupForTest(buffer, 0, usage, 'fragment'); switch (pass) { case 'render': { + const bindGroup = t.createBindGroupForTest(buffer, 0, usage, 'fragment'); const renderPassEncoder = t.beginSimpleRenderPass(encoder); renderPassEncoder.setBindGroup(0, bindGroup); renderPassEncoder.end(); break; } case 'compute': { + const bindGroup = t.createBindGroupForTest(buffer, 0, usage, 'compute'); const computePassEncoder = encoder.beginComputePass(); computePassEncoder.setBindGroup(0, bindGroup); computePassEncoder.end(); diff --git a/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts b/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts index 6b65a620fc11..03bbbfcf23f6 100644 --- a/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts +++ b/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts @@ -4,6 +4,7 @@ Texture Usages Validation Tests in Same or Different Render Pass Encoders. import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { assert, unreachable } from '../../../../../common/util/util.js'; +import { MaxLimitsTestMixin } from '../../../../gpu_test.js'; import { ValidationTest } from '../../validation_test.js'; export type TextureBindingType = @@ -92,7 +93,7 @@ class F extends ValidationTest { } } -export const g = makeTestGroup(F); +export const g = makeTestGroup(MaxLimitsTestMixin(F)); const kTextureSize = 16; const kTextureLevels = 3; @@ -207,6 +208,13 @@ g.test('subresources,color_attachment_and_bind_group') inSamePass, } = t.params; + t.skipIf( + t.isCompatibility && + bgUsage !== 'sampled-texture' && + !(t.device.limits.maxStorageTexturesInFragmentStage! >= 1), + `maxStorageTexturesInFragmentStage(${t.device.limits.maxStorageTexturesInFragmentStage}) < 1` + ); + const texture = t.createTextureTracked({ format: 'r32float', usage: @@ -469,6 +477,13 @@ g.test('subresources,multiple_bind_groups') .fn(t => { const { bg0Levels, bg0Layers, bg1Levels, bg1Layers, bgUsage0, bgUsage1, inSamePass } = t.params; + t.skipIf( + t.isCompatibility && + (bgUsage0 !== 'sampled-texture' || bgUsage1 !== 'sampled-texture') && + !(t.device.limits.maxStorageTexturesInFragmentStage! >= 2), + `maxStorageTexturesInFragmentStage(${t.device.limits.maxStorageTexturesInFragmentStage}) < 2` + ); + const texture = t.createTextureTracked({ format: 'r32float', usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING, diff --git a/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts b/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts index 6d7cdb28c13e..661a32b2c0f6 100644 --- a/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts +++ b/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts @@ -5,6 +5,7 @@ Texture Usages Validation Tests on All Kinds of WebGPU Subresource Usage Scopes. import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { unreachable } from '../../../../../common/util/util.js'; import { kTextureUsages } from '../../../../capability_info.js'; +import { MaxLimitsTestMixin } from '../../../../gpu_test.js'; import { ValidationTest } from '../../validation_test.js'; import { TextureBindingType, @@ -12,6 +13,21 @@ import { IsReadOnlyTextureBindingType, } from '../texture/in_render_common.spec.js'; +function skipIfStorageTexturesUsedAndNotAvailableInFragmentStage( + t: ValidationTest, + usage: (typeof kTextureBindingTypes)[number] | 'copy-src' | 'copy-dst' | 'color-attachment', + numRequired: number +) { + t.skipIf( + t.isCompatibility && + (usage === 'writeonly-storage-texture' || + usage === 'readonly-storage-texture' || + usage === 'readwrite-storage-texture') && + !(t.device.limits.maxStorageTexturesInFragmentStage! > numRequired), + `maxStorageTexturesInFragmentStage${t.device.limits.maxStorageTexturesInFragmentStage} < ${numRequired}` + ); +} + class F extends ValidationTest { createBindGroupLayoutForTest( textureUsage: TextureBindingType, @@ -70,7 +86,7 @@ class F extends ValidationTest { } } -export const g = makeTestGroup(F); +export const g = makeTestGroup(MaxLimitsTestMixin(F)); const kTextureSize = 16; const kTextureLayers = 3; @@ -507,6 +523,9 @@ g.test('subresources,texture_usages_in_copy_and_render_pass') .fn(t => { const { usage0, usage1 } = t.params; + skipIfStorageTexturesUsedAndNotAvailableInFragmentStage(t, usage0, 1); + skipIfStorageTexturesUsedAndNotAvailableInFragmentStage(t, usage1, 1); + const texture = t.createTextureTracked({ format: 'r32float', usage: @@ -599,6 +618,8 @@ g.test('subresources,texture_view_usages') .fn(t => { const { bindingType, viewUsage } = t.params; + skipIfStorageTexturesUsedAndNotAvailableInFragmentStage(t, bindingType, 1); + const texture = t.createTextureTracked({ format: 'r32float', usage: