Skip to content

Commit 20fb019

Browse files
Use midpoint rounding for SIMD. And add feature tests
1 parent 775c95e commit 20fb019

File tree

6 files changed

+649
-82
lines changed

6 files changed

+649
-82
lines changed

src/ImageSharp.Drawing/Shapes/Rasterization/ScanEdgeCollection.Build.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ static void RoundY(ReadOnlySpan<PointF> vertices, Span<float> destination, float
103103

104104
Vector256<float> ssRatio = Vector256.Create(subsamplingRatio);
105105
Vector256<float> inverseSsRatio = Vector256.Create(1F / subsamplingRatio);
106+
Vector256<float> half = Vector256.Create(.5F);
106107

107108
// For every 1 vector we add to the destination we read 2 from the vertices.
108109
for (nint i = 0, j = 0; i < maxIterations; i++, j += 2)
@@ -118,7 +119,7 @@ static void RoundY(ReadOnlySpan<PointF> vertices, Span<float> destination, float
118119

119120
// Multiply by the subsampling ratio, round, then multiply by the inverted subsampling ratio and assign.
120121
// https://www.ocf.berkeley.edu/~horie/rounding.html
121-
Vector256<float> rounded = Avx.RoundToPositiveInfinity(Avx.Multiply(pointsY, ssRatio));
122+
Vector256<float> rounded = Avx.RoundToPositiveInfinity(Avx.Subtract(Avx.Multiply(pointsY, ssRatio), half));
122123
Unsafe.Add(ref destinationBase, i) = Avx.Multiply(rounded, inverseSsRatio);
123124
}
124125
}
@@ -140,6 +141,7 @@ static void RoundY(ReadOnlySpan<PointF> vertices, Span<float> destination, float
140141

141142
Vector128<float> ssRatio = Vector128.Create(subsamplingRatio);
142143
Vector128<float> inverseSsRatio = Vector128.Create(1F / subsamplingRatio);
144+
Vector128<float> half = Vector128.Create(.5F);
143145

144146
// For every 1 vector we add to the destination we read 2 from the vertices.
145147
for (nint i = 0, j = 0; i < maxIterations; i++, j += 2)
@@ -149,18 +151,17 @@ static void RoundY(ReadOnlySpan<PointF> vertices, Span<float> destination, float
149151
Vector128<float> points2 = Unsafe.Add(ref sourceBase, j + 1);
150152

151153
// Shuffle the points to group the Y properties
152-
Vector128<float> points1Y = Sse.Shuffle(points1, points1, 0b11_01_11_01);
153-
Vector128<float> points2Y = Sse.Shuffle(points2, points2, 0b11_01_11_01);
154-
Vector128<float> pointsY = Vector128.Create(points1Y.GetLower(), points2Y.GetLower());
154+
Vector128<float> pointsY = Sse.Shuffle(points1, points2, 0b11_01_11_01);
155155

156156
// Multiply by the subsampling ratio, round, then multiply by the inverted subsampling ratio and assign.
157157
// https://www.ocf.berkeley.edu/~horie/rounding.html
158-
Vector128<float> rounded = Sse41.RoundToPositiveInfinity(Sse.Multiply(pointsY, ssRatio));
158+
Vector128<float> rounded = Sse41.RoundToPositiveInfinity(Sse.Subtract(Sse.Multiply(pointsY, ssRatio), half));
159159
Unsafe.Add(ref destinationBase, i) = Sse.Multiply(rounded, inverseSsRatio);
160160
}
161161
}
162162
}
163163

164+
// TODO: Arm64
164165
for (; ri < vertices.Length; ri++)
165166
{
166167
destination[ri] = MathF.Round(vertices[ri].Y * subsamplingRatio, MidpointRounding.AwayFromZero) / subsamplingRatio;

tests/Directory.Build.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
<!-- Test Dependencies -->
2121
<PackageReference Update="BenchmarkDotNet" Version="0.13.1" />
2222
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="8.3.3" />
23+
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.21311.3" />
24+
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.21311.3" />
2325
<PackageReference Update="Moq" Version="4.16.1" />
2426
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="5.8.64" Condition="'$(IsOSX)'=='true'" />
2527
<PackageReference Update="System.Drawing.Common" Version="5.0.2" />

tests/ImageSharp.Drawing.Tests/ImageSharp.Drawing.Tests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<LangVersion>latest</LangVersion>
77
<DebugSymbols>True</DebugSymbols>
88
<AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName>
9-
<Platforms>AnyCPU;x64;x86</Platforms>
9+
<Platforms>AnyCPU;x64;x86;ARM64</Platforms>
1010
<RootNamespace>SixLabors.ImageSharp.Drawing.Tests</RootNamespace>
1111
</PropertyGroup>
1212

@@ -25,6 +25,8 @@
2525

2626
<ItemGroup>
2727
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
28+
<PackageReference Include="Microsoft.DotNet.RemoteExecutor" />
29+
<PackageReference Include="Microsoft.DotNet.XUnitExtensions" />
2830
<PackageReference Include="Moq" />
2931
<PackageReference Include="System.Drawing.Common" />
3032
<PackageReference Include="GeoJSON.Net" />

tests/ImageSharp.Drawing.Tests/Shapes/Scan/ScanEdgeCollectionTests.cs

Lines changed: 81 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ public class ScanEdgeCollectionTests
1111
{
1212
private static MemoryAllocator MemoryAllocator => Configuration.Default.MemoryAllocator;
1313

14-
private static readonly DebugDraw DebugDraw = new DebugDraw(nameof(ScanEdgeCollectionTests));
14+
private static readonly DebugDraw DebugDraw = new(nameof(ScanEdgeCollectionTests));
1515

16-
private void VerifyEdge(
16+
private static void VerifyEdge(
1717
ScanEdgeCollection edges,
1818
float y0,
1919
float y1,
@@ -46,60 +46,65 @@ private void VerifyEdge(
4646
[ValidateDisposedMemoryAllocations]
4747
public void SimplePolygon_AllEmitCases()
4848
{
49-
// see: SimplePolygon_AllEmitCases.png
50-
Polygon polygon = PolygonFactory.CreatePolygon(
51-
(1, 2),
52-
(2, 2),
53-
(3, 1),
54-
(4, 3),
55-
(6, 1),
56-
(7, 2),
57-
(8, 2),
58-
(9, 3),
59-
(9, 4),
60-
(10, 5),
61-
(9, 6),
62-
(8, 6),
63-
(8, 7),
64-
(9, 7),
65-
(9, 8),
66-
(7, 8),
67-
(6, 7),
68-
(5, 8),
69-
(4, 7),
70-
(3, 8),
71-
(2, 8),
72-
(2, 6),
73-
(3, 5),
74-
(2, 5),
75-
(2, 4),
76-
(1, 3));
77-
78-
DebugDraw.Polygon(polygon, 1, 100);
49+
static void RunTest()
50+
{
51+
// see: SimplePolygon_AllEmitCases.png
52+
Polygon polygon = PolygonFactory.CreatePolygon(
53+
(1, 2),
54+
(2, 2),
55+
(3, 1),
56+
(4, 3),
57+
(6, 1),
58+
(7, 2),
59+
(8, 2),
60+
(9, 3),
61+
(9, 4),
62+
(10, 5),
63+
(9, 6),
64+
(8, 6),
65+
(8, 7),
66+
(9, 7),
67+
(9, 8),
68+
(7, 8),
69+
(6, 7),
70+
(5, 8),
71+
(4, 7),
72+
(3, 8),
73+
(2, 8),
74+
(2, 6),
75+
(3, 5),
76+
(2, 5),
77+
(2, 4),
78+
(1, 3));
79+
80+
DebugDraw.Polygon(polygon, 1, 100);
81+
82+
using ScanEdgeCollection edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
83+
84+
Assert.Equal(19, edges.Edges.Length);
85+
86+
VerifyEdge(edges, 1f, 2f, (2.5f, 1.5f), 1, 2, true);
87+
VerifyEdge(edges, 1f, 3f, (3.5f, 2f), 1, 1, false);
88+
VerifyEdge(edges, 1f, 3f, (5f, 2f), 1, 1, true);
89+
VerifyEdge(edges, 1f, 2f, (6.5f, 1.5f), 1, 2, false);
90+
VerifyEdge(edges, 2f, 3f, (8.5f, 2.5f), 1, 0, false);
91+
VerifyEdge(edges, 3f, 4f, (9f, 3.5f), 1, 0, false);
92+
VerifyEdge(edges, 4f, 5f, (9.5f, 4.5f), 1, 0, false);
93+
VerifyEdge(edges, 5f, 6f, (9.5f, 5.5f), 1, 1, false);
94+
VerifyEdge(edges, 6f, 7f, (8f, 6.5f), 2, 2, false);
95+
VerifyEdge(edges, 7f, 8f, (9f, 7.5f), 1, 1, false);
96+
VerifyEdge(edges, 7f, 8f, (6.5f, 7.5f), 1, 1, true);
97+
VerifyEdge(edges, 7f, 8f, (5.5f, 7.5f), 1, 1, false);
98+
VerifyEdge(edges, 7f, 8f, (4.5f, 7.5f), 1, 1, true);
99+
VerifyEdge(edges, 7f, 8f, (3.5f, 7.5f), 1, 1, false);
100+
VerifyEdge(edges, 6f, 8f, (2f, 7f), 0, 1, true);
101+
VerifyEdge(edges, 5f, 6f, (2.5f, 5.5f), 2, 1, true);
102+
VerifyEdge(edges, 4f, 5f, (2f, 4.5f), 0, 1, true);
103+
VerifyEdge(edges, 3f, 4f, (1.5f, 3.5f), 0, 1, true);
104+
VerifyEdge(edges, 2f, 3f, (1f, 1.5f), 1, 1, true);
105+
}
79106

80-
using var edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
81-
82-
Assert.Equal(19, edges.Edges.Length);
83-
84-
this.VerifyEdge(edges, 1f, 2f, (2.5f, 1.5f), 1, 2, true);
85-
this.VerifyEdge(edges, 1f, 3f, (3.5f, 2f), 1, 1, false);
86-
this.VerifyEdge(edges, 1f, 3f, (5f, 2f), 1, 1, true);
87-
this.VerifyEdge(edges, 1f, 2f, (6.5f, 1.5f), 1, 2, false);
88-
this.VerifyEdge(edges, 2f, 3f, (8.5f, 2.5f), 1, 0, false);
89-
this.VerifyEdge(edges, 3f, 4f, (9f, 3.5f), 1, 0, false);
90-
this.VerifyEdge(edges, 4f, 5f, (9.5f, 4.5f), 1, 0, false);
91-
this.VerifyEdge(edges, 5f, 6f, (9.5f, 5.5f), 1, 1, false);
92-
this.VerifyEdge(edges, 6f, 7f, (8f, 6.5f), 2, 2, false);
93-
this.VerifyEdge(edges, 7f, 8f, (9f, 7.5f), 1, 1, false);
94-
this.VerifyEdge(edges, 7f, 8f, (6.5f, 7.5f), 1, 1, true);
95-
this.VerifyEdge(edges, 7f, 8f, (5.5f, 7.5f), 1, 1, false);
96-
this.VerifyEdge(edges, 7f, 8f, (4.5f, 7.5f), 1, 1, true);
97-
this.VerifyEdge(edges, 7f, 8f, (3.5f, 7.5f), 1, 1, false);
98-
this.VerifyEdge(edges, 6f, 8f, (2f, 7f), 0, 1, true);
99-
this.VerifyEdge(edges, 5f, 6f, (2.5f, 5.5f), 2, 1, true);
100-
this.VerifyEdge(edges, 4f, 5f, (2f, 4.5f), 0, 1, true);
101-
this.VerifyEdge(edges, 3f, 4f, (1.5f, 3.5f), 0, 1, true);
102-
this.VerifyEdge(edges, 2f, 3f, (1f, 1.5f), 1, 1, true);
107+
FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE41);
103108
}
104109

105110
[Fact]
@@ -113,55 +118,55 @@ public void ComplexPolygon()
113118
IPath polygon = contour.Clip(hole);
114119
DebugDraw.Polygon(polygon, 1, 100);
115120

116-
using var edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
121+
using ScanEdgeCollection edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
117122

118123
Assert.Equal(8, edges.Count);
119124

120-
this.VerifyEdge(edges, 1, 4, (1, 2), 1, 1, true);
121-
this.VerifyEdge(edges, 1, 2, (4, 1.5f), 1, 2, false);
122-
this.VerifyEdge(edges, 4, 5, (2, 4.5f), 2, 1, true);
123-
this.VerifyEdge(edges, 2, 5, (5, 3f), 1, 1, false);
125+
VerifyEdge(edges, 1, 4, (1, 2), 1, 1, true);
126+
VerifyEdge(edges, 1, 2, (4, 1.5f), 1, 2, false);
127+
VerifyEdge(edges, 4, 5, (2, 4.5f), 2, 1, true);
128+
VerifyEdge(edges, 2, 5, (5, 3f), 1, 1, false);
124129

125-
this.VerifyEdge(edges, 2, 3, (2, 2.5f), 2, 2, false);
126-
this.VerifyEdge(edges, 2, 3, (3.5f, 2.5f), 2, 1, true);
127-
this.VerifyEdge(edges, 3, 4, (3, 3.5f), 1, 2, false);
128-
this.VerifyEdge(edges, 3, 4, (4, 3.5f), 0, 2, true);
130+
VerifyEdge(edges, 2, 3, (2, 2.5f), 2, 2, false);
131+
VerifyEdge(edges, 2, 3, (3.5f, 2.5f), 2, 1, true);
132+
VerifyEdge(edges, 3, 4, (3, 3.5f), 1, 2, false);
133+
VerifyEdge(edges, 3, 4, (4, 3.5f), 0, 2, true);
129134
}
130135

131136
[Fact]
132137
public void NumericCornerCase_C()
133138
{
134-
using var edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.C, MemoryAllocator, 4);
139+
using ScanEdgeCollection edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.C, MemoryAllocator, 4);
135140
Assert.Equal(2, edges.Count);
136-
this.VerifyEdge(edges, 3.5f, 4f, (2f, 3.75f), 1, 1, true);
137-
this.VerifyEdge(edges, 3.5f, 4f, (8f, 3.75f), 1, 1, false);
141+
VerifyEdge(edges, 3.5f, 4f, (2f, 3.75f), 1, 1, true);
142+
VerifyEdge(edges, 3.5f, 4f, (8f, 3.75f), 1, 1, false);
138143
}
139144

140145
[Fact]
141146
public void NumericCornerCase_D()
142147
{
143-
using var edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.D, MemoryAllocator, 4);
148+
using ScanEdgeCollection edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.D, MemoryAllocator, 4);
144149
Assert.Equal(5, edges.Count);
145150

146-
this.VerifyEdge(edges, 3.25f, 4f, (12f, 3.75f), 1, 1, true);
147-
this.VerifyEdge(edges, 3.25f, 3.5f, (15f, 3.375f), 1, 0, false);
148-
this.VerifyEdge(edges, 3.5f, 4f, (18f, 3.75f), 1, 1, false);
151+
VerifyEdge(edges, 3.25f, 4f, (12f, 3.75f), 1, 1, true);
152+
VerifyEdge(edges, 3.25f, 3.5f, (15f, 3.375f), 1, 0, false);
153+
VerifyEdge(edges, 3.5f, 4f, (18f, 3.75f), 1, 1, false);
149154

150155
// TODO: verify 2 more edges
151156
}
152157

153158
[Fact]
154159
public void NumericCornerCase_H_ShouldCollapseNearZeroEdge()
155160
{
156-
using var edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.H, MemoryAllocator, 4);
161+
using ScanEdgeCollection edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.H, MemoryAllocator, 4);
157162

158163
Assert.Equal(3, edges.Count);
159-
this.VerifyEdge(edges, 1.75f, 2f, (15f, 1.875f), 1, 1, true);
160-
this.VerifyEdge(edges, 1.75f, 2.25f, (16f, 2f), 1, 1, false);
164+
VerifyEdge(edges, 1.75f, 2f, (15f, 1.875f), 1, 1, true);
165+
VerifyEdge(edges, 1.75f, 2.25f, (16f, 2f), 1, 1, false);
161166

162167
// this places two dummy points:
163-
this.VerifyEdge(edges, 2f, 2.25f, (15f, 2.125f), 2, 1, true);
168+
VerifyEdge(edges, 2f, 2.25f, (15f, 2.125f), 2, 1, true);
164169
}
165170

166-
private static FuzzyFloat F(float value, float eps) => new FuzzyFloat(value, eps);
171+
private static FuzzyFloat F(float value, float eps) => new(value, eps);
167172
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.ComponentModel;
5+
using Xunit.Abstractions;
6+
7+
namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities;
8+
9+
/// <summary>
10+
/// RemoteExecutor can only execute static methods, which can only consume string arguments,
11+
/// because data is being passed on command line interface. This utility allows serialization
12+
/// of <see cref="IXunitSerializable"/> types to strings.
13+
/// </summary>
14+
internal class BasicSerializer : IXunitSerializationInfo
15+
{
16+
private readonly Dictionary<string, string> map = new();
17+
18+
public const char Separator = ':';
19+
20+
private string DumpToString(Type type)
21+
{
22+
using MemoryStream ms = new();
23+
using StreamWriter writer = new(ms);
24+
writer.WriteLine(type.FullName);
25+
foreach (KeyValuePair<string, string> kv in this.map)
26+
{
27+
writer.WriteLine($"{kv.Key}{Separator}{kv.Value}");
28+
}
29+
30+
writer.Flush();
31+
byte[] data = ms.ToArray();
32+
return Convert.ToBase64String(data);
33+
}
34+
35+
private Type LoadDump(string dump)
36+
{
37+
byte[] data = Convert.FromBase64String(dump);
38+
39+
using MemoryStream ms = new(data);
40+
using StreamReader reader = new(ms);
41+
Type type = Type.GetType(reader.ReadLine());
42+
for (string s = reader.ReadLine(); s != null; s = reader.ReadLine())
43+
{
44+
string[] kv = s.Split(Separator);
45+
this.map[kv[0]] = kv[1];
46+
}
47+
48+
return type;
49+
}
50+
51+
public static string Serialize(IXunitSerializable serializable)
52+
{
53+
BasicSerializer serializer = new();
54+
serializable.Serialize(serializer);
55+
return serializer.DumpToString(serializable.GetType());
56+
}
57+
58+
public static T Deserialize<T>(string dump)
59+
where T : IXunitSerializable
60+
{
61+
BasicSerializer serializer = new();
62+
Type type = serializer.LoadDump(dump);
63+
64+
T result = (T)Activator.CreateInstance(type);
65+
result.Deserialize(serializer);
66+
return result;
67+
}
68+
69+
public void AddValue(string key, object value, Type type = null)
70+
{
71+
if (value == null)
72+
{
73+
return;
74+
}
75+
76+
type ??= value.GetType();
77+
78+
this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value);
79+
}
80+
81+
public object GetValue(string key, Type type)
82+
{
83+
if (!this.map.TryGetValue(key, out string str))
84+
{
85+
return type.IsValueType ? Activator.CreateInstance(type) : null;
86+
}
87+
88+
return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str);
89+
}
90+
91+
public T GetValue<T>(string key) => (T)this.GetValue(key, typeof(T));
92+
}

0 commit comments

Comments
 (0)