diff --git a/src/webgpu/api/operation/rendering/draw.spec.ts b/src/webgpu/api/operation/rendering/draw.spec.ts index c8db36724009..262a96aaa7a0 100644 --- a/src/webgpu/api/operation/rendering/draw.spec.ts +++ b/src/webgpu/api/operation/rendering/draw.spec.ts @@ -11,10 +11,10 @@ import { TypedArrayBufferView, TypedArrayBufferViewConstructor, } from '../../../../common/util/util.js'; -import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; +import { MaxLimitsTest, TextureTestMixin } from '../../../gpu_test.js'; import { PerPixelComparison } from '../../../util/texture/texture_ok.js'; -class DrawTest extends TextureTestMixin(GPUTest) { +class DrawTest extends TextureTestMixin(MaxLimitsTest) { checkTriangleDraw(opts: { firstIndex: number | undefined; count: number; diff --git a/src/webgpu/gpu_test.ts b/src/webgpu/gpu_test.ts index ed8c170d93d2..66c7c1208c28 100644 --- a/src/webgpu/gpu_test.ts +++ b/src/webgpu/gpu_test.ts @@ -41,7 +41,13 @@ import { import { checkElementsEqual, checkElementsBetween } from './util/check_contents.js'; import { CommandBufferMaker, EncoderType } from './util/command_buffer_maker.js'; import { ScalarType } from './util/conversion.js'; -import { DevicePool, DeviceProvider, UncanonicalizedDeviceDescriptor } from './util/device_pool.js'; +import { + CanonicalDeviceDescriptor, + DescriptorModifierFn, + DevicePool, + DeviceProvider, + UncanonicalizedDeviceDescriptor, +} from './util/device_pool.js'; import { align, roundDown } from './util/math.js'; import { physicalMipSizeFromTexture, virtualMipSize } from './util/texture/base.js'; import { @@ -137,11 +143,15 @@ export class GPUTestSubcaseBatchState extends SubcaseBatchState { * * If the request isn't supported, throws a SkipTestCase exception to skip the entire test case. */ - selectDeviceOrSkipTestCase(descriptor: DeviceSelectionDescriptor): void { + selectDeviceOrSkipTestCase( + descriptor: DeviceSelectionDescriptor, + descriptorModifierFn?: DescriptorModifierFn + ): void { assert(this.provider === undefined, "Can't selectDeviceOrSkipTestCase() multiple times"); this.provider = devicePool.acquire( this.recorder, - initUncanonicalizedDeviceDescriptor(descriptor) + initUncanonicalizedDeviceDescriptor(descriptor), + descriptorModifierFn ); // Suppress uncaught promise rejection (we'll catch it later). this.provider.catch(() => {}); @@ -202,7 +212,8 @@ export class GPUTestSubcaseBatchState extends SubcaseBatchState { this.mismatchedProvider = mismatchedDevicePool.acquire( this.recorder, - initUncanonicalizedDeviceDescriptor(descriptor) + initUncanonicalizedDeviceDescriptor(descriptor), + undefined ); // Suppress uncaught promise rejection (we'll catch it later). this.mismatchedProvider.catch(() => {}); @@ -1276,6 +1287,59 @@ export class GPUTest extends GPUTestBase { } } +/** + * Gets the adapter limits as a standard JavaScript object. + */ +function getAdapterLimitsAsDeviceRequiredLimits(adapter: GPUAdapter) { + const requiredLimits: Record = {}; + const adapterLimits = adapter.limits as unknown as Record; + for (const key in adapter.limits) { + requiredLimits[key] = adapterLimits[key]; + } + return requiredLimits; +} + +function setAllLimitsToAdapterLimits( + adapter: GPUAdapter, + desc: CanonicalDeviceDescriptor | undefined +) { + const descWithMaxLimits: CanonicalDeviceDescriptor = { + requiredFeatures: [], + defaultQueue: {}, + ...desc, + requiredLimits: getAdapterLimitsAsDeviceRequiredLimits(adapter), + }; + return descWithMaxLimits; +} + +/** + * Used by MaxLimitsTest to request a device with all the max limits of the adapter. + */ +export class MaxLimitsGPUTestSubcaseBatchState extends GPUTestSubcaseBatchState { + override selectDeviceOrSkipTestCase( + descriptor: DeviceSelectionDescriptor, + descriptorModifierFn?: DescriptorModifierFn + ): void { + const wrapper = (adapter: GPUAdapter, desc: CanonicalDeviceDescriptor | undefined) => { + desc = descriptorModifierFn ? descriptorModifierFn(adapter, desc) : desc; + return setAllLimitsToAdapterLimits(adapter, desc); + }; + super.selectDeviceOrSkipTestCase(initUncanonicalizedDeviceDescriptor(descriptor), wrapper); + } +} + +/** + * A Test that requests all the max limits from the adapter on the device. + */ +export class MaxLimitsTest extends GPUTest { + public static override MakeSharedState( + recorder: TestCaseRecorder, + params: TestParams + ): GPUTestSubcaseBatchState { + return new MaxLimitsGPUTestSubcaseBatchState(recorder, params); + } +} + /** * Texture expectation mixin can be applied on top of GPUTest to add texture * related expectation helpers. @@ -1494,11 +1558,11 @@ type LinearCopyParameters = { data: Uint8Array; }; -export function TextureTestMixin>( +export function TextureTestMixin>( Base: F ): FixtureClassWithMixin { class TextureExpectations - extends (Base as FixtureClassInterface) + extends (Base as FixtureClassInterface) implements TextureTestMixinType { /** diff --git a/src/webgpu/util/device_pool.ts b/src/webgpu/util/device_pool.ts index aa96c86cbdca..20db6afd1b2e 100644 --- a/src/webgpu/util/device_pool.ts +++ b/src/webgpu/util/device_pool.ts @@ -23,19 +23,25 @@ class TestFailedButDeviceReusable extends Error {} class FeaturesNotSupported extends Error {} export class TestOOMedShouldAttemptGC extends Error {} +export type DescriptorModifierFn = ( + adapter: GPUAdapter, + desc: CanonicalDeviceDescriptor | undefined +) => CanonicalDeviceDescriptor; + export class DevicePool { private holders: 'uninitialized' | 'failed' | DescriptorToHolderMap = 'uninitialized'; /** Acquire a device from the pool and begin the error scopes. */ async acquire( recorder: TestCaseRecorder, - descriptor?: UncanonicalizedDeviceDescriptor + descriptor: UncanonicalizedDeviceDescriptor | undefined, + descriptorModifierFn: DescriptorModifierFn | undefined ): Promise { let errorMessage = ''; if (this.holders === 'uninitialized') { this.holders = new DescriptorToHolderMap(); try { - await this.holders.getOrCreate(recorder, undefined); + await this.holders.getOrCreate(recorder, undefined, descriptorModifierFn); } catch (ex) { this.holders = 'failed'; if (ex instanceof Error) { @@ -49,7 +55,7 @@ export class DevicePool { `WebGPU device failed to initialize${errorMessage}; not retrying` ); - const holder = await this.holders.getOrCreate(recorder, descriptor); + const holder = await this.holders.getOrCreate(recorder, descriptor, descriptorModifierFn); assert(holder.state === 'free', 'Device was in use on DevicePool.acquire'); holder.state = 'acquired'; @@ -143,7 +149,8 @@ class DescriptorToHolderMap { */ async getOrCreate( recorder: TestCaseRecorder, - uncanonicalizedDescriptor: UncanonicalizedDeviceDescriptor | undefined + uncanonicalizedDescriptor: UncanonicalizedDeviceDescriptor | undefined, + descriptorModifierFn: DescriptorModifierFn | undefined ): Promise { const [descriptor, key] = canonicalizeDescriptor(uncanonicalizedDescriptor); // Quick-reject descriptors that are known to be unsupported already. @@ -167,7 +174,7 @@ class DescriptorToHolderMap { // No existing item was found; add a new one. let value; try { - value = await DeviceHolder.create(recorder, descriptor); + value = await DeviceHolder.create(recorder, descriptor, descriptorModifierFn); } catch (ex) { if (ex instanceof FeaturesNotSupported) { this.unsupported.add(key); @@ -209,7 +216,7 @@ export type UncanonicalizedDeviceDescriptor = { /** @deprecated this field cannot be used */ features?: undefined; }; -type CanonicalDeviceDescriptor = Omit< +export type CanonicalDeviceDescriptor = Omit< Required, 'label' | 'nonGuaranteedFeatures' | 'nonGuaranteedLimits' >; @@ -305,11 +312,15 @@ class DeviceHolder implements DeviceProvider { // If the device is lost, DeviceHolder.lost gets set. static async create( recorder: TestCaseRecorder, - descriptor: CanonicalDeviceDescriptor | undefined + descriptor: CanonicalDeviceDescriptor | undefined, + descriptorModifierFn: DescriptorModifierFn | undefined ): Promise { const gpu = getGPU(recorder); const adapter = await gpu.requestAdapter(); assert(adapter !== null, 'requestAdapter returned null'); + if (descriptorModifierFn) { + descriptor = descriptorModifierFn(adapter, descriptor); + } if (!supportsFeature(adapter, descriptor)) { throw new FeaturesNotSupported('One or more features are not supported'); } diff --git a/src/webgpu/util/texture.ts b/src/webgpu/util/texture.ts index 0bde14ff0549..508854943a5b 100644 --- a/src/webgpu/util/texture.ts +++ b/src/webgpu/util/texture.ts @@ -5,7 +5,7 @@ import { isStencilTextureFormat, kTextureFormatInfo, } from '../format_info.js'; -import { GPUTest } from '../gpu_test.js'; +import { GPUTestBase } from '../gpu_test.js'; import { getTextureCopyLayout } from './texture/layout.js'; import { TexelView } from './texture/texel_view.js'; @@ -328,7 +328,7 @@ const s_copyBufferToTextureViaRenderPipelines = new WeakMap< >(); function copyBufferToTextureViaRender( - t: GPUTest, + t: GPUTestBase, encoder: GPUCommandEncoder, source: GPUTexelCopyBufferInfo, sourceFormat: GPUTextureFormat, @@ -500,7 +500,7 @@ function copyBufferToTextureViaRender( * from `texelViews[i]`. */ export function createTextureFromTexelViews( - t: GPUTest, + t: GPUTestBase, texelViews: TexelView[], desc: Omit & { format?: GPUTextureFormat } ): GPUTexture { diff --git a/src/webgpu/util/texture/texture_ok.ts b/src/webgpu/util/texture/texture_ok.ts index 3eb94c2deea0..4a9e4ffb0f3b 100644 --- a/src/webgpu/util/texture/texture_ok.ts +++ b/src/webgpu/util/texture/texture_ok.ts @@ -1,6 +1,6 @@ import { assert, ErrorWithExtra, unreachable } from '../../../common/util/util.js'; import { kTextureFormatInfo, EncodableTextureFormat } from '../../format_info.js'; -import { GPUTest } from '../../gpu_test.js'; +import { GPUTestBase } from '../../gpu_test.js'; import { numbersApproximatelyEqual } from '../conversion.js'; import { generatePrettyTable, numericToStringBuilder } from '../pretty_diff_tables.js'; import { reifyExtent3D, reifyOrigin3D } from '../unions.js'; @@ -166,7 +166,7 @@ function comparePerComponent( /** Create a new mappable GPUBuffer, and copy a subrectangle of GPUTexture data into it. */ function createTextureCopyForMapRead( - t: GPUTest, + t: GPUTestBase, source: GPUTexelCopyTextureInfo, copySize: GPUExtent3D, { format }: { format: EncodableTextureFormat } @@ -297,7 +297,7 @@ ${generatePrettyTable(opts, [ * subnormal numbers (where ULP is defined for float, normalized, and integer formats). */ export async function textureContentIsOKByT2B( - t: GPUTest, + t: GPUTestBase, source: GPUTexelCopyTextureInfo, copySize_: GPUExtent3D, { expTexelView }: { expTexelView: TexelView },