Skip to content

Commit 0e4ebc5

Browse files
Include the WebGPU assembly in coverage and close the largest test gaps
Coverage previously excluded the entire WebGPU assembly via a temporary runsettings filter; the assembly is now measured, with generated interop bindings and embedded shader text excluded as non-executable. New tests pin the Region rect-set contract (previously untested), the DrawingClipState stack including inline-slot overflow, translation, transformation and bounds queries, and completeness of the environment error messages, which caught SoftwareAdapterUnsupported mapping to the unknown-error fallback.
1 parent c3a6909 commit 0e4ebc5

5 files changed

Lines changed: 553 additions & 5 deletions

File tree

src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ public static string CreateEnvironmentExceptionMessage(WebGPUEnvironmentError er
676676
WebGPUEnvironmentError.InstanceCreationFailed => "The WebGPU runtime could not create an instance.",
677677
WebGPUEnvironmentError.AdapterRequestTimedOut => "Timed out while waiting for the WebGPU adapter request callback.",
678678
WebGPUEnvironmentError.AdapterRequestFailed => "The WebGPU runtime failed to acquire a WebGPU adapter.",
679+
WebGPUEnvironmentError.SoftwareAdapterUnsupported => "The only available WebGPU adapter is a software rasterizer, which this backend does not support; use the CPU drawing backend instead.",
679680
WebGPUEnvironmentError.DeviceRequestTimedOut => "Timed out while waiting for the WebGPU device request callback.",
680681
WebGPUEnvironmentError.DeviceRequestFailed => "The WebGPU runtime failed to acquire a WebGPU device.",
681682
WebGPUEnvironmentError.QueueAcquisitionFailed => "The WebGPU runtime acquired a device but could not retrieve its default queue.",
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.Drawing.Processing.Backends;
5+
6+
namespace SixLabors.ImageSharp.Drawing.Tests.Processing.Backends;
7+
8+
public class WebGPUEnvironmentErrorTests
9+
{
10+
[Fact]
11+
public void EveryErrorValue_HasADedicatedExceptionMessage()
12+
{
13+
string fallback = WebGPURuntime.CreateEnvironmentExceptionMessage((WebGPUEnvironmentError)int.MaxValue);
14+
15+
foreach (WebGPUEnvironmentError error in Enum.GetValues<WebGPUEnvironmentError>())
16+
{
17+
if (error == WebGPUEnvironmentError.Success)
18+
{
19+
continue;
20+
}
21+
22+
string message = WebGPURuntime.CreateEnvironmentExceptionMessage(error);
23+
24+
Assert.False(string.IsNullOrWhiteSpace(message));
25+
Assert.NotEqual(fallback, message);
26+
}
27+
}
28+
}
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
using SixLabors.ImageSharp.Drawing.Processing;
6+
7+
namespace SixLabors.ImageSharp.Drawing.Tests.Processing;
8+
9+
public class DrawingClipStateTests
10+
{
11+
private static DrawingClipDescriptor Rectangle(float x, float y, float width, float height)
12+
=> DrawingClipDescriptor.CreateRectangle(
13+
new RectangleF(x, y, width, height),
14+
ClipOperation.Intersection,
15+
DrawingClipEdgeMode.Hard,
16+
0.5F);
17+
18+
[Fact]
19+
public void Empty_HasNoClips()
20+
{
21+
DrawingClipState state = DrawingClipState.Empty;
22+
23+
Assert.Equal(0, state.Count);
24+
Assert.False(state.HasClips);
25+
Assert.False(state.TryGetConservativeBounds(Point.Empty, out _));
26+
Assert.False(state.TryGetTargetBoundsClip(Point.Empty, out _));
27+
}
28+
29+
[Fact]
30+
public void FromPaths_EmptyList_ReturnsEmpty()
31+
{
32+
DrawingClipState state = DrawingClipState.FromPaths([], ClipOperation.Intersection, DrawingClipEdgeMode.Hard, 0.5F);
33+
34+
Assert.False(state.HasClips);
35+
}
36+
37+
[Fact]
38+
public void FromPaths_MultiplePaths_FormOneOperand()
39+
{
40+
IPath[] paths =
41+
[
42+
new RectanglePolygon(0, 0, 10, 10),
43+
new RectanglePolygon(20, 0, 10, 10),
44+
];
45+
46+
DrawingClipState state = DrawingClipState.FromPaths(paths, ClipOperation.Intersection, DrawingClipEdgeMode.Antialiased, 0.5F);
47+
48+
Assert.Equal(1, state.Count);
49+
Assert.Equal(2, state.GetDescriptor(0).Paths.Count);
50+
}
51+
52+
[Fact]
53+
public void AppendDescriptor_GrowsThroughInlineSlotsAndOverflow()
54+
{
55+
DrawingClipState state = DrawingClipState.Empty;
56+
for (int i = 0; i < 6; i++)
57+
{
58+
state = state.Append(Rectangle(i * 10, 0, 10, 10));
59+
60+
Assert.Equal(i + 1, state.Count);
61+
for (int j = 0; j <= i; j++)
62+
{
63+
Assert.Equal(new RectangleF(j * 10, 0, 10, 10), state.GetDescriptor(j).Rectangle);
64+
}
65+
}
66+
}
67+
68+
[Theory]
69+
[InlineData(1, 1)]
70+
[InlineData(1, 2)]
71+
[InlineData(2, 2)]
72+
[InlineData(3, 1)]
73+
[InlineData(2, 3)]
74+
[InlineData(4, 2)]
75+
public void AppendState_PreservesStackOrder(int firstCount, int secondCount)
76+
{
77+
DrawingClipState first = DrawingClipState.Empty;
78+
for (int i = 0; i < firstCount; i++)
79+
{
80+
first = first.Append(Rectangle(i * 10, 0, 10, 10));
81+
}
82+
83+
DrawingClipState second = DrawingClipState.Empty;
84+
for (int i = 0; i < secondCount; i++)
85+
{
86+
second = second.Append(Rectangle((firstCount + i) * 10, 0, 10, 10));
87+
}
88+
89+
DrawingClipState combined = first.Append(second);
90+
91+
Assert.Equal(firstCount + secondCount, combined.Count);
92+
for (int i = 0; i < combined.Count; i++)
93+
{
94+
Assert.Equal(new RectangleF(i * 10, 0, 10, 10), combined.GetDescriptor(i).Rectangle);
95+
}
96+
}
97+
98+
[Fact]
99+
public void AppendState_WithEmptyOperands_ReturnsOtherOperand()
100+
{
101+
DrawingClipState populated = DrawingClipState.Empty.Append(Rectangle(0, 0, 10, 10));
102+
103+
Assert.Equal(1, DrawingClipState.Empty.Append(populated).Count);
104+
Assert.Equal(1, populated.Append(DrawingClipState.Empty).Count);
105+
}
106+
107+
[Theory]
108+
[InlineData(1)]
109+
[InlineData(2)]
110+
[InlineData(3)]
111+
[InlineData(4)]
112+
[InlineData(5)]
113+
public void Translate_ShiftsEveryDescriptor(int count)
114+
{
115+
DrawingClipState state = DrawingClipState.Empty;
116+
for (int i = 0; i < count; i++)
117+
{
118+
state = state.Append(Rectangle(i * 10, 0, 10, 10));
119+
}
120+
121+
DrawingClipState translated = state.Translate(new Vector2(5, 7));
122+
123+
Assert.Equal(count, translated.Count);
124+
for (int i = 0; i < count; i++)
125+
{
126+
Assert.Equal(new RectangleF((i * 10) + 5, 7, 10, 10), translated.GetDescriptor(i).Rectangle);
127+
}
128+
}
129+
130+
[Fact]
131+
public void Translate_ZeroOffset_ReturnsUnchangedState()
132+
{
133+
DrawingClipState state = DrawingClipState.Empty.Append(Rectangle(1, 2, 3, 4));
134+
DrawingClipState translated = state.Translate(Vector2.Zero);
135+
136+
Assert.Equal(state.GetDescriptor(0).Rectangle, translated.GetDescriptor(0).Rectangle);
137+
}
138+
139+
[Theory]
140+
[InlineData(1)]
141+
[InlineData(4)]
142+
[InlineData(5)]
143+
public void Transform_TranslationMatrix_ShiftsEveryDescriptor(int count)
144+
{
145+
DrawingClipState state = DrawingClipState.Empty;
146+
for (int i = 0; i < count; i++)
147+
{
148+
state = state.Append(Rectangle(i * 10, 0, 10, 10));
149+
}
150+
151+
DrawingClipState transformed = state.Transform(Matrix4x4.CreateTranslation(5, 7, 0));
152+
153+
Assert.Equal(count, transformed.Count);
154+
for (int i = 0; i < count; i++)
155+
{
156+
Assert.Equal(new RectangleF((i * 10) + 5, 7, 10, 10), transformed.GetDescriptor(i).Rectangle);
157+
}
158+
}
159+
160+
[Fact]
161+
public void Transform_Identity_ReturnsUnchangedState()
162+
{
163+
DrawingClipState state = DrawingClipState.Empty.Append(Rectangle(1, 2, 3, 4));
164+
DrawingClipState transformed = state.Transform(Matrix4x4.Identity);
165+
166+
Assert.Equal(state.GetDescriptor(0).Rectangle, transformed.GetDescriptor(0).Rectangle);
167+
}
168+
169+
[Fact]
170+
public void TryGetConservativeBounds_IntersectsIntersectionDescriptors()
171+
{
172+
DrawingClipState state = DrawingClipState.Empty
173+
.Append(Rectangle(0, 0, 20, 20))
174+
.Append(Rectangle(10, 10, 20, 20));
175+
176+
Assert.True(state.TryGetConservativeBounds(new Point(100, 200), out Rectangle bounds));
177+
Assert.Equal(new Rectangle(110, 210, 10, 10), bounds);
178+
}
179+
180+
[Fact]
181+
public void TryGetConservativeBounds_IgnoresDifferenceDescriptors()
182+
{
183+
DrawingClipDescriptor difference = DrawingClipDescriptor.CreateRectangle(
184+
new RectangleF(0, 0, 10, 10),
185+
ClipOperation.Difference,
186+
DrawingClipEdgeMode.Hard,
187+
0.5F);
188+
DrawingClipState state = DrawingClipState.Empty.Append(difference);
189+
190+
Assert.False(state.TryGetConservativeBounds(Point.Empty, out _));
191+
}
192+
193+
[Fact]
194+
public void TryGetTargetBoundsClip_PixelAlignedSingleRectangle_ReturnsOffsetBounds()
195+
{
196+
DrawingClipState state = DrawingClipState.Empty.Append(Rectangle(10, 20, 30, 40));
197+
198+
Assert.True(state.TryGetTargetBoundsClip(new Point(1, 2), out Rectangle bounds));
199+
Assert.Equal(new Rectangle(11, 22, 30, 40), bounds);
200+
}
201+
202+
[Fact]
203+
public void TryGetTargetBoundsClip_FractionalRectangle_ReturnsFalse()
204+
{
205+
DrawingClipState state = DrawingClipState.Empty.Append(Rectangle(10.5F, 20, 30, 40));
206+
207+
Assert.False(state.TryGetTargetBoundsClip(Point.Empty, out _));
208+
}
209+
210+
[Fact]
211+
public void TryGetTargetBoundsClip_MultipleDescriptors_ReturnsFalse()
212+
{
213+
DrawingClipState state = DrawingClipState.Empty
214+
.Append(Rectangle(0, 0, 10, 10))
215+
.Append(Rectangle(0, 0, 10, 10));
216+
217+
Assert.False(state.TryGetTargetBoundsClip(Point.Empty, out _));
218+
}
219+
220+
[Fact]
221+
public void TryGetTargetBoundsClip_DifferenceOperation_ReturnsFalse()
222+
{
223+
DrawingClipDescriptor difference = DrawingClipDescriptor.CreateRectangle(
224+
new RectangleF(0, 0, 10, 10),
225+
ClipOperation.Difference,
226+
DrawingClipEdgeMode.Hard,
227+
0.5F);
228+
DrawingClipState state = DrawingClipState.Empty.Append(difference);
229+
230+
Assert.False(state.TryGetTargetBoundsClip(Point.Empty, out _));
231+
}
232+
233+
[Fact]
234+
public void TryGetTargetBoundsClip_SingleRectangleIntegerRegion_ReturnsOffsetBounds()
235+
{
236+
DrawingClipDescriptor region = DrawingClipDescriptor.CreateIntegerRegion(
237+
[new Rectangle(10, 20, 30, 40)],
238+
ClipOperation.Intersection,
239+
0.5F);
240+
DrawingClipState state = DrawingClipState.Empty.Append(region);
241+
242+
Assert.True(state.TryGetTargetBoundsClip(new Point(1, 2), out Rectangle bounds));
243+
Assert.Equal(new Rectangle(11, 22, 30, 40), bounds);
244+
}
245+
246+
[Fact]
247+
public void TryGetTargetBoundsClip_MultiRectangleIntegerRegion_ReturnsFalse()
248+
{
249+
DrawingClipDescriptor region = DrawingClipDescriptor.CreateIntegerRegion(
250+
[new Rectangle(0, 0, 10, 10), new Rectangle(20, 0, 10, 10)],
251+
ClipOperation.Intersection,
252+
0.5F);
253+
DrawingClipState state = DrawingClipState.Empty.Append(region);
254+
255+
Assert.False(state.TryGetTargetBoundsClip(Point.Empty, out _));
256+
}
257+
}

0 commit comments

Comments
 (0)