Skip to content

Commit 03bd40a

Browse files
committed
Merge branch 'js/canvas-api' of https://github.com/SixLabors/ImageSharp.Drawing into js/canvas-api
# Conflicts: # src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs # src/ImageSharp.Drawing.WebGPU/WebGPUTextureSampleTypeHelper.cs
2 parents 2ed1c0a + fd5a72d commit 03bd40a

87 files changed

Lines changed: 4556 additions & 2224 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

samples/DrawShapesWithImageSharp/Program.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public static class Program
2929
private const string DisplayFontFile = "WendyOne-Regular.ttf";
3030

3131
private static readonly Size SampleSize = new(960, 640);
32-
private static readonly Brush PageBrush = Brushes.Solid(Color.White);
3332
private static readonly FontCollection SampleFonts = new();
3433
private static readonly FontFamily ArabicFontFamily = LoadFontFamily(ArabicFontFile);
3534
private static readonly FontFamily CjkFontFamily = LoadFontFamily(CjkFontFile);
@@ -1199,7 +1198,7 @@ private static void SaveSample(string fileName, Size size, CanvasAction draw)
11991198
private static void SaveImage(Image image, string fileName)
12001199
{
12011200
string fullPath = IOPath.Combine(AppContext.BaseDirectory, OutputDirectory, fileName);
1202-
IODirectory.CreateDirectory(IOPath.GetDirectoryName(fullPath)!);
1201+
IODirectory.CreateDirectory(IOPath.GetDirectoryName(fullPath));
12031202
image.Save(fullPath);
12041203
}
12051204

samples/DrawingBackendBenchmark/README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,21 @@ For each iteration the form:
4545
4. updates the running mean and standard deviation
4646
5. captures a preview image only on the last iteration
4747

48-
That last point matters: preview capture is intentionally outside the measured timing path. The reported time measures scene submission and backend flush work only. Any readback, clone, or bitmap conversion needed for the UI happens afterward.
48+
That last point matters: preview capture is intentionally outside the measured timing path. The reported time measures canvas replay and backend submission work only. Any readback, clone, or bitmap conversion needed for the UI happens afterward.
4949

5050
## Timing model
5151

5252
All backends follow the same basic timing rule:
5353

5454
- start the stopwatch immediately before drawing the scene
55-
- stop it immediately after the backend flush or submission boundary
55+
- stop it immediately after the backend render or submission boundary
5656
- capture preview pixels only after the stopwatch stops
5757

5858
In practice that means:
5959

60-
- `CPU` measures drawing into the cached `Image<Bgra32>` through `DrawingCanvas.Flush()`
60+
- `CPU` measures drawing into the cached `Image<Bgra32>` through canvas disposal replay
6161
- `SkiaSharp` measures drawing through `SKCanvas.Flush()` and optional GPU context flush
62-
- `WebGPU` measures drawing through `DrawingCanvas.Flush()` into the offscreen `WebGPURenderTarget<Bgra32>`
62+
- `WebGPU` measures drawing through canvas disposal replay into the offscreen `WebGPURenderTarget`
6363

6464
So the numbers are comparable as "render and submit" timings, not "render plus preview extraction" timings.
6565

@@ -69,7 +69,7 @@ The sample uses a fixed benchmark size, so the backends keep their render target
6969

7070
- `CpuBenchmarkBackend` caches one `Image<Bgra32>`
7171
- `SkiaSharpBenchmarkBackend` caches one `SKSurface`
72-
- `WebGpuBenchmarkBackend` caches one `WebGPURenderTarget<Bgra32>`
72+
- `WebGpuBenchmarkBackend` caches one `WebGPURenderTarget`
7373

7474
This keeps the benchmark focused on scene rendering rather than repeated target allocation noise.
7575

@@ -78,11 +78,11 @@ This keeps the benchmark focused on scene rendering rather than repeated target
7878
The WebGPU backend is intentionally small:
7979

8080
- it probes support up front with `WebGPUEnvironment.ProbeComputePipelineSupport()`
81-
- it renders into an owned offscreen `WebGPURenderTarget<Bgra32>`
82-
- it draws through `CreateCanvas(...)`, not a hybrid CPU plus GPU canvas
81+
- it renders into an owned offscreen `WebGPURenderTarget`
82+
- it draws through `CreateCanvas(...)`
8383
- it reads back the final frame only when the UI requests the last-iteration preview
8484

85-
The status line also reports whether the last WebGPU flush completed on the staged GPU path or had to fall back to CPU execution.
85+
The status line also reports whether the last WebGPU render used chunked staged execution.
8686

8787
## File guide
8888

samples/DrawingBackendBenchmark/WebGpuBenchmarkBackend.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace DrawingBackendBenchmark;
1515
/// </summary>
1616
internal sealed class WebGpuBenchmarkBackend : IBenchmarkBackend
1717
{
18-
private WebGPURenderTarget<Bgra32>? renderTarget;
18+
private WebGPURenderTarget? renderTarget;
1919
private int cachedWidth;
2020
private int cachedHeight;
2121

@@ -43,7 +43,7 @@ public static bool TryCreate([NotNullWhen(true)] out WebGpuBenchmarkBackend? res
4343
/// </summary>
4444
public BenchmarkRenderResult Render(ReadOnlySpan<VisualLine> lines, int width, int height, bool capturePreview)
4545
{
46-
WebGPURenderTarget<Bgra32> renderTarget = this.EnsureRenderTarget(width, height);
46+
WebGPURenderTarget renderTarget = this.EnsureRenderTarget(width, height);
4747

4848
Stopwatch stopwatch = Stopwatch.StartNew();
4949
using (DrawingCanvas canvas = renderTarget.CreateCanvas(new DrawingOptions()))
@@ -60,7 +60,7 @@ public BenchmarkRenderResult Render(ReadOnlySpan<VisualLine> lines, int width, i
6060
{
6161
try
6262
{
63-
preview = renderTarget.Readback();
63+
preview = renderTarget.Readback<Bgra32>();
6464
}
6565
catch (Exception ex)
6666
{
@@ -87,15 +87,15 @@ public void Dispose()
8787
this.renderTarget = null;
8888
}
8989

90-
private WebGPURenderTarget<Bgra32> EnsureRenderTarget(int width, int height)
90+
private WebGPURenderTarget EnsureRenderTarget(int width, int height)
9191
{
9292
if (this.renderTarget is not null && this.cachedWidth == width && this.cachedHeight == height)
9393
{
9494
return this.renderTarget;
9595
}
9696

9797
this.renderTarget?.Dispose();
98-
this.renderTarget = new WebGPURenderTarget<Bgra32>(width, height);
98+
this.renderTarget = new WebGPURenderTarget(WebGPUTextureFormat.Bgra8Unorm, width, height);
9999
this.cachedWidth = width;
100100
this.cachedHeight = height;
101101
return this.renderTarget;

samples/WebGPUExternalSurfaceDemo/Controls/WebGPURenderControl.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
using System.Runtime.InteropServices;
66
using SixLabors.ImageSharp.Drawing.Processing;
77
using SixLabors.ImageSharp.Drawing.Processing.Backends;
8-
using SixLabors.ImageSharp.PixelFormats;
98
using ImageSharpSize = SixLabors.ImageSharp.Size;
109

1110
namespace WebGPUExternalSurfaceDemo.Controls;
1211

1312
/// <summary>
14-
/// A reusable WinForms control that embeds a <see cref="WebGPUExternalSurface{TPixel}"/> and drives a continuous
13+
/// A reusable WinForms control that embeds a <see cref="WebGPUExternalSurface"/> and drives a continuous
1514
/// render loop via <see cref="Application.Idle"/>. Callers hook <see cref="PaintFrame"/> with their scene logic;
1615
/// the control handles construction, resize, acquire/present, and teardown.
1716
/// </summary>
@@ -20,7 +19,7 @@ public sealed partial class WebGPURenderControl : Control
2019
private const int WM_MOVING = 0x0216;
2120
private const int WM_EXITSIZEMOVE = 0x0232;
2221

23-
private WebGPUExternalSurface<Bgra32>? surface;
22+
private WebGPUExternalSurface? surface;
2423
private Size framebufferSize;
2524
private bool idleHooked;
2625
private long lastTicks;
@@ -63,11 +62,15 @@ protected override void OnHandleCreated(EventArgs e)
6362

6463
// The module handle is required by the Win32 surface descriptor. It identifies the process module
6564
// that owns the window class backing this control.
66-
this.surface = new WebGPUExternalSurface<Bgra32>(
65+
this.surface = new WebGPUExternalSurface(
6766
WebGPUSurfaceHost.Win32(
6867
this.Handle,
6968
Marshal.GetHINSTANCE(typeof(WebGPURenderControl).Module)),
70-
initialFramebufferSize);
69+
initialFramebufferSize,
70+
new WebGPUExternalSurfaceOptions
71+
{
72+
Format = WebGPUTextureFormat.Bgra8Unorm
73+
});
7174

7275
this.lastTicks = Stopwatch.GetTimestamp();
7376
Application.Idle += this.OnApplicationIdle;
@@ -179,7 +182,7 @@ private void RenderOnce()
179182

180183
// Frame acquisition can fail transiently while the surface is unavailable, for example during resize
181184
// or device recovery. Skipping the frame keeps the UI message loop responsive.
182-
if (!this.surface.TryAcquireFrame(out WebGPUSurfaceFrame<Bgra32>? frame))
185+
if (!this.surface.TryAcquireFrame(out WebGPUSurfaceFrame? frame))
183186
{
184187
return;
185188
}
@@ -191,7 +194,7 @@ private void RenderOnce()
191194
using (frame)
192195
{
193196
// The canvas records drawing work against the acquired surface texture. Disposing the frame
194-
// flushes that work, presents the texture, and releases the per-frame native handles.
197+
// renders that work, presents the texture, and releases the per-frame native handles.
195198
this.PaintFrame?.Invoke(frame.Canvas, delta);
196199
}
197200
}

samples/WebGPUExternalSurfaceDemo/README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
It exists to demonstrate:
66

7-
- creating a `WebGPUExternalSurface<Bgra32>` from a `WebGPUSurfaceHost`
7+
- creating a `WebGPUExternalSurface` from a `WebGPUSurfaceHost`
88
- keeping the external surface synchronized with the host control's drawable framebuffer size
9-
- acquiring `WebGPUSurfaceFrame<TPixel>` instances manually
9+
- acquiring `WebGPUSurfaceFrame` instances manually
1010
- drawing with the normal `DrawingCanvas` API
1111
- presenting by disposing the acquired frame
1212

@@ -21,7 +21,7 @@ Requirements:
2121
- .NET 8.0 SDK or later
2222
- Windows, because this sample is a WinForms app
2323
- a WebGPU-capable desktop backend such as D3D12 or Vulkan
24-
- adapter support for the storage-capable BGRA format required by `Bgra32`
24+
- adapter support for the storage-capable BGRA format selected by the sample
2525

2626
When the sample starts you should see a WinForms window with three tabs:
2727

@@ -31,7 +31,7 @@ When the sample starts you should see a WinForms window with three tabs:
3131

3232
## Why This Sample Matters
3333

34-
`WebGPUWindow<TPixel>` owns a top-level native window. `WebGPUExternalSurface<TPixel>` is different: it attaches WebGPU rendering to something the application already owns, such as a control, view, widget, or native surface.
34+
`WebGPUWindow` owns a top-level native window. `WebGPUExternalSurface` is different: it attaches WebGPU rendering to something the application already owns, such as a control, view, widget, or native surface.
3535

3636
That makes it the integration path for UI frameworks. The host owns:
3737

@@ -57,18 +57,22 @@ The reusable integration point is [WebGPURenderControl.cs](d:/GitHub/SixLabors/I
5757
`WebGPURenderControl.OnHandleCreated(...)` creates the external surface from the WinForms control handle:
5858

5959
```csharp
60-
this.surface = new WebGPUExternalSurface<Bgra32>(
60+
this.surface = new WebGPUExternalSurface(
6161
WebGPUSurfaceHost.Win32(
6262
this.Handle,
6363
Marshal.GetHINSTANCE(typeof(WebGPURenderControl).Module)),
64-
initialFramebufferSize);
64+
initialFramebufferSize,
65+
new WebGPUExternalSurfaceOptions
66+
{
67+
Format = WebGPUTextureFormat.Bgra8Unorm
68+
});
6569
```
6670

67-
`WebGPUSurfaceHost` is a small public descriptor for externally-owned native handles. The host keeps ownership of those handles; `WebGPUExternalSurface<TPixel>` only uses them to create and manage the WebGPU rendering surface.
71+
`WebGPUSurfaceHost` is a small public descriptor for externally-owned native handles. The host keeps ownership of those handles; `WebGPUExternalSurface` only uses them to create and manage the WebGPU rendering surface.
6872

6973
### Resize
7074

71-
`WebGPUExternalSurface<TPixel>.Resize(...)` expects the drawable framebuffer size in pixels:
75+
`WebGPUExternalSurface.Resize(...)` expects the drawable framebuffer size in pixels:
7276

7377
```csharp
7478
this.framebufferSize = this.ClientSize;
@@ -82,7 +86,7 @@ The acquired frame exposes the same pixel coordinate space through `frame.Canvas
8286
`RenderOnce()` acquires a frame, invokes user drawing code, and disposes the frame:
8387

8488
```csharp
85-
if (!this.surface.TryAcquireFrame(out WebGPUSurfaceFrame<Bgra32>? frame))
89+
if (!this.surface.TryAcquireFrame(out WebGPUSurfaceFrame? frame))
8690
{
8791
return;
8892
}
@@ -93,7 +97,7 @@ using (frame)
9397
}
9498
```
9599

96-
Disposing the frame flushes pending canvas work, presents the surface texture, and releases the per-frame WebGPU handles.
100+
Disposing the frame renders pending canvas work, presents the surface texture, and releases the per-frame WebGPU handles.
97101

98102
### Rendering Loop
99103

samples/WebGPUWindowDemo/Program.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ public static class Program
2525
public static void Main()
2626
{
2727
// FIFO is the safest sample default: it presents in display order with normal v-sync behavior.
28-
using WebGPUWindow<Bgra32> window = new(new WebGPUWindowOptions
28+
using WebGPUWindow window = new(new WebGPUWindowOptions
2929
{
3030
Title = "ImageSharp.Drawing WebGPU Demo",
3131
Size = new Size(800, 600),
32+
Format = WebGPUTextureFormat.Bgra8Unorm,
3233
PresentMode = WebGPUPresentMode.Fifo,
3334
});
3435

@@ -46,7 +47,7 @@ private sealed class DemoApp
4647
private static readonly Brush BackgroundBrush = Brushes.Solid(Color.FromPixel(new Bgra32(30, 30, 40, 255)));
4748
private static readonly Brush TextBrush = Brushes.Solid(Color.FromPixel(new Bgra32(70, 70, 100, 255)));
4849

49-
private readonly WebGPUWindow<Bgra32> window;
50+
private readonly WebGPUWindow window;
5051
private readonly Random rng = new(42);
5152
private readonly Stopwatch fpsWindow = Stopwatch.StartNew();
5253
private Ball[] balls = [];
@@ -70,18 +71,17 @@ private sealed class DemoApp
7071
"using a WebGPU compute pipeline that evaluates " +
7172
"Porter-Duff blending per pixel.\n\n" +
7273
"The drawing backend automatically manages texture " +
73-
"atlases, bind groups, and pipeline state. It falls " +
74-
"back to the CPU backend for unsupported pixel " +
75-
"formats or when no GPU device is available.\n\n" +
74+
"atlases, bind groups, and pipeline state for the " +
75+
"native target selected by the window.\n\n" +
7676
"SixLabors ImageSharp.Drawing\n" +
7777
"github.com/SixLabors/ImageSharp.Drawing\n\n" +
78-
"Built on the new WebGPUWindow<TPixel> wrapper.";
78+
"Built on the WebGPUWindow wrapper.";
7979

8080
/// <summary>
8181
/// Initializes a new instance of the <see cref="DemoApp"/> class.
8282
/// </summary>
8383
/// <param name="window">The demo window that supplies update and render callbacks.</param>
84-
public DemoApp(WebGPUWindow<Bgra32> window)
84+
public DemoApp(WebGPUWindow window)
8585
{
8686
this.window = window;
8787
this.window.Update += this.OnUpdate;
@@ -146,10 +146,10 @@ private void OnUpdate(TimeSpan deltaTime)
146146
/// </summary>
147147
/// <param name="frame">The acquired frame that exposes the drawing canvas.</param>
148148
/// <remarks>
149-
/// The window loop disposes the frame after this callback returns, which flushes any queued canvas work,
149+
/// The window loop disposes the frame after this callback returns, which renders queued canvas work,
150150
/// presents the swapchain texture, and releases the per-frame WebGPU handles.
151151
/// </remarks>
152-
private void OnRender(WebGPUSurfaceFrame<Bgra32> frame)
152+
private void OnRender(WebGPUSurfaceFrame frame)
153153
{
154154
DrawingCanvas canvas = frame.Canvas;
155155
Rectangle bounds = canvas.Bounds;

0 commit comments

Comments
 (0)