@@ -16,26 +16,41 @@ g.test('sample_texture_combos')
1616 . desc (
1717 `
1818Test that you can use the maximum number of textures with the maximum number of samplers.
19+ and the maximum number of storage textures.
1920
20- The test works by making the maximum number of texture+sampler combos.
21- Each texture is [maxSamplersPerShaderStage, 1] in size where each texel is [textureId, samplerId]
22- A function "useCombo<StageNum>(comboId)" is made that returns stage[stageNum].combo[comboId].texel[id, 0]
23- or to put it another way, it returns the nth texel from the nth combo for that stage.
21+ The test works by making the maximum number of texture+sampler combos and the max storage
22+ textures per stage. Each texture is [maxSamplersPerShaderStage + maxStorageTexturesInStage, 1]
23+ in size and each texel is [textureId, samplerId]. A function "useCombo<StageNum>(comboId)" is
24+ made that returns stage[stageNum].combo[comboId].texel[id, 0] or to put it another way, it
25+ returns the nth texel from the nth combo for that stage.
2426
25- These are read in both the vertex shader and fragment shader and written to a [maxSamplerPerShaderStage, 2]
26- texture where the top row is the values from the vertex shader and the bottom row from the fragment shader.
27+ These are read in both the vertex shader and fragment shader and written to a
28+ [maxSamplerPerShaderStage + maxStorageTexturesInStage, 2] texture where the top row is the
29+ values from the vertex shader and the bottom row from the fragment shader.
2730
2831The result should be a texture that has a value in each texel unique to a particular combo
32+ or storage texture.
2933`
3034 )
3135 . fn ( t => {
3236 const { device } = t ;
33- const { maxSampledTexturesPerShaderStage, maxSamplersPerShaderStage, maxBindingsPerBindGroup } =
34- device . limits ;
37+ const {
38+ maxSampledTexturesPerShaderStage,
39+ maxSamplersPerShaderStage,
40+ maxBindingsPerBindGroup,
41+ maxStorageTexturesInVertexStage,
42+ maxStorageTexturesInFragmentStage,
43+ maxStorageTexturesPerShaderStage,
44+ } = device . limits ;
3545
3646 assert ( maxSampledTexturesPerShaderStage < 0xfffe ) ;
3747 assert ( maxSamplersPerShaderStage < 0xfffe ) ;
3848
49+ const numStorageTexturesInVertexStage =
50+ maxStorageTexturesInVertexStage ?? maxStorageTexturesPerShaderStage ;
51+ const numStorageTexturesInFragmentStage =
52+ maxStorageTexturesInFragmentStage ?? maxStorageTexturesPerShaderStage ;
53+
3954 const maxTestableCombosPerStage = t . isCompatibility
4055 ? Math . min ( maxSampledTexturesPerShaderStage , maxSamplersPerShaderStage )
4156 : maxSampledTexturesPerShaderStage * maxSamplersPerShaderStage ;
@@ -44,12 +59,17 @@ The result should be a texture that has a value in each texel unique to a partic
4459 const declarationLines : string [ ] = [ ] ;
4560 const groups : GPUBindGroupEntry [ ] [ ] = [ [ ] ] ;
4661 const layouts : GPUBindGroupLayoutEntry [ ] [ ] = [ [ ] ] ;
47- const textureIds = new Set < string > ( ) ;
62+ const textureIdToTexelValue = new Map < string , number > ( ) ;
4863 const samplerIds = new Set < string > ( ) ;
4964 // per stage, per texel, each texel has 2 numbers, the texture id, and sampler id
5065 const expected : number [ ] [ ] [ ] = [ [ ] , [ ] ] ;
5166
52- function addResource ( stage : number , resourceId : string , resource : GPUTextureView | GPUSampler ) {
67+ function addResource (
68+ stage : number ,
69+ resourceId : string ,
70+ resource : GPUTextureView | GPUSampler ,
71+ storageTexture ?: boolean
72+ ) {
5373 let bindGroupEntries = groups [ groups . length - 1 ] ;
5474 let bindGroupLayoutEntries = layouts [ groups . length - 1 ] ;
5575 if ( bindGroupEntries . length === maxBindingsPerBindGroup ) {
@@ -58,7 +78,12 @@ The result should be a texture that has a value in each texel unique to a partic
5878 groups . push ( bindGroupEntries ) ;
5979 layouts . push ( bindGroupLayoutEntries ) ;
6080 }
61- const resourceType = resource instanceof GPUSampler ? 'sampler' : 'texture_2d<f32>' ;
81+ const resourceType =
82+ resource instanceof GPUSampler
83+ ? 'sampler'
84+ : storageTexture
85+ ? 'texture_storage_2d<rgba8unorm, read>'
86+ : 'texture_2d<f32>' ;
6287 const binding = bindGroupEntries . length ;
6388 declarationLines . push (
6489 ` @group(${ groups . length - 1 } ) @binding(${ binding } ) var ${ resourceId } : ${ resourceType } ;`
@@ -74,37 +99,54 @@ The result should be a texture that has a value in each texel unique to a partic
7499 ? {
75100 sampler : { } ,
76101 }
102+ : storageTexture
103+ ? {
104+ storageTexture : {
105+ access : 'read-only' ,
106+ format : 'rgba8unorm' ,
107+ } ,
108+ }
77109 : {
78110 texture : { } ,
79111 } ) ,
80112 } ) ;
81113 }
82114
83- function addTexture ( stage : number , textureNum : number ) {
115+ const width =
116+ maxSamplersPerShaderStage +
117+ Math . max ( numStorageTexturesInVertexStage , numStorageTexturesInFragmentStage ) ;
118+ t . debug ( `width: ${ width } ` ) ;
119+
120+ function addTexture ( stage : number , textureNum : number , storageTexture : boolean ) {
84121 const textureId = `tex${ stage } _${ textureNum } ` ;
85- if ( ! textureIds . has ( textureId ) ) {
86- textureIds . add ( textureId ) ;
122+ let texelValue = textureIdToTexelValue . get ( textureId ) ;
123+ if ( texelValue === undefined ) {
124+ texelValue = textures . length + 1 ;
125+ textureIdToTexelValue . set ( textureId , texelValue ) ;
87126 const texture = t . createTextureTracked ( {
88127 format : 'rgba8unorm' ,
89- size : [ maxSamplersPerShaderStage , 1 ] ,
90- usage : GPUTextureUsage . TEXTURE_BINDING | GPUTextureUsage . COPY_DST ,
128+ size : [ width , 1 ] ,
129+ usage :
130+ GPUTextureUsage . STORAGE_BINDING |
131+ GPUTextureUsage . TEXTURE_BINDING |
132+ GPUTextureUsage . COPY_DST ,
91133 } ) ;
92134 textures . push ( texture ) ;
93135 // Encode an rgba8unorm texture with rg16uint data where each texel is
94- // [(textureId + 1) | (stage << 15), {samplerId + 1}]
136+ // [texelValue | (stage << 15), {samplerId + 1}]
95137 // The +1 is to avoid 0.
96- const data = new Uint16Array ( maxSamplersPerShaderStage * 2 ) ;
97- const rg = ( textureNum + 1 ) | ( stage << 15 ) ;
98- for ( let x = 0 ; x < maxSamplersPerShaderStage ; ++ x ) {
138+ const data = new Uint16Array ( width * 2 ) ;
139+ const rg = texelValue | ( stage << 15 ) ;
140+ for ( let x = 0 ; x < width ; ++ x ) {
99141 const offset = x * 2 ;
100- const samplerNum = x + 1 ;
142+ const samplerNum = ( x % maxSamplersPerShaderStage ) + 1 ;
101143 data [ offset + 0 ] = rg ;
102- data [ offset + 1 ] = samplerNum ;
144+ data [ offset + 1 ] = storageTexture ? 0 : samplerNum ;
103145 }
104- device . queue . writeTexture ( { texture } , data , { } , [ maxSamplersPerShaderStage ] ) ;
105- addResource ( stage , textureId , texture . createView ( ) ) ;
146+ device . queue . writeTexture ( { texture } , data , { } , [ width ] ) ;
147+ addResource ( stage , textureId , texture . createView ( ) , storageTexture ) ;
106148 }
107- return textureId ;
149+ return { textureId, texelValue } ;
108150 }
109151
110152 const kAddressModes = [ 'repeat' , 'clamp-to-edge' , 'mirror-repeat' ] as const ;
@@ -135,32 +177,52 @@ The result should be a texture that has a value in each texel unique to a partic
135177 return samplerId ;
136178 }
137179
180+ const numStorageTexturesInStage = [
181+ numStorageTexturesInVertexStage ,
182+ numStorageTexturesInFragmentStage ,
183+ ] ;
184+
138185 // Note: We are storing textureId, samplerId in the texture. That suggests we could use rgba32uint
139186 // texture but we can't do that because we want to be able to set the samplers to linear.
140187 // Similarly we can't use rgba32float since they're not filterable by default.
141188 // So, we encode via rgba8unorm where rg is a 16bit textureId and ba is a 16bit samplerId
142189 const code = `
143190 // maxTestableCombosPerStage: ${ maxTestableCombosPerStage }
191+ // numStorageTexturesPerVertexStage: ${ numStorageTexturesInVertexStage }
192+ // numStorageTexturesPerFragmentStage: ${ numStorageTexturesInFragmentStage }
144193
145194 fn sample(t: texture_2d<f32>, s: sampler, validId: u32, currentId: u32, c: vec4f) -> vec4f {
146195 let size = textureDimensions(t, 0);
147- let uv = vec2f((f32(currentId) + 0.5) / f32(size.x), 0.5);
196+ let uv = vec2f((f32(currentId % ${ maxSamplersPerShaderStage } ) + 0.5) / f32(size.x), 0.5);
148197 let v = textureSampleLevel(t, s, uv, 0);
149198 return select(c, v, currentId == validId);
150199 }
151200
201+ fn load(t: texture_storage_2d<rgba8unorm, read>, validId: u32, currentId: u32, c: vec4f) -> vec4f {
202+ let size = textureDimensions(t);
203+ let uv = vec2u(currentId % size.x, 0);
204+ let v = textureLoad(t, uv);
205+ return select(c, v, currentId == validId);
206+ }
207+
152208 ${ range (
153209 2 ,
154210 stage => `
155211 fn useCombos${ stage } (id: u32) -> vec4f {
156212 var c: vec4f;
157213${ range ( maxTestableCombosPerStage , i => {
158214 const texNum = ( i / maxSamplersPerShaderStage ) | 0 ;
159- const textureId = addTexture ( stage , texNum ) ;
215+ const { textureId, texelValue } = addTexture ( stage , texNum , false ) ;
160216 const smpNum = i % maxSamplersPerShaderStage ;
161217 const samplerId = addSampler ( stage , smpNum ) ;
162- expected [ stage ] . push ( [ ( texNum + 1 ) | ( stage << 15 ) , smpNum + 1 ] ) ;
218+ expected [ stage ] . push ( [ texelValue | ( stage << 15 ) , smpNum + 1 ] ) ;
163219 return ` c = sample(${ textureId } , ${ samplerId } , ${ i } , id, c);` ;
220+ } ) . join ( '\n' ) }
221+ ${ range ( numStorageTexturesInStage [ stage ] , i => {
222+ const texNum = textures . length ;
223+ const { textureId, texelValue } = addTexture ( stage , texNum , true ) ;
224+ expected [ stage ] . push ( [ texelValue | ( stage << 15 ) , 0 ] ) ;
225+ return ` c = load(${ textureId } , ${ i + maxTestableCombosPerStage } , id, c);` ;
164226} ) . join ( '\n' ) }
165227 return c;
166228 }
@@ -192,6 +254,8 @@ ${declarationLines.join('\n')}
192254 }
193255 ` ;
194256
257+ t . debug ( code ) ;
258+
195259 const module = device . createShaderModule ( { code } ) ;
196260 const bindGroupLayouts = layouts . map ( entries => device . createBindGroupLayout ( { entries } ) ) ;
197261
@@ -214,9 +278,14 @@ ${declarationLines.join('\n')}
214278 } )
215279 ) ;
216280
281+ const numAcross =
282+ maxTestableCombosPerStage +
283+ numStorageTexturesInVertexStage +
284+ numStorageTexturesInFragmentStage ;
285+
217286 const renderTarget = t . createTextureTracked ( {
218287 format : 'rg16uint' ,
219- size : [ maxTestableCombosPerStage , 2 ] ,
288+ size : [ numAcross , 2 ] ,
220289 usage : GPUTextureUsage . RENDER_ATTACHMENT | GPUTextureUsage . COPY_SRC ,
221290 } ) ;
222291 textures . push ( renderTarget ) ;
@@ -234,7 +303,7 @@ ${declarationLines.join('\n')}
234303 pass . setPipeline ( pipeline ) ;
235304 bindGroups . forEach ( ( bindGroup , i ) => pass . setBindGroup ( i , bindGroup ) ) ;
236305 for ( let y = 0 ; y < 2 ; ++ y ) {
237- for ( let x = 0 ; x < maxTestableCombosPerStage ; ++ x ) {
306+ for ( let x = 0 ; x < numAcross ; ++ x ) {
238307 pass . setViewport ( x , y , 1 , 1 , 0 , 1 ) ;
239308 pass . draw ( 1 , 1 , 0 , x ) ;
240309 }
@@ -243,10 +312,10 @@ ${declarationLines.join('\n')}
243312
244313 device . queue . submit ( [ encoder . finish ( ) ] ) ;
245314
246- const expectedData = new Uint16Array ( maxTestableCombosPerStage * 2 * 2 ) ;
315+ const expectedData = new Uint16Array ( numAcross * 2 * 2 ) ;
247316 for ( let stage = 0 ; stage < 2 ; ++ stage ) {
248317 expected [ stage ] . forEach ( ( [ tid , sid ] , i ) => {
249- const offset = ( maxTestableCombosPerStage * stage + i ) * 2 ;
318+ const offset = ( numAcross * stage + i ) * 2 ;
250319 expectedData [ offset + 0 ] = tid ;
251320 expectedData [ offset + 1 ] = sid ;
252321 } ) ;
@@ -256,14 +325,14 @@ ${declarationLines.join('\n')}
256325 'rg16uint' ,
257326 new Uint8Array ( expectedData . buffer ) ,
258327 {
259- bytesPerRow : maxTestableCombosPerStage * 4 ,
328+ bytesPerRow : numAcross * 4 ,
260329 rowsPerImage : 2 ,
261330 subrectOrigin : [ 0 , 0 , 0 ] ,
262- subrectSize : [ maxTestableCombosPerStage , 2 ] ,
331+ subrectSize : [ numAcross , 2 ] ,
263332 }
264333 ) ;
265334
266- const size = [ maxSamplersPerShaderStage , 2 ] ;
335+ const size = [ numAcross , 2 ] ;
267336 t . expectTexelViewComparisonIsOkInTexture ( { texture : renderTarget } , expTexelView , size ) ;
268337
269338 textures . forEach ( texture => texture . destroy ( ) ) ;
0 commit comments