diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 1a4c6ab44..2056075e7 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -31,7 +31,12 @@ namespace SixLabors.ImageSharp void RunFallbackShuffle(ReadOnlySpan source, Span dest); } - internal readonly struct DefaultShuffle4 : IComponentShuffle + /// + internal interface IShuffle4 : IComponentShuffle + { + } + + internal readonly struct DefaultShuffle4 : IShuffle4 { private readonly byte p3; private readonly byte p2; @@ -40,10 +45,10 @@ namespace SixLabors.ImageSharp public DefaultShuffle4(byte p3, byte p2, byte p1, byte p0) { - Guard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); - Guard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); - Guard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); - Guard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); + DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); this.p3 = p3; this.p2 = p2; @@ -75,7 +80,7 @@ namespace SixLabors.ImageSharp } } - internal readonly struct WXYZShuffle4 : IComponentShuffle + internal readonly struct WXYZShuffle4 : IShuffle4 { private static readonly byte WXYZ = SimdUtils.Shuffle.MmShuffle(2, 1, 0, 3); @@ -99,7 +104,7 @@ namespace SixLabors.ImageSharp } } - internal readonly struct WZYXShuffle4 : IComponentShuffle + internal readonly struct WZYXShuffle4 : IShuffle4 { private static readonly byte WZYX = SimdUtils.Shuffle.MmShuffle(0, 1, 2, 3); @@ -123,7 +128,7 @@ namespace SixLabors.ImageSharp } } - internal readonly struct YZWXShuffle4 : IComponentShuffle + internal readonly struct YZWXShuffle4 : IShuffle4 { private static readonly byte YZWX = SimdUtils.Shuffle.MmShuffle(0, 3, 2, 1); @@ -147,7 +152,7 @@ namespace SixLabors.ImageSharp } } - internal readonly struct ZYXWShuffle4 : IComponentShuffle + internal readonly struct ZYXWShuffle4 : IShuffle4 { private static readonly byte ZYXW = SimdUtils.Shuffle.MmShuffle(3, 0, 1, 2); diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs index b223a6bc2..1f3fce541 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs @@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp public DefaultPad3Shuffle4(byte p3, byte p2, byte p1, byte p0) { - Guard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); - Guard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); - Guard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); - Guard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); + DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); this.p3 = p3; this.p2 = p2; diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs index fa4260e63..61e99890e 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp public DefaultShuffle3(byte p2, byte p1, byte p0) { - Guard.MustBeBetweenOrEqualTo(p2, 0, 2, nameof(p2)); - Guard.MustBeBetweenOrEqualTo(p1, 0, 2, nameof(p1)); - Guard.MustBeBetweenOrEqualTo(p0, 0, 2, nameof(p0)); + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 2, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 2, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 2, nameof(p0)); this.p2 = p2; this.p1 = p1; @@ -50,41 +50,4 @@ namespace SixLabors.ImageSharp } } } - - internal readonly struct ZYXShuffle3 : IShuffle3 - { - private static readonly byte ZYX = SimdUtils.Shuffle.MmShuffle(3, 0, 1, 2); - - public byte Control => ZYX; - - [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref Byte3 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref Byte3 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 3; - - for (int i = 0; i < n; i++) - { - uint packed = Unsafe.As(ref Unsafe.Add(ref sBase, i)); - - // packed = [W Z Y X] - // tmp1 = [W 0 Y 0] - // tmp2 = [0 Z 0 X] - // tmp3=ROTL(16, tmp2) = [0 X 0 Z] - // tmp1 + tmp3 = [W X Y Z] - uint tmp1 = packed & 0xFF00FF00; - uint tmp2 = packed & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); - packed = tmp1 + tmp3; - - Unsafe.Add(ref dBase, i) = Unsafe.As(ref packed); - } - } - } - - [StructLayout(LayoutKind.Explicit, Size = 3)] - internal readonly struct Byte3 - { - } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs index 1ceb38f1a..c9b51aba2 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs @@ -20,10 +20,10 @@ namespace SixLabors.ImageSharp public DefaultShuffle4Slice3(byte p3, byte p2, byte p1, byte p0) { - Guard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); - Guard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); - Guard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); - Guard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); + DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); this.p2 = p2; this.p1 = p1; @@ -71,4 +71,9 @@ namespace SixLabors.ImageSharp } } } + + [StructLayout(LayoutKind.Explicit, Size = 3)] + internal readonly struct Byte3 + { + } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs index 79cb0da37..8fd6bcce6 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp ReadOnlySpan source, Span dest, TShuffle shuffle) - where TShuffle : struct, IComponentShuffle + where TShuffle : struct, IShuffle4 { VerifyShuffle4SpanInput(source, dest); diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index c5f92648c..7215fa860 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -164,7 +164,14 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2)); - // TODO: Bgr24 + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); } public static class FromBgr24 @@ -196,7 +203,14 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Pad3Shuffle4(source, dest, default); - // TODO: Rgb24 + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); } } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs new file mode 100644 index 000000000..3667b973e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + [Config(typeof(Config.HwIntrinsics_SSE_AVX))] + public class Shuffle3Channel + { + private static readonly DefaultShuffle3 Control = new DefaultShuffle3(1, 0, 2); + private byte[] source; + private byte[] destination; + + [GlobalSetup] + public void Setup() + { + this.source = new byte[this.Count]; + new Random(this.Count).NextBytes(this.source); + this.destination = new byte[this.Count]; + } + + [Params(96, 384, 768, 1536)] + public int Count { get; set; } + + [Benchmark] + public void Shuffle3() + { + SimdUtils.Shuffle3(this.source, this.destination, Control); + } + } + + // 2020-11-02 + // ########## + // + // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) + // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores + // .NET Core SDK=3.1.403 + // [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT + // 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT + // 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT + // 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT + // + // Runtime=.NET Core 3.1 + // + // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |--------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| + // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - | + // | Shuffle3 | 2. AVX | Empty | 96 | 32.42 ns | 0.537 ns | 0.476 ns | 32.34 ns | 0.66 | 0.04 | - | - | - | - | + // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - | + // | | | | | | | | | | | | | | | + // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - | + // | Shuffle3 | 2. AVX | Empty | 384 | 71.20 ns | 2.654 ns | 7.784 ns | 69.60 ns | 0.41 | 0.02 | - | - | - | - | + // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | | + // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - | + // | Shuffle3 | 2. AVX | Empty | 768 | 109.12 ns | 2.149 ns | 2.010 ns | 108.66 ns | 0.28 | 0.01 | - | - | - | - | + // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | | + // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - | + // | Shuffle3 | 2. AVX | Empty | 1536 | 190.41 ns | 1.090 ns | 0.851 ns | 190.38 ns | 0.25 | 0.00 | - | - | - | - | + // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - | +} diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs index 75d7c8729..f1bfaa4ad 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Common // These cannot be expressed as a theory as you cannot // use RemoteExecutor within generic methods nor pass - // IComponentShuffle to the generic utils method. + // IShuffle4 to the generic utils method. WXYZShuffle4 wxyz = default; TestShuffleByte4Channel( size, @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.Common // These cannot be expressed as a theory as you cannot // use RemoteExecutor within generic methods nor pass // IShuffle3 to the generic utils method. - ZYXShuffle3 zyx = default; + var zyx = new DefaultShuffle3(0, 1, 2); TestShuffleByte3Channel( size, (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zyx),