diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
index 10cbee5e6..6a336ad2b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
@@ -10,86 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
- ///
- /// Fallback method to transpose a block into the destination block on non AVX supported CPUs.
- ///
- /// The destination block
- [MethodImpl(InliningOptions.ShortMethod)]
- public void TransposeIntoFallback(ref Block8x8F d)
- {
- d.V0L.X = V0L.X;
- d.V1L.X = V0L.Y;
- d.V2L.X = V0L.Z;
- d.V3L.X = V0L.W;
- d.V4L.X = V0R.X;
- d.V5L.X = V0R.Y;
- d.V6L.X = V0R.Z;
- d.V7L.X = V0R.W;
-
- d.V0L.Y = V1L.X;
- d.V1L.Y = V1L.Y;
- d.V2L.Y = V1L.Z;
- d.V3L.Y = V1L.W;
- d.V4L.Y = V1R.X;
- d.V5L.Y = V1R.Y;
- d.V6L.Y = V1R.Z;
- d.V7L.Y = V1R.W;
-
- d.V0L.Z = V2L.X;
- d.V1L.Z = V2L.Y;
- d.V2L.Z = V2L.Z;
- d.V3L.Z = V2L.W;
- d.V4L.Z = V2R.X;
- d.V5L.Z = V2R.Y;
- d.V6L.Z = V2R.Z;
- d.V7L.Z = V2R.W;
-
- d.V0L.W = V3L.X;
- d.V1L.W = V3L.Y;
- d.V2L.W = V3L.Z;
- d.V3L.W = V3L.W;
- d.V4L.W = V3R.X;
- d.V5L.W = V3R.Y;
- d.V6L.W = V3R.Z;
- d.V7L.W = V3R.W;
-
- d.V0R.X = V4L.X;
- d.V1R.X = V4L.Y;
- d.V2R.X = V4L.Z;
- d.V3R.X = V4L.W;
- d.V4R.X = V4R.X;
- d.V5R.X = V4R.Y;
- d.V6R.X = V4R.Z;
- d.V7R.X = V4R.W;
-
- d.V0R.Y = V5L.X;
- d.V1R.Y = V5L.Y;
- d.V2R.Y = V5L.Z;
- d.V3R.Y = V5L.W;
- d.V4R.Y = V5R.X;
- d.V5R.Y = V5R.Y;
- d.V6R.Y = V5R.Z;
- d.V7R.Y = V5R.W;
-
- d.V0R.Z = V6L.X;
- d.V1R.Z = V6L.Y;
- d.V2R.Z = V6L.Z;
- d.V3R.Z = V6L.W;
- d.V4R.Z = V6R.X;
- d.V5R.Z = V6R.Y;
- d.V6R.Z = V6R.Z;
- d.V7R.Z = V6R.W;
-
- d.V0R.W = V7L.X;
- d.V1R.W = V7L.Y;
- d.V2R.W = V7L.Z;
- d.V3R.W = V7L.W;
- d.V4R.W = V7R.X;
- d.V5R.W = V7R.Y;
- d.V6R.W = V7R.Z;
- d.V7R.W = V7R.W;
- }
-
///
/// Level shift by +maximum/2, clip to [0, maximum]
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
index f47d9106e..26cd5c2ac 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
@@ -23,38 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
- ///
- /// Fallback method to transpose a block into the destination block on non AVX supported CPUs.
- ///
- /// The destination block
- [MethodImpl(InliningOptions.ShortMethod)]
- public void TransposeIntoFallback(ref Block8x8F d)
- {
- <#
- PushIndent(" ");
-
- for (int i = 0; i < 8; i++)
- {
- char destCoord = coordz[i % 4];
- char destSide = (i / 4) % 2 == 0 ? 'L' : 'R';
-
- for (int j = 0; j < 8; j++)
- {
- if(i > 0 && j == 0){
- WriteLine("");
- }
-
- char srcCoord = coordz[j % 4];
- char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R';
-
- var expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord};\r\n";
- Write(expression);
- }
- }
- PopIndent();
- #>
- }
-
///
/// Level shift by +maximum/2, clip to [0, maximum]
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
index 547e11623..ccdba4885 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
@@ -611,87 +611,146 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
- this.TransposeIntoAvx(ref d);
+ // https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536
+ Vector256 r0 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V0L).ToVector256(),
+ Unsafe.As>(ref this.V4L),
+ 1);
+
+ Vector256 r1 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V1L).ToVector256(),
+ Unsafe.As>(ref this.V5L),
+ 1);
+
+ Vector256 r2 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V2L).ToVector256(),
+ Unsafe.As>(ref this.V6L),
+ 1);
+
+ Vector256 r3 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V3L).ToVector256(),
+ Unsafe.As>(ref this.V7L),
+ 1);
+
+ Vector256 r4 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V0R).ToVector256(),
+ Unsafe.As>(ref this.V4R),
+ 1);
+
+ Vector256 r5 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V1R).ToVector256(),
+ Unsafe.As>(ref this.V5R),
+ 1);
+
+ Vector256 r6 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V2R).ToVector256(),
+ Unsafe.As>(ref this.V6R),
+ 1);
+
+ Vector256 r7 = Avx.InsertVector128(
+ Unsafe.As>(ref this.V3R).ToVector256(),
+ Unsafe.As>(ref this.V7R),
+ 1);
+
+ Vector256 t0 = Avx.UnpackLow(r0, r1);
+ Vector256 t2 = Avx.UnpackLow(r2, r3);
+ Vector256 v = Avx.Shuffle(t0, t2, 0x4E);
+ Unsafe.As>(ref d.V0L) = Avx.Blend(t0, v, 0xCC);
+ Unsafe.As>(ref d.V1L) = Avx.Blend(t2, v, 0x33);
+
+ Vector256 t4 = Avx.UnpackLow(r4, r5);
+ Vector256 t6 = Avx.UnpackLow(r6, r7);
+ v = Avx.Shuffle(t4, t6, 0x4E);
+ Unsafe.As>(ref d.V4L) = Avx.Blend(t4, v, 0xCC);
+ Unsafe.As>(ref d.V5L) = Avx.Blend(t6, v, 0x33);
+
+ Vector256 t1 = Avx.UnpackHigh(r0, r1);
+ Vector256 t3 = Avx.UnpackHigh(r2, r3);
+ v = Avx.Shuffle(t1, t3, 0x4E);
+ Unsafe.As>(ref d.V2L) = Avx.Blend(t1, v, 0xCC);
+ Unsafe.As>(ref d.V3L) = Avx.Blend(t3, v, 0x33);
+
+ Vector256 t5 = Avx.UnpackHigh(r4, r5);
+ Vector256 t7 = Avx.UnpackHigh(r6, r7);
+ v = Avx.Shuffle(t5, t7, 0x4E);
+ Unsafe.As>(ref d.V6L) = Avx.Blend(t5, v, 0xCC);
+ Unsafe.As>(ref d.V7L) = Avx.Blend(t7, v, 0x33);
}
else
#endif
{
- this.TransposeIntoFallback(ref d);
+ d.V0L.X = this.V0L.X;
+ d.V1L.X = this.V0L.Y;
+ d.V2L.X = this.V0L.Z;
+ d.V3L.X = this.V0L.W;
+ d.V4L.X = this.V0R.X;
+ d.V5L.X = this.V0R.Y;
+ d.V6L.X = this.V0R.Z;
+ d.V7L.X = this.V0R.W;
+
+ d.V0L.Y = this.V1L.X;
+ d.V1L.Y = this.V1L.Y;
+ d.V2L.Y = this.V1L.Z;
+ d.V3L.Y = this.V1L.W;
+ d.V4L.Y = this.V1R.X;
+ d.V5L.Y = this.V1R.Y;
+ d.V6L.Y = this.V1R.Z;
+ d.V7L.Y = this.V1R.W;
+
+ d.V0L.Z = this.V2L.X;
+ d.V1L.Z = this.V2L.Y;
+ d.V2L.Z = this.V2L.Z;
+ d.V3L.Z = this.V2L.W;
+ d.V4L.Z = this.V2R.X;
+ d.V5L.Z = this.V2R.Y;
+ d.V6L.Z = this.V2R.Z;
+ d.V7L.Z = this.V2R.W;
+
+ d.V0L.W = this.V3L.X;
+ d.V1L.W = this.V3L.Y;
+ d.V2L.W = this.V3L.Z;
+ d.V3L.W = this.V3L.W;
+ d.V4L.W = this.V3R.X;
+ d.V5L.W = this.V3R.Y;
+ d.V6L.W = this.V3R.Z;
+ d.V7L.W = this.V3R.W;
+
+ d.V0R.X = this.V4L.X;
+ d.V1R.X = this.V4L.Y;
+ d.V2R.X = this.V4L.Z;
+ d.V3R.X = this.V4L.W;
+ d.V4R.X = this.V4R.X;
+ d.V5R.X = this.V4R.Y;
+ d.V6R.X = this.V4R.Z;
+ d.V7R.X = this.V4R.W;
+
+ d.V0R.Y = this.V5L.X;
+ d.V1R.Y = this.V5L.Y;
+ d.V2R.Y = this.V5L.Z;
+ d.V3R.Y = this.V5L.W;
+ d.V4R.Y = this.V5R.X;
+ d.V5R.Y = this.V5R.Y;
+ d.V6R.Y = this.V5R.Z;
+ d.V7R.Y = this.V5R.W;
+
+ d.V0R.Z = this.V6L.X;
+ d.V1R.Z = this.V6L.Y;
+ d.V2R.Z = this.V6L.Z;
+ d.V3R.Z = this.V6L.W;
+ d.V4R.Z = this.V6R.X;
+ d.V5R.Z = this.V6R.Y;
+ d.V6R.Z = this.V6R.Z;
+ d.V7R.Z = this.V6R.W;
+
+ d.V0R.W = this.V7L.X;
+ d.V1R.W = this.V7L.Y;
+ d.V2R.W = this.V7L.Z;
+ d.V3R.W = this.V7L.W;
+ d.V4R.W = this.V7R.X;
+ d.V5R.W = this.V7R.Y;
+ d.V6R.W = this.V7R.Z;
+ d.V7R.W = this.V7R.W;
}
}
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- ///
- /// AVX-only variant for executing .
- ///
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void TransposeIntoAvx(ref Block8x8F d)
- {
- Vector256 r0 = Avx.InsertVector128(
- Unsafe.As>(ref this.V0L).ToVector256(),
- Unsafe.As>(ref this.V4L),
- 1);
-
- Vector256 r1 = Avx.InsertVector128(
- Unsafe.As>(ref this.V1L).ToVector256(),
- Unsafe.As>(ref this.V5L),
- 1);
-
- Vector256 r2 = Avx.InsertVector128(
- Unsafe.As>(ref this.V2L).ToVector256(),
- Unsafe.As>(ref this.V6L),
- 1);
-
- Vector256 r3 = Avx.InsertVector128(
- Unsafe.As>(ref this.V3L).ToVector256(),
- Unsafe.As>(ref this.V7L),
- 1);
-
- Vector256 r4 = Avx.InsertVector128(
- Unsafe.As>(ref this.V0R).ToVector256(),
- Unsafe.As>(ref this.V4R),
- 1);
-
- Vector256 r5 = Avx.InsertVector128(
- Unsafe.As>(ref this.V1R).ToVector256(),
- Unsafe.As>(ref this.V5R),
- 1);
-
- Vector256 r6 = Avx.InsertVector128(
- Unsafe.As>(ref this.V2R).ToVector256(),
- Unsafe.As>(ref this.V6R),
- 1);
-
- Vector256 r7 = Avx.InsertVector128(
- Unsafe.As>(ref this.V3R).ToVector256(),
- Unsafe.As>(ref this.V7R),
- 1);
-
- Vector256 t0 = Avx.UnpackLow(r0, r1);
- Vector256 t2 = Avx.UnpackLow(r2, r3);
- Vector256 v = Avx.Shuffle(t0, t2, 0x4E);
- Unsafe.As>(ref d.V0L) = Avx.Blend(t0, v, 0xCC);
- Unsafe.As>(ref d.V1L) = Avx.Blend(t2, v, 0x33);
-
- Vector256 t4 = Avx.UnpackLow(r4, r5);
- Vector256 t6 = Avx.UnpackLow(r6, r7);
- v = Avx.Shuffle(t4, t6, 0x4E);
- Unsafe.As>(ref d.V4L) = Avx.Blend(t4, v, 0xCC);
- Unsafe.As>(ref d.V5L) = Avx.Blend(t6, v, 0x33);
-
- Vector256 t1 = Avx.UnpackHigh(r0, r1);
- Vector256 t3 = Avx.UnpackHigh(r2, r3);
- v = Avx.Shuffle(t1, t3, 0x4E);
- Unsafe.As>(ref d.V2L) = Avx.Blend(t1, v, 0xCC);
- Unsafe.As>(ref d.V3L) = Avx.Blend(t3, v, 0x33);
-
- Vector256 t5 = Avx.UnpackHigh(r4, r5);
- Vector256 t7 = Avx.UnpackHigh(r6, r7);
- v = Avx.Shuffle(t5, t7, 0x4E);
- Unsafe.As>(ref d.V6L) = Avx.Blend(t5, v, 0xCC);
- Unsafe.As>(ref d.V7L) = Avx.Blend(t7, v, 0x33);
- }
-#endif
}
}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs
index ae1b23df9..1d103cd1a 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs
@@ -6,25 +6,17 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
{
+ [Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Block8x8F_Transpose
{
private static readonly Block8x8F Source = Create8x8FloatData();
- [Benchmark(Baseline=true)]
- public void TransposeIntoVector4()
- {
- var dest = default(Block8x8F);
- Source.TransposeIntoFallback(ref dest);
- }
-
-#if SUPPORTS_RUNTIME_INTRINSICS
[Benchmark]
- public void TransposeIntoAvx()
+ public void TransposeInto()
{
var dest = default(Block8x8F);
- Source.TransposeIntoAvx(ref dest);
+ Source.TransposeInto(ref dest);
}
-#endif
private static Block8x8F Create8x8FloatData()
{
diff --git a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs
new file mode 100644
index 000000000..e860c5491
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics.X86;
+#endif
+using BenchmarkDotNet.Environments;
+using BenchmarkDotNet.Jobs;
+
+namespace SixLabors.ImageSharp.Benchmarks
+{
+ public partial class Config
+ {
+ private const string On = "1";
+ private const string Off = "0";
+
+ // See https://github.com/SixLabors/ImageSharp/pull/1229#discussion_r440477861
+ // * EnableHWIntrinsic
+ // * EnableSSE
+ // * EnableSSE2
+ // * EnableAES
+ // * EnablePCLMULQDQ
+ // * EnableSSE3
+ // * EnableSSSE3
+ // * EnableSSE41
+ // * EnableSSE42
+ // * EnablePOPCNT
+ // * EnableAVX
+ // * EnableFMA
+ // * EnableAVX2
+ // * EnableBMI1
+ // * EnableBMI2
+ // * EnableLZCNT
+ //
+ // `FeatureSIMD` ends up impacting all SIMD support(including `System.Numerics`) but not things
+ // like `LZCNT`, `BMI1`, or `BMI2`
+ // `EnableSSE3_4` is a legacy switch that exists for compat and is basically the same as `EnableSSE3`
+ private const string EnableAES = "COMPlus_EnableAES";
+ private const string EnableAVX = "COMPlus_EnableAVX";
+ private const string EnableAVX2 = "COMPlus_EnableAVX2";
+ private const string EnableBMI1 = "COMPlus_EnableBMI1";
+ private const string EnableBMI2 = "COMPlus_EnableBMI2";
+ private const string EnableFMA = "COMPlus_EnableFMA";
+ private const string EnableHWIntrinsic = "COMPlus_EnableHWIntrinsic";
+ private const string EnableLZCNT = "COMPlus_EnableLZCNT";
+ private const string EnablePCLMULQDQ = "COMPlus_EnablePCLMULQDQ";
+ private const string EnablePOPCNT = "COMPlus_EnablePOPCNT";
+ private const string EnableSSE = "COMPlus_EnableSSE";
+ private const string EnableSSE2 = "COMPlus_EnableSSE2";
+ private const string EnableSSE3 = "COMPlus_EnableSSE3";
+ private const string EnableSSE3_4 = "COMPlus_EnableSSE3_4";
+ private const string EnableSSE41 = "COMPlus_EnableSSE41";
+ private const string EnableSSE42 = "COMPlus_EnableSSE42";
+ private const string EnableSSSE3 = "COMPlus_EnableSSSE3";
+ private const string FeatureSIMD = "COMPlus_FeatureSIMD";
+
+ public class HwIntrinsics_SSE_AVX : Config
+ {
+ public HwIntrinsics_SSE_AVX()
+ {
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx.IsSupported)
+ {
+ this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)
+ .WithId("AVX").AsBaseline());
+ }
+
+ if (Sse.IsSupported)
+ {
+ this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)
+ .WithEnvironmentVariables(new EnvironmentVariable(EnableAVX, Off))
+ .WithId("SSE"));
+ }
+#endif
+ this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)
+ .WithEnvironmentVariables(new EnvironmentVariable(EnableHWIntrinsic, Off))
+ .WithId("No HwIntrinsics"));
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs
index f9240779b..53271f522 100644
--- a/tests/ImageSharp.Benchmarks/Config.cs
+++ b/tests/ImageSharp.Benchmarks/Config.cs
@@ -12,7 +12,7 @@ using BenchmarkDotNet.Jobs;
namespace SixLabors.ImageSharp.Benchmarks
{
- public class Config : ManualConfig
+ public partial class Config : ManualConfig
{
public Config()
{
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
index 73a68063c..548238088 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
@@ -5,10 +5,9 @@
// #define BENCHMARKING
using System;
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;
@@ -163,42 +162,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
[Fact]
- public void TransposeIntoFallback()
+ public void TransposeInto()
{
- float[] expected = Create8x8FloatData();
- ReferenceImplementations.Transpose8x8(expected);
-
- var source = default(Block8x8F);
- source.LoadFrom(Create8x8FloatData());
-
- var dest = default(Block8x8F);
- source.TransposeIntoFallback(ref dest);
-
- float[] actual = new float[64];
- dest.ScaledCopyTo(actual);
-
- Assert.Equal(expected, actual);
- }
+ static void RunTest()
+ {
+ float[] expected = Create8x8FloatData();
+ ReferenceImplementations.Transpose8x8(expected);
-#if SUPPORTS_RUNTIME_INTRINSICS
- [Fact]
- public void TransposeIntoAvx()
- {
- float[] expected = Create8x8FloatData();
- ReferenceImplementations.Transpose8x8(expected);
+ var source = default(Block8x8F);
+ source.LoadFrom(Create8x8FloatData());
- var source = default(Block8x8F);
- source.LoadFrom(Create8x8FloatData());
+ var dest = default(Block8x8F);
+ source.TransposeInto(ref dest);
- var dest = default(Block8x8F);
- source.TransposeIntoAvx(ref dest);
+ float[] actual = new float[64];
+ dest.ScaledCopyTo(actual);
- float[] actual = new float[64];
- dest.ScaledCopyTo(actual);
+ Assert.Equal(expected, actual);
+ }
- Assert.Equal(expected, actual);
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ RunTest,
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX);
}
-#endif
private class BufferHolder
{
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index b35e55887..9ba956d72 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -2,13 +2,8 @@
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
-using System.Diagnostics;
using System.IO;
using System.Linq;
-#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Runtime.Intrinsics.X86;
-#endif
-using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Metadata;
@@ -16,7 +11,6 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
-
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png
@@ -538,13 +532,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
public void EncodeWorksWithoutSsse3Intrinsics(TestImageProvider provider)
{
- static void RunTest(string providerDump)
+ static void RunTest(string serialized)
{
TestImageProvider provider =
- BasicSerializer.Deserialize>(providerDump);
-#if SUPPORTS_RUNTIME_INTRINSICS
- Assert.False(Ssse3.IsSupported);
-#endif
+ FeatureTestRunner.Deserialize>(serialized);
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
@@ -559,19 +550,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
- string providerDump = BasicSerializer.Serialize(provider);
-
- var processStartInfo = new ProcessStartInfo();
- processStartInfo.Environment[TestEnvironment.Features.EnableSSE3] = TestEnvironment.Features.Off;
-
- RemoteExecutor.Invoke(
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
- providerDump,
- new RemoteInvokeOptions
- {
- StartInfo = processStartInfo
- })
- .Dispose();
+ HwIntrinsics.DisableSSSE3,
+ provider);
}
private static void TestPngEncoderCore(
diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
new file mode 100644
index 000000000..eb1714baa
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
@@ -0,0 +1,266 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.DotNet.RemoteExecutor;
+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);
+
+ ///
+ /// 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 (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
+ {
+ var processStartInfo = new ProcessStartInfo();
+ if (intrinsic.Key != HwIntrinsics.AllowAll)
+ {
+ processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
+
+ RemoteExecutor.Invoke(
+ action,
+ new RemoteInvokeOptions
+ {
+ StartInfo = processStartInfo
+ })
+ .Dispose();
+ }
+ else
+ {
+ // Since we are running using the default architecture there is no
+ // point creating the overhead of running the action in a separate process.
+ action();
+ }
+ }
+ }
+
+ ///
+ /// Runs the given test within an environment
+ /// where the given features.
+ ///
+ ///
+ /// The test action to run.
+ /// The parameter passed will be a string representing the currently testing .
+ /// The intrinsics features.
+ public static void RunWithHwIntrinsicsFeature(
+ Action action,
+ HwIntrinsics intrinsics)
+ {
+ if (!RemoteExecutor.IsSupported)
+ {
+ return;
+ }
+
+ foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
+ {
+ var processStartInfo = new ProcessStartInfo();
+ if (intrinsic.Key != HwIntrinsics.AllowAll)
+ {
+ processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
+
+ RemoteExecutor.Invoke(
+ action,
+ intrinsic.Key.ToString(),
+ new RemoteInvokeOptions
+ {
+ StartInfo = processStartInfo
+ })
+ .Dispose();
+ }
+ else
+ {
+ // Since we are running using the default architecture there is no
+ // point creating the overhead of running the action in a separate process.
+ action(intrinsic.Key.ToString());
+ }
+ }
+ }
+
+ ///
+ /// 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 (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
+ {
+ var processStartInfo = new ProcessStartInfo();
+ if (intrinsic.Key != HwIntrinsics.AllowAll)
+ {
+ processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
+
+ RemoteExecutor.Invoke(
+ action,
+ BasicSerializer.Serialize(serializable),
+ new RemoteInvokeOptions
+ {
+ StartInfo = processStartInfo
+ })
+ .Dispose();
+ }
+ else
+ {
+ // Since we are running using the default architecture there is no
+ // point creating the overhead of running the action in a separate process.
+ action(BasicSerializer.Serialize(serializable));
+ }
+ }
+ }
+
+ ///
+ /// 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 (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
+ {
+ var processStartInfo = new ProcessStartInfo();
+ if (intrinsic.Key != HwIntrinsics.AllowAll)
+ {
+ processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
+
+ RemoteExecutor.Invoke(
+ action,
+ BasicSerializer.Serialize(serializable),
+ intrinsic.Key.ToString(),
+ new RemoteInvokeOptions
+ {
+ StartInfo = processStartInfo
+ })
+ .Dispose();
+ }
+ else
+ {
+ // Since we are running using the default architecture there is no
+ // point creating the overhead of running the action in a separate process.
+ action(BasicSerializer.Serialize(serializable), intrinsic.Key.ToString());
+ }
+ }
+ }
+
+ internal static Dictionary ToFeatureKeyValueCollection(this HwIntrinsics intrinsics)
+ {
+ // Loop through and translate the given values into COMPlus equivaluents
+ var features = new Dictionary();
+ foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries))
+ {
+ var key = (HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic);
+ switch (intrinsic)
+ {
+ case nameof(HwIntrinsics.DisableSIMD):
+ features.Add(key, "FeatureSIMD");
+ break;
+
+ case nameof(HwIntrinsics.AllowAll):
+
+ // Not a COMPlus value. We filter in calling method.
+ features.Add(key, nameof(HwIntrinsics.AllowAll));
+ break;
+
+ default:
+ features.Add(key, intrinsic.Replace("Disable", "Enable"));
+ break;
+ }
+ }
+
+ return features;
+ }
+ }
+
+ ///
+ /// See
+ ///
+ /// ends up impacting all SIMD support(including System.Numerics)
+ /// but not things like , , and .
+ ///
+ ///
+ [Flags]
+#pragma warning disable RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute).
+ public enum HwIntrinsics
+#pragma warning restore RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute).
+ {
+ // Use flags so we can pass multiple values without using params.
+ // Don't base on 0 or use inverse for All as that doesn't translate to string values.
+ DisableSIMD = 1 << 0,
+ DisableHWIntrinsic = 1 << 1,
+ DisableSSE = 1 << 2,
+ DisableSSE2 = 1 << 3,
+ DisableAES = 1 << 4,
+ DisablePCLMULQDQ = 1 << 5,
+ DisableSSE3 = 1 << 6,
+ DisableSSSE3 = 1 << 7,
+ DisableSSE41 = 1 << 8,
+ DisableSSE42 = 1 << 9,
+ DisablePOPCNT = 1 << 10,
+ DisableAVX = 1 << 11,
+ DisableFMA = 1 << 12,
+ DisableAVX2 = 1 << 13,
+ DisableBMI1 = 1 << 14,
+ DisableBMI2 = 1 << 15,
+ DisableLZCNT = 1 << 16,
+ AllowAll = 1 << 17
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Features.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Features.cs
deleted file mode 100644
index 3568c1e5d..000000000
--- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Features.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Tests
-{
- public static partial class TestEnvironment
- {
- internal static class Features
- {
- public const string On = "1";
- public const string Off = "0";
-
- // See https://github.com/SixLabors/ImageSharp/pull/1229#discussion_r440477861
- // * EnableHWIntrinsic
- // * EnableSSE
- // * EnableSSE2
- // * EnableAES
- // * EnablePCLMULQDQ
- // * EnableSSE3
- // * EnableSSSE3
- // * EnableSSE41
- // * EnableSSE42
- // * EnablePOPCNT
- // * EnableAVX
- // * EnableFMA
- // * EnableAVX2
- // * EnableBMI1
- // * EnableBMI2
- // * EnableLZCNT
- //
- // `FeatureSIMD` ends up impacting all SIMD support(including `System.Numerics`) but not things
- // like `LZCNT`, `BMI1`, or `BMI2`
- // `EnableSSE3_4` is a legacy switch that exists for compat and is basically the same as `EnableSSE3`
- public const string EnableAES = "COMPlus_EnableAES";
- public const string EnableAVX = "COMPlus_EnableAVX";
- public const string EnableAVX2 = "COMPlus_EnableAVX2";
- public const string EnableBMI1 = "COMPlus_EnableBMI1";
- public const string EnableBMI2 = "COMPlus_EnableBMI2";
- public const string EnableFMA = "COMPlus_EnableFMA";
- public const string EnableHWIntrinsic = "COMPlus_EnableHWIntrinsic";
- public const string EnableLZCNT = "COMPlus_EnableLZCNT";
- public const string EnablePCLMULQDQ = "COMPlus_EnablePCLMULQDQ";
- public const string EnablePOPCNT = "COMPlus_EnablePOPCNT";
- public const string EnableSSE = "COMPlus_EnableSSE";
- public const string EnableSSE2 = "COMPlus_EnableSSE2";
- public const string EnableSSE3 = "COMPlus_EnableSSE3";
- public const string EnableSSE3_4 = "COMPlus_EnableSSE3_4";
- public const string EnableSSE41 = "COMPlus_EnableSSE41";
- public const string EnableSSE42 = "COMPlus_EnableSSE42";
- public const string EnableSSSE3 = "COMPlus_EnableSSSE3";
- public const string FeatureSIMD = "COMPlus_FeatureSIMD";
- }
- }
-}
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs
new file mode 100644
index 000000000..646000120
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs
@@ -0,0 +1,296 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics.X86;
+#endif
+using Xunit;
+using Xunit.Abstractions;
+
+namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
+{
+ public class FeatureTestRunnerTests
+ {
+ public static TheoryData Intrinsics =>
+ new TheoryData
+ {
+ { HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new string[] { "EnableAES", "AllowAll" } },
+ { HwIntrinsics.DisableSIMD | HwIntrinsics.DisableHWIntrinsic, new string[] { "FeatureSIMD", "EnableHWIntrinsic" } },
+ { HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new string[] { "EnableSSE42", "EnableAVX" } }
+ };
+
+ [Theory]
+ [MemberData(nameof(Intrinsics))]
+ public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedItrinsics, string[] expectedValues)
+ {
+ Dictionary features = expectedItrinsics.ToFeatureKeyValueCollection();
+ HwIntrinsics[] keys = features.Keys.ToArray();
+
+ HwIntrinsics actualIntrinsics = keys[0];
+ for (int i = 1; i < keys.Length; i++)
+ {
+ actualIntrinsics |= keys[i];
+ }
+
+ Assert.Equal(expectedItrinsics, actualIntrinsics);
+
+ IEnumerable actualValues = features.Select(x => x.Value);
+ Assert.Equal(expectedValues, actualValues);
+ }
+
+ [Fact]
+ public void AllowsAllHwIntrinsicFeatures()
+ {
+ if (!Vector.IsHardwareAccelerated)
+ {
+ return;
+ }
+
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ () => Assert.True(Vector.IsHardwareAccelerated),
+ HwIntrinsics.AllowAll);
+ }
+
+ [Fact]
+ public void CanLimitHwIntrinsicSIMDFeatures()
+ {
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ () => Assert.False(Vector.IsHardwareAccelerated),
+ HwIntrinsics.DisableSIMD);
+ }
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ [Fact]
+ public void CanLimitHwIntrinsicBaseFeatures()
+ {
+ static void AssertDisabled()
+ {
+ Assert.False(Sse.IsSupported);
+ Assert.False(Sse2.IsSupported);
+ Assert.False(Aes.IsSupported);
+ Assert.False(Pclmulqdq.IsSupported);
+ Assert.False(Sse3.IsSupported);
+ Assert.False(Ssse3.IsSupported);
+ Assert.False(Sse41.IsSupported);
+ Assert.False(Sse42.IsSupported);
+ Assert.False(Popcnt.IsSupported);
+ Assert.False(Avx.IsSupported);
+ Assert.False(Fma.IsSupported);
+ Assert.False(Avx2.IsSupported);
+ Assert.False(Bmi1.IsSupported);
+ Assert.False(Bmi2.IsSupported);
+ Assert.False(Lzcnt.IsSupported);
+ }
+
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ AssertDisabled,
+ HwIntrinsics.DisableHWIntrinsic);
+ }
+#endif
+
+ [Fact]
+ public void CanLimitHwIntrinsicFeaturesWithIntrinsicsParam()
+ {
+ static void AssertHwIntrinsicsFeatureDisabled(string intrinsic)
+ {
+ Assert.NotNull(intrinsic);
+
+ switch ((HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic))
+ {
+ case HwIntrinsics.DisableSIMD:
+ Assert.False(Vector.IsHardwareAccelerated);
+ break;
+#if SUPPORTS_RUNTIME_INTRINSICS
+ case HwIntrinsics.DisableHWIntrinsic:
+ Assert.False(Sse.IsSupported);
+ Assert.False(Sse2.IsSupported);
+ Assert.False(Aes.IsSupported);
+ Assert.False(Pclmulqdq.IsSupported);
+ Assert.False(Sse3.IsSupported);
+ Assert.False(Ssse3.IsSupported);
+ Assert.False(Sse41.IsSupported);
+ Assert.False(Sse42.IsSupported);
+ Assert.False(Popcnt.IsSupported);
+ Assert.False(Avx.IsSupported);
+ Assert.False(Fma.IsSupported);
+ Assert.False(Avx2.IsSupported);
+ Assert.False(Bmi1.IsSupported);
+ Assert.False(Bmi2.IsSupported);
+ Assert.False(Lzcnt.IsSupported);
+ 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
+ }
+ }
+
+ foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics)))
+ {
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic);
+ }
+ }
+
+ [Fact]
+ public void CanLimitHwIntrinsicFeaturesWithSerializableParam()
+ {
+ static void AssertHwIntrinsicsFeatureDisabled(string serializable)
+ {
+ Assert.NotNull(serializable);
+ Assert.NotNull(FeatureTestRunner.Deserialize(serializable));
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ Assert.False(Sse.IsSupported);
+#endif
+ }
+
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ AssertHwIntrinsicsFeatureDisabled,
+ HwIntrinsics.DisableSSE,
+ new FakeSerializable());
+ }
+
+ [Fact]
+ public void CanLimitHwIntrinsicFeaturesWithSerializableAndIntrinsicsParams()
+ {
+ static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrinsic)
+ {
+ Assert.NotNull(serializable);
+ Assert.NotNull(FeatureTestRunner.Deserialize(serializable));
+
+ switch ((HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic))
+ {
+ case HwIntrinsics.DisableSIMD:
+ Assert.False(Vector.IsHardwareAccelerated, nameof(Vector.IsHardwareAccelerated));
+ break;
+#if SUPPORTS_RUNTIME_INTRINSICS
+ case HwIntrinsics.DisableHWIntrinsic:
+ Assert.False(Sse.IsSupported);
+ Assert.False(Sse2.IsSupported);
+ Assert.False(Aes.IsSupported);
+ Assert.False(Pclmulqdq.IsSupported);
+ Assert.False(Sse3.IsSupported);
+ Assert.False(Ssse3.IsSupported);
+ Assert.False(Sse41.IsSupported);
+ Assert.False(Sse42.IsSupported);
+ Assert.False(Popcnt.IsSupported);
+ Assert.False(Avx.IsSupported);
+ Assert.False(Fma.IsSupported);
+ Assert.False(Avx2.IsSupported);
+ Assert.False(Bmi1.IsSupported);
+ Assert.False(Bmi2.IsSupported);
+ Assert.False(Lzcnt.IsSupported);
+ 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
+ }
+ }
+
+ foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics)))
+ {
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic, new FakeSerializable());
+ }
+ }
+
+ public class FakeSerializable : IXunitSerializable
+ {
+ public void Deserialize(IXunitSerializationInfo info)
+ {
+ }
+
+ public void Serialize(IXunitSerializationInfo info)
+ {
+ }
+ }
+ }
+}