Skip to content

Commit f1883d1

Browse files
committed
feat: Use SIMD to compage images in a test to accelerate builds.
1 parent df20ef5 commit f1883d1

File tree

1 file changed

+59
-46
lines changed

1 file changed

+59
-46
lines changed

src/Uno.UI.TestComparer/Comparer/TestFilesComparer.cs

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Globalization;
44
using System.IO;
55
using System.Linq;
6+
using System.Numerics;
67
using System.Security.Cryptography;
78
using System.Text;
89
using System.Threading.Tasks;
@@ -24,10 +25,10 @@ internal CompareResult Compare(string[] artifacts)
2425
{
2526
var testResult = new CompareResult(_platform);
2627

27-
string path = _basePath;
28+
var path = _basePath;
2829
var resultsId = $"{DateTime.Now:yyyyMMdd-hhmmss}";
29-
string diffPath = Path.Combine(_basePath, $"Results-{_platform}-{resultsId}");
30-
string resultsFilePath = Path.Combine(_basePath, $"Results-{_platform}-{resultsId}.html");
30+
var diffPath = Path.Combine(_basePath, $"Results-{_platform}-{resultsId}");
31+
var resultsFilePath = Path.Combine(_basePath, $"Results-{_platform}-{resultsId}.html");
3132

3233
Directory.CreateDirectory(path);
3334
Directory.CreateDirectory(diffPath);
@@ -97,7 +98,7 @@ select testFileIncrements
9798
return a;
9899
}
99100

100-
var otherMatch = a.Reverse().Where(i => i.IdSha != null).FirstOrDefault();
101+
var otherMatch = a.Reverse().FirstOrDefault(i => i.IdSha != null);
101102
if (platformFiles.FileInfo?.Id.sha != otherMatch?.IdSha)
102103
{
103104
return a
@@ -114,7 +115,7 @@ select testFileIncrements
114115
)
115116
).ToArray();
116117

117-
var hasChanges = increments.Any(i => i.Where(v => v.IdSha != null).Count() - 1 > 1);
118+
var hasChanges = increments.Any(i => i.Count(v => v.IdSha != null) - 1 > 1);
118119

119120
var compareResultFile = new CompareResultFile();
120121
compareResultFile.HasChanged = hasChanges;
@@ -126,13 +127,13 @@ select testFileIncrements
126127

127128
var firstFolder = changeResult.Where(i => i.FolderIndex != -1).Min(i => i.FolderIndex);
128129

129-
for (int folderIndex = 0; folderIndex < allFolders.Length; folderIndex++)
130+
for (var folderIndex = 0; folderIndex < allFolders.Length; folderIndex++)
130131
{
131132
var folderInfo = changeResult.FirstOrDefault(inc => inc.FolderIndex == folderIndex);
132133

133134
if (folderInfo != null)
134135
{
135-
var hasChangedFromPrevious = folderIndex != firstFolder && folderInfo?.IdSha != null;
136+
var hasChangedFromPrevious = folderIndex != firstFolder && folderInfo.IdSha != null;
136137

137138
var compareResultFileRun = new CompareResultFileRun();
138139
compareResultFile.ResultRun.Add(compareResultFileRun);
@@ -144,37 +145,34 @@ select testFileIncrements
144145

145146
compareResultFileRun.HasChanged = hasChangedFromPrevious;
146147

147-
if (folderInfo != null)
148+
var previousFolderInfo = changeResult.FirstOrDefault(inc => inc.FolderIndex == folderIndex - 1);
149+
if (hasChangedFromPrevious && previousFolderInfo != null)
148150
{
149-
var previousFolderInfo = changeResult.FirstOrDefault(inc => inc.FolderIndex == folderIndex - 1);
150-
if (hasChangedFromPrevious && previousFolderInfo != null)
151+
try
151152
{
152-
try
153-
{
154-
var currentImage = DecodeImage(folderInfo.Path);
155-
var previousImage = DecodeImage(previousFolderInfo.Path);
156-
157-
if (currentImage.pixels.Length == previousImage.pixels.Length)
158-
{
159-
var diff = DiffImages(currentImage.pixels, previousImage.pixels, currentImage.frame.Format.BitsPerPixel / 8);
153+
var currentImage = DecodeImage(folderInfo.Path);
154+
var previousImage = DecodeImage(previousFolderInfo.Path);
160155

161-
var diffFilePath = Path.Combine(diffPath, $"{folderInfo.Id}-{folderInfo.CompareeId}.png");
162-
WriteImage(diffFilePath, diff, currentImage.frame, currentImage.stride);
156+
if (currentImage.pixels.Length == previousImage.pixels.Length)
157+
{
158+
var diff = DiffImages(currentImage.pixels, previousImage.pixels, currentImage.frame.Format.BitsPerPixel / 8);
163159

164-
compareResultFileRun.DiffResultImage = diffFilePath;
165-
}
160+
var diffFilePath = Path.Combine(diffPath, $"{folderInfo.Id}-{folderInfo.CompareeId}.png");
161+
WriteImage(diffFilePath, diff, currentImage.frame, currentImage.stride);
166162

167-
changedList.Add(testFile);
163+
compareResultFileRun.DiffResultImage = diffFilePath;
168164
}
169-
catch (Exception ex)
170-
{
171-
Helpers.WriteLineWithTime($"[ERROR] Failed to process [{folderInfo.Path}] and [{previousFolderInfo.Path}]\n({ex})");
172-
}
173-
}
174165

175-
GC.Collect(2, GCCollectionMode.Forced);
176-
GC.WaitForPendingFinalizers();
166+
changedList.Add(testFile);
167+
}
168+
catch (Exception ex)
169+
{
170+
Helpers.WriteLineWithTime($"[ERROR] Failed to process [{folderInfo.Path}] and [{previousFolderInfo.Path}]\n({ex})");
171+
}
177172
}
173+
174+
GC.Collect(2, GCCollectionMode.Forced);
175+
GC.WaitForPendingFinalizers();
178176
}
179177
}
180178
}
@@ -187,7 +185,7 @@ select testFileIncrements
187185

188186
private bool CanBeUsedForCompare(string sample)
189187
{
190-
if (ReadScreenshotMetadata(sample) is IDictionary<string, string> options)
188+
if (ReadScreenshotMetadata(sample) is { } options)
191189
{
192190
if (options.TryGetValue("IgnoreInSnapshotCompare", out var ignore) && ignore.Equals("true", StringComparison.OrdinalIgnoreCase))
193191
{
@@ -200,7 +198,8 @@ private bool CanBeUsedForCompare(string sample)
200198

201199
private static IDictionary<string, string> ReadScreenshotMetadata(string sample)
202200
{
203-
var metadataFile = Path.Combine(Path.GetDirectoryName(sample), Path.GetFileNameWithoutExtension(sample) + ".metadata");
201+
var metadataFile = Path.Combine(Path.GetDirectoryName(sample),
202+
$"{Path.GetFileNameWithoutExtension(sample)}.metadata");
204203

205204
if (File.Exists(metadataFile))
206205
{
@@ -222,7 +221,7 @@ private static IDictionary<string, string> ReadScreenshotMetadata(string sample)
222221
{
223222
using (var sha1 = SHA1.Create())
224223
{
225-
using (var file = File.OpenRead(@"\\?\" + sample))
224+
using (var file = File.OpenRead($@"\\?\{sample}"))
226225
{
227226
var data = sha1.ComputeHash(file);
228227

@@ -232,7 +231,7 @@ private static IDictionary<string, string> ReadScreenshotMetadata(string sample)
232231

233232
// Loop through each byte of the hashed data
234233
// and format each one as a hexadecimal string.
235-
for (int i = 0; i < data.Length; i++)
234+
for (var i = 0; i < data.Length; i++)
236235
{
237236
sBuilder.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
238237
}
@@ -261,7 +260,7 @@ private IEnumerable<string> EnumerateFiles(string path, string pattern)
261260
}
262261
else
263262
{
264-
return new string[0];
263+
return [];
265264
}
266265
}
267266

@@ -289,30 +288,45 @@ private void WriteImage(string diffPath, byte[] diff, BitmapFrame frameInfo, int
289288
}
290289
}
291290

292-
private byte[] DiffImages(byte[] currentImage, byte[] previousImage, int pixelSize)
291+
private byte[] DiffImages(ReadOnlySpan<byte> currentImage, ReadOnlySpan<byte> previousImage, int pixelSize)
293292
{
294-
for (int i = 0; i < currentImage.Length; i++)
293+
var length = currentImage.Length;
294+
var resultImage = new byte[length];
295+
var vectorSize = Vector<byte>.Count;
296+
var i = 0;
297+
298+
// Apply XOR with SIMD on blocks of size 'vectorSize'
299+
for (; i <= length - vectorSize; i += vectorSize)
295300
{
296-
currentImage[i] = (byte)(currentImage[i] ^ previousImage[i]);
301+
var currentVector = new Vector<byte>(currentImage.Slice(i, vectorSize));
302+
var previousVector = new Vector<byte>(previousImage.Slice(i, vectorSize));
303+
var resultVector = currentVector ^ previousVector;
304+
resultVector.CopyTo(resultImage.AsSpan(i, vectorSize));
297305
}
298306

307+
// Process remaining pixels without SIMD if necessary
308+
for (; i < length; i++)
309+
{
310+
resultImage[i] = (byte)(currentImage[i] ^ previousImage[i]);
311+
}
312+
313+
// If pixelSize is 4, force opacity on every fourth byte
299314
if (pixelSize == 4)
300315
{
301-
// Force result to be opaque
302-
for (int i = 0; i < currentImage.Length; i += 4)
316+
for (i = 3; i < length; i += 4)
303317
{
304-
currentImage[i + 3] = 0xFF;
318+
resultImage[i] = 0xFF;
305319
}
306320
}
307321

308-
return currentImage;
322+
return resultImage;
309323
}
310324

311325
private (BitmapFrame frame, byte[] pixels, int stride) DecodeImage(string path1)
312326
{
313327
try
314328
{
315-
using (Stream imageStreamSource = new FileStream(@"\\?\" + path1, FileMode.Open, FileAccess.Read, FileShare.Read))
329+
using (Stream imageStreamSource = new FileStream($@"\\?\{path1}", FileMode.Open, FileAccess.Read, FileShare.Read))
316330
{
317331
var decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
318332

@@ -322,9 +336,9 @@ private byte[] DiffImages(byte[] currentImage, byte[] previousImage, int pixelSi
322336
sourceStride += (4 - sourceStride % 4);
323337

324338
var image = new byte[sourceStride * (f.PixelHeight * sourceBytesPerPixels)];
325-
decoder.Frames[0].CopyPixels(image, (int)sourceStride, 0);
339+
f.CopyPixels(image, (int)sourceStride, 0);
326340

327-
return (decoder.Frames[0], image, sourceStride);
341+
return (f, image, sourceStride);
328342
}
329343
}
330344
catch (Exception ex)
@@ -341,6 +355,5 @@ private static IEnumerable<T> LogForeach<T>(IEnumerable<T> q, Action<T> action)
341355
yield return item;
342356
}
343357
}
344-
345358
}
346359
}

0 commit comments

Comments
 (0)