export const description = ` Tests for GPUAdapterInfo. `; import { Fixture } from '../../../../common/framework/fixture.js'; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { keysOf } from '../../../../common/util/data_tables.js'; import { getGPU } from '../../../../common/util/navigator_gpu.js'; import { assert, hasFeature, objectEquals } from '../../../../common/util/util.js'; import { isPowerOfTwo } from '../../../util/math.js'; export const g = makeTestGroup(Fixture); const normalizedIdentifierRegex = /^$|^[a-z0-9]+(-[a-z0-9]+)*$/; g.test('adapter_info') .desc( ` Test that every member in the GPUAdapter.info except description is properly formatted` ) .fn(async t => { const gpu = getGPU(t.rec); const adapter = await gpu.requestAdapter(); assert(adapter !== null); const adapterInfo = adapter.info; assert(adapterInfo instanceof GPUAdapterInfo); t.expect( normalizedIdentifierRegex.test(adapterInfo.vendor), `adapterInfo.vendor should be a normalized identifier. But it's '${adapterInfo.vendor}'` ); t.expect( normalizedIdentifierRegex.test(adapterInfo.architecture), `adapterInfo.architecture should be a normalized identifier. But it's '${adapterInfo.architecture}'` ); t.expect( normalizedIdentifierRegex.test(adapterInfo.device), `adapterInfo.device should be a normalized identifier. But it's '${adapterInfo.device}'` ); }); g.test('same_object') .desc( ` GPUAdapter.info and GPUDevice.adapterInfo provide the same object each time they're accessed, but different objects from one another.` ) .fn(async t => { const gpu = getGPU(t.rec); const adapter = await gpu.requestAdapter(); assert(adapter !== null); assert(adapter.info instanceof GPUAdapterInfo); const adapterInfo1 = adapter.info; const adapterInfo2 = adapter.info; t.expect(adapterInfo1 === adapterInfo2, 'adapter.info should obey [SameObject]'); const device = await t.requestDeviceTracked(adapter); assert(device !== null); assert(device.adapterInfo instanceof GPUAdapterInfo); const deviceAdapterInfo1 = device.adapterInfo; const deviceAdapterInfo2 = device.adapterInfo; t.expect( deviceAdapterInfo1 === deviceAdapterInfo2, 'device.adapterInfo should obey [SameObject]' ); t.expect( adapter.info !== device.adapterInfo, 'adapter.info and device.adapterInfo should NOT return the same object' ); }); g.test('device_matches_adapter') .desc( ` Test that GPUDevice.adapterInfo matches GPUAdapter.info. Cases access the members in different orders to make sure that they are consistent regardless of the access order.` ) .paramsSubcasesOnly(u => u.combine('testDeviceFirst', [true, false]).combine('testMembersFirst', [true, false]) ) .fn(async t => { const { testDeviceFirst, testMembersFirst } = t.params; const gpu = getGPU(t.rec); const adapter = await gpu.requestAdapter(); assert(adapter !== null); const device = await t.requestDeviceTracked(adapter); assert(device !== null); const deviceInfo: unknown[] = []; const adapterInfo: unknown[] = []; const kGPUAdapterInfoKeys = keysOf(GPUAdapterInfo.prototype); if (testMembersFirst) { if (testDeviceFirst) { assert(device.adapterInfo instanceof GPUAdapterInfo); for (const k of kGPUAdapterInfoKeys) { deviceInfo.push(device.adapterInfo[k]); } assert(adapter.info instanceof GPUAdapterInfo); for (const k of kGPUAdapterInfoKeys) { adapterInfo.push(adapter.info[k]); } } else { assert(adapter.info instanceof GPUAdapterInfo); for (const k of kGPUAdapterInfoKeys) { adapterInfo.push(adapter.info[k]); } assert(device.adapterInfo instanceof GPUAdapterInfo); for (const k of kGPUAdapterInfoKeys) { deviceInfo.push(device.adapterInfo[k]); } } } else { if (testDeviceFirst) { assert(device.adapterInfo instanceof GPUAdapterInfo); assert(adapter.info instanceof GPUAdapterInfo); for (const k of kGPUAdapterInfoKeys) { deviceInfo.push(device.adapterInfo[k]); adapterInfo.push(adapter.info[k]); } } else { assert(adapter.info instanceof GPUAdapterInfo); assert(device.adapterInfo instanceof GPUAdapterInfo); for (const k of kGPUAdapterInfoKeys) { adapterInfo.push(adapter.info[k]); deviceInfo.push(device.adapterInfo[k]); } } t.expect(objectEquals(deviceInfo, adapterInfo)); } }); const kSubgroupMinSizeBound = 4; const kSubgroupMaxSizeBound = 128; g.test('subgroup_sizes') .desc( ` Verify GPUAdapterInfo.subgroupMinSize, GPUAdapterInfo.subgroupMaxSize. If the subgroups feature is supported, they must both exist. If they exist, they must both exist and be powers of two, and 4 <= subgroupMinSize <= subgroupMaxSize <= 128. ` ) .fn(async t => { const gpu = getGPU(t.rec); const adapter = await gpu.requestAdapter(); assert(adapter !== null); const { subgroupMinSize, subgroupMaxSize } = adapter.info; // Once 'subgroups' lands, the properties should be defined with default values 4 and 128 // when adapter does not support the feature. // https://github.com/gpuweb/gpuweb/pull/4963 if (hasFeature(adapter.features, 'subgroups')) { t.expect( subgroupMinSize !== undefined, 'GPUAdapterInfo.subgroupMinSize must exist when subgroups supported' ); t.expect( subgroupMaxSize !== undefined, 'GPUAdapterInfo.subgroupMaxSize must exist when subgroups supported' ); } t.expect( (subgroupMinSize === undefined) === (subgroupMinSize === undefined), 'GPUAdapterInfo.subgropuMinSize and GPUAdapterInfo.subgroupMaxSize must both be defined, or neither should be' ); if (subgroupMinSize !== undefined && subgroupMaxSize !== undefined) { t.expect(isPowerOfTwo(subgroupMinSize)); t.expect(isPowerOfTwo(subgroupMaxSize)); t.expect(kSubgroupMinSizeBound <= subgroupMinSize); t.expect(subgroupMinSize <= subgroupMaxSize); t.expect(subgroupMaxSize <= kSubgroupMaxSizeBound); } });