Skip to content

Commit aee48a5

Browse files
Make API public
1 parent 23c5c37 commit aee48a5

35 files changed

Lines changed: 552 additions & 740 deletions

ImageSharp.Drawing.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ Global
457457
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
458458
EndGlobalSection
459459
GlobalSection(SharedMSBuildProjectFiles) = preSolution
460+
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{061582c2-658f-40ae-a978-7d74a4eb2c0a}*SharedItemsImports = 5
460461
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2e33181e-6e28-4662-a801-e2e7dc206029}*SharedItemsImports = 5
461462
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
462463
EndGlobalSection

samples/WebGPUWindowDemo/Program.cs

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using SixLabors.ImageSharp.Drawing.Processing;
1313
using SixLabors.ImageSharp.Drawing.Processing.Backends;
1414
using SixLabors.ImageSharp.Drawing.Text;
15-
using SixLabors.ImageSharp.Memory;
1615
using SixLabors.ImageSharp.PixelFormats;
1716
using Color = SixLabors.ImageSharp.Color;
1817
using Rectangle = SixLabors.ImageSharp.Rectangle;
@@ -59,7 +58,7 @@ public static unsafe class Program
5958
// Scrolling text state — glyph geometry is built once at startup via TextBuilder
6059
// and translated vertically each frame. Only glyphs whose bounds intersect the
6160
// visible viewport are submitted for rasterization.
62-
private static IPathCollection scrollPaths = null!;
61+
private static IPathCollection scrollPaths;
6362
private static float scrollOffset;
6463
private static float scrollTextHeight;
6564
private const string ScrollText =
@@ -156,10 +155,6 @@ private static void OnLoad()
156155

157156
Console.WriteLine($"Device: 0x{(nuint)device:X}, Queue: 0x{(nuint)queue:X}");
158157

159-
// Register shared handles so the drawing backend uses our device rather than
160-
// provisioning its own.
161-
WebGPURuntime.SetSharedHandles((nint)device, (nint)queue);
162-
163158
// Query surface capabilities and configure the swap chain.
164159
wgpu.SurfaceGetCapabilities(surface, adapter, ref surfaceCapabilities);
165160
Console.WriteLine($"Surface format: {surfaceCapabilities.Formats[0]}");
@@ -292,14 +287,11 @@ private static void OnRender(double deltaTime)
292287
(nint)textureView,
293288
WebGPUTextureFormatId.Bgra8Unorm,
294289
w,
295-
h,
296-
isSrgb: false,
297-
isPremultipliedAlpha: false,
298-
supportsTextureSampling: true);
290+
h);
299291

300-
// NativeSurfaceOnlyFrame exposes only the GPU surface (no CPU region),
292+
// NativeCanvasFrame exposes only the GPU surface (no CPU region),
301293
// so the backend always takes the GPU composition path.
302-
NativeSurfaceOnlyFrame<Bgra32> frame = new(new Rectangle(0, 0, w, h), nativeSurface);
294+
NativeCanvasFrame<Bgra32> frame = new(new Rectangle(0, 0, w, h), nativeSurface);
303295

304296
// Create a drawing canvas targeting the swap chain frame.
305297
using DrawingCanvas<Bgra32> canvas = new(drawingConfiguration, frame, new DrawingOptions());
@@ -389,15 +381,12 @@ private static void DrawScrollingText(DrawingCanvas<Bgra32> canvas, int w, int h
389381
private static void OnClosing()
390382
{
391383
backend.Dispose();
392-
WebGPURuntime.ClearSharedHandles();
393384

394385
wgpu.DeviceRelease(device);
395386
wgpu.AdapterRelease(adapter);
396387
wgpu.SurfaceRelease(surface);
397388
wgpu.InstanceRelease(instance);
398389
wgpu.Dispose();
399-
400-
WebGPURuntime.Shutdown();
401390
}
402391

403392
/// <summary>WebGPU device-lost callback — logs the reason to the console.</summary>
@@ -408,37 +397,6 @@ private static void DeviceLost(DeviceLostReason reason, byte* message, void* use
408397
private static void UncapturedError(ErrorType type, byte* message, void* userData)
409398
=> Console.WriteLine($"WebGPU {type}: {SilkMarshal.PtrToString((nint)message)}");
410399

411-
/// <summary>
412-
/// Wraps a <see cref="NativeSurface"/> as an <see cref="ICanvasFrame{TPixel}"/> that
413-
/// exposes only the GPU surface. <see cref="TryGetCpuRegion"/> returns false, ensuring
414-
/// the drawing backend always takes the native GPU composition path.
415-
/// </summary>
416-
private sealed class NativeSurfaceOnlyFrame<TPixel> : ICanvasFrame<TPixel>
417-
where TPixel : unmanaged, IPixel<TPixel>
418-
{
419-
private readonly NativeSurface nativeSurface;
420-
421-
public NativeSurfaceOnlyFrame(Rectangle bounds, NativeSurface nativeSurface)
422-
{
423-
this.Bounds = bounds;
424-
this.nativeSurface = nativeSurface;
425-
}
426-
427-
public Rectangle Bounds { get; }
428-
429-
public bool TryGetCpuRegion(out Buffer2DRegion<TPixel> region)
430-
{
431-
region = default;
432-
return false;
433-
}
434-
435-
public bool TryGetNativeSurface(out NativeSurface surface)
436-
{
437-
surface = this.nativeSurface;
438-
return true;
439-
}
440-
}
441-
442400
/// <summary>
443401
/// A simple bouncing ball with position, velocity, radius, and color.
444402
/// Reflects off the window edges each frame.

src/Directory.Build.props

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
3131
<InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" Key="$(SixLaborsPublicKey)" />
3232
<InternalsVisibleTo Include="ImageSharp.Drawing.Benchmarks" Key="$(SixLaborsPublicKey)" />
33-
<InternalsVisibleTo Include="WebGPUWindowDemo" Key="$(SixLaborsPublicKey)" />
3433
</ItemGroup>
3534

3635
</Project>

src/ImageSharp.Drawing.WebGPU/ImageSharp.Drawing.WebGPU.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
<PackageReference Include="Silk.NET.WebGPU.Native.WGPU" Version="2.23.0" PrivateAssets="compile;build;analyzers;contentfiles;buildtransitive" />
6868
</ItemGroup>
6969

70+
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
71+
7072
<ItemGroup>
7173
<ProjectReference Include="..\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
7274
</ItemGroup>

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

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
1717
/// Only formats that support <c>storage</c> texture binding (required by the compute compositor)
1818
/// are included. Formats that lack storage support are omitted and fall back to the CPU backend.
1919
/// </remarks>
20-
internal sealed partial class WebGPUDrawingBackend
20+
public sealed partial class WebGPUDrawingBackend
2121
{
2222
/// <summary>
2323
/// Builds the static registration table that maps <see cref="IPixel{TSelf}"/> implementations to
@@ -27,9 +27,6 @@ internal sealed partial class WebGPUDrawingBackend
2727
private static Dictionary<Type, CompositePixelRegistration> CreateCompositePixelHandlers() =>
2828

2929
// Only formats with native or feature-gated storage binding support.
30-
// Non-storable formats (R8Unorm, RG8Unorm, RG8Snorm, R16Float, RG16Float,
31-
// RG16Sint, Rgb10A2Unorm, R16Uint, RG16Uint) are omitted — they cannot be
32-
// used as compute shader write targets and fall back to DefaultDrawingBackend.
3330
new()
3431
{
3532
[typeof(Byte4)] = CompositePixelRegistration.Create<Byte4>(TextureFormat.Rgba8Uint),
@@ -47,13 +44,12 @@ private static Dictionary<Type, CompositePixelRegistration> CreateCompositePixel
4744
};
4845

4946
/// <summary>
50-
/// Resolves the WebGPU texture format identifier for <typeparamref name="TPixel"/> when supported
51-
/// by the current device.
47+
/// Resolves the WebGPU texture format identifier for <typeparamref name="TPixel"/>.
5248
/// </summary>
5349
/// <typeparam name="TPixel">The requested pixel type.</typeparam>
5450
/// <param name="formatId">Receives the mapped texture format identifier on success.</param>
5551
/// <returns>
56-
/// <see langword="true"/> when the pixel type is supported for GPU composition; otherwise <see langword="false"/>.
52+
/// <see langword="true"/> when the pixel type has a registered GPU format mapping; otherwise <see langword="false"/>.
5753
/// </returns>
5854
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5955
internal static bool TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureFormatId formatId)
@@ -65,14 +61,36 @@ internal static bool TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureForma
6561
return false;
6662
}
6763

68-
if (registration.RequiredFeature != FeatureName.Undefined
69-
&& !WebGPURuntime.HasDeviceFeature(registration.RequiredFeature))
64+
formatId = WebGPUTextureFormatMapper.FromSilk(registration.TextureFormat);
65+
return true;
66+
}
67+
68+
/// <summary>
69+
/// Resolves the WebGPU texture format identifier and any required device feature
70+
/// for <typeparamref name="TPixel"/>.
71+
/// </summary>
72+
/// <typeparam name="TPixel">The requested pixel type.</typeparam>
73+
/// <param name="formatId">Receives the mapped texture format identifier on success.</param>
74+
/// <param name="requiredFeature">
75+
/// Receives the device feature required for storage binding, or
76+
/// <see cref="FeatureName.Undefined"/> when no special feature is needed.
77+
/// </param>
78+
/// <returns>
79+
/// <see langword="true"/> when the pixel type has a registered GPU format mapping; otherwise <see langword="false"/>.
80+
/// </returns>
81+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
82+
internal static bool TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureFormatId formatId, out FeatureName requiredFeature)
83+
where TPixel : unmanaged, IPixel<TPixel>
84+
{
85+
if (!CompositePixelHandlers.TryGetValue(typeof(TPixel), out CompositePixelRegistration registration))
7086
{
7187
formatId = default;
88+
requiredFeature = FeatureName.Undefined;
7289
return false;
7390
}
7491

7592
formatId = WebGPUTextureFormatMapper.FromSilk(registration.TextureFormat);
93+
requiredFeature = registration.RequiredFeature;
7694
return true;
7795
}
7896

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
1212

13-
internal sealed unsafe partial class WebGPUDrawingBackend
13+
/// <content>
14+
/// Coverage rasterization helpers.
15+
/// </content>
16+
public sealed unsafe partial class WebGPUDrawingBackend
1417
{
1518
private const int TileHeight = 16;
1619
private const int EdgeStrideBytes = 16;

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
1313

14-
internal sealed unsafe partial class WebGPUDrawingBackend
14+
/// <content>
15+
/// GPU readback helpers.
16+
/// </content>
17+
public sealed unsafe partial class WebGPUDrawingBackend
1518
{
1619
private const int ReadbackCallbackTimeoutMilliseconds = 5000;
1720

@@ -46,7 +49,7 @@ public bool TryReadRegion<TPixel>(
4649
return false;
4750
}
4851

49-
if (!TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureFormatId expectedFormat) ||
52+
if (!TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureFormatId expectedFormat, out FeatureName requiredFeature) ||
5053
expectedFormat != capability.TargetFormat)
5154
{
5255
return false;
@@ -68,6 +71,13 @@ public bool TryReadRegion<TPixel>(
6871
using WebGPURuntime.Lease lease = WebGPURuntime.Acquire();
6972
WebGPU api = lease.Api;
7073
Device* device = (Device*)capability.Device;
74+
75+
if (requiredFeature != FeatureName.Undefined
76+
&& !WebGPUFlushContext.GetOrCreateDeviceState(api, device).HasFeature(requiredFeature))
77+
{
78+
return false;
79+
}
80+
7181
Queue* queue = (Queue*)capability.Queue;
7282

7383
int pixelSizeInBytes = Unsafe.SizeOf<TPixel>();

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,8 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
3232
/// -> Blit once and optionally read back to CPU region
3333
/// -> On failure: delegate scene to DefaultDrawingBackend
3434
/// </code>
35-
/// <para>
36-
/// See src/ImageSharp.Drawing.WebGPU/WEBGPU_BACKEND_PROCESS.md for a full process walkthrough.
37-
/// </para>
3835
/// </remarks>
39-
internal sealed unsafe partial class WebGPUDrawingBackend : IDrawingBackend, IDisposable
36+
public sealed unsafe partial class WebGPUDrawingBackend : IDrawingBackend, IDisposable
4037
{
4138
private const int CompositeTileWidth = 16;
4239
private const int CompositeTileHeight = 16;
@@ -119,29 +116,6 @@ public WebGPUDrawingBackend()
119116
/// </summary>
120117
internal int TestingComputePathBatchCount { get; private set; }
121118

122-
/// <summary>
123-
/// Attempts to expose native WebGPU device and queue handles for interop.
124-
/// </summary>
125-
/// <param name="deviceHandle">Receives the device pointer when available.</param>
126-
/// <param name="queueHandle">Receives the queue pointer when available.</param>
127-
/// <returns><see langword="true"/> when both handles are available; otherwise <see langword="false"/>.</returns>
128-
internal bool TryGetInteropHandles(out nint deviceHandle, out nint queueHandle)
129-
{
130-
this.ThrowIfDisposed();
131-
if (WebGPUFlushContext.TryGetInteropHandles(out deviceHandle, out queueHandle, out string? error))
132-
{
133-
this.TestingGPUInitializationAttempted = true;
134-
this.TestingIsGPUReady = true;
135-
this.TestingLastGPUInitializationFailure = null;
136-
return true;
137-
}
138-
139-
this.TestingGPUInitializationAttempted = true;
140-
this.TestingIsGPUReady = false;
141-
this.TestingLastGPUInitializationFailure = error;
142-
return false;
143-
}
144-
145119
/// <inheritdoc />
146120
public void FlushCompositions<TPixel>(
147121
Configuration configuration,
@@ -158,7 +132,7 @@ public void FlushCompositions<TPixel>(
158132
return;
159133
}
160134

161-
if (!TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureFormatId formatId) ||
135+
if (!TryGetCompositeTextureFormat<TPixel>(out WebGPUTextureFormatId formatId, out FeatureName requiredFeature) ||
162136
!AreAllCompositionBrushesSupported<TPixel>(compositionScene.Commands))
163137
{
164138
int fallbackCommandCount = compositionScene.Commands.Count;
@@ -232,13 +206,27 @@ public void FlushCompositions<TPixel>(
232206
string? failure = null;
233207
int pixelSizeInBytes = Unsafe.SizeOf<TPixel>();
234208

235-
WebGPUFlushContext flushContext = WebGPUFlushContext.Create(
209+
WebGPUFlushContext? flushContext = WebGPUFlushContext.Create(
236210
target,
237211
textureFormat,
212+
requiredFeature,
238213
pixelSizeInBytes,
239214
configuration.MemoryAllocator,
240215
compositionBounds);
241216

217+
if (flushContext is null)
218+
{
219+
this.TestingFallbackPrepareCoverageCallCount += commandCount;
220+
this.TestingFallbackCompositeCoverageCallCount += commandCount;
221+
this.FlushCompositionsFallback(
222+
configuration,
223+
target,
224+
compositionScene,
225+
target.TryGetCpuRegion(out Buffer2DRegion<TPixel> _),
226+
compositionBounds);
227+
return;
228+
}
229+
242230
try
243231
{
244232
gpuReady = true;
@@ -253,7 +241,9 @@ public void FlushCompositions<TPixel>(
253241
compositionBounds.Value,
254242
commandCount,
255243
out failure);
244+
256245
bool finalizeOk = renderOk && this.TryFinalizeFlush(flushContext, cpuRegion, compositionBounds);
246+
257247
gpuSuccess = finalizeOk;
258248
}
259249
catch (Exception ex)
@@ -345,7 +335,7 @@ private void FlushCompositionsFallback<TPixel>(
345335
WebGPUFlushContext.RentFallbackStaging<TPixel>(configuration.MemoryAllocator, in targetBounds);
346336

347337
Buffer2DRegion<TPixel> stagingRegion = stagingLease.Region;
348-
ICanvasFrame<TPixel> stagingFrame = new CpuCanvasFrame<TPixel>(stagingRegion);
338+
ICanvasFrame<TPixel> stagingFrame = new MemoryCanvasFrame<TPixel>(stagingRegion);
349339
this.fallbackBackend.FlushCompositions(configuration, stagingFrame, compositionScene);
350340

351341
using WebGPUFlushContext uploadContext = WebGPUFlushContext.CreateUploadContext(target, configuration.MemoryAllocator);

0 commit comments

Comments
 (0)