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 + } +}