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+ function getCombinations < T > ( arr : readonly T [ ] , sizes : number [ ] ) : T [ ] [ ] {
25+ const combos : T [ ] [ ] = [ ] ;
26+
27+ function backtrack ( start : number , path : T [ ] , size : number ) {
28+ if ( path . length === size ) {
29+ combos . push ( path . slice ( ) ) ;
30+ return ;
31+ }
32+
33+ for ( let i = start ; i < arr . length ; i ++ ) {
34+ path . push ( arr [ i ] ) ;
35+ backtrack ( i + 1 , path , size ) ;
36+ path . pop ( ) ;
37+ }
38+ }
39+
40+ sizes . forEach ( size => {
41+ backtrack ( 0 , [ ] , size ) ;
42+ } ) ;
43+
44+ return combos ;
45+ }
46+
47+ const kTestItems = [ ...kItemsThatCountAgainstLimit , ...kExtraItems ] as const ;
48+ const kTestItemCombinations = [
49+ [ ] , // no builtins case
50+ ...getCombinations ( kTestItems , [ 1 , 2 , 3 ] ) ,
51+ kTestItems , // all case
52+ ] as const ;
53+
54+ const requiresSubgroupsFeature = ( items : Set < ( typeof kTestItems ) [ number ] > ) =>
55+ items . has ( 'subgroup_invocation_id' ) || items . has ( 'subgroup_size' ) ;
56+
557function getPipelineDescriptor (
58+ t : Fixture ,
659 device : GPUDevice ,
760 testValue : number ,
8- pointList : boolean ,
9- frontFacing : boolean ,
10- sampleIndex : boolean ,
11- sampleMaskIn : boolean ,
12- sampleMaskOut : boolean
61+ items : Set < ( typeof kTestItems ) [ number ] >
1362) : GPURenderPipelineDescriptor {
14- const vertexOutputDeductions = pointList ? 1 : 0 ;
15- const fragmentInputDeductions = [ frontFacing , sampleIndex , sampleMaskIn ]
63+ const vertexOutputDeductions = items . has ( 'point-list' ) ? 1 : 0 ;
64+ const usedFragInputs = [ ...items . values ( ) ] . filter ( p => p in kFragmentInputTypes ) ;
65+ const fragmentInputDeductions = usedFragInputs
1666 . map ( p => ( p ? 1 : 0 ) as number )
17- . reduce ( ( acc , p ) => acc + p ) ;
67+ . reduce ( ( acc , p ) => acc + p , 0 ) ;
1868
19- const vertexOutputVariables = testValue - vertexOutputDeductions ;
20- const fragmentInputVariables = testValue - fragmentInputDeductions ;
21- const numInterStageVariables = Math . min ( vertexOutputVariables , fragmentInputVariables ) ;
69+ t . debug ( ( ) => `device features: ${ [ ...device . features ] . join ( ', ' ) } ` ) ;
70+
71+ const numVertexOutputVariables = testValue - vertexOutputDeductions ;
72+ const numFragmentInputVariables = testValue - fragmentInputDeductions ;
73+ const numInterStageVariables = Math . min ( numVertexOutputVariables , numFragmentInputVariables ) ;
2274
2375 const maxVertexOutputVariables =
2476 device . limits . maxInterStageShaderVariables - vertexOutputDeductions ;
2577 const maxFragmentInputVariables =
2678 device . limits . maxInterStageShaderVariables - fragmentInputDeductions ;
2779 const maxInterStageVariables = Math . min ( maxVertexOutputVariables , maxFragmentInputVariables ) ;
2880
29- const varyings = `
30- ${ range ( numInterStageVariables , i => `@location(${ i } ) v4_${ i } : vec4f,` ) . join ( '\n' ) }
31- ` ;
81+ const fragInputs = usedFragInputs
82+ . map (
83+ ( input , i ) =>
84+ ` @builtin(${ input } ) i_${ i } : ${
85+ kFragmentInputTypes [ input as keyof typeof kFragmentInputTypes ]
86+ } ,`
87+ )
88+ . join ( '\n' ) ;
89+
90+ const varyings = `${ range (
91+ numInterStageVariables ,
92+ i => ` @location(${ i } ) v4_${ i } : vec4f,`
93+ ) . join ( '\n' ) } `;
3294
3395 const code = `
3496 // 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' : '' }
97+ // maxInterStageShaderVariables : ${ device . limits . maxInterStageShaderVariables }
98+ // num variables in vertex shader : ${ numVertexOutputVariables } ${
99+ items . has ( 'point-list' ) ? ' + point-list' : ''
100+ }
101+ // num variables in fragment shader : ${ numFragmentInputVariables } + ${ usedFragInputs . join (
102+ ' + '
103+ ) }
40104 // maxInterStageVariables: : ${ maxInterStageVariables }
41105 // num used inter stage variables : ${ numInterStageVariables }
42106
107+ ${ items . has ( 'primitive_index' ) ? 'enable primitive_index;' : '' }
108+ ${ requiresSubgroupsFeature ( items ) ? 'enable subgroups;' : '' }
109+
43110 struct VSOut {
44111 @builtin(position) p: vec4f,
45- ${ varyings }
112+ ${ varyings }
46113 }
47114 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 }
115+ ${ fragInputs }
116+ ${ varyings }
52117 }
118+
53119 struct FSOut {
54120 @location(0) color: vec4f,
55- ${ sampleMaskOut ? '@builtin(sample_mask) sampleMask: u32,' : '' }
121+ ${ items . has ( 'sample_mask_out' ) ? '@builtin(sample_mask) sampleMask: u32,' : '' }
56122 }
123+
57124 @vertex fn vs() -> VSOut {
58125 var o: VSOut;
59126 o.p = vec4f(0);
60127 return o;
61128 }
129+
62130 @fragment fn fs(i: FSIn) -> FSOut {
63131 var o: FSOut;
132+
64133 o.color = vec4f(0);
65134 return o;
66135 }
67136 ` ;
137+ t . debug ( code ) ;
68138 const module = device . createShaderModule ( { code } ) ;
69139 const pipelineDescriptor : GPURenderPipelineDescriptor = {
70140 layout : 'auto' ,
71141 primitive : {
72- topology : pointList ? 'point-list' : 'triangle-list' ,
142+ topology : items . has ( 'point-list' ) ? 'point-list' : 'triangle-list' ,
73143 } ,
74144 vertex : {
75145 module,
@@ -92,51 +162,59 @@ const limit = 'maxInterStageShaderVariables';
92162export const { g, description } = makeLimitTestGroup ( limit ) ;
93163
94164g . test ( 'createRenderPipeline,at_over' )
95- . desc ( `Test using at and over ${ limit } limit in createRenderPipeline(Async)` )
165+ . desc (
166+ `
167+ Test using at and over ${ limit } limit in createRenderPipeline(Async)
168+
169+ Note: We test combinations to make sure each entry is counted separately.
170+ and that implementations don't accidentally add only 1 to the count when
171+ 2 or more builtins are used. We also include sample_mask as an output
172+ to make sure it does not count against the limit since it has the same
173+ name as sample_mask as an input.
174+ `
175+ )
96176 . 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 ] )
177+ kMaximumLimitBaseParams . combine ( 'async' , [ false , true ] ) . combine ( 'items' , kTestItemCombinations )
104178 )
105- . beforeAllSubcases ( t => {
179+ . fn ( async t => {
180+ const { limitTest, testValueName, async, items : itemsAsArray } = t . params ;
181+ const items = new Set ( itemsAsArray ) ;
182+
106183 if ( t . isCompatibility ) {
107184 t . skipIf (
108- t . params . sampleMaskIn || t . params . sampleMaskOut ,
185+ items . has ( 'sample_mask' ) || items . has ( 'sample_mask_out' ) ,
109186 'sample_mask not supported in compatibility mode'
110187 ) ;
111- t . skipIf ( t . params . sampleIndex , 'sample_index not supported in compatibility mode' ) ;
188+ t . skipIf ( items . has ( 'sample_index' ) , 'sample_index not supported in compatibility mode' ) ;
112189 }
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 ;
190+
191+ const features : GPUFeatureName [ ] = [ ] ;
192+
193+ if ( items . has ( 'primitive_index' ) ) {
194+ if ( hasFeature ( t . adapter . features , 'primitive-index' ) ) {
195+ features . push ( 'primitive-index' ) ;
196+ } else {
197+ t . skip ( 'primitive_index requires primitive-index feature' ) ;
198+ }
199+ }
200+
201+ if ( requiresSubgroupsFeature ( items ) ) {
202+ if ( hasFeature ( t . adapter . features , 'subgroups' ) ) {
203+ features . push ( 'subgroups' ) ;
204+ } else {
205+ t . skip ( 'subgroup_invocation_id or subgroup_size requires subgroups feature' ) ;
206+ }
207+ }
208+
125209 await t . testDeviceWithRequestedMaximumLimits (
126210 limitTest ,
127211 testValueName ,
128212 async ( { device, testValue, shouldError } ) => {
129- const pipelineDescriptor = getPipelineDescriptor (
130- device ,
131- testValue ,
132- pointList ,
133- frontFacing ,
134- sampleIndex ,
135- sampleMaskIn ,
136- sampleMaskOut
137- ) ;
213+ const pipelineDescriptor = getPipelineDescriptor ( t , device , testValue , items ) ;
138214
139215 await t . testCreateRenderPipeline ( pipelineDescriptor , async , shouldError ) ;
140- }
216+ } ,
217+ undefined ,
218+ features
141219 ) ;
142220 } ) ;
0 commit comments