diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index e4fd9bce60..1395975ec8 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -39,22 +39,31 @@ namespace SixLabors.ImageSharp return (a / GreatestCommonDivisor(a, b)) * b; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo4(int a) => a & 3; + /// + /// Calculates % 4 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int Modulo4(int x) => x & 3; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo8(int a) => a & 7; + /// + /// Calculates % 8 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int Modulo8(int x) => x & 7; /// - /// Fast (mod m) calculator, - /// where should be a power of 2. + /// Fast (x mod m) calculator, with the restriction that + /// should be power of 2. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ModuloP2(int a, int m) + [MethodImpl(InliningOptions.ShortMethod)] + public static int ModuloP2(int x, int m) { - return a & (m - 1); + return x & (m - 1); } + [MethodImpl(InliningOptions.ShortMethod)] + public static float Clamp(float x, float min, float max) => Math.Min(max, Math.Max(min, x)); + /// /// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation. /// @@ -62,7 +71,7 @@ namespace SixLabors.ImageSharp /// A number that is greater than , but less than or equal to /// /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static int FastAbs(int x) { int y = x >> 31; @@ -74,7 +83,7 @@ namespace SixLabors.ImageSharp /// /// A single-precision floating-point number /// The number raised to the power of 2. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float Pow2(float x) => x * x; /// @@ -82,7 +91,7 @@ namespace SixLabors.ImageSharp /// /// A single-precision floating-point number /// The number raised to the power of 3. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float Pow3(float x) => x * x * x; /// @@ -93,7 +102,7 @@ namespace SixLabors.ImageSharp /// /// The /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static int GetBitsNeededForColorDepth(int colors) => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2))); /// @@ -101,7 +110,7 @@ namespace SixLabors.ImageSharp /// /// The bit depth. /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static int GetColorCountForBitDepth(int bitDepth) => 1 << bitDepth; /// @@ -110,7 +119,7 @@ namespace SixLabors.ImageSharp /// The x provided to G(x). /// The spread of the blur. /// The Gaussian G(x) - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float Gaussian(float x, float sigma) { const float Numerator = 1.0f; @@ -133,7 +142,7 @@ namespace SixLabors.ImageSharp /// /// The sine cardinal of . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float SinC(float f) { if (MathF.Abs(f) > Constants.Epsilon) @@ -156,7 +165,7 @@ namespace SixLabors.ImageSharp /// /// The . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float GetBcValue(float x, float b, float c) { if (x < 0F) @@ -192,7 +201,7 @@ namespace SixLabors.ImageSharp /// /// The bounding . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) => new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); /// diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs index c7fd21a8f0..713d606e79 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs @@ -21,28 +21,58 @@ namespace SixLabors.ImageSharp public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture; /// - /// as much elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// + [MethodImpl(InliningOptions.ShortMethod)] internal static void BulkConvertByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); - if (IsAvailable) + if (!IsAvailable) { - int remainder = source.Length % 8; - int alignedCount = source.Length - remainder; - - if (alignedCount > 0) - { - BulkConvertByteToNormalizedFloat( - source.Slice(0, alignedCount), - dest.Slice(0, alignedCount)); - - source = source.Slice(alignedCount); - dest = dest.Slice(alignedCount); - } + return; + } + + int remainder = ImageMaths.Modulo8(source.Length); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertByteToNormalizedFloat( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); + + if (!IsAvailable) + { + return; + } + + int remainder = ImageMaths.Modulo8(source.Length); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); } } @@ -57,7 +87,7 @@ namespace SixLabors.ImageSharp { GuardAvx2(nameof(BulkConvertByteToNormalizedFloat)); - DebugGuard.IsTrue((dest.Length % 8) == 0, nameof(source), "dest.Length should be divisable by 8!"); + DebugGuard.IsTrue(ImageMaths.Modulo8(dest.Length) == 0, nameof(source), "dest.Length should be divisable by 8!"); var bVec = new Vector(256.0f / 255.0f); var magicFloat = new Vector(32768.0f); @@ -93,30 +123,6 @@ namespace SixLabors.ImageSharp } } - /// - /// as much elements as possible, slicing them down (keeping the remainder). - /// - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( - ref ReadOnlySpan source, - ref Span dest) - { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); - - if (IsAvailable) - { - int remainder = source.Length % Vector.Count; - int alignedCount = source.Length - remainder; - - if (alignedCount > 0) - { - BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, alignedCount), dest.Slice(0, alignedCount)); - - source = source.Slice(alignedCount); - dest = dest.Slice(alignedCount); - } - } - } - /// /// Implementation of which is faster on older runtimes. /// @@ -124,7 +130,7 @@ namespace SixLabors.ImageSharp { GuardAvx2(nameof(BulkConvertNormalizedFloatToByteClampOverflows)); - DebugGuard.IsTrue((source.Length % 8) == 0, nameof(source), "source.Length should be divisible by 8!"); + DebugGuard.IsTrue(ImageMaths.Modulo8(source.Length) == 0, nameof(source), "source.Length should be divisible by 8!"); if (source.Length == 0) { @@ -174,7 +180,10 @@ namespace SixLabors.ImageSharp { GuardAvx2(nameof(BulkConvertNormalizedFloatToByte)); - DebugGuard.IsTrue((source.Length % Vector.Count) == 0, nameof(source), "source.Length should be divisable by Vector.Count!"); + DebugGuard.IsTrue( + ImageMaths.Modulo8(source.Length) == 0, + nameof(source), + "source.Length should be divisible by 8!"); if (source.Length == 0) { diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs index 996a08fb4b..dfa6f189c8 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -28,27 +28,58 @@ namespace SixLabors.ImageSharp #endif /// - /// as much elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// - [Conditional("NETCOREAPP2_1")] + [MethodImpl(InliningOptions.ShortMethod)] internal static void BulkConvertByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); - if (IsAvailable) + if (!IsAvailable) { - int remainder = source.Length % Vector.Count; - int alignedCount = source.Length - remainder; + return; + } + + int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; - if (alignedCount > 0) - { - BulkConvertByteToNormalizedFloat(source.Slice(0, alignedCount), dest.Slice(0, alignedCount)); + if (adjustedCount > 0) + { + BulkConvertByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); - source = source.Slice(alignedCount); - dest = dest.Slice(alignedCount); - } + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); + + if (!IsAvailable) + { + return; + } + + int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertNormalizedFloatToByteClampOverflows( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); } } @@ -58,7 +89,7 @@ namespace SixLabors.ImageSharp internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) { DebugGuard.IsTrue( - dest.Length % Vector.Count == 0, + ImageMaths.ModuloP2(dest.Length, Vector.Count) == 0, nameof(source), "dest.Length should be divisible by Vector.Count!"); @@ -67,8 +98,6 @@ namespace SixLabors.ImageSharp ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - var scale = new Vector(1f / 255f); - for (int i = 0; i < n; i++) { Vector b = Unsafe.Add(ref sourceBase, i); @@ -77,10 +106,10 @@ namespace SixLabors.ImageSharp Vector.Widen(s0, out Vector w0, out Vector w1); Vector.Widen(s1, out Vector w2, out Vector w3); - Vector f0 = ConvertToSingle(w0, scale); - Vector f1 = ConvertToSingle(w1, scale); - Vector f2 = ConvertToSingle(w2, scale); - Vector f3 = ConvertToSingle(w3, scale); + Vector f0 = ConvertToSingle(w0); + Vector f1 = ConvertToSingle(w1); + Vector f2 = ConvertToSingle(w2); + Vector f3 = ConvertToSingle(w3); ref Vector d = ref Unsafe.Add(ref destBase, i * 4); d = f0; @@ -90,31 +119,6 @@ namespace SixLabors.ImageSharp } } - /// - /// as much elements as possible, slicing them down (keeping the remainder). - /// - [Conditional("NETCOREAPP2_1")] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( - ref ReadOnlySpan source, - ref Span dest) - { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); - - if (IsAvailable) - { - int remainder = source.Length % Vector.Count; - int alignedCount = source.Length - remainder; - - if (alignedCount > 0) - { - BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, alignedCount), dest.Slice(0, alignedCount)); - - source = source.Slice(alignedCount); - dest = dest.Slice(alignedCount); - } - } - } - /// /// Implementation of , which is faster on new .NET runtime. /// @@ -123,7 +127,7 @@ namespace SixLabors.ImageSharp Span dest) { DebugGuard.IsTrue( - dest.Length % Vector.Count == 0, + ImageMaths.ModuloP2(dest.Length, Vector.Count) == 0, nameof(dest), "dest.Length should be divisible by Vector.Count!"); @@ -168,11 +172,11 @@ namespace SixLabors.ImageSharp } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector ConvertToSingle(Vector u, Vector scale) + private static Vector ConvertToSingle(Vector u) { Vector vi = Vector.AsVectorInt32(u); Vector v = Vector.ConvertToSingle(vi); - v *= scale; + v *= new Vector(1f / 255f); return v; } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs index bb21474660..2d9f53eafa 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs @@ -1,71 +1,81 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +// ReSharper disable MemberHidesStaticFromOuterClass namespace SixLabors.ImageSharp { internal static partial class SimdUtils { /// /// Fallback implementation based on (128bit). - /// For , efficient software fallback implementations are present - /// + maybe even mono can emit intrinsics for that type :P + /// For , efficient software fallback implementations are present, + /// and we hope that even mono's JIT is able to emit SIMD instructions for that type :P /// public static class FallbackIntrinsics128 { /// - /// as much elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// + [MethodImpl(InliningOptions.ShortMethod)] internal static void BulkConvertByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); - int remainder = source.Length % 4; - int alignedCount = source.Length - remainder; + int remainder = ImageMaths.Modulo4(source.Length); + int adjustedCount = source.Length - remainder; - if (alignedCount > 0) + if (adjustedCount > 0) { BulkConvertByteToNormalizedFloat( - source.Slice(0, alignedCount), - dest.Slice(0, alignedCount)); + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); - source = source.Slice(alignedCount); - dest = dest.Slice(alignedCount); + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); } } /// - /// as much elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// + [MethodImpl(InliningOptions.ShortMethod)] internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( ref ReadOnlySpan source, ref Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); - int remainder = source.Length % 4; - int alignedCount = source.Length - remainder; + int remainder = ImageMaths.Modulo4(source.Length); + int adjustedCount = source.Length - remainder; - if (alignedCount > 0) + if (adjustedCount > 0) { BulkConvertNormalizedFloatToByteClampOverflows( - source.Slice(0, alignedCount), - dest.Slice(0, alignedCount)); + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); - source = source.Slice(alignedCount); - dest = dest.Slice(alignedCount); + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); } } /// /// Implementation of using . /// + [MethodImpl(InliningOptions.ColdPath)] internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) { - DebugGuard.IsTrue((dest.Length % 4) == 0, nameof(dest), "dest.Length should be divisible by 4!"); + DebugGuard.IsTrue( + ImageMaths.Modulo4(dest.Length) == 0, + nameof(dest), + "dest.Length should be divisible by 4!"); int count = dest.Length / 4; if (count == 0) @@ -94,11 +104,15 @@ namespace SixLabors.ImageSharp /// /// Implementation of using . /// + [MethodImpl(InliningOptions.ColdPath)] internal static void BulkConvertNormalizedFloatToByteClampOverflows( ReadOnlySpan source, Span dest) { - DebugGuard.IsTrue((source.Length % 4) == 0, nameof(source), "source.Length should be divisible by 4!"); + DebugGuard.IsTrue( + ImageMaths.Modulo4(source.Length) == 0, + nameof(source), + "source.Length should be divisible by 4!"); int count = source.Length / 4; if (count == 0) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index bc75dc8caa..95a6030fd1 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -61,25 +62,22 @@ namespace SixLabors.ImageSharp /// /// The source span of bytes /// The destination span of floats + [MethodImpl(InliningOptions.ShortMethod)] internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); +#if NETCOREAPP2_1 ExtendedIntrinsics.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); +#else BasicIntrinsics256.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); +#endif FallbackIntrinsics128.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); // Deal with the remainder: - int count = source.Length; - if (count > 0) + if (source.Length > 0) { - // TODO: Do we need to optimize anything on this? (There are at most 7 remainders) - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref float dBase = ref MemoryMarshal.GetReference(dest); - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, i) / 255f; - } + ConverByteToNormalizedFloatRemainder(source, dest); } } @@ -91,35 +89,71 @@ namespace SixLabors.ImageSharp /// /// The source span of floats /// The destination span of bytes + [MethodImpl(InliningOptions.ShortMethod)] internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same size!"); +#if NETCOREAPP2_1 ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); +#else BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); +#endif FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); // Deal with the remainder: - int count = source.Length; - if (count > 0) + if (source.Length > 0) { - ref float sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(dest); - - for (int i = 0; i < count; i++) - { - // TODO: Do we need to optimize anything on this? (There are at most 7 remainders) - float f = Unsafe.Add(ref sBase, i); - f *= 255f; - f += 0.5f; - f = MathF.Max(0, f); - f = MathF.Min(255f, f); - - Unsafe.Add(ref dBase, i) = (byte)f; - } + ConvertNormalizedFloatToByteRemainder(source, dest); } } + [MethodImpl(InliningOptions.ColdPath)] + private static void ConverByteToNormalizedFloatRemainder(ReadOnlySpan source, Span dest) + { + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref float dBase = ref MemoryMarshal.GetReference(dest); + + // There are at most 3 elements at this point, having a for loop is overkill. + // Let's minimize the no. of instructions! + switch (source.Length) + { + case 3: + Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2) / 255f; + goto case 2; + case 2: + Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1) / 255f; + goto case 1; + case 1: + dBase = sBase / 255f; + break; + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan source, Span dest) + { + ref float sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); + + switch (source.Length) + { + case 3: + Unsafe.Add(ref dBase, 2) = ConvertToByte(Unsafe.Add(ref sBase, 2)); + goto case 2; + case 2: + Unsafe.Add(ref dBase, 1) = ConvertToByte(Unsafe.Add(ref sBase, 1)); + goto case 1; + case 1: + dBase = ConvertToByte(sBase); + break; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte ConvertToByte(float f) => (byte)ImageMaths.Clamp((f * 255f) + 0.5f, 0, 255f); + + [Conditional("DEBUG")] private static void GuardAvx2(string operation) { if (!IsAvx2CompatibleArchitecture) diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs index a56082fcd3..eaa52a9750 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs @@ -99,30 +99,30 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk } // RESULTS (2018 October): - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ------------------------------------------------------------------ |-------- |------ |-------------:|-------------:|-----------:|-------:|---------:|-------:|----------:| - // BasicBulk | Clr | 64 | 581.62 ns | 33.625 ns | 1.8999 ns | 2.27 | 0.02 | - | 0 B | - // BasicIntrinsics256_BulkConvertNormalizedFloatToByteClampOverflows | Clr | 64 | 256.66 ns | 45.153 ns | 2.5512 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic_BulkConvertNormalizedFloatToByteClampOverflows | Clr | 64 | 201.92 ns | 30.161 ns | 1.7042 ns | 0.79 | 0.01 | - | 0 B | - // PixelOperations_Base | Clr | 64 | 665.01 ns | 13.032 ns | 0.7363 ns | 2.59 | 0.02 | 0.0067 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 295.14 ns | 26.335 ns | 1.4880 ns | 1.15 | 0.01 | - | 0 B | - // | | | | | | | | | | - // BasicBulk | Core | 64 | 513.22 ns | 91.110 ns | 5.1479 ns | 3.19 | 0.03 | - | 0 B | - // BasicIntrinsics256_BulkConvertNormalizedFloatToByteClampOverflows | Core | 64 | 160.76 ns | 2.760 ns | 0.1559 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic_BulkConvertNormalizedFloatToByteClampOverflows | Core | 64 | 95.98 ns | 10.077 ns | 0.5694 ns | 0.60 | 0.00 | - | 0 B | - // PixelOperations_Base | Core | 64 | 591.74 ns | 49.856 ns | 2.8170 ns | 3.68 | 0.01 | 0.0067 | 24 B | - // PixelOperations_Specialized | Core | 64 | 149.11 ns | 4.485 ns | 0.2534 ns | 0.93 | 0.00 | - | 0 B | - // | | | | | | | | | | - // BasicBulk | Clr | 2048 | 15,345.85 ns | 1,213.551 ns | 68.5679 ns | 3.90 | 0.01 | - | 0 B | - // BasicIntrinsics256_BulkConvertNormalizedFloatToByteClampOverflows | Clr | 2048 | 3,939.49 ns | 71.101 ns | 4.0173 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic_BulkConvertNormalizedFloatToByteClampOverflows | Clr | 2048 | 2,272.61 ns | 110.671 ns | 6.2531 ns | 0.58 | 0.00 | - | 0 B | - // PixelOperations_Base | Clr | 2048 | 17,422.47 ns | 811.733 ns | 45.8644 ns | 4.42 | 0.01 | - | 24 B | - // PixelOperations_Specialized | Clr | 2048 | 3,984.26 ns | 110.352 ns | 6.2351 ns | 1.01 | 0.00 | - | 0 B | - // | | | | | | | | | | - // BasicBulk | Core | 2048 | 14,950.43 ns | 699.309 ns | 39.5123 ns | 3.76 | 0.02 | - | 0 B | - // BasicIntrinsics256_BulkConvertNormalizedFloatToByteClampOverflows | Core | 2048 | 3,978.28 ns | 481.105 ns | 27.1833 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic_BulkConvertNormalizedFloatToByteClampOverflows | Core | 2048 | 2,169.54 ns | 75.606 ns | 4.2719 ns | !!0.55!| 0.00 | - | 0 B | - // PixelOperations_Base | Core | 2048 | 18,403.62 ns | 1,494.056 ns | 84.4169 ns | 4.63 | 0.03 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 2,227.60 ns | 486.761 ns | 27.5029 ns | !!0.56!| 0.01 | - | 0 B | + // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + // ---------------------------- |-------- |------ |-------------:|-------------:|------------:|-------:|---------:|-------:|----------:| + // FallbackIntrinsics128 | Clr | 64 | 340.38 ns | 22.319 ns | 1.2611 ns | 1.41 | 0.01 | - | 0 B | + // BasicIntrinsics256 | Clr | 64 | 240.79 ns | 11.421 ns | 0.6453 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Clr | 64 | 199.09 ns | 124.239 ns | 7.0198 ns | 0.83 | 0.02 | - | 0 B | + // PixelOperations_Base | Clr | 64 | 647.99 ns | 24.003 ns | 1.3562 ns | 2.69 | 0.01 | 0.0067 | 24 B | + // PixelOperations_Specialized | Clr | 64 | 259.79 ns | 13.391 ns | 0.7566 ns | 1.08 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 64 | 234.64 ns | 12.320 ns | 0.6961 ns | 1.58 | 0.00 | - | 0 B | + // BasicIntrinsics256 | Core | 64 | 148.87 ns | 2.794 ns | 0.1579 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Core | 64 | 94.06 ns | 10.015 ns | 0.5659 ns | 0.63 | 0.00 | - | 0 B | + // PixelOperations_Base | Core | 64 | 573.52 ns | 31.865 ns | 1.8004 ns | 3.85 | 0.01 | 0.0067 | 24 B | + // PixelOperations_Specialized | Core | 64 | 117.21 ns | 13.264 ns | 0.7494 ns | 0.79 | 0.00 | - | 0 B | + // | | | | | | | | | | + // FallbackIntrinsics128 | Clr | 2048 | 6,735.93 ns | 2,139.340 ns | 120.8767 ns | 1.71 | 0.03 | - | 0 B | + // BasicIntrinsics256 | Clr | 2048 | 3,929.29 ns | 334.027 ns | 18.8731 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Clr | 2048 | 2,226.01 ns | 130.525 ns | 7.3749 ns |!! 0.57 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Clr | 2048 | 16,760.84 ns | 367.800 ns | 20.7814 ns | 4.27 | 0.02 | - | 24 B | <--- Extra copies using "Vector4 TPixel.ToVector4()" + // PixelOperations_Specialized | Clr | 2048 | 3,986.03 ns | 237.238 ns | 13.4044 ns | 1.01 | 0.00 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 2048 | 6,644.65 ns | 2,677.090 ns | 151.2605 ns | 1.69 | 0.05 | - | 0 B | + // BasicIntrinsics256 | Core | 2048 | 3,923.70 ns | 1,971.760 ns | 111.4081 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Core | 2048 | 2,092.32 ns | 375.657 ns | 21.2253 ns |!! 0.53 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Core | 2048 | 16,875.73 ns | 1,271.957 ns | 71.8679 ns | 4.30 | 0.10 | - | 24 B | + // PixelOperations_Specialized | Core | 2048 | 2,129.92 ns | 262.888 ns | 14.8537 ns |!! 0.54 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 519edaa31f..2cbe549e4a 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -191,30 +191,30 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk // RESULTS (2018 October): // - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------------------------------- |-------- |------ |------------:|-------------:|-----------:|-------:|---------:|-------:|----------:| - // BasicBulk | Clr | 64 | 267.40 ns | 30.711 ns | 1.7352 ns | 1.07 | 0.01 | - | 0 B | - // BasicIntrinsics256_BulkConvertByteToNormalizedFloat | Clr | 64 | 249.97 ns | 33.838 ns | 1.9119 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics_BulkConvertByteToNormalizedFloat | Clr | 64 | 176.97 ns | 5.221 ns | 0.2950 ns | 0.71 | 0.00 | - | 0 B | - // PixelOperations_Base | Clr | 64 | 349.70 ns | 104.331 ns | 5.8949 ns | 1.40 | 0.02 | 0.0072 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 288.31 ns | 26.833 ns | 1.5161 ns | 1.15 | 0.01 | - | 0 B | - // | | | | | | | | | | - // BasicBulk | Core | 64 | 185.36 ns | 30.051 ns | 1.6979 ns | 1.26 | 0.01 | - | 0 B | - // BasicIntrinsics256_BulkConvertByteToNormalizedFloat | Core | 64 | 146.84 ns | 12.674 ns | 0.7161 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics_BulkConvertByteToNormalizedFloat | Core | 64 | 67.31 ns | 2.542 ns | 0.1436 ns | 0.46 | 0.00 | - | 0 B | - // PixelOperations_Base | Core | 64 | 272.03 ns | 94.419 ns | 5.3348 ns | 1.85 | 0.03 | 0.0072 | 24 B | - // PixelOperations_Specialized | Core | 64 | 121.91 ns | 31.477 ns | 1.7785 ns | 0.83 | 0.01 | - | 0 B | - // | | | | | | | | | | - // BasicBulk | Clr | 2048 | 5,133.04 ns | 284.052 ns | 16.0494 ns | 1.21 | 0.01 | - | 0 B | - // BasicIntrinsics256_BulkConvertByteToNormalizedFloat | Clr | 2048 | 4,248.58 ns | 1,095.887 ns | 61.9196 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics_BulkConvertByteToNormalizedFloat | Clr | 2048 | 1,214.02 ns | 184.349 ns | 10.4160 ns | 0.29 | 0.00 | - | 0 B | - // PixelOperations_Base | Clr | 2048 | 7,096.04 ns | 362.350 ns | 20.4734 ns | 1.67 | 0.02 | - | 24 B | - // PixelOperations_Specialized | Clr | 2048 | 4,314.19 ns | 204.964 ns | 11.5809 ns | 1.02 | 0.01 | - | 0 B | - // | | | | | | | | | | - // BasicBulk | Core | 2048 | 5,038.38 ns | 223.282 ns | 12.6158 ns | 1.20 | 0.01 | - | 0 B | - // BasicIntrinsics256_BulkConvertByteToNormalizedFloat | Core | 2048 | 4,199.17 ns | 897.985 ns | 50.7378 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics_BulkConvertByteToNormalizedFloat | Core | 2048 | 1,113.86 ns | 64.799 ns | 3.6613 ns | !!0.27!| 0.00 | - | 0 B | - // PixelOperations_Base | Core | 2048 | 7,015.00 ns | 920.083 ns | 51.9864 ns | 1.67 | 0.02 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 1,176.59 ns | 256.955 ns | 14.5184 ns | !!0.28!| 0.00 | - | 0 B | + // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + // ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:| + // FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B | + // BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B | + // PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B | + // PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B | + // BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B | + // PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B | + // PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B | + // | | | | | | | | | | + // FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B | + // BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B | + // PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B | + // BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B | + // PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Abs.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs similarity index 88% rename from tests/ImageSharp.Benchmarks/General/Abs.cs rename to tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs index a67f3f1078..ea53959b6a 100644 --- a/tests/ImageSharp.Benchmarks/General/Abs.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs @@ -1,9 +1,9 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; +using System; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +{ public class Abs { [Params(-1, 1)] diff --git a/tests/ImageSharp.Benchmarks/General/Clamp.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Clamp.cs similarity index 94% rename from tests/ImageSharp.Benchmarks/General/Clamp.cs rename to tests/ImageSharp.Benchmarks/General/BasicMath/Clamp.cs index ef6bc3c402..d486cb2f37 100644 --- a/tests/ImageSharp.Benchmarks/General/Clamp.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Clamp.cs @@ -3,13 +3,13 @@ // Licensed under the Apache License, Version 2.0. // -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; - using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; - using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +{ public class Clamp { [Params(-1, 0, 255, 256)] diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs new file mode 100644 index 0000000000..9ddfad7222 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs @@ -0,0 +1,23 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Jobs; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +{ + [LongRunJob] + public class ModuloPowerOfTwoConstant + { + private readonly int value = 42; + + [Benchmark(Baseline = true)] + public int Standard() + { + return this.value % 8; + } + + [Benchmark] + public int Bitwise() + { + return ImageMaths.Modulo8(this.value); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs new file mode 100644 index 0000000000..5c2fe81fa2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs @@ -0,0 +1,32 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Jobs; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +{ + [LongRunJob] + public class ModuloPowerOfTwoVariable + { + private readonly int value = 42; + + private readonly int m = 32; + + [Benchmark(Baseline = true)] + public int Standard() + { + return this.value % this.m; + } + + [Benchmark] + public int Bitwise() + { + return ImageMaths.ModuloP2(this.value, this.m); + } + + // RESULTS: + // + // Method | Mean | Error | StdDev | Median | Scaled | ScaledSD | + // --------- |----------:|----------:|----------:|----------:|-------:|---------:| + // Standard | 1.2465 ns | 0.0093 ns | 0.0455 ns | 1.2423 ns | 1.00 | 0.00 | + // Bitwise | 0.0265 ns | 0.0103 ns | 0.0515 ns | 0.0000 ns | 0.02 | 0.04 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Pow.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs similarity index 93% rename from tests/ImageSharp.Benchmarks/General/Pow.cs rename to tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs index 325bd9d20e..0f256fc781 100644 --- a/tests/ImageSharp.Benchmarks/General/Pow.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs @@ -1,7 +1,8 @@ using System; + using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { public class Pow { diff --git a/tests/ImageSharp.Benchmarks/General/Modulus.cs b/tests/ImageSharp.Benchmarks/General/Modulus.cs deleted file mode 100644 index e6d5ccce62..0000000000 --- a/tests/ImageSharp.Benchmarks/General/Modulus.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using BenchmarkDotNet.Attributes; - - public class Modulus - { - [Benchmark(Baseline = true, Description = "Standard Modulus using %")] - public int StandardModulus() - { - return 255 % 256; - } - - [Benchmark(Description = "Bitwise Modulus using &")] - public int BitwiseModulus() - { - return 255 & 255; - } - } -} diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index feefd17580..c63cb3438f 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -264,13 +264,26 @@ namespace SixLabors.ImageSharp.Tests.Common TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) ); + + // for small values, let's stress test the implementation a bit: + if (count > 0 && count < 10) + { + for (int i = 0; i < 20; i++) + { + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span), + i + 42); + } + } } private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( int count, - Action, Memory> convert) + Action, Memory> convert, int seed = -1) { - float[] source = new Random(count).GenerateRandomFloatArray(count, -0.1f, 1.2f); + seed = seed > 0 ? seed : count; + float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); byte[] expected = source.Select(NormalizedFloatToByte).ToArray(); byte[] actual = new byte[count]; diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs index aec4d0b810..d8b1525bed 100644 --- a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs @@ -10,56 +10,70 @@ namespace SixLabors.ImageSharp.Tests.Helpers public class ImageMathsTests { [Theory] - [InlineData(0, 0)] - [InlineData(1, 1)] - [InlineData(2, 2)] - [InlineData(3, 3)] - [InlineData(4, 0)] - [InlineData(100, 0)] - [InlineData(123, 3)] - [InlineData(53436353, 1)] - public void Modulo4(int a, int expected) + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(100)] + [InlineData(123)] + [InlineData(53436353)] + public void Modulo4(int x) { - int actual = ImageMaths.Modulo4(a); - Assert.Equal(expected, actual); + int actual = ImageMaths.Modulo4(x); + Assert.Equal(x % 4, actual); } [Theory] - [InlineData(0, 0)] - [InlineData(1, 1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(6)] + [InlineData(7)] + [InlineData(8)] + [InlineData(100)] + [InlineData(123)] + [InlineData(53436353)] + [InlineData(975)] + public void Modulo8(int x) + { + int actual = ImageMaths.Modulo8(x); + Assert.Equal(x % 8, actual); + } + + [Theory] + [InlineData(0, 2)] + [InlineData(1, 2)] [InlineData(2, 2)] - [InlineData(6, 6)] - [InlineData(7, 7)] - [InlineData(8, 0)] - [InlineData(100, 4)] - [InlineData(123, 3)] - [InlineData(53436353, 1)] - [InlineData(975, 7)] - public void Modulo8(int a, int expected) + [InlineData(0, 4)] + [InlineData(3, 4)] + [InlineData(5, 4)] + [InlineData(5, 8)] + [InlineData(8, 8)] + [InlineData(8, 16)] + [InlineData(15, 16)] + [InlineData(17, 16)] + [InlineData(17, 32)] + [InlineData(31, 32)] + [InlineData(32, 32)] + [InlineData(33, 32)] + public void Modulo2P(int x, int m) { - int actual = ImageMaths.Modulo8(a); - Assert.Equal(expected, actual); + int actual = ImageMaths.ModuloP2(x, m); + Assert.Equal(x % m, actual); } [Theory] - [InlineData(0, 2, 0)] - [InlineData(1, 2, 1)] - [InlineData(2, 2, 0)] - [InlineData(0, 4, 0)] - [InlineData(3, 4, 3)] - [InlineData(5, 4, 1)] - [InlineData(5, 8, 5)] - [InlineData(8, 8, 0)] - [InlineData(8, 16, 8)] - [InlineData(15, 16, 15)] - [InlineData(17, 16, 1)] - [InlineData(17, 32, 17)] - [InlineData(31, 32, 31)] - [InlineData(32, 32, 0)] - [InlineData(33, 32, 1)] - public void Modulo2P(int a, int m, int expected) + [InlineData(0, 0, 0, 0)] + [InlineData(0.5f, 0, 1, 0.5f)] + [InlineData(-0.5f, -0.1f, 10, -0.1f)] + [InlineData(-0.05f, -0.1f, 10, -0.05f)] + [InlineData(9.9f, -0.1f, 10, 9.9f)] + [InlineData(10f, -0.1f, 10, 10f)] + [InlineData(10.1f, -0.1f, 10, 10f)] + public void Clamp(float x, float min, float max, float expected) { - int actual = ImageMaths.ModuloP2(a, m); + float actual = ImageMaths.Clamp(x, min, max); Assert.Equal(expected, actual); }