Skip to content

Commit

Permalink
Add MaxLimitsTest that sets all limits to the adapter's (#4059)
Browse files Browse the repository at this point in the history
* Add MaxLimitsTest that sets all limits to the adapter's

This is what I came up with. Without a major refactor the
issue is the adapter is requested at an extremely low level
but a test needs access to an adapter to select limits.

So, made selectDeviceOrSkipTestCase pass an optional callback
all the way down that gets passed the adapter and at
CanonicalDeviceDescriptor giving it a chance to modify it
down at the level where an adapter is available.

More of the CTS tests need to be testing with max limits.

Issue: #3363
  • Loading branch information
greggman authored Nov 25, 2024
1 parent b78115a commit 86ce55a
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/webgpu/api/operation/rendering/draw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
76 changes: 70 additions & 6 deletions src/webgpu/gpu_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(() => {});
Expand Down Expand Up @@ -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(() => {});
Expand Down Expand Up @@ -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<string, GPUSize64> = {};
const adapterLimits = adapter.limits as unknown as Record<string, GPUSize64>;
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.
Expand Down Expand Up @@ -1494,11 +1558,11 @@ type LinearCopyParameters = {
data: Uint8Array;
};

export function TextureTestMixin<F extends FixtureClass<GPUTest>>(
export function TextureTestMixin<F extends FixtureClass<GPUTestBase>>(
Base: F
): FixtureClassWithMixin<F, TextureTestMixinType> {
class TextureExpectations
extends (Base as FixtureClassInterface<GPUTest>)
extends (Base as FixtureClassInterface<GPUTestBase>)
implements TextureTestMixinType
{
/**
Expand Down
25 changes: 18 additions & 7 deletions src/webgpu/util/device_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeviceProvider> {
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) {
Expand All @@ -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';
Expand Down Expand Up @@ -143,7 +149,8 @@ class DescriptorToHolderMap {
*/
async getOrCreate(
recorder: TestCaseRecorder,
uncanonicalizedDescriptor: UncanonicalizedDeviceDescriptor | undefined
uncanonicalizedDescriptor: UncanonicalizedDeviceDescriptor | undefined,
descriptorModifierFn: DescriptorModifierFn | undefined
): Promise<DeviceHolder> {
const [descriptor, key] = canonicalizeDescriptor(uncanonicalizedDescriptor);
// Quick-reject descriptors that are known to be unsupported already.
Expand All @@ -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);
Expand Down Expand Up @@ -209,7 +216,7 @@ export type UncanonicalizedDeviceDescriptor = {
/** @deprecated this field cannot be used */
features?: undefined;
};
type CanonicalDeviceDescriptor = Omit<
export type CanonicalDeviceDescriptor = Omit<
Required<GPUDeviceDescriptor>,
'label' | 'nonGuaranteedFeatures' | 'nonGuaranteedLimits'
>;
Expand Down Expand Up @@ -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<DeviceHolder> {
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');
}
Expand Down
6 changes: 3 additions & 3 deletions src/webgpu/util/texture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -328,7 +328,7 @@ const s_copyBufferToTextureViaRenderPipelines = new WeakMap<
>();

function copyBufferToTextureViaRender(
t: GPUTest,
t: GPUTestBase,
encoder: GPUCommandEncoder,
source: GPUTexelCopyBufferInfo,
sourceFormat: GPUTextureFormat,
Expand Down Expand Up @@ -500,7 +500,7 @@ function copyBufferToTextureViaRender(
* from `texelViews[i]`.
*/
export function createTextureFromTexelViews(
t: GPUTest,
t: GPUTestBase,
texelViews: TexelView[],
desc: Omit<GPUTextureDescriptor, 'format'> & { format?: GPUTextureFormat }
): GPUTexture {
Expand Down
6 changes: 3 additions & 3 deletions src/webgpu/util/texture/texture_ok.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 },
Expand Down

0 comments on commit 86ce55a

Please sign in to comment.