Skip to content

Commit

Permalink
Merge branch 'main' into print_environment-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
kainino0x committed Jan 10, 2025
2 parents f1eb276 + 3fae862 commit 08b1399
Show file tree
Hide file tree
Showing 19 changed files with 1,174 additions and 484 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,
};
4 changes: 3 additions & 1 deletion 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 All @@ -121,7 +123,7 @@ for (let i = 0; i < sys.args.length; ++i) {
let codeCoverage: CodeCoverageProvider | undefined = undefined;

if (globalTestConfig.compatibility || globalTestConfig.forceFallbackAdapter) {
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
// MAINTENANCE_TODO: remove compatibilityMode (and the typecast) once no longer needed.
setDefaultRequestAdapterOptions({
compatibilityMode: globalTestConfig.compatibility,
featureLevel: globalTestConfig.compatibility ? 'compatibility' : 'core',
Expand Down
6 changes: 6 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,10 @@ export const kCTSOptionsInfo: OptionsInfos<CTSOptions> = {
debug: { description: 'show more info' },
compatibility: { description: 'request adapters with featureLevel: "compatibility"' },
forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' },
enforceDefaultLimits: {
description: `force the adapter limits to the default limits.
Note: May fail on tests for low-power/high-performance`,
},
unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' },
powerPreference: {
description: 'set default powerPreference for some tests',
Expand Down
3 changes: 2 additions & 1 deletion src/common/runtime/helper/utils_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ 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();

if (powerPreference || compatibility) {
setDefaultRequestAdapterOptions({
...(powerPreference && { powerPreference }),
// MAINTENANCE_TODO: Change this to whatever the option ends up being
// MAINTENANCE_TODO: remove compatibilityMode once no longer needed.
...(compatibility && { compatibilityMode: true, featureLevel: 'compatibility' }),
});
}
Expand Down
4 changes: 3 additions & 1 deletion 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 All @@ -120,7 +122,7 @@ for (let i = 0; i < sys.args.length; ++i) {
let codeCoverage: CodeCoverageProvider | undefined = undefined;

if (globalTestConfig.compatibility || globalTestConfig.forceFallbackAdapter) {
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
// MAINTENANCE_TODO: remove compatibilityMode (and the typecast) once no longer needed.
setDefaultRequestAdapterOptions({
compatibilityMode: globalTestConfig.compatibility,
featureLevel: globalTestConfig.compatibility ? 'compatibility' : 'core',
Expand Down
3 changes: 2 additions & 1 deletion 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 Expand Up @@ -84,7 +85,7 @@ stopButtonElem.addEventListener('click', () => {
if (powerPreference || compatibility || forceFallbackAdapter) {
setDefaultRequestAdapterOptions({
...(powerPreference && { powerPreference }),
// MAINTENANCE_TODO: Change this to whatever the option ends up being
// MAINTENANCE_TODO: remove compatibilityMode once no longer needed.
...(compatibility && { compatibilityMode: true, featureLevel: 'compatibility' }),
...(forceFallbackAdapter && { forceFallbackAdapter: true }),
});
Expand Down
84 changes: 84 additions & 0 deletions src/common/util/navigator_gpu.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// eslint-disable-next-line import/no-restricted-paths
import { TestCaseRecorder } from '../framework/fixture.js';
import { globalTestConfig } from '../framework/test_config.js';

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

Expand Down Expand Up @@ -31,6 +33,7 @@ export function setGPUProvider(provider: GPUProvider) {
}

let impl: GPU | undefined = undefined;
let s_defaultLimits: Record<string, GPUSize64> | undefined = undefined;

let defaultRequestAdapterOptions: GPURequestAdapterOptions | undefined;

Expand All @@ -49,6 +52,14 @@ export function getDefaultRequestAdapterOptions() {
return defaultRequestAdapterOptions;
}

function copyLimits(objLike: GPUSupportedLimits) {
const obj: Record<string, number> = {};
for (const key in objLike) {
obj[key] = (objLike as unknown as Record<string, number>)[key];
}
return obj;
}

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

impl = gpuProvider();

if (globalTestConfig.enforceDefaultLimits) {
// eslint-disable-next-line @typescript-eslint/unbound-method
const origRequestAdapterFn = impl.requestAdapter;
// eslint-disable-next-line @typescript-eslint/unbound-method
const origRequestDeviceFn = GPUAdapter.prototype.requestDevice;

impl.requestAdapter = async function (options?: GPURequestAdapterOptions) {
if (!s_defaultLimits) {
const tempAdapter = await origRequestAdapterFn.call(this, {
...defaultRequestAdapterOptions,
...options,
});
// eslint-disable-next-line no-restricted-syntax
const tempDevice = await tempAdapter?.requestDevice();
s_defaultLimits = copyLimits(tempDevice!.limits);
tempDevice?.destroy();
}
const adapter = await origRequestAdapterFn.call(this, {
...defaultRequestAdapterOptions,
...options,
});
if (adapter) {
const limits = Object.fromEntries(
Object.entries(s_defaultLimits).map(([key, v]) => [key, v])
);

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

const enforceDefaultLimits = (adapter: GPUAdapter, desc: GPUDeviceDescriptor | undefined) => {
if (desc?.requiredLimits) {
for (const [key, value] of Object.entries(desc.requiredLimits)) {
const limit = s_defaultLimits![key];
if (limit !== undefined && value !== undefined) {
const [beyondLimit, condition] = key.startsWith('max')
? [value > limit, 'greater']
: [value < limit, 'less'];
if (beyondLimit) {
throw new DOMException(
`requestedLimit ${value} for ${key} is ${condition} than adapter limit ${limit}`,
'OperationError'
);
}
}
}
}
};

GPUAdapter.prototype.requestDevice = async function (
this: GPUAdapter,
desc?: GPUDeviceDescriptor | undefined
) {
// We need to enforce the default limits because even though we patched the adapter to
// show defaults for adapter.limits, there are tests that test we throw when we request more than the max.
// In other words.
//
// adapter.requestDevice({ requiredLimits: {
// maxXXX: addapter.limits.maxXXX + 1, // should throw
// });
//
// But unless we enforce this manually, it won't actual through if the adapter's
// true limits are higher than we patched above.
enforceDefaultLimits(this, desc);
return await origRequestDeviceFn.call(this, desc);
};
}

if (defaultRequestAdapterOptions) {
// eslint-disable-next-line @typescript-eslint/unbound-method
const oldFn = impl.requestAdapter;
Expand Down
Loading

0 comments on commit 08b1399

Please sign in to comment.