Skip to content

Commit

Permalink
Add "enforceDefaultLimits" flag
Browse files Browse the repository at this point in the history
I'm not sure what to do about this but dealing with dependent
limits is kind of a PITA.

Tests that deal with storage buffers and storage textures need to
take into account that they might have 0. But, they also need to
be tested with the maxiumum number of storage buffers/textures.

So, if you set the test to use max storage buffers/textures then,
unless you have a device that supports 0 you have no easy way to
test that the test functions when the limit is 0.

While refactoring the tests I start without requesting the limit
so on compat I get zero. Once that works I add `MaxLimitsTestMixin`
or similar to request the maximum limits, otherwise a compat device
would get no coverage.

But, now I have the issue that if I'm modifying the test I need to
remember to test with 0 so I have to go manually comment out the code
that's requesting max limits.

So, I thought I'd add this option.

Unfortunately, the default limits are defined in src/webgpu/capability_info.ts
which is listed as off limits to navigator_gpu.ts. I tried moving the limits
table to src/common/limits.ts but then src/webgpu/capabilities_info.ts complained
that src/common/limits.ts is off limits. So, I'm not sure where I can put a
shared limits.ts file that both navigator_gpu.ts and capabilities_info.ts can access.

In the webgpu-dev-extension, I implemented this by requesting 2 adapters and a temp
device.

```js
// pseudo code
tempAdapter = await requestAdapter(options);
tempDevice = await requestDevice(...)
const defaultLimits = tempDevice.limits;
tempDevice.destroy();
adapter = await requestAdapter(options)
adapter.limits = defaultLimits;
```

But I wasn't sure that was a good solution in the CTS vs
using the limits table.

Also, in the webgpu-dev-extension I don't really care about
actually enforcing the limits but in the cts, the limits tests
in src/webgpu/api/validation/capability_checks/limits will fail
if the limits are not enforced and I needed this flag to be able
to debug them and make sure they work when a limit is 0.
  • Loading branch information
greggman committed Jan 7, 2025
1 parent 10b66cd commit f16432c
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 110 deletions.
6 changes: 6 additions & 0 deletions src/common/framework/test_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export type TestConfig = {
*/
forceFallbackAdapter: boolean;

/**
* Enforce the default limits on the adapter
*/
enforceDefaultLimits: boolean;

/**
* Whether to enable the `logToWebSocket` function used for out-of-band test logging.
*/
Expand All @@ -59,5 +64,6 @@ export const globalTestConfig: TestConfig = {
unrollConstEvalLoops: false,
compatibility: false,
forceFallbackAdapter: false,
enforceDefaultLimits: false,
logToWebSocket: false,
};
2 changes: 2 additions & 0 deletions src/common/runtime/cmdline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ for (let i = 0; i < sys.args.length; ++i) {
globalTestConfig.compatibility = true;
} else if (a === '--force-fallback-adapter') {
globalTestConfig.forceFallbackAdapter = true;
} else if (a === '--enforce-default-limits') {
globalTestConfig.enforceDefaultLimits = true;
} else if (a === '--log-to-websocket') {
globalTestConfig.logToWebSocket = true;
} else {
Expand Down
3 changes: 3 additions & 0 deletions src/common/runtime/helper/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface CTSOptions {
debug: boolean;
compatibility: boolean;
forceFallbackAdapter: boolean;
enforceDefaultLimits: boolean;
unrollConstEvalLoops: boolean;
powerPreference: GPUPowerPreference | null;
logToWebSocket: boolean;
Expand All @@ -63,6 +64,7 @@ export const kDefaultCTSOptions: CTSOptions = {
debug: true,
compatibility: false,
forceFallbackAdapter: false,
enforceDefaultLimits: false,
unrollConstEvalLoops: false,
powerPreference: null,
logToWebSocket: false,
Expand Down Expand Up @@ -100,6 +102,7 @@ export const kCTSOptionsInfo: OptionsInfos<CTSOptions> = {
debug: { description: 'show more info' },
compatibility: { description: 'run in compatibility mode' },
forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' },
enforceDefaultLimits: { description: 'force the adapter limits to the default limits' },
unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' },
powerPreference: {
description: 'set default powerPreference for some tests',
Expand Down
1 change: 1 addition & 0 deletions src/common/runtime/helper/utils_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function setupWorkerEnvironment(ctsOptions: CTSOptions): Logger {
globalTestConfig.enableDebugLogs = ctsOptions.debug;
globalTestConfig.unrollConstEvalLoops = ctsOptions.unrollConstEvalLoops;
globalTestConfig.compatibility = compatibility;
globalTestConfig.enforceDefaultLimits = ctsOptions.enforceDefaultLimits;
globalTestConfig.logToWebSocket = ctsOptions.logToWebSocket;

const log = new Logger();
Expand Down
2 changes: 2 additions & 0 deletions src/common/runtime/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ for (let i = 0; i < sys.args.length; ++i) {
emitCoverage = true;
} else if (a === '--force-fallback-adapter') {
globalTestConfig.forceFallbackAdapter = true;
} else if (a === '--enforce-default-limits') {
globalTestConfig.enforceDefaultLimits = true;
} else if (a === '--log-to-websocket') {
globalTestConfig.logToWebSocket = true;
} else if (a === '--gpu-provider') {
Expand Down
1 change: 1 addition & 0 deletions src/common/runtime/standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const { runnow, powerPreference, compatibility, forceFallbackAdapter } = options
globalTestConfig.enableDebugLogs = options.debug;
globalTestConfig.unrollConstEvalLoops = options.unrollConstEvalLoops;
globalTestConfig.compatibility = compatibility;
globalTestConfig.enforceDefaultLimits = options.enforceDefaultLimits;
globalTestConfig.logToWebSocket = options.logToWebSocket;

const logger = new Logger();
Expand Down
63 changes: 63 additions & 0 deletions src/common/util/navigator_gpu.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// eslint-disable-next-line import/no-restricted-paths
import { getDefaultLimitsForAdapter } from '../../webgpu/capability_info.js';
import { TestCaseRecorder } from '../framework/fixture.js';
import { globalTestConfig } from '../framework/test_config.js';

import { ErrorWithExtra, assert, objectEquals } from './util.js';

Expand Down Expand Up @@ -49,6 +52,8 @@ export function getDefaultRequestAdapterOptions() {
return defaultRequestAdapterOptions;
}

let s_wrappedForEnforceDefaultLimits = false;

/**
* Finds and returns the `navigator.gpu` object (or equivalent, for non-browser implementations).
* Throws an exception if not found.
Expand All @@ -60,6 +65,64 @@ export function getGPU(recorder: TestCaseRecorder | null): GPU {

impl = gpuProvider();

if (globalTestConfig.enforceDefaultLimits) {
// eslint-disable-next-line @typescript-eslint/unbound-method
const oldFn = impl.requestAdapter;
impl.requestAdapter = async function (options?: GPURequestAdapterOptions) {
const adapter = await oldFn.call(this, { ...defaultRequestAdapterOptions, ...options });
if (adapter) {
const limits = Object.fromEntries(
Object.entries(getDefaultLimitsForAdapter(adapter)).map(([key, { default: v }]) => [
key,
v,
])
);

Object.defineProperty(adapter, 'limits', {
get() {
return limits;
},
});
}
return adapter;
};

if (!s_wrappedForEnforceDefaultLimits) {
s_wrappedForEnforceDefaultLimits = true;

const enforceDefaultLimits = (adapter: GPUAdapter, desc: GPUDeviceDescriptor | undefined) => {
if (desc?.requiredLimits) {
const limits = getDefaultLimitsForAdapter(adapter);
for (const [key, value] of Object.entries(desc.requiredLimits)) {
const info = limits[key as keyof ReturnType<typeof getDefaultLimitsForAdapter>];
if (info && value !== undefined) {
const [beyondLimit, condition] =
info.class === 'maximum'
? [value > info.default, 'greater']
: [value < info.default, 'less'];
if (beyondLimit) {
throw new DOMException(
`requestedLimit ${value} for ${key} is ${condition} than adapter limit ${info.default}`,
'OperationError'
);
}
}
}
}
};

// eslint-disable-next-line @typescript-eslint/unbound-method
const origFn = GPUAdapter.prototype.requestDevice;
GPUAdapter.prototype.requestDevice = async function (
this: GPUAdapter,
desc?: GPUDeviceDescriptor | undefined
) {
enforceDefaultLimits(this, desc);
return await origFn.call(this, desc);
};
}
}

if (defaultRequestAdapterOptions) {
// eslint-disable-next-line @typescript-eslint/unbound-method
const oldFn = impl.requestAdapter;
Expand Down
220 changes: 110 additions & 110 deletions src/resources/cache/hashes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f16432c

Please sign in to comment.