Skip to content

Commit 9b31222

Browse files
Centralize WebGPU probes into WebGPURuntime
1 parent eba4bd0 commit 9b31222

File tree

15 files changed

+277
-250
lines changed

15 files changed

+277
-250
lines changed

samples/DrawingBackendBenchmark/BenchmarkForm.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
using SkiaSharp.Views.Desktop;
54
using SixLabors.ImageSharp;
65
using SixLabors.ImageSharp.PixelFormats;
6+
using SkiaSharp.Views.Desktop;
77
using Color = SixLabors.ImageSharp.Color;
88
using PointF = SixLabors.ImageSharp.PointF;
99

samples/DrawingBackendBenchmark/WebGpuBenchmarkBackend.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@ private WebGpuBenchmarkBackend()
2323

2424
public static bool TryCreate([NotNullWhen(true)] out WebGpuBenchmarkBackend? result, [NotNullWhen(false)] out string? error)
2525
{
26-
using WebGPUDrawingBackend backend = new();
27-
if (!backend.IsSupported)
26+
if (!WebGPUEnvironment.TryProbeComputePipelineSupport(out error))
2827
{
2928
result = null;
30-
error = "WebGPU unsupported";
3129
return false;
3230
}
3331

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1010
<RepositoryUrl Condition="'$(RepositoryUrl)' == ''">https://github.com/SixLabors/ImageSharp.Drawing/</RepositoryUrl>
1111
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
12-
<PackageTags>Image Draw Shape Path Font</PackageTags>
13-
<Description>An extension to ImageSharp that allows the drawing of images, paths, and text.</Description>
12+
<PackageTags>ImageSharp Drawing WebGPU GPU Graphics Shapes Paths Text Raster Compute</PackageTags>
13+
<Description>A WebGPU backend for ImageSharp.Drawing with GPU-accelerated rendering for windows and render targets.</Description>
1414
<Configurations>Debug;Release</Configurations>
1515
<!--
1616
OutputType=Exe is required so that runtimeconfig.json and deps.json are generated.
@@ -22,9 +22,6 @@
2222
<IsTrimmable>true</IsTrimmable>
2323
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
2424

25-
<!-- TEMP: To avoid publishing while we experiment. -->
26-
<IsPublishable>false</IsPublishable>
27-
2825
<!--
2926
This project is strong-name signed by the shared SixLabors build settings.
3027
Silk.NET WebGPU packages are currently unsigned, which triggers CS8002.

src/ImageSharp.Drawing.WebGPU/RemoteExecutor/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ private static int Main(string[] args)
2525

2626
return methodName switch
2727
{
28-
nameof(WebGPUDrawingBackend.ProbeComputePipelineSupport) => WebGPUDrawingBackend.ProbeComputePipelineSupport(),
28+
nameof(WebGPURuntime.ProbeComputePipelineSupport) => WebGPURuntime.ProbeComputePipelineSupport(),
2929
_ => -1
3030
};
3131
}

src/ImageSharp.Drawing.WebGPU/WebGPUDeviceContext{TPixel}.cs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,6 @@ public unsafe WebGPUDeviceContext(Configuration configuration)
5151
this.queueHandle = (nint)queue;
5252
this.runtimeLease = lease;
5353
this.Configuration = configuration;
54-
this.Configuration.SetDrawingBackend(this.Backend);
55-
56-
if (!this.Backend.IsSupported)
57-
{
58-
throw new InvalidOperationException("WebGPU unsupported.");
59-
}
6054
}
6155
catch
6256
{
@@ -101,7 +95,6 @@ public WebGPUDeviceContext(Configuration configuration, nint deviceHandle, nint
10195
this.queueHandle = queueHandle;
10296
this.Backend = new WebGPUDrawingBackend();
10397
this.Configuration = configuration;
104-
this.Configuration.SetDrawingBackend(this.Backend);
10598
}
10699

107100
/// <summary>
@@ -261,7 +254,7 @@ public DrawingCanvas<TPixel> CreateCanvas(
261254
WebGPUTextureFormatId format,
262255
int width,
263256
int height)
264-
=> new(this.Configuration, this.CreateFrame(textureHandle, textureViewHandle, format, width, height), new DrawingOptions());
257+
=> new(this.Configuration, this.Backend, this.CreateFrame(textureHandle, textureViewHandle, format, width, height), new DrawingOptions());
265258

266259
/// <summary>
267260
/// Creates a drawing canvas over an externally-owned WebGPU texture.
@@ -280,7 +273,7 @@ public DrawingCanvas<TPixel> CreateCanvas(
280273
int width,
281274
int height,
282275
DrawingOptions options)
283-
=> new(this.Configuration, this.CreateFrame(textureHandle, textureViewHandle, format, width, height), options);
276+
=> new(this.Configuration, this.Backend, this.CreateFrame(textureHandle, textureViewHandle, format, width, height), options);
284277

285278
/// <summary>
286279
/// Creates a hybrid drawing canvas over an externally-owned WebGPU texture and a caller-provided CPU region.
@@ -299,7 +292,7 @@ public DrawingCanvas<TPixel> CreateHybridCanvas(
299292
int width,
300293
int height,
301294
Buffer2DRegion<TPixel> cpuRegion)
302-
=> new(this.Configuration, this.CreateHybridFrame(textureHandle, textureViewHandle, format, width, height, cpuRegion), new DrawingOptions());
295+
=> new(this.Configuration, this.Backend, this.CreateHybridFrame(textureHandle, textureViewHandle, format, width, height, cpuRegion), new DrawingOptions());
303296

304297
/// <summary>
305298
/// Creates a hybrid drawing canvas over an externally-owned WebGPU texture and a caller-provided CPU region.
@@ -320,7 +313,7 @@ public DrawingCanvas<TPixel> CreateHybridCanvas(
320313
int height,
321314
Buffer2DRegion<TPixel> cpuRegion,
322315
DrawingOptions options)
323-
=> new(this.Configuration, this.CreateHybridFrame(textureHandle, textureViewHandle, format, width, height, cpuRegion), options);
316+
=> new(this.Configuration, this.Backend, this.CreateHybridFrame(textureHandle, textureViewHandle, format, width, height, cpuRegion), options);
324317

325318
/// <summary>
326319
/// Creates a hybrid drawing canvas over an externally-owned WebGPU texture and the root frame of a CPU image.
@@ -335,7 +328,7 @@ public DrawingCanvas<TPixel> CreateHybridCanvas(
335328
nint textureViewHandle,
336329
WebGPUTextureFormatId format,
337330
Image<TPixel> image)
338-
=> new(this.Configuration, this.CreateHybridFrame(textureHandle, textureViewHandle, format, image), new DrawingOptions());
331+
=> new(this.Configuration, this.Backend, this.CreateHybridFrame(textureHandle, textureViewHandle, format, image), new DrawingOptions());
339332

340333
/// <summary>
341334
/// Creates a hybrid drawing canvas over an externally-owned WebGPU texture and a CPU image frame.
@@ -350,7 +343,7 @@ public DrawingCanvas<TPixel> CreateHybridCanvas(
350343
nint textureViewHandle,
351344
WebGPUTextureFormatId format,
352345
ImageFrame<TPixel> imageFrame)
353-
=> new(this.Configuration, this.CreateHybridFrame(textureHandle, textureViewHandle, format, imageFrame), new DrawingOptions());
346+
=> new(this.Configuration, this.Backend, this.CreateHybridFrame(textureHandle, textureViewHandle, format, imageFrame), new DrawingOptions());
354347

355348
/// <summary>
356349
/// Creates a hybrid drawing canvas over an externally-owned WebGPU texture and the root frame of a CPU image.
@@ -367,7 +360,7 @@ public DrawingCanvas<TPixel> CreateHybridCanvas(
367360
WebGPUTextureFormatId format,
368361
Image<TPixel> image,
369362
DrawingOptions options)
370-
=> new(this.Configuration, this.CreateHybridFrame(textureHandle, textureViewHandle, format, image), options);
363+
=> new(this.Configuration, this.Backend, this.CreateHybridFrame(textureHandle, textureViewHandle, format, image), options);
371364

372365
/// <summary>
373366
/// Creates a hybrid drawing canvas over an externally-owned WebGPU texture and a CPU image frame.
@@ -384,7 +377,7 @@ public DrawingCanvas<TPixel> CreateHybridCanvas(
384377
WebGPUTextureFormatId format,
385378
ImageFrame<TPixel> imageFrame,
386379
DrawingOptions options)
387-
=> new(this.Configuration, this.CreateHybridFrame(textureHandle, textureViewHandle, format, imageFrame), options);
380+
=> new(this.Configuration, this.Backend, this.CreateHybridFrame(textureHandle, textureViewHandle, format, imageFrame), options);
388381

389382
/// <summary>
390383
/// Disposes the drawing backend owned by this context.

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs

Lines changed: 0 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ public sealed unsafe partial class WebGPUDrawingBackend : IDrawingBackend, IDisp
3030
private const int MaxDynamicGrowthAttempts = 8;
3131

3232
private readonly DefaultDrawingBackend fallbackBackend;
33-
private static bool? isSupported;
3433

3534
// The staged pipeline keeps the most recently successful scratch capacities so later flushes
3635
// can start closer to the scene sizes the current device has already proven it needs.
@@ -90,153 +89,6 @@ public WebGPUDrawingBackend()
9089
/// </summary>
9190
public string DiagnosticLastChunkingBindingFailure => this.TestingLastChunkingBindingFailure.ToString();
9291

93-
/// <summary>
94-
/// Gets a value indicating whether WebGPU is available on the current system.
95-
/// This probes the runtime by attempting to acquire an adapter and device.
96-
/// The result is cached after the first probe.
97-
/// </summary>
98-
public bool IsSupported => isSupported ??= ProbeFullSupport();
99-
100-
/// <summary>
101-
/// Probes whether WebGPU compute is fully supported on the current system.
102-
/// First checks adapter/device availability in-process. If that succeeds,
103-
/// spawns a child process via <see cref="RemoteExecutor"/> to test compute
104-
/// pipeline creation, which can crash with an unrecoverable access violation
105-
/// on some systems. If the remote executor is not available, falls back to
106-
/// the device-only check.
107-
/// </summary>
108-
/// <returns>Returns <see langword="true"/> if WebGPU compute support is available; otherwise, <see langword="false"/>.</returns>
109-
private static bool ProbeFullSupport()
110-
{
111-
if (!ProbeSupport())
112-
{
113-
return false;
114-
}
115-
116-
if (!RemoteExecutor.IsSupported)
117-
{
118-
return true;
119-
}
120-
121-
return RemoteExecutor.Invoke(ProbeComputePipelineSupport) == 0;
122-
}
123-
124-
/// <summary>
125-
/// Determines whether WebGPU adapter and device are available on the current system.
126-
/// </summary>
127-
/// <remarks>This method only checks adapter and device availability. It does not attempt
128-
/// compute pipeline creation. Use <see cref="ProbeFullSupport"/> for a complete check.</remarks>
129-
/// <returns>Returns <see langword="true"/> if a WebGPU device is available; otherwise, <see langword="false"/>.</returns>
130-
public static bool ProbeSupport()
131-
{
132-
try
133-
{
134-
using WebGPURuntime.Lease lease = WebGPURuntime.Acquire();
135-
return WebGPURuntime.TryGetOrCreateDevice(out _, out _, out _);
136-
}
137-
catch
138-
{
139-
return false;
140-
}
141-
}
142-
143-
/// <summary>
144-
/// Probes full WebGPU compute pipeline support by compiling a trivial shader and
145-
/// creating a compute pipeline. This method may crash with an access violation on
146-
/// systems with broken WebGPU compute support - callers should run it in a child
147-
/// process (for example via <c>RemoteExecutor</c>) to isolate the crash.
148-
/// </summary>
149-
/// <returns>Exit code: 0 on success, 1 on failure.</returns>
150-
public static int ProbeComputePipelineSupport()
151-
{
152-
try
153-
{
154-
using WebGPURuntime.Lease lease = WebGPURuntime.Acquire();
155-
if (!WebGPURuntime.TryGetOrCreateDevice(out Device* device, out _, out _))
156-
{
157-
return 1;
158-
}
159-
160-
WebGPU api = lease.Api;
161-
162-
ReadOnlySpan<byte> probeShader = "@compute @workgroup_size(1) fn main() {}\0"u8;
163-
fixed (byte* shaderCodePtr = probeShader)
164-
{
165-
ShaderModuleWGSLDescriptor wgslDescriptor = new()
166-
{
167-
Chain = new ChainedStruct { SType = SType.ShaderModuleWgslDescriptor },
168-
Code = shaderCodePtr
169-
};
170-
171-
ShaderModuleDescriptor shaderDescriptor = new()
172-
{
173-
NextInChain = (ChainedStruct*)&wgslDescriptor
174-
};
175-
176-
ShaderModule* shaderModule = api.DeviceCreateShaderModule(device, in shaderDescriptor);
177-
if (shaderModule is null)
178-
{
179-
return 1;
180-
}
181-
182-
try
183-
{
184-
ReadOnlySpan<byte> entryPoint = "main\0"u8;
185-
fixed (byte* entryPointPtr = entryPoint)
186-
{
187-
ProgrammableStageDescriptor computeStage = new()
188-
{
189-
Module = shaderModule,
190-
EntryPoint = entryPointPtr
191-
};
192-
193-
PipelineLayoutDescriptor layoutDescriptor = new()
194-
{
195-
BindGroupLayoutCount = 0,
196-
BindGroupLayouts = null
197-
};
198-
199-
PipelineLayout* pipelineLayout = api.DeviceCreatePipelineLayout(device, in layoutDescriptor);
200-
if (pipelineLayout is null)
201-
{
202-
return 1;
203-
}
204-
205-
try
206-
{
207-
ComputePipelineDescriptor pipelineDescriptor = new()
208-
{
209-
Layout = pipelineLayout,
210-
Compute = computeStage
211-
};
212-
213-
ComputePipeline* pipeline = api.DeviceCreateComputePipeline(device, in pipelineDescriptor);
214-
if (pipeline is null)
215-
{
216-
return 1;
217-
}
218-
219-
api.ComputePipelineRelease(pipeline);
220-
return 0;
221-
}
222-
finally
223-
{
224-
api.PipelineLayoutRelease(pipelineLayout);
225-
}
226-
}
227-
}
228-
finally
229-
{
230-
api.ShaderModuleRelease(shaderModule);
231-
}
232-
}
233-
}
234-
catch
235-
{
236-
return 1;
237-
}
238-
}
239-
24092
/// <inheritdoc />
24193
public void FlushCompositions<TPixel>(
24294
Configuration configuration,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
7+
8+
/// <summary>
9+
/// Provides explicit support probes for the library-managed WebGPU environment.
10+
/// Use this type when you want to check availability or compute pipeline support before constructing WebGPU objects.
11+
/// </summary>
12+
public static class WebGPUEnvironment
13+
{
14+
/// <summary>
15+
/// Tries to acquire the library-managed WebGPU device and queue.
16+
/// </summary>
17+
/// <param name="error">Receives the failure reason when the probe fails.</param>
18+
/// <returns><see langword="true"/> when the library-managed WebGPU device and queue are available; otherwise, <see langword="false"/>.</returns>
19+
public static bool TryProbeAvailability([NotNullWhen(false)] out string? error)
20+
=> WebGPURuntime.TryProbeAvailability(out error);
21+
22+
/// <summary>
23+
/// Tries to create a trivial compute pipeline using the library-managed WebGPU device.
24+
/// </summary>
25+
/// <param name="error">Receives the failure reason when the probe fails.</param>
26+
/// <returns><see langword="true"/> when compute pipeline creation succeeds; otherwise, <see langword="false"/>.</returns>
27+
public static bool TryProbeComputePipelineSupport([NotNullWhen(false)] out string? error)
28+
=> WebGPURuntime.TryProbeComputePipelineSupport(out error);
29+
}

src/ImageSharp.Drawing.WebGPU/WebGPURenderTarget{TPixel}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ public DrawingCanvas<TPixel> CreateCanvas(DrawingOptions options)
212212
{
213213
this.ThrowIfDisposed();
214214
this.Graphics.ThrowIfDisposed();
215-
return new DrawingCanvas<TPixel>(this.Graphics.Configuration, this.NativeFrame, options);
215+
return new DrawingCanvas<TPixel>(this.Graphics.Configuration, this.Graphics.Backend, this.NativeFrame, options);
216216
}
217217

218218
/// <summary>
@@ -233,7 +233,7 @@ public DrawingCanvas<TPixel> CreateHybridCanvas(Buffer2DRegion<TPixel> cpuRegion
233233
{
234234
this.ThrowIfDisposed();
235235
this.Graphics.ThrowIfDisposed();
236-
return new DrawingCanvas<TPixel>(this.Graphics.Configuration, this.CreateHybridFrame(cpuRegion), options);
236+
return new DrawingCanvas<TPixel>(this.Graphics.Configuration, this.Graphics.Backend, this.CreateHybridFrame(cpuRegion), options);
237237
}
238238

239239
/// <summary>

0 commit comments

Comments
 (0)