Skip to content

Commit c4cfd73

Browse files
authored
Add new builtins to maxInterStageShaderVariables test (#4554)
* Add new builtins to maxInterStageShaderVariables test primitive_index, subgroup_id, subgroup_size were missing from the test.
1 parent db3a77f commit c4cfd73

1 file changed

Lines changed: 139 additions & 60 deletions

File tree

Lines changed: 139 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,146 @@
1-
import { range } from '../../../../../common/util/util.js';
1+
import { Fixture } from '../../../../../common/framework/fixture.js';
2+
import { keysOf } from '../../../../../common/util/data_tables.js';
3+
import { hasFeature, range } from '../../../../../common/util/util.js';
24

35
import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
46

7+
const kFragmentInputTypes = {
8+
front_facing: 'bool',
9+
sample_index: 'u32',
10+
sample_mask: 'u32',
11+
primitive_index: 'u32',
12+
subgroup_invocation_id: 'u32',
13+
subgroup_size: 'u32',
14+
} as const;
15+
16+
const kFragmentInputs = keysOf(kFragmentInputTypes);
17+
18+
const kItemsThatCountAgainstLimit = ['point-list', ...kFragmentInputs] as const;
19+
20+
const kExtraItems = [
21+
'sample_mask_out', // special - see below
22+
] as const;
23+
24+
/**
25+
* Generates every combination of size elements from array
26+
* combinations([a, b, c], 2) generates [a, b], [a, c], [b, c]
27+
*/
28+
function* combinations<T>(
29+
arr: readonly T[],
30+
size: number,
31+
start = 0,
32+
path: T[] = []
33+
): Generator<T[]> {
34+
if (path.length === size) {
35+
yield [...path];
36+
return;
37+
}
38+
39+
for (let i = start; i < arr.length; i++) {
40+
path.push(arr[i]);
41+
yield* combinations(arr, size, i + 1, path);
42+
path.pop();
43+
}
44+
}
45+
46+
const kTestItems = [...kItemsThatCountAgainstLimit, ...kExtraItems] as const;
47+
const kTestItemCombinations = [
48+
[], // no builtins case
49+
...combinations(kTestItems, 1), // one builtin
50+
...combinations(kTestItems, 2), // 2 builtins
51+
...combinations(kTestItems, 3), // 3 builtins
52+
kTestItems, // all builtins case
53+
] as const;
54+
55+
const requiresSubgroupsFeature = (items: Set<(typeof kTestItems)[number]>) =>
56+
items.has('subgroup_invocation_id') || items.has('subgroup_size');
57+
558
function getPipelineDescriptor(
59+
t: Fixture,
660
device: GPUDevice,
761
testValue: number,
8-
pointList: boolean,
9-
frontFacing: boolean,
10-
sampleIndex: boolean,
11-
sampleMaskIn: boolean,
12-
sampleMaskOut: boolean
62+
items: Set<(typeof kTestItems)[number]>
1363
): GPURenderPipelineDescriptor {
14-
const vertexOutputDeductions = pointList ? 1 : 0;
15-
const fragmentInputDeductions = [frontFacing, sampleIndex, sampleMaskIn]
64+
const vertexOutputDeductions = items.has('point-list') ? 1 : 0;
65+
const usedFragInputs = [...items.values()].filter(p => p in kFragmentInputTypes);
66+
const fragmentInputDeductions = usedFragInputs
1667
.map(p => (p ? 1 : 0) as number)
17-
.reduce((acc, p) => acc + p);
68+
.reduce((acc, p) => acc + p, 0);
1869

19-
const vertexOutputVariables = testValue - vertexOutputDeductions;
20-
const fragmentInputVariables = testValue - fragmentInputDeductions;
21-
const numInterStageVariables = Math.min(vertexOutputVariables, fragmentInputVariables);
70+
t.debug(() => `device features: ${[...device.features].join(', ')}`);
71+
72+
const numVertexOutputVariables = testValue - vertexOutputDeductions;
73+
const numFragmentInputVariables = testValue - fragmentInputDeductions;
74+
const numInterStageVariables = Math.min(numVertexOutputVariables, numFragmentInputVariables);
2275

2376
const maxVertexOutputVariables =
2477
device.limits.maxInterStageShaderVariables - vertexOutputDeductions;
2578
const maxFragmentInputVariables =
2679
device.limits.maxInterStageShaderVariables - fragmentInputDeductions;
2780
const maxInterStageVariables = Math.min(maxVertexOutputVariables, maxFragmentInputVariables);
2881

29-
const varyings = `
30-
${range(numInterStageVariables, i => `@location(${i}) v4_${i}: vec4f,`).join('\n')}
31-
`;
82+
const fragInputs = usedFragInputs
83+
.map(
84+
(input, i) =>
85+
` @builtin(${input}) i_${i}: ${
86+
kFragmentInputTypes[input as keyof typeof kFragmentInputTypes]
87+
},`
88+
)
89+
.join('\n');
90+
91+
const varyings = `${range(
92+
numInterStageVariables,
93+
i => ` @location(${i}) v4_${i}: vec4f,`
94+
).join('\n')}`;
3295

3396
const code = `
3497
// test value : ${testValue}
35-
// maxInterStageShaderVariables : ${device.limits.maxInterStageShaderVariables}
36-
// num variables in vertex shader : ${vertexOutputVariables}${pointList ? ' + point-list' : ''}
37-
// num variables in fragment shader : ${fragmentInputVariables}${
38-
frontFacing ? ' + front-facing' : ''
39-
}${sampleIndex ? ' + sample_index' : ''}${sampleMaskIn ? ' + sample_mask' : ''}
98+
// maxInterStageShaderVariables : ${device.limits.maxInterStageShaderVariables}
99+
// num variables in vertex shader : ${numVertexOutputVariables}${
100+
items.has('point-list') ? ' + point-list' : ''
101+
}
102+
// num variables in fragment shader : ${numFragmentInputVariables} + ${usedFragInputs.join(
103+
' + '
104+
)}
40105
// maxInterStageVariables: : ${maxInterStageVariables}
41106
// num used inter stage variables : ${numInterStageVariables}
42107
108+
${items.has('primitive_index') ? 'enable primitive_index;' : ''}
109+
${requiresSubgroupsFeature(items) ? 'enable subgroups;' : ''}
110+
43111
struct VSOut {
44112
@builtin(position) p: vec4f,
45-
${varyings}
113+
${varyings}
46114
}
47115
struct FSIn {
48-
${frontFacing ? '@builtin(front_facing) frontFacing: bool,' : ''}
49-
${sampleIndex ? '@builtin(sample_index) sampleIndex: u32,' : ''}
50-
${sampleMaskIn ? '@builtin(sample_mask) sampleMask: u32,' : ''}
51-
${varyings}
116+
${fragInputs}
117+
${varyings}
52118
}
119+
53120
struct FSOut {
54121
@location(0) color: vec4f,
55-
${sampleMaskOut ? '@builtin(sample_mask) sampleMask: u32,' : ''}
122+
${items.has('sample_mask_out') ? '@builtin(sample_mask) sampleMask: u32,' : ''}
56123
}
124+
57125
@vertex fn vs() -> VSOut {
58126
var o: VSOut;
59127
o.p = vec4f(0);
60128
return o;
61129
}
130+
62131
@fragment fn fs(i: FSIn) -> FSOut {
63132
var o: FSOut;
133+
64134
o.color = vec4f(0);
65135
return o;
66136
}
67137
`;
138+
t.debug(code);
68139
const module = device.createShaderModule({ code });
69140
const pipelineDescriptor: GPURenderPipelineDescriptor = {
70141
layout: 'auto',
71142
primitive: {
72-
topology: pointList ? 'point-list' : 'triangle-list',
143+
topology: items.has('point-list') ? 'point-list' : 'triangle-list',
73144
},
74145
vertex: {
75146
module,
@@ -92,51 +163,59 @@ const limit = 'maxInterStageShaderVariables';
92163
export const { g, description } = makeLimitTestGroup(limit);
93164

94165
g.test('createRenderPipeline,at_over')
95-
.desc(`Test using at and over ${limit} limit in createRenderPipeline(Async)`)
166+
.desc(
167+
`
168+
Test using at and over ${limit} limit in createRenderPipeline(Async)
169+
170+
Note: We test combinations to make sure each entry is counted separately.
171+
and that implementations don't accidentally add only 1 to the count when
172+
2 or more builtins are used. We also include sample_mask as an output
173+
to make sure it does not count against the limit since it has the same
174+
name as sample_mask as an input.
175+
`
176+
)
96177
.params(
97-
kMaximumLimitBaseParams
98-
.combine('async', [false, true])
99-
.combine('pointList', [false, true])
100-
.combine('frontFacing', [false, true])
101-
.combine('sampleIndex', [false, true])
102-
.combine('sampleMaskIn', [false, true])
103-
.combine('sampleMaskOut', [false, true])
178+
kMaximumLimitBaseParams.combine('async', [false, true]).combine('items', kTestItemCombinations)
104179
)
105-
.beforeAllSubcases(t => {
180+
.fn(async t => {
181+
const { limitTest, testValueName, async, items: itemsAsArray } = t.params;
182+
const items = new Set(itemsAsArray);
183+
106184
if (t.isCompatibility) {
107185
t.skipIf(
108-
t.params.sampleMaskIn || t.params.sampleMaskOut,
186+
items.has('sample_mask') || items.has('sample_mask_out'),
109187
'sample_mask not supported in compatibility mode'
110188
);
111-
t.skipIf(t.params.sampleIndex, 'sample_index not supported in compatibility mode');
189+
t.skipIf(items.has('sample_index'), 'sample_index not supported in compatibility mode');
112190
}
113-
})
114-
.fn(async t => {
115-
const {
116-
limitTest,
117-
testValueName,
118-
async,
119-
pointList,
120-
frontFacing,
121-
sampleIndex,
122-
sampleMaskIn,
123-
sampleMaskOut,
124-
} = t.params;
191+
192+
const features: GPUFeatureName[] = [];
193+
194+
if (items.has('primitive_index')) {
195+
if (hasFeature(t.adapter.features, 'primitive-index')) {
196+
features.push('primitive-index');
197+
} else {
198+
t.skip('primitive_index requires primitive-index feature');
199+
}
200+
}
201+
202+
if (requiresSubgroupsFeature(items)) {
203+
if (hasFeature(t.adapter.features, 'subgroups')) {
204+
features.push('subgroups');
205+
} else {
206+
t.skip('subgroup_invocation_id or subgroup_size requires subgroups feature');
207+
}
208+
}
209+
125210
await t.testDeviceWithRequestedMaximumLimits(
126211
limitTest,
127212
testValueName,
128213
async ({ device, testValue, shouldError }) => {
129-
const pipelineDescriptor = getPipelineDescriptor(
130-
device,
131-
testValue,
132-
pointList,
133-
frontFacing,
134-
sampleIndex,
135-
sampleMaskIn,
136-
sampleMaskOut
137-
);
214+
const pipelineDescriptor = getPipelineDescriptor(t, device, testValue, items);
138215

139216
await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
140-
}
217+
},
218+
undefined,
219+
features
141220
);
142221
});

0 commit comments

Comments
 (0)