Skip to content

Commit 8dfce85

Browse files
committed
simplify CompositePixelRegistration storage and related lookup methods
1 parent 6fa5d73 commit 8dfce85

3 files changed

Lines changed: 66 additions & 110 deletions

File tree

src/ImageSharp.Drawing.WebGPU/Shaders/FineAreaComputeShader.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ internal static class FineAreaComputeShader
1616
private const string OutputStoreMarker = "textureStore(output, vec2<i32>(coords), rgba_sep);";
1717
private const string PremulAlphaMarker = "fn premul_alpha(rgba: vec4<f32>) -> vec4<f32> {";
1818

19-
private static readonly object CacheSync = new();
2019
private static readonly Dictionary<TextureFormat, byte[]> ShaderCache = [];
2120

2221
/// <summary>
@@ -36,7 +35,7 @@ public static bool TryGetCode(TextureFormat textureFormat, out byte[] code, out
3635
return false;
3736
}
3837

39-
lock (CacheSync)
38+
lock (ShaderCache)
4039
{
4140
if (ShaderCache.TryGetValue(textureFormat, out byte[]? cachedCode))
4241
{

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs

Lines changed: 65 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
2019
public 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
}

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ public sealed unsafe partial class WebGPUDrawingBackend : IDrawingBackend, IDisp
4343
private WebGPUSceneDispatch.BindingLimitBuffer lastChunkingBindingFailure;
4444
private bool isDisposed;
4545

46-
private static readonly Dictionary<Type, CompositePixelRegistration> CompositePixelHandlers = CreateCompositePixelHandlers();
47-
4846
/// <summary>
4947
/// Initializes a new instance of the <see cref="WebGPUDrawingBackend"/> class.
5048
/// </summary>

0 commit comments

Comments
 (0)