diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
index 73a68063c0..a09472b46f 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
@@ -8,7 +8,7 @@ using System.Diagnostics;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
-
+using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
@@ -200,6 +200,34 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
#endif
+ [Fact]
+ public void TransposeInto()
+ {
+ static void RunTest()
+ {
+ // Just testing this fails in CI. RemoteExecutor is not working on my machine.
+ Assert.True(false);
+
+ float[] expected = Create8x8FloatData();
+ ReferenceImplementations.Transpose8x8(expected);
+
+ var source = default(Block8x8F);
+ source.LoadFrom(Create8x8FloatData());
+
+ var dest = default(Block8x8F);
+ source.TransposeInto(ref dest);
+
+ float[] actual = new float[64];
+ dest.ScaledCopyTo(actual);
+
+ Assert.Equal(expected, actual);
+ }
+
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ RunTest,
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX);
+ }
+
private class BufferHolder
{
public Block8x8F Buffer;
diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
new file mode 100644
index 0000000000..8b5ed8d48b
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
@@ -0,0 +1,232 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics.X86;
+#endif
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace SixLabors.ImageSharp.Tests.TestUtilities
+{
+ ///
+ /// Allows the testing against specific feature sets.
+ ///
+ public static class FeatureTestRunner
+ {
+ private static readonly char[] SplitChars = new[] { ',', ' ' };
+
+ ///
+ /// Allows the deserialization of parameters passed to the feature test.
+ ///
+ ///
+ /// This is required because does not allow
+ /// marshalling of fields so we cannot pass a wrapped
+ /// allowing automatic deserialization.
+ ///
+ ///
+ ///
+ /// The type to deserialize to.
+ /// The string value to deserialize.
+ /// The value.
+ public static T Deserialize(string value)
+ where T : IXunitSerializable
+ => BasicSerializer.Deserialize(value);
+
+ // TODO: Write runner test and use this.
+ private static void AssertHwIntrinsicsFeatureDisabled(HwIntrinsics intrinsics)
+ {
+ switch (intrinsics)
+ {
+ case HwIntrinsics.DisableSIMD:
+ Assert.False(Vector.IsHardwareAccelerated);
+ break;
+#if SUPPORTS_RUNTIME_INTRINSICS
+ case HwIntrinsics.DisableHWIntrinsic:
+ Assert.False(Vector.IsHardwareAccelerated);
+ break;
+ case HwIntrinsics.DisableSSE:
+ Assert.False(Sse.IsSupported);
+ break;
+ case HwIntrinsics.DisableSSE2:
+ Assert.False(Sse2.IsSupported);
+ break;
+ case HwIntrinsics.DisableAES:
+ Assert.False(Aes.IsSupported);
+ break;
+ case HwIntrinsics.DisablePCLMULQDQ:
+ Assert.False(Pclmulqdq.IsSupported);
+ break;
+ case HwIntrinsics.DisableSSE3:
+ Assert.False(Sse3.IsSupported);
+ break;
+ case HwIntrinsics.DisableSSSE3:
+ Assert.False(Ssse3.IsSupported);
+ break;
+ case HwIntrinsics.DisableSSE41:
+ Assert.False(Sse41.IsSupported);
+ break;
+ case HwIntrinsics.DisableSSE42:
+ Assert.False(Sse42.IsSupported);
+ break;
+ case HwIntrinsics.DisablePOPCNT:
+ Assert.False(Popcnt.IsSupported);
+ break;
+ case HwIntrinsics.DisableAVX:
+ Assert.False(Avx.IsSupported);
+ break;
+ case HwIntrinsics.DisableFMA:
+ Assert.False(Fma.IsSupported);
+ break;
+ case HwIntrinsics.DisableAVX2:
+ Assert.False(Avx2.IsSupported);
+ break;
+ case HwIntrinsics.DisableBMI1:
+ Assert.False(Bmi1.IsSupported);
+ break;
+ case HwIntrinsics.DisableBMI2:
+ Assert.False(Bmi2.IsSupported);
+ break;
+ case HwIntrinsics.DisableLZCNT:
+ Assert.False(Lzcnt.IsSupported);
+ break;
+#endif
+ }
+ }
+
+ ///
+ /// Runs the given test within an environment
+ /// where the given features.
+ ///
+ /// The test action to run.
+ /// The intrinsics features.
+ public static void RunWithHwIntrinsicsFeature(
+ Action action,
+ HwIntrinsics intrinsics)
+ {
+ if (!RemoteExecutor.IsSupported)
+ {
+ return;
+ }
+
+ foreach (string intrinsic in intrinsics.ToFeatureCollection())
+ {
+ var processStartInfo = new ProcessStartInfo();
+ if (intrinsic != nameof(HwIntrinsics.AllowAll))
+ {
+ processStartInfo.Environment[$"COMPlus_{intrinsic}"] = "0";
+ }
+
+ RemoteExecutor.Invoke(
+ action,
+ new RemoteInvokeOptions
+ {
+ StartInfo = processStartInfo
+ })
+ .Dispose();
+ }
+ }
+
+ ///
+ /// Runs the given test within an environment
+ /// where the given features.
+ ///
+ /// The test action to run.
+ /// The intrinsics features.
+ /// The value to pass as a parameter to the test action.
+ public static void RunWithHwIntrinsicsFeature(
+ Action action,
+ HwIntrinsics intrinsics,
+ T serializable)
+ where T : IXunitSerializable
+ {
+ if (!RemoteExecutor.IsSupported)
+ {
+ return;
+ }
+
+ foreach (string intrinsic in intrinsics.ToFeatureCollection())
+ {
+ var processStartInfo = new ProcessStartInfo();
+ if (intrinsic != nameof(HwIntrinsics.AllowAll))
+ {
+ processStartInfo.Environment[$"COMPlus_Enable{intrinsic}"] = "0";
+ }
+
+ RemoteExecutor.Invoke(
+ action,
+ BasicSerializer.Serialize(serializable),
+ new RemoteInvokeOptions
+ {
+ StartInfo = processStartInfo
+ })
+ .Dispose();
+ }
+ }
+
+ private static IEnumerable ToFeatureCollection(this HwIntrinsics intrinsics)
+ {
+ // Loop through and translate the given values into COMPlus equivaluents
+ var features = new List();
+ var split = intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries).ToArray();
+ foreach (string intrinsic in split)
+ {
+ switch (intrinsic)
+ {
+ case nameof(HwIntrinsics.DisableSIMD):
+ features.Add("FeatureSIMD");
+ break;
+
+ case nameof(HwIntrinsics.AllowAll):
+
+ // Not a COMPlus value. We filter in calling method.
+ features.Add(nameof(HwIntrinsics.AllowAll));
+ break;
+
+ default:
+ features.Add(intrinsic.Replace("Disable", "Enable"));
+ break;
+ }
+ }
+
+ return features;
+ }
+ }
+
+ ///
+ /// See
+ ///
+ /// ends up impacting all SIMD support(including System.Numerics)
+ /// but not things like , , and .
+ ///
+ ///
+ [Flags]
+ public enum HwIntrinsics
+ {
+ // Use flags so we can pass multiple values without using params.
+ DisableSIMD = 0,
+ DisableHWIntrinsic = 1 << 0,
+ DisableSSE = 1 << 1,
+ DisableSSE2 = 1 << 2,
+ DisableAES = 1 << 3,
+ DisablePCLMULQDQ = 1 << 4,
+ DisableSSE3 = 1 << 5,
+ DisableSSSE3 = 1 << 6,
+ DisableSSE41 = 1 << 7,
+ DisableSSE42 = 1 << 8,
+ DisablePOPCNT = 1 << 9,
+ DisableAVX = 1 << 10,
+ DisableFMA = 1 << 11,
+ DisableAVX2 = 1 << 12,
+ DisableBMI1 = 1 << 13,
+ DisableBMI2 = 1 << 14,
+ DisableLZCNT = 1 << 15,
+ AllowAll = 1 << 16
+ }
+}