Skip to content

Commit 2b361b7

Browse files
Add premultiplied rendering and native WebGPU interop
Replace the Silk.NET WebGPU bindings with checked-in wgpu-native bindings and packaged RID-specific native libraries. Add shared external-surface sessions and persistent presentation targets with direct-copy and render-pipeline fallback paths across supported native window hosts. Carry alpha representation and numeric encoding through canvas creation, shaders, copies, readback, and presentation. Implement CSS Color 4 premultiplied interpolation for CPU and WebGPU gradients, including path gradients and half-float targets. Expand callback lifetime, surface, format, gradient, and CPU/WebGPU parity coverage, and update the verified rendering references.
1 parent 17b1c49 commit 2b361b7

179 files changed

Lines changed: 20771 additions & 2219 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.

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,15 @@
8484
# treat as binary
8585
###############################################################################
8686
*.basis binary
87+
*.a binary
8788
*.dll binary
89+
*.dylib binary
8890
*.exe binary
8991
*.pdf binary
9092
*.ppt binary
9193
*.pptx binary
9294
*.pvr binary
95+
*.so binary
9396
*.snk binary
9497
*.xls binary
9598
*.xlsx binary

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,5 +231,6 @@ SixLabors.Shapes.Coverage.xml
231231
.dotnet
232232
.codex-*
233233
.claude
234+
/plans/
234235

235236
samples/Avalonia**

shared-infrastructure

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"clangsharppinvokegenerator": {
6+
"version": "21.1.8.3",
7+
"commands": [
8+
"ClangSharpPInvokeGenerator"
9+
]
10+
}
11+
}
12+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
5+
6+
/// <summary>
7+
/// Describes how a WebGPU buffer may be used.
8+
/// </summary>
9+
[Flags]
10+
internal enum BufferUsage : ulong
11+
{
12+
/// <summary>
13+
/// The buffer has no permitted usage.
14+
/// </summary>
15+
None = 0,
16+
17+
/// <summary>
18+
/// The buffer can be mapped for CPU reads.
19+
/// </summary>
20+
MapRead = 1,
21+
22+
/// <summary>
23+
/// The buffer can be mapped for CPU writes.
24+
/// </summary>
25+
MapWrite = 2,
26+
27+
/// <summary>
28+
/// The buffer can be the source of a copy operation.
29+
/// </summary>
30+
CopySrc = 4,
31+
32+
/// <summary>
33+
/// The buffer can be the destination of a copy operation.
34+
/// </summary>
35+
CopyDst = 8,
36+
37+
/// <summary>
38+
/// The buffer can supply index data.
39+
/// </summary>
40+
Index = 16,
41+
42+
/// <summary>
43+
/// The buffer can supply vertex data.
44+
/// </summary>
45+
Vertex = 32,
46+
47+
/// <summary>
48+
/// The buffer can supply uniform data.
49+
/// </summary>
50+
Uniform = 64,
51+
52+
/// <summary>
53+
/// The buffer can be bound as storage.
54+
/// </summary>
55+
Storage = 128,
56+
57+
/// <summary>
58+
/// The buffer can supply indirect command arguments.
59+
/// </summary>
60+
Indirect = 256,
61+
62+
/// <summary>
63+
/// The buffer can receive resolved query results.
64+
/// </summary>
65+
QueryResolve = 512
66+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
5+
6+
/// <summary>
7+
/// Identifies the color channels written by a render target.
8+
/// </summary>
9+
[Flags]
10+
internal enum ColorWriteMask : ulong
11+
{
12+
/// <summary>
13+
/// No color channel is written.
14+
/// </summary>
15+
None = 0,
16+
17+
/// <summary>
18+
/// The red channel is written.
19+
/// </summary>
20+
Red = 1,
21+
22+
/// <summary>
23+
/// The green channel is written.
24+
/// </summary>
25+
Green = 2,
26+
27+
/// <summary>
28+
/// The blue channel is written.
29+
/// </summary>
30+
Blue = 4,
31+
32+
/// <summary>
33+
/// The alpha channel is written.
34+
/// </summary>
35+
Alpha = 8,
36+
37+
/// <summary>
38+
/// Every color channel is written.
39+
/// </summary>
40+
All = Red | Green | Blue | Alpha
41+
}

src/ImageSharp.Drawing.WebGPU/GpuSceneDrawTag.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ internal static class GpuSceneDrawTag
1414
// scene-word-count, and info-word-count increments consumed by the scan/reduction stages.
1515
// Visible-fill tags carry five extra info words: coverage threshold plus raster interest. Must match drawtag.wgsl.
1616
public const uint Nop = 0U;
17-
public const uint FillColor = 0x184U;
18-
public const uint FillRecolor = 0x18CU;
17+
public const uint FillColor = 0x188U;
18+
public const uint FillRecolor = 0x184U;
1919
public const uint FillLinGradient = 0x254U;
2020
public const uint FillRadGradient = 0x3DCU;
21-
public const uint FillEllipticGradient = 0x31CU;
21+
public const uint FillEllipticGradient = 0x35CU;
2222
public const uint FillSweepGradient = 0x394U;
2323
public const uint FillPathGradient = 0x190U;
2424
public const uint FillImage = 0x3D4U;

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@
2323
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
2424

2525
<!--
26-
This project is strong-name signed by the shared SixLabors build settings.
27-
Silk.NET WebGPU packages are currently unsigned, which triggers CS8002.
28-
Keep CS8002 visible as a warning, but do not fail the build for it.
26+
Silk.NET.Windowing.Glfw is unsigned, while this project is strong-name signed.
27+
Keep CS8002 visible as a warning without allowing warnings-as-errors to fail the build.
2928
-->
3029
<WarningsNotAsErrors>$(WarningsNotAsErrors);8002;NU1900</WarningsNotAsErrors>
3130
</PropertyGroup>
@@ -49,24 +48,35 @@
4948
</Otherwise>
5049
</Choose>
5150

51+
<PropertyGroup>
52+
<!-- An explicit application RID takes precedence when cross-compiling; otherwise use the SDK host RID for local execution. -->
53+
<_WebGPUNativeRuntimeIdentifier Condition="'$(RuntimeIdentifier)' != ''">$(RuntimeIdentifier)</_WebGPUNativeRuntimeIdentifier>
54+
<_WebGPUNativeRuntimeIdentifier Condition="'$(_WebGPUNativeRuntimeIdentifier)' == ''">$(NETCoreSdkRuntimeIdentifier)</_WebGPUNativeRuntimeIdentifier>
55+
</PropertyGroup>
56+
5257
<ItemGroup>
5358
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
5459
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp.drawing\sixlabors.imagesharp.drawing.128.png" Pack="true" PackagePath="" />
55-
<None Include="buildTransitive\$(PackageId).targets" Pack="true" PackagePath="buildTransitive\$(PackageId).targets" />
60+
61+
<!-- Platform SDKs consume native libraries from their standard RID-specific package paths. -->
62+
<None Include="runtimes/**/*" Pack="true" PackagePath="%(Identity)" />
63+
64+
<!-- ProjectReference builds need the selected native library beside the managed assembly because NuGet runtime asset resolution is not involved. -->
65+
<None Update="runtimes/$(_WebGPUNativeRuntimeIdentifier)/native/*"
66+
CopyToOutputDirectory="PreserveNewest"
67+
CopyToPublishDirectory="PreserveNewest"
68+
TargetPath="%(Filename)%(Extension)" />
5669
</ItemGroup>
5770

5871
<ItemGroup>
5972
<!--
60-
Silk.NET packages are listed as NuGet dependencies so the native wgpu binaries
61-
and managed interop are restored for consumers. Compile assets are marked private
62-
so that ProjectReference consumers within this solution do not see Silk types
63-
or CS8002 warnings; however, NuGet package consumers will receive the full
64-
Silk packages as transitive dependencies.
73+
Silk.NET windowing packages remain NuGet dependencies so their managed runtimes and native
74+
assets are restored for consumers using the corresponding external-surface host. Compile assets
75+
are marked private so ProjectReference consumers do not see Silk types or CS8002 warnings; the
76+
packages remain transitive NuGet dependencies so their runtime assets reach package consumers.
6577
-->
66-
<PackageReference Include="Silk.NET.WebGPU" Version="2.23.0" PrivateAssets="compile;build;analyzers;contentfiles;buildtransitive" />
67-
<PackageReference Include="Silk.NET.WebGPU.Extensions.WGPU" Version="2.23.0" PrivateAssets="compile;build;analyzers;contentfiles;buildtransitive" />
68-
<PackageReference Include="Silk.NET.WebGPU.Native.WGPU" Version="2.23.0" PrivateAssets="compile;build;analyzers;contentfiles;buildtransitive" />
6978
<PackageReference Include="Silk.NET.Windowing.Glfw" Version="2.23.0" PrivateAssets="compile;build;analyzers;contentfiles;buildtransitive" />
79+
<PackageReference Include="Silk.NET.Windowing.Sdl" Version="2.23.0" PrivateAssets="compile;build;analyzers;contentfiles;buildtransitive" />
7080
</ItemGroup>
7181

7282
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
@@ -80,4 +90,17 @@
8090
<AdditionalFiles Include="Shaders\WgslSource\*.wgsl" />
8191
<AdditionalFiles Include="Shaders\WgslSource\Shared\*.wgsl" />
8292
</ItemGroup>
93+
94+
<!--
95+
Binding generation is explicit because the generated source is checked in. Keeping it out of
96+
the normal Build target avoids restoring ClangSharp or locating native C headers for consumers.
97+
-->
98+
<Target Name="GenerateWebGPUBindings">
99+
<Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="pwsh -NoProfile -NonInteractive -File &quot;$(MSBuildProjectDirectory)\Native\Bindings\Generate-WebGPUBindings.ps1&quot;" />
100+
</Target>
101+
102+
<!-- Native downloads are explicit so normal builds remain offline and use only restored assets. -->
103+
<Target Name="DownloadWebGPUNative">
104+
<Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="pwsh -NoProfile -NonInteractive -File &quot;$(MSBuildProjectDirectory)\Native\Bindings\Download-WebGPUNative.ps1&quot;" />
105+
</Target>
83106
</Project>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Runtime.InteropServices;
5+
6+
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
7+
8+
/// <summary>
9+
/// Attaches a Core Animation metal layer to a Cocoa window for WebGPU presentation.
10+
/// </summary>
11+
internal static partial class MacOSMetalLayerFactory
12+
{
13+
/// <summary>
14+
/// The Objective-C runtime library used to construct and attach the Core Animation layer.
15+
/// </summary>
16+
private const string ObjectiveCLibrary = "/usr/lib/libobjc.A.dylib";
17+
18+
/// <summary>
19+
/// Creates and attaches a metal layer to a Cocoa window.
20+
/// </summary>
21+
/// <param name="nsWindow">The Cocoa <c>NSWindow*</c>.</param>
22+
/// <returns>The attached <c>CAMetalLayer*</c>.</returns>
23+
/// <remarks>
24+
/// The returned layer carries the ownership acquired by <c>alloc/init</c>. Call <see cref="Release"/> after
25+
/// wgpu-native has created its surface; the AppKit content view retains the attached layer independently.
26+
/// </remarks>
27+
public static nint Create(nint nsWindow)
28+
{
29+
nint metalLayerClass = GetClass("CAMetalLayer");
30+
nint metalLayer = SendPointer(SendPointer(metalLayerClass, RegisterSelector("alloc")), RegisterSelector("init"));
31+
nint contentView = SendPointer(nsWindow, RegisterSelector("contentView"));
32+
33+
// WebGPU presents through a CAMetalLayer. AppKit creates a default backing layer only
34+
// after wantsLayer is enabled, so install the explicitly-created metal layer afterwards.
35+
SendByte(contentView, RegisterSelector("setWantsLayer:"), 1);
36+
SendPointer(contentView, RegisterSelector("setLayer:"), metalLayer);
37+
return metalLayer;
38+
}
39+
40+
/// <summary>
41+
/// Releases the factory's ownership of a metal layer after its AppKit view has retained it.
42+
/// </summary>
43+
/// <param name="metalLayer">The <c>CAMetalLayer*</c> to release.</param>
44+
public static void Release(nint metalLayer)
45+
=> SendVoid(metalLayer, RegisterSelector("release"));
46+
47+
/// <summary>
48+
/// Resolves an Objective-C class by name.
49+
/// </summary>
50+
/// <param name="name">The Objective-C class name.</param>
51+
/// <returns>The Objective-C class object.</returns>
52+
[LibraryImport(ObjectiveCLibrary, EntryPoint = "objc_getClass", StringMarshalling = StringMarshalling.Utf8)]
53+
private static partial nint GetClass(string name);
54+
55+
/// <summary>
56+
/// Registers an Objective-C selector by name.
57+
/// </summary>
58+
/// <param name="name">The selector name.</param>
59+
/// <returns>The registered selector.</returns>
60+
[LibraryImport(ObjectiveCLibrary, EntryPoint = "sel_registerName", StringMarshalling = StringMarshalling.Utf8)]
61+
private static partial nint RegisterSelector(string name);
62+
63+
/// <summary>
64+
/// Sends a parameterless Objective-C message that returns an object pointer.
65+
/// </summary>
66+
/// <param name="receiver">The receiving object or class.</param>
67+
/// <param name="selector">The selector to invoke.</param>
68+
/// <returns>The returned object pointer.</returns>
69+
[LibraryImport(ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
70+
private static partial nint SendPointer(nint receiver, nint selector);
71+
72+
/// <summary>
73+
/// Sends an Objective-C message with one object-pointer argument and no return value.
74+
/// </summary>
75+
/// <param name="receiver">The receiving object.</param>
76+
/// <param name="selector">The selector to invoke.</param>
77+
/// <param name="value">The object pointer passed to the selector.</param>
78+
[LibraryImport(ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
79+
private static partial void SendPointer(nint receiver, nint selector, nint value);
80+
81+
/// <summary>
82+
/// Sends an Objective-C message with one Boolean-sized argument and no return value.
83+
/// </summary>
84+
/// <param name="receiver">The receiving object.</param>
85+
/// <param name="selector">The selector to invoke.</param>
86+
/// <param name="value">The Objective-C <c>BOOL</c> value passed to the selector.</param>
87+
[LibraryImport(ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
88+
private static partial void SendByte(nint receiver, nint selector, byte value);
89+
90+
/// <summary>
91+
/// Sends a parameterless Objective-C message with no return value.
92+
/// </summary>
93+
/// <param name="receiver">The receiving object.</param>
94+
/// <param name="selector">The selector to invoke.</param>
95+
[LibraryImport(ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
96+
private static partial void SendVoid(nint receiver, nint selector);
97+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
5+
6+
/// <summary>
7+
/// Describes the CPU access requested when mapping a WebGPU buffer.
8+
/// </summary>
9+
[Flags]
10+
internal enum MapMode : ulong
11+
{
12+
/// <summary>
13+
/// No CPU mapping access is requested.
14+
/// </summary>
15+
None = 0,
16+
17+
/// <summary>
18+
/// CPU read access is requested.
19+
/// </summary>
20+
Read = 1,
21+
22+
/// <summary>
23+
/// CPU write access is requested.
24+
/// </summary>
25+
Write = 2
26+
}

0 commit comments

Comments
 (0)