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
35import { 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+
558function 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';
92163export const { g, description } = makeLimitTestGroup ( limit ) ;
93164
94165g . 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