Skip to content

Commit 3314e41

Browse files
authored
Add hasFeature function and disallow features.has (#4506)
The problem with `features.has` is that it takes a string isntead of a `GPUFeatureName`. To prevent typos when checking for features, add a lint warning for `features.has(feature)` and suggest using `hasFeature(features, feature)`. `hasFeature` takes a `GPUFeatureName` and so will warn if the string pass is not a valid feature.
1 parent 5a1dcdc commit 3314e41

18 files changed

Lines changed: 193 additions & 162 deletions

File tree

.eslintrc.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@
5858
"message": "Use requestDeviceTracked() instead of requestDevice().",
5959
// We don't seem to need direct calls to requestDevice() at all so we can just disallow all of them.
6060
"selector": "CallExpression > MemberExpression > Identifier[name=\"requestDevice\"]"
61+
},
62+
{
63+
"message": "Use hasFeature() instead of features.has().",
64+
// features.has takes any string. We want only valid feature names.
65+
"selector": "CallExpression[callee.property.name='has'][callee.object.name='features'],CallExpression[callee.property.name='has'][callee.object.property.name='features']"
6166
}
6267
],
6368

src/common/framework/test_config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assert } from '../util/util.js';
1+
import { assert, hasFeature } from '../util/util.js';
22

33
export type TestConfig = {
44
/**
@@ -90,7 +90,7 @@ export const globalTestConfig: TestConfig = {
9090
// is trying to test that compatibility devices have the correct validation.
9191
export function isCompatibilityDevice(device: GPUDevice) {
9292
if (globalTestConfig.compatibility) {
93-
assert(!device.features.has('core-features-and-limits'));
93+
assert(!hasFeature(device.features, 'core-features-and-limits'));
9494
}
9595
return globalTestConfig.compatibility;
9696
}

src/common/util/navigator_gpu.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { TestCaseRecorder } from '../framework/fixture.js';
33
import { globalTestConfig } from '../framework/test_config.js';
44

5-
import { ErrorWithExtra, assert, objectEquals } from './util.js';
5+
import { ErrorWithExtra, assert, hasFeature, objectEquals } from './util.js';
66

77
/**
88
* Finds and returns the `navigator.gpu` object (or equivalent, for non-browser implementations).
@@ -164,7 +164,9 @@ export function getGPU(recorder: TestCaseRecorder | null): GPU {
164164
Object.defineProperty(adapter, 'features', {
165165
enumerable: false,
166166
value: new Set(
167-
adapter.features.has('core-features-and-limits') ? ['core-features-and-limits'] : []
167+
hasFeature(adapter.features, 'core-features-and-limits')
168+
? ['core-features-and-limits']
169+
: []
168170
),
169171
});
170172
}
@@ -177,6 +179,7 @@ export function getGPU(recorder: TestCaseRecorder | null): GPU {
177179
for (const [feature] of desc.requiredFeatures) {
178180
// Note: This adapter has had its features property over-ridden and will only return
179181
// have nothing or 'core-features-and-limits'.
182+
// eslint-disable-next-line no-restricted-syntax
180183
if (!adapter.features.has(feature)) {
181184
throw new TypeError(`requested feature ${feature} does not exist on adapter`);
182185
}

src/common/util/util.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,3 +496,12 @@ export function filterUniqueValueTestVariants(valueTestVariants: ValueTestVarian
496496
export function makeValueTestVariant(base: number, variant: ValueTestVariant) {
497497
return base * variant.mult + variant.add;
498498
}
499+
500+
/**
501+
* Use instead of features.has because feature's has takes any string
502+
* and we want to prevent typos.
503+
*/
504+
export function hasFeature(features: GPUSupportedFeatures, feature: GPUFeatureName) {
505+
// eslint-disable-next-line no-restricted-syntax
506+
return features.has(feature);
507+
}

src/resources/cache/hashes.json

Lines changed: 110 additions & 110 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/webgpu/api/operation/adapter/info.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Fixture } from '../../../../common/framework/fixture.js';
66
import { makeTestGroup } from '../../../../common/framework/test_group.js';
77
import { keysOf } from '../../../../common/util/data_tables.js';
88
import { getGPU } from '../../../../common/util/navigator_gpu.js';
9-
import { assert, objectEquals } from '../../../../common/util/util.js';
9+
import { assert, hasFeature, objectEquals } from '../../../../common/util/util.js';
1010
import { isPowerOfTwo } from '../../../util/math.js';
1111

1212
export const g = makeTestGroup(Fixture);
@@ -158,7 +158,7 @@ If they exist, they must both exist and be powers of two, and
158158
// Once 'subgroups' lands, the properties should be defined with default values 4 and 128
159159
// when adapter does not support the feature.
160160
// https://github.com/gpuweb/gpuweb/pull/4963
161-
if (adapter.features.has('subgroups')) {
161+
if (hasFeature(adapter.features, 'subgroups')) {
162162
t.expect(
163163
subgroupMinSize !== undefined,
164164
'GPUAdapterInfo.subgroupMinSize must exist when subgroups supported'

src/webgpu/api/operation/adapter/requestDevice.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ potentially limited native resources.
88
import { Fixture } from '../../../../common/framework/fixture.js';
99
import { makeTestGroup } from '../../../../common/framework/test_group.js';
1010
import { getGPU } from '../../../../common/util/navigator_gpu.js';
11-
import { assert, assertReject, typedEntries } from '../../../../common/util/util.js';
11+
import { assert, assertReject, hasFeature, typedEntries } from '../../../../common/util/util.js';
1212
import {
1313
getDefaultLimitsForCTS,
1414
kFeatureNames,
@@ -44,7 +44,7 @@ g.test('default')
4444

4545
if (device.features.size === 1) {
4646
t.expect(
47-
device.features.has('core-features-and-limits'),
47+
hasFeature(device.features, 'core-features-and-limits'),
4848
'Default device should not have any features other than "core-features-and-limits"'
4949
);
5050
} else {
@@ -204,9 +204,9 @@ g.test('features,known')
204204
assert(adapter !== null);
205205

206206
const promise = t.requestDeviceTracked(adapter, { requiredFeatures: [feature] });
207-
if (adapter.features.has(feature)) {
207+
if (hasFeature(adapter.features, feature)) {
208208
const device = await promise;
209-
t.expect(device.features.has(feature), 'Device should include the required feature');
209+
t.expect(hasFeature(device.features, feature), 'Device should include the required feature');
210210
} else {
211211
t.shouldReject('TypeError', promise);
212212
}
@@ -496,12 +496,12 @@ g.test('always_returns_device')
496496
const device = await t.requestDeviceTracked(adapter);
497497
assert(device instanceof GPUDevice, 'requestDevice must return a device or throw');
498498

499-
if (featureLevel === 'core' && adapter.features.has('core-features-and-limits')) {
499+
if (featureLevel === 'core' && hasFeature(adapter.features, 'core-features-and-limits')) {
500500
// Check if the device supports core, when featureLevel is core and adapter supports core.
501501
// This check is to make sure something lower-level is not forcing compatibility mode.
502502

503503
t.expect(
504-
device.features.has('core-features-and-limits'),
504+
hasFeature(device.features, 'core-features-and-limits'),
505505
'must not get a Compatibility adapter if not requested'
506506
);
507507
}

src/webgpu/api/operation/device/all_limits_and_features.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ changes to WebGPU do not break sites requesting everything.
103103

104104
// Test that all the adapter features are on the device.
105105
for (const feature of t.adapter.features) {
106+
// eslint-disable-next-line no-restricted-syntax
106107
t.expect(t.device.features.has(feature), `device has feature: ${feature}`);
107108
}
108109
});

0 commit comments

Comments
 (0)