diff --git a/src/webgpu/api/operation/adapter/info.spec.ts b/src/webgpu/api/operation/adapter/info.spec.ts index 4fef9a56ddf7..0d9e120015eb 100644 --- a/src/webgpu/api/operation/adapter/info.spec.ts +++ b/src/webgpu/api/operation/adapter/info.spec.ts @@ -7,6 +7,7 @@ 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, objectEquals } from '../../../../common/util/util.js'; +import { isPowerOfTwo } from '../../../util/math.js'; export const g = makeTestGroup(Fixture); @@ -136,3 +137,53 @@ different orders to make sure that they are consistent regardless of the access t.expect(objectEquals(deviceInfo, adapterInfo)); } }); + +// This can be removed once 'subgroups' lands. +// See https://github.com/gpuweb/gpuweb/pull/4963 +interface SubgroupProperties extends GPUAdapterInfo { + subgroupMinSize?: number; + subgroupMaxSize?: number; +} + +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 as SubgroupProperties; + // 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 (adapter.features.has('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); + } + }); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index ce8fb3a2bcb4..b764febcc02e 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1,6 +1,9 @@ { "_comment": "SEMI AUTO-GENERATED. This list is NOT exhaustive. Please read docs/adding_timing_metadata.md.", "webgpu:api,operation,adapter,info:adapter_info:*": { "subcaseMS": 32.901 }, + "webgpu:api,operation,adapter,info:device_matches_adapter:*": { "subcaseMS": 14.708 }, + "webgpu:api,operation,adapter,info:same_object:*": { "subcaseMS": 25.153 }, + "webgpu:api,operation,adapter,info:subgroup_sizes:*": { "subcaseMS": 18.831 }, "webgpu:api,operation,adapter,requestAdapter:requestAdapter:*": { "subcaseMS": 152.083 }, "webgpu:api,operation,adapter,requestAdapter:requestAdapter_no_parameters:*": { "subcaseMS": 384.601 }, "webgpu:api,operation,adapter,requestDevice:always_returns_device:*": { "subcaseMS": 19.450 }, diff --git a/src/webgpu/shader/execution/expression/call/builtin/subgroupBitwise.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/subgroupBitwise.spec.ts index a8f423e1f729..b134e5db633d 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/subgroupBitwise.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/subgroupBitwise.spec.ts @@ -547,12 +547,12 @@ g.test('fragment,all_active') .fn(async t => { const numInputs = t.params.size[0] * t.params.size[1]; - interface SubgroupLimits extends GPUSupportedLimits { - minSubgroupSize: number; + interface SubgroupProperties extends GPUAdapterInfo { + subgroupMinSize: number; } - const { minSubgroupSize } = t.device.limits as SubgroupLimits; + const { subgroupMinSize } = t.device.adapterInfo as SubgroupProperties; const innerTexels = (t.params.size[0] - 1) * (t.params.size[1] - 1); - t.skipIf(innerTexels < minSubgroupSize, 'Too few texels to be reliable'); + t.skipIf(innerTexels < subgroupMinSize, 'Too few texels to be reliable'); const inputData = generateInputData(t.params.case, numInputs, identity(t.params.op)); diff --git a/src/webgpu/shader/execution/expression/call/builtin/subgroupBroadcast.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/subgroupBroadcast.spec.ts index 5c9650778a68..50dac8c4d894 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/subgroupBroadcast.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/subgroupBroadcast.spec.ts @@ -444,12 +444,12 @@ g.test('compute,split') const testcase = kPredicateCases[t.params.predicate]; const wgThreads = t.params.wgSize[0] * t.params.wgSize[1] * t.params.wgSize[2]; - interface SubgroupLimits extends GPUSupportedLimits { - minSubgroupSize: number; - maxSubgroupSize: number; + interface SubgroupProperties extends GPUAdapterInfo { + subgroupMinSize: number; + subgroupMaxSize: number; } - const { minSubgroupSize, maxSubgroupSize } = t.device.limits as SubgroupLimits; - for (let size = minSubgroupSize; size <= maxSubgroupSize; size *= 2) { + const { subgroupMinSize, subgroupMaxSize } = t.device.adapterInfo as SubgroupProperties; + for (let size = subgroupMinSize; size <= subgroupMaxSize; size *= 2) { t.skipIf(!testcase.filter(t.params.id, size), 'Skipping potential undefined behavior'); } @@ -669,11 +669,11 @@ g.test('fragment') }) .fn(async t => { const innerTexels = (t.params.size[0] - 1) * (t.params.size[1] - 1); - interface SubgroupLimits extends GPUSupportedLimits { - maxSubgroupSize: number; + interface SubgroupProperties extends GPUAdapterInfo { + subgroupMaxSize: number; } - const { maxSubgroupSize } = t.device.limits as SubgroupLimits; - t.skipIf(innerTexels < maxSubgroupSize, 'Too few texels to be reliable'); + const { subgroupMaxSize } = t.device.adapterInfo as SubgroupProperties; + t.skipIf(innerTexels < subgroupMaxSize, 'Too few texels to be reliable'); const broadcast = t.params.id === 0 diff --git a/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts b/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts index a08d6eb39533..f264052f8cb5 100644 --- a/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts +++ b/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts @@ -398,11 +398,11 @@ g.test('subgroup_size') t.selectDeviceOrSkipTestCase('subgroups' as GPUFeatureName); }) .fn(async t => { - interface SubgroupLimits extends GPUSupportedLimits { - minSubgroupSize: number; - maxSubgroupSize: number; + interface SubgroupProperties extends GPUAdapterInfo { + subgroupMinSize: number; + subgroupMaxSize: number; } - const { minSubgroupSize, maxSubgroupSize } = t.device.limits as SubgroupLimits; + const { subgroupMinSize, subgroupMaxSize } = t.device.adapterInfo as SubgroupProperties; const wgx = t.params.sizes[0]; const wgy = t.params.sizes[1]; @@ -518,8 +518,8 @@ fn main(@builtin(subgroup_size) size : u32, checkSubgroupSizeConsistency( sizesData, compareData, - minSubgroupSize, - maxSubgroupSize, + subgroupMinSize, + subgroupMaxSize, wgThreads ) ); diff --git a/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts b/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts index bb7f3b113e26..5d10a3dafe2b 100644 --- a/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts +++ b/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts @@ -1655,16 +1655,16 @@ g.test('subgroup_size') t.selectDeviceOrSkipTestCase('subgroups' as GPUFeatureName); }) .fn(async t => { - interface SubgroupLimits extends GPUSupportedLimits { - minSubgroupSize: number; - maxSubgroupSize: number; + interface SubgroupProperties extends GPUAdapterInfo { + subgroupMinSize: number; + subgroupMaxSize: number; } - const { minSubgroupSize, maxSubgroupSize } = t.device.limits as SubgroupLimits; + const { subgroupMinSize, subgroupMaxSize } = t.device.adapterInfo as SubgroupProperties; const fsShader = ` enable subgroups; -const maxSubgroupSize = ${kMaximiumSubgroupSize}u; +const subgroupMaxSize = ${kMaximiumSubgroupSize}u; const noError = ${kSubgroupShaderNoError}u; const width = ${t.params.size[0]}; @@ -1686,7 +1686,7 @@ fn fsMain( var subgroupSizeBallotedInvocations: u32 = 0u; var ballotedSubgroupSize: u32 = 0u; - for (var i: u32 = 0; i <= maxSubgroupSize; i++) { + for (var i: u32 = 0; i <= subgroupMaxSize; i++) { let ballotSubgroupSizeEqualI = countOneBits(subgroupBallot(sg_size == i)); let countSubgroupSizeEqualI = ballotSubgroupSizeEqualI.x + ballotSubgroupSizeEqualI.y + ballotSubgroupSizeEqualI.z + ballotSubgroupSizeEqualI.w; subgroupSizeBallotedInvocations += countSubgroupSizeEqualI; @@ -1716,8 +1716,8 @@ fn fsMain( return checkSubgroupSizeConsistency( data, t.params.format, - minSubgroupSize, - maxSubgroupSize, + subgroupMinSize, + subgroupMaxSize, t.params.size[0], t.params.size[1] ); @@ -1816,7 +1816,7 @@ enable subgroups; const width = ${t.params.size[0]}; const height = ${t.params.size[1]}; -const maxSubgroupSize = ${kMaximiumSubgroupSize}u; +const subgroupMaxSize = ${kMaximiumSubgroupSize}u; // A non-zero magic number indicating no expectation error, in order to prevent the // false no-error result from zero-initialization. const noError = ${kSubgroupShaderNoError}u; @@ -1830,8 +1830,8 @@ fn fsMain( var error: u32 = noError; - // Validate that reported subgroup size is no larger than maxSubgroupSize - if (sg_size > maxSubgroupSize) { + // Validate that reported subgroup size is no larger than subgroupMaxSize + if (sg_size > subgroupMaxSize) { error++; } @@ -1843,7 +1843,7 @@ fn fsMain( // Validate that each subgroup id is assigned to at most one active invocation // in the subgroup var countAssignedId: u32 = 0u; - for (var i: u32 = 0; i < maxSubgroupSize; i++) { + for (var i: u32 = 0; i < subgroupMaxSize; i++) { let ballotIdEqualsI = countOneBits(subgroupBallot(id == i)); let countInvocationIdEqualsI = ballotIdEqualsI.x + ballotIdEqualsI.y + ballotIdEqualsI.z + ballotIdEqualsI.w; // Validate an id assigned at most once