Browse Source

Cleanup and fix tests

pull/1409/head
James Jackson-South 5 years ago
parent
commit
49062c4ee5
  1. 23
      src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
  2. 8
      src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs
  3. 43
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs
  4. 13
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs
  5. 2
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  6. 18
      src/ImageSharp/PixelFormats/Utils/PixelConverter.cs
  7. 64
      tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs
  8. 4
      tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs

23
src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs

@ -31,7 +31,12 @@ namespace SixLabors.ImageSharp
void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest);
}
internal readonly struct DefaultShuffle4 : IComponentShuffle
/// <inheritdoc/>
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<byte>(p3, 0, 3, nameof(p3));
Guard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
Guard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
Guard.MustBeBetweenOrEqualTo<byte>(p0, 0, 3, nameof(p0));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p3, 0, 3, nameof(p3));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(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);

8
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<byte>(p3, 0, 3, nameof(p3));
Guard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
Guard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
Guard.MustBeBetweenOrEqualTo<byte>(p0, 0, 3, nameof(p0));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p3, 0, 3, nameof(p3));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p0, 0, 3, nameof(p0));
this.p3 = p3;
this.p2 = p2;

43
src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs

@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp
public DefaultShuffle3(byte p2, byte p1, byte p0)
{
Guard.MustBeBetweenOrEqualTo<byte>(p2, 0, 2, nameof(p2));
Guard.MustBeBetweenOrEqualTo<byte>(p1, 0, 2, nameof(p1));
Guard.MustBeBetweenOrEqualTo<byte>(p0, 0, 2, nameof(p0));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 2, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 2, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(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<byte> source, Span<byte> dest)
{
ref Byte3 sBase = ref Unsafe.As<byte, Byte3>(ref MemoryMarshal.GetReference(source));
ref Byte3 dBase = ref Unsafe.As<byte, Byte3>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 3;
for (int i = 0; i < n; i++)
{
uint packed = Unsafe.As<Byte3, uint>(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<uint, Byte3>(ref packed);
}
}
}
[StructLayout(LayoutKind.Explicit, Size = 3)]
internal readonly struct Byte3
{
}
}

13
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<byte>(p3, 0, 3, nameof(p3));
Guard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
Guard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
Guard.MustBeBetweenOrEqualTo<byte>(p0, 0, 3, nameof(p0));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p3, 0, 3, nameof(p3));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p2, 0, 3, nameof(p2));
DebugGuard.MustBeBetweenOrEqualTo<byte>(p1, 0, 3, nameof(p1));
DebugGuard.MustBeBetweenOrEqualTo<byte>(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
{
}
}

2
src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs

@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp
ReadOnlySpan<byte> source,
Span<byte> dest,
TShuffle shuffle)
where TShuffle : struct, IComponentShuffle
where TShuffle : struct, IShuffle4
{
VerifyShuffle4SpanInput(source, dest);

18
src/ImageSharp/PixelFormats/Utils/PixelConverter.cs

@ -164,7 +164,14 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils
public static void ToBgra32(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2));
// TODO: Bgr24
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Rgb24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Bgr24"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToBgr24(ReadOnlySpan<byte> source, Span<byte> 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<byte> source, Span<byte> dest)
=> SimdUtils.Pad3Shuffle4<XYZWPad3Shuffle4>(source, dest, default);
// TODO: Rgb24
/// <summary>
/// Converts a <see cref="ReadOnlySpan{Byte}"/> representing a collection of
/// <see cref="Bgr24"/> pixels to a <see cref="Span{Byte}"/> representing
/// a collection of <see cref="Rgb24"/> pixels.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToRgb24(ReadOnlySpan<byte> source, Span<byte> dest)
=> SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2));
}
}
}

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

4
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),

Loading…
Cancel
Save