@@ -11,19 +11,20 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
1111/// Pixel-format registration for composite session I/O.
1212/// </summary>
1313/// <remarks>
14- /// The map defined by <see cref="CreateCompositePixelHandlers"/> is intentionally explicit and only
15- /// includes one-to-one format mappings where the GPU texture format can round-trip the pixel payload
16- /// without channel swizzle or custom conversion logic.
17- /// Only formats that support <c>storage</c> texture binding (required by the compute compositor)
18- /// are included. Formats that lack storage support are omitted and fall back to the CPU backend.
14+ /// <see cref="CompositeRegistrations"/> is intentionally explicit and only includes one-to-one format mappings
15+ /// where the GPU texture format can round-trip the pixel payload without channel swizzle or custom conversion logic.
16+ /// Only formats that support <c>storage</c> texture binding (required by the compute compositor) are included.
17+ /// Formats that lack storage support are omitted and fall back to the CPU backend.
1918/// </remarks>
2019public sealed partial class WebGPUDrawingBackend
2120{
22- private static readonly Lazy < Dictionary < TextureFormat , TextureSampleType > > CompositeTextureSampleTypes =
23- new ( CreateCompositeTextureSampleTypes ) ;
24-
25- private static readonly Lazy < Dictionary < TextureFormat , CompositeTextureShaderTraits > > CompositeTextureShaderTraitsMap =
26- new ( CreateCompositeTextureShaderTraits ) ;
21+ private static readonly CompositePixelRegistration [ ] CompositeRegistrations =
22+ [
23+ CompositePixelRegistration . Create < NormalizedByte4 > ( TextureFormat . Rgba8Snorm , TextureSampleType . Float , new ( "rgba8snorm" , CompositeTextureEncodingKind . Snorm ) ) ,
24+ CompositePixelRegistration . Create < HalfVector4 > ( TextureFormat . Rgba16float , TextureSampleType . Float , new ( "rgba16float" , CompositeTextureEncodingKind . Float ) ) ,
25+ CompositePixelRegistration . Create < Rgba32 > ( TextureFormat . Rgba8Unorm , TextureSampleType . Float , new ( "rgba8unorm" , CompositeTextureEncodingKind . Float ) ) ,
26+ CompositePixelRegistration . Create < Bgra32 > ( TextureFormat . Bgra8Unorm , TextureSampleType . Float , new ( "bgra8unorm" , CompositeTextureEncodingKind . Float ) , FeatureName . Bgra8UnormStorage ) ,
27+ ] ;
2728
2829 /// <summary>
2930 /// Describes how one registered composite texture format encodes channel values in shader space.
@@ -37,37 +38,34 @@ internal enum CompositeTextureEncodingKind
3738 Sint16
3839 }
3940
40- /// <summary>
41- /// Builds the static registration table that maps <see cref="IPixel{TSelf}"/> implementations to
42- /// compatible WebGPU storage/sampling formats.
43- /// </summary>
44- /// <returns>The registration map used during flush dispatch.</returns>
45- private static Dictionary < Type , CompositePixelRegistration > CreateCompositePixelHandlers ( ) =>
46-
47- // Only formats with native or feature-gated storage binding support.
48- new ( )
41+ private static bool TryFind ( Type pixelType , out CompositePixelRegistration registration )
42+ {
43+ foreach ( CompositePixelRegistration r in CompositeRegistrations )
4944 {
50- [ typeof ( NormalizedByte4 ) ] = CompositePixelRegistration . Create < NormalizedByte4 > ( TextureFormat . Rgba8Snorm , TextureSampleType . Float ) ,
51-
52- [ typeof ( HalfVector4 ) ] = CompositePixelRegistration . Create < HalfVector4 > ( TextureFormat . Rgba16float , TextureSampleType . Float ) ,
45+ if ( r . PixelType == pixelType )
46+ {
47+ registration = r ;
48+ return true ;
49+ }
50+ }
5351
54- [ typeof ( Rgba32 ) ] = CompositePixelRegistration . Create < Rgba32 > ( TextureFormat . Rgba8Unorm , TextureSampleType . Float ) ,
55- [ typeof ( Bgra32 ) ] = CompositePixelRegistration . Create < Bgra32 > ( TextureFormat . Bgra8Unorm , TextureSampleType . Float , FeatureName . Bgra8UnormStorage )
56- } ;
52+ registration = default ;
53+ return false ;
54+ }
5755
58- /// <summary>
59- /// Builds the sampled-texture-type lookup keyed by the explicit composite format registrations.
60- /// </summary>
61- /// <returns>The lookup used by shader/bind-group setup.</returns>
62- private static Dictionary < TextureFormat , TextureSampleType > CreateCompositeTextureSampleTypes ( )
56+ private static bool TryFind ( TextureFormat textureFormat , out CompositePixelRegistration registration )
6357 {
64- Dictionary < TextureFormat , TextureSampleType > sampleTypes = [ ] ;
65- foreach ( CompositePixelRegistration registration in CompositePixelHandlers . Values )
58+ foreach ( CompositePixelRegistration r in CompositeRegistrations )
6659 {
67- sampleTypes [ registration . TextureFormat ] = registration . SampleType ;
60+ if ( r . TextureFormat == textureFormat )
61+ {
62+ registration = r ;
63+ return true ;
64+ }
6865 }
6966
70- return sampleTypes ;
67+ registration = default ;
68+ return false ;
7169 }
7270
7371 /// <summary>
@@ -82,13 +80,13 @@ private static Dictionary<TextureFormat, TextureSampleType> CreateCompositeTextu
8280 internal static bool TryGetCompositeTextureFormat < TPixel > ( out WebGPUTextureFormatId formatId )
8381 where TPixel : unmanaged, IPixel < TPixel >
8482 {
85- if ( ! CompositePixelHandlers . TryGetValue ( typeof ( TPixel ) , out CompositePixelRegistration registration ) )
83+ if ( ! TryFind ( typeof ( TPixel ) , out CompositePixelRegistration r ) )
8684 {
8785 formatId = default ;
8886 return false ;
8987 }
9088
91- formatId = WebGPUTextureFormatMapper . FromSilk ( registration . TextureFormat ) ;
89+ formatId = WebGPUTextureFormatMapper . FromSilk ( r . TextureFormat ) ;
9290 return true ;
9391 }
9492
@@ -109,96 +107,66 @@ internal static bool TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureForma
109107 internal static bool TryGetCompositeTextureFormat < TPixel > ( out WebGPUTextureFormatId formatId , out FeatureName requiredFeature )
110108 where TPixel : unmanaged, IPixel < TPixel >
111109 {
112- if ( ! CompositePixelHandlers . TryGetValue ( typeof ( TPixel ) , out CompositePixelRegistration registration ) )
110+ if ( ! TryFind ( typeof ( TPixel ) , out CompositePixelRegistration r ) )
113111 {
114112 formatId = default ;
115113 requiredFeature = FeatureName . Undefined ;
116114 return false ;
117115 }
118116
119- formatId = WebGPUTextureFormatMapper . FromSilk ( registration . TextureFormat ) ;
120- requiredFeature = registration . RequiredFeature ;
117+ formatId = WebGPUTextureFormatMapper . FromSilk ( r . TextureFormat ) ;
118+ requiredFeature = r . RequiredFeature ;
121119 return true ;
122120 }
123121
124122 /// <summary>
125- /// Resolves the unmanaged size in bytes of a registered composite pixel type .
123+ /// Resolves the sampled texture type for a registered composite texture format .
126124 /// </summary>
127- /// <typeparam name="TPixel ">The requested pixel type .</typeparam >
128- /// <param name="pixelSizeInBytes ">Receives the unmanaged pixel size in bytes on success.</param>
125+ /// <param name="textureFormat ">The WebGPU texture format .</param >
126+ /// <param name="sampleType ">Receives the sampled texture type on success.</param>
129127 /// <returns>
130- /// <see langword="true"/> when the pixel type has a registered GPU format mapping; otherwise <see langword="false"/>.
128+ /// <see langword="true"/> when the format is one of the explicitly registered composite formats;
129+ /// otherwise <see langword="false"/>.
131130 /// </returns>
132131 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
133- internal static bool TryGetCompositePixelSize < TPixel > ( out int pixelSizeInBytes )
134- where TPixel : unmanaged, IPixel < TPixel >
132+ internal static bool TryGetCompositeTextureSampleType ( TextureFormat textureFormat , out TextureSampleType sampleType )
135133 {
136- if ( ! CompositePixelHandlers . TryGetValue ( typeof ( TPixel ) , out CompositePixelRegistration registration ) )
134+ if ( ! TryFind ( textureFormat , out CompositePixelRegistration r ) )
137135 {
138- pixelSizeInBytes = 0 ;
136+ sampleType = default ;
139137 return false ;
140138 }
141139
142- pixelSizeInBytes = registration . PixelSizeInBytes ;
140+ sampleType = r . SampleType ;
143141 return true ;
144142 }
145143
146- /// <summary>
147- /// Resolves the sampled texture type for a registered composite texture format.
148- /// </summary>
149- /// <param name="textureFormat">The WebGPU texture format.</param>
150- /// <param name="sampleType">Receives the sampled texture type on success.</param>
151- /// <returns>
152- /// <see langword="true"/> when the format is one of the explicitly registered composite formats;
153- /// otherwise <see langword="false"/>.
154- /// </returns>
155- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
156- internal static bool TryGetCompositeTextureSampleType ( TextureFormat textureFormat , out TextureSampleType sampleType )
157- => CompositeTextureSampleTypes . Value . TryGetValue ( textureFormat , out sampleType ) ;
158-
159144 /// <summary>
160145 /// Resolves the shader-side read/write traits for a registered composite texture format.
161146 /// </summary>
162147 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
163148 internal static bool TryGetCompositeTextureShaderTraits ( TextureFormat textureFormat , out CompositeTextureShaderTraits traits )
164- => CompositeTextureShaderTraitsMap . Value . TryGetValue ( textureFormat , out traits ) ;
165-
166- /// <summary>
167- /// Builds the format-to-shader-traits lookup used when specializing composition shaders.
168- /// </summary>
169- private static Dictionary < TextureFormat , CompositeTextureShaderTraits > CreateCompositeTextureShaderTraits ( )
170- => new ( )
149+ {
150+ if ( ! TryFind ( textureFormat , out CompositePixelRegistration r ) )
171151 {
172- [ TextureFormat . Rgba8Snorm ] = new CompositeTextureShaderTraits ( "rgba8snorm" , "f32" , TextureSampleType . Float , CompositeTextureEncodingKind . Snorm ) ,
173- [ TextureFormat . Rgba16float ] = new CompositeTextureShaderTraits ( "rgba16float" , "f32" , TextureSampleType . Float , CompositeTextureEncodingKind . Float ) ,
174- [ TextureFormat . Rgba8Unorm ] = new CompositeTextureShaderTraits ( "rgba8unorm" , "f32" , TextureSampleType . Float , CompositeTextureEncodingKind . Float ) ,
175- [ TextureFormat . Bgra8Unorm ] = new CompositeTextureShaderTraits ( "bgra8unorm" , "f32" , TextureSampleType . Float , CompositeTextureEncodingKind . Float )
176- } ;
152+ traits = default ;
153+ return false ;
154+ }
155+
156+ traits = r . ShaderTraits ;
157+ return true ;
158+ }
177159
178160 /// <summary>
179161 /// Shader-facing traits derived from one registered composite texture format.
180162 /// </summary>
181- internal readonly struct CompositeTextureShaderTraits (
182- string outputFormat ,
183- string texelType ,
184- TextureSampleType sampleType ,
185- CompositeTextureEncodingKind encodingKind )
163+ internal readonly struct CompositeTextureShaderTraits ( string outputFormat , CompositeTextureEncodingKind encodingKind )
186164 {
187165 /// <summary>
188166 /// Gets the WGSL storage-texture format token used for writes.
189167 /// </summary>
190168 public string OutputFormat { get ; } = outputFormat ;
191169
192- /// <summary>
193- /// Gets the WGSL sampled texel type used when reading the texture.
194- /// </summary>
195- public string TexelType { get ; } = texelType ;
196-
197- /// <summary>
198- /// Gets the WebGPU sampled texture type required by bind-group validation.
199- /// </summary>
200- public TextureSampleType SampleType { get ; } = sampleType ;
201-
202170 /// <summary>
203171 /// Gets the numeric encoding that the shader must apply when storing output texels.
204172 /// </summary>
@@ -218,18 +186,19 @@ private readonly struct CompositePixelRegistration
218186 /// <param name="sampleType">The sampled texture type for this format.</param>
219187 /// <param name="pixelSizeInBytes">The unmanaged pixel size in bytes.</param>
220188 /// <param name="requiredFeature">Optional device feature required for storage binding support.</param>
189+ /// <param name="shaderTraits">Shader-facing read/write traits for this format.</param>
221190 public CompositePixelRegistration (
222191 Type pixelType ,
223192 TextureFormat textureFormat ,
224193 TextureSampleType sampleType ,
225- int pixelSizeInBytes ,
226- FeatureName requiredFeature )
194+ FeatureName requiredFeature ,
195+ CompositeTextureShaderTraits shaderTraits )
227196 {
228197 this . PixelType = pixelType ;
229198 this . TextureFormat = textureFormat ;
230199 this . SampleType = sampleType ;
231- this . PixelSizeInBytes = pixelSizeInBytes ;
232200 this . RequiredFeature = requiredFeature ;
201+ this . ShaderTraits = shaderTraits ;
233202 }
234203
235204 /// <summary>
@@ -242,11 +211,6 @@ public CompositePixelRegistration(
242211 /// </summary>
243212 public TextureFormat TextureFormat { get ; }
244213
245- /// <summary>
246- /// Gets the unmanaged size of the pixel type in bytes.
247- /// </summary>
248- public int PixelSizeInBytes { get ; }
249-
250214 /// <summary>
251215 /// Gets the sampled texture type used when reading this format.
252216 /// </summary>
@@ -259,26 +223,21 @@ public CompositePixelRegistration(
259223 public FeatureName RequiredFeature { get ; }
260224
261225 /// <summary>
262- /// Creates a registration record for <typeparamref name="TPixel"/> with native storage support .
226+ /// Gets the shader-facing read/write traits for this format .
263227 /// </summary>
264- /// <param name="textureFormat">The matching WebGPU texture format.</param>
265- /// <param name="sampleType">The sampled texture type for this format.</param>
266- /// <returns>The initialized registration.</returns>
267- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
268- public static CompositePixelRegistration Create < TPixel > ( TextureFormat textureFormat , TextureSampleType sampleType )
269- where TPixel : unmanaged, IPixel < TPixel >
270- => new ( typeof ( TPixel ) , textureFormat , sampleType , Unsafe . SizeOf < TPixel > ( ) , FeatureName . Undefined ) ;
228+ public CompositeTextureShaderTraits ShaderTraits { get ; }
271229
272230 /// <summary>
273231 /// Creates a registration record for <typeparamref name="TPixel"/> with a required device feature.
274232 /// </summary>
275233 /// <param name="textureFormat">The matching WebGPU texture format.</param>
276234 /// <param name="sampleType">The sampled texture type for this format.</param>
235+ /// <param name="shaderTraits">Shader-facing read/write traits for this format.</param>
277236 /// <param name="requiredFeature">The device feature required for storage binding.</param>
278237 /// <returns>The initialized registration.</returns>
279238 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
280- public static CompositePixelRegistration Create < TPixel > ( TextureFormat textureFormat , TextureSampleType sampleType , FeatureName requiredFeature )
239+ public static CompositePixelRegistration Create < TPixel > ( TextureFormat textureFormat , TextureSampleType sampleType , CompositeTextureShaderTraits shaderTraits , FeatureName requiredFeature = FeatureName . Undefined )
281240 where TPixel : unmanaged, IPixel < TPixel >
282- => new ( typeof ( TPixel ) , textureFormat , sampleType , Unsafe . SizeOf < TPixel > ( ) , requiredFeature ) ;
241+ => new ( typeof ( TPixel ) , textureFormat , sampleType , requiredFeature , shaderTraits ) ;
283242 }
284243}
0 commit comments