From eb1e4316b7f553255286d1ce2ab081c9cb84e878 Mon Sep 17 00:00:00 2001 From: Mordechai Zuber Date: Sun, 26 Feb 2017 07:14:01 +0200 Subject: [PATCH 01/18] Wyam documentation --- .gitignore | 3 ++- config.wyam | 4 ++++ input/about.md | 3 +++ theme/index.cshtml | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 config.wyam create mode 100644 input/about.md create mode 100644 theme/index.cshtml diff --git a/.gitignore b/.gitignore index 6e291ec94..d1463bd54 100644 --- a/.gitignore +++ b/.gitignore @@ -212,4 +212,5 @@ artifacts/ **/BenchmarkDotNet.Artifacts/ #CodeCoverage -**/CodeCoverage/* \ No newline at end of file +**/CodeCoverage/* +docs/ diff --git a/config.wyam b/config.wyam new file mode 100644 index 000000000..3a4b64c54 --- /dev/null +++ b/config.wyam @@ -0,0 +1,4 @@ +#recipe Docs +Settings[Keys.Host] = "imagesharp.org"; +Settings[Keys.Title] = "Image Sharp"; +FileSystem.OutputPath = "./docs"; \ No newline at end of file diff --git a/input/about.md b/input/about.md new file mode 100644 index 000000000..42739928a --- /dev/null +++ b/input/about.md @@ -0,0 +1,3 @@ +Title: About This Project +--- +This project is awesome! \ No newline at end of file diff --git a/theme/index.cshtml b/theme/index.cshtml new file mode 100644 index 000000000..d3656f800 --- /dev/null +++ b/theme/index.cshtml @@ -0,0 +1,3 @@ +Title: Home +--- +Welcome to the documentation for ImageSharp \ No newline at end of file From 553d4f2ab725c7244c3300adc64c6c19dd78a484 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 6 Mar 2017 21:53:36 +0100 Subject: [PATCH 02/18] DebugGuard --- codecov.yml | 4 + src/ImageSharp/Colors/Color.BulkOperations.cs | 25 ++++ src/ImageSharp/Common/Helpers/DebugGuard.cs | 132 ++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 codecov.yml create mode 100644 src/ImageSharp/Colors/Color.BulkOperations.cs diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..ae6dd5f6b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,4 @@ +ignore: + "src/ImageSharp/Common/Helpers/DebugGuard.cs" + + \ No newline at end of file diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs new file mode 100644 index 000000000..75d9eb580 --- /dev/null +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -0,0 +1,25 @@ +namespace ImageSharp +{ + using System.Numerics; + + public partial struct Color + { + internal class BulkOperations : BulkPixelOperations + { + private static readonly int VectorSize = Vector.Count; + + internal static void PackToVector4Aligned( + ArrayPointer source, + ArrayPointer destination, + int count) + { + DebugGuard.IsTrue( + count % VectorSize == 0, + nameof(count), + "Argument 'count' should divisible by Vector.Count!"); + + + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 0ca6f0912..1375636f4 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -29,5 +29,137 @@ namespace ImageSharp throw new ArgumentNullException(parameterName); } } + + + /// + /// Verifies that the specified value is less than a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [Conditional("DEBUG")] + public static void MustBeLessThan(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) >= 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}."); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [Conditional("DEBUG")] + public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}."); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [Conditional("DEBUG")] + public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) <= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [Conditional("DEBUG")] + public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}."); + } + } + + + /// + /// Verifies, that the method parameter with specified target value is true + /// and throws an exception if it is found to be so. + /// + /// + /// The target value, which cannot be false. + /// + /// + /// The name of the parameter that is to be checked. + /// + /// + /// The error message, if any to add to the exception. + /// + /// + /// is false + /// + [Conditional("DEBUG")] + public static void IsTrue(bool target, string parameterName, string message) + { + if (!target) + { + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Verifies, that the method parameter with specified target value is false + /// and throws an exception if it is found to be so. + /// + /// The target value, which cannot be true. + /// The name of the parameter that is to be checked. + /// The error message, if any to add to the exception. + /// + /// is true + /// + [Conditional("DEBUG")] + public static void IsFalse(bool target, string parameterName, string message) + { + if (target) + { + throw new ArgumentException(message, parameterName); + } + } } } \ No newline at end of file From 793fdf41d6c4b296153386e9b8a5af79990bbd5f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Mar 2017 00:45:13 +0100 Subject: [PATCH 03/18] implemented Color.BulkOperations.ToVector4() --- src/ImageSharp/Colors/Color.BulkOperations.cs | 117 ++++++++++++++- src/ImageSharp/Colors/Color.cs | 2 +- .../ImageSharp.Sandbox46.csproj | 3 + .../Colors/BulkPixelOperationsTests.cs | 133 ++++++++++++------ .../Formats/Jpg/JpegUtilityTestFixture.cs | 25 +--- .../TestUtilities/ApproximateFloatComparer.cs | 38 +++++ 6 files changed, 246 insertions(+), 72 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 75d9eb580..9d698b8ac 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -1,24 +1,129 @@ namespace ImageSharp { + using System; using System.Numerics; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; public partial struct Color { + /// + /// implementation optimized for . + /// internal class BulkOperations : BulkPixelOperations { - private static readonly int VectorSize = Vector.Count; + /// + /// Value type to store -s unpacked into multiple -s. + /// + private struct RGBAUint + { + private uint r; + private uint g; + private uint b; + private uint a; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Load(uint p) + { + this.r = p; + this.g = p >> Color.GreenShift; + this.b = p >> Color.BlueShift; + this.a = p >> Color.AlphaShift; + } + } - internal static void PackToVector4Aligned( - ArrayPointer source, - ArrayPointer destination, + /// + /// SIMD optimized bulk implementation of + /// that works only with `count` divisible by . + /// + /// The to the source colors. + /// The to the dstination vectors. + /// The number of pixels to convert. + /// + /// Implementation adapted from: + /// + /// http://stackoverflow.com/a/5362789 + /// + /// + internal static unsafe void ToVector4SimdAligned( + BufferPointer sourceColors, + BufferPointer destVectors, int count) { + int vecSize = Vector.Count; + DebugGuard.IsTrue( - count % VectorSize == 0, + count % vecSize == 0, nameof(count), - "Argument 'count' should divisible by Vector.Count!"); + "Argument 'count' should divisible by Vector.Count!" + ); + + Vector bVec = new Vector(256.0f / 255.0f); + Vector magicInt = new Vector(1191182336); + Vector magicFloat = new Vector(32768.0f); + Vector mask = new Vector(255); + + int rawInputSize = count * 4; + + uint* src = (uint*)sourceColors.PointerAtOffset; + uint* srcEnd = src + count; + + using (PinnedBuffer tempBuf = new PinnedBuffer(rawInputSize + Vector.Count)) + { + uint* tPtr = (uint*)tempBuf.Pointer; + uint[] temp = tempBuf.Array; + float[] fTemp = Unsafe.As(temp); + RGBAUint* dst = (RGBAUint*)tPtr; + + for (; src < srcEnd; src++, dst++) + { + dst->Load(*src); + } + + for (int i = 0; i < rawInputSize; i += vecSize) + { + Vector vi = new Vector(temp, i); + + vi &= mask; + vi |= magicInt; + + Vector vf = Vector.AsVectorSingle(vi); + vf = (vf - magicFloat) * bVec; + vf.CopyTo(fTemp, i); + } + + // TODO: Replace this with an optimized ArrayPointer.Copy() implementation: + uint byteCount = (uint)rawInputSize * sizeof(float); + + if (byteCount > 1024u) + { + Marshal.Copy(fTemp, 0, destVectors.PointerAtOffset, rawInputSize); + } + else + { + Unsafe.CopyBlock((void*)destVectors, tPtr, byteCount); + } + } + } + + /// + internal override void ToVector4(BufferPointer sourceColors, BufferPointer destVectors, int count) + { + int remainder = count % Vector.Count; + + int alignedCount = count - remainder; + if (alignedCount > 0) + { + ToVector4SimdAligned(sourceColors, destVectors, alignedCount); + } + if (remainder > 0) + { + sourceColors = sourceColors.Slice(alignedCount); + destVectors = destVectors.Slice(alignedCount); + base.ToVector4(sourceColors, destVectors, remainder); + } } } } diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 1ad6f9a64..597730937 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -246,7 +246,7 @@ namespace ImageSharp } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public BulkPixelOperations CreateBulkOperations() => new Color.BulkOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 1d7899486..a8b7ceb33 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -272,6 +272,9 @@ Tests\TestImages.cs + + Tests\TestUtilities\ApproximateFloatComparer.cs + Tests\TestUtilities\Attributes\ImageDataAttributeBase.cs diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 80d5952a1..41abd9d4a 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -10,13 +10,32 @@ public class Color : BulkPixelOperationsTests { // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: - public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + + [Fact] + public void IsSpecialImplementation() + { + Assert.IsType(BulkPixelOperations.Instance); + } + + [Fact] + public void ToVector4SimdAligned() + { + ImageSharp.Color[] source = CreatePixelTestData(64); + Vector4[] expected = CreateExpectedVector4Data(source); + + TestOperation( + source, + expected, + (s, d) => ImageSharp.Color.BulkOperations.ToVector4SimdAligned(s, d, 64) + ); + } } public class Argb : BulkPixelOperationsTests { // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: - public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; } [Theory] @@ -32,42 +51,56 @@ where TColor : struct, IPixel { public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void PackFromVector4(int count) + + private static BulkPixelOperations Operations => BulkPixelOperations.Instance; + + internal static TColor[] CreateExpectedPixelData(Vector4[] source) { - Vector4[] source = CreateVector4TestData(count); - TColor[] expected = new TColor[count]; + TColor[] expected = new TColor[source.Length]; - for (int i = 0; i < count; i++) + for (int i = 0; i < expected.Length; i++) { expected[i].PackFromVector4(source[i]); } + return expected; + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromVector4(int count) + { + Vector4[] source = CreateVector4TestData(count); + TColor[] expected = CreateExpectedPixelData(source); TestOperation( source, expected, - (ops, s, d) => ops.PackFromVector4(s, d, count) + (s, d) => Operations.PackFromVector4(s, d, count) ); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void PackToVector4(int count) + internal static Vector4[] CreateExpectedVector4Data(TColor[] source) { - TColor[] source = CreatePixelTestData(count); - Vector4[] expected = new Vector4[count]; + Vector4[] expected = new Vector4[source.Length]; - for (int i = 0; i < count; i++) + for (int i = 0; i < expected.Length; i++) { expected[i] = source[i].ToVector4(); } + return expected; + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToVector4(int count) + { + TColor[] source = CreatePixelTestData(count); + Vector4[] expected = CreateExpectedVector4Data(source); TestOperation( source, expected, - (ops, s, d) => ops.ToVector4(s, d, count) + (s, d) => Operations.ToVector4(s, d, count) ); } @@ -89,13 +122,13 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackFromXyzBytes(s, d, count) + (s, d) => Operations.PackFromXyzBytes(s, d, count) ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackToXyzBytes(int count) + public void ToXyzBytes(int count) { TColor[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 3]; @@ -109,7 +142,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.ToXyzBytes(s, d, count) + (s, d) => Operations.ToXyzBytes(s, d, count) ); } @@ -130,13 +163,13 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackFromXyzwBytes(s, d, count) + (s, d) => Operations.PackFromXyzwBytes(s, d, count) ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackToXyzwBytes(int count) + public void ToXyzwBytes(int count) { TColor[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 4]; @@ -150,7 +183,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.ToXyzwBytes(s, d, count) + (s, d) => Operations.ToXyzwBytes(s, d, count) ); } @@ -171,13 +204,13 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackFromZyxBytes(s, d, count) + (s, d) => Operations.PackFromZyxBytes(s, d, count) ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackToZyxBytes(int count) + public void ToZyxBytes(int count) { TColor[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 3]; @@ -191,7 +224,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.ToZyxBytes(s, d, count) + (s, d) => Operations.ToZyxBytes(s, d, count) ); } @@ -212,13 +245,13 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackFromZyxwBytes(s, d, count) + (s, d) => Operations.PackFromZyxwBytes(s, d, count) ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackToZyxwBytes(int count) + public void ToZyxwBytes(int count) { TColor[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 4]; @@ -232,7 +265,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.ToZyxwBytes(s, d, count) + (s, d) => Operations.ToZyxwBytes(s, d, count) ); } @@ -262,33 +295,51 @@ this.ExpectedDestBuffer.Dispose(); } + private const float Tolerance = 0.0001f; + public void Verify() { int count = this.ExpectedDestBuffer.Count; - TDest[] expected = this.ExpectedDestBuffer.Array; - TDest[] actual = this.ActualDestBuffer.Array; - for (int i = 0; i < count; i++) + + if (typeof(TDest) == typeof(Vector4)) { - Assert.Equal(expected[i], actual[i]); + Vector4[] expected = this.ExpectedDestBuffer.Array as Vector4[]; + Vector4[] actual = this.ActualDestBuffer.Array as Vector4[]; + + for (int i = 0; i < count; i++) + { + // ReSharper disable PossibleNullReferenceException + Assert.Equal(expected[i], actual[i], new ApproximateFloatComparer(0.001f)); + // ReSharper restore PossibleNullReferenceException + } + } + else + { + TDest[] expected = this.ExpectedDestBuffer.Array; + TDest[] actual = this.ActualDestBuffer.Array; + for (int i = 0; i < count; i++) + { + Assert.Equal(expected[i], actual[i]); + } } } } - private static void TestOperation( + internal static void TestOperation( TSource[] source, TDest[] expected, - Action, BufferPointer, BufferPointer> action) + Action, BufferPointer> action) where TSource : struct where TDest : struct { using (var buffers = new TestBuffers(source, expected)) { - action(BulkPixelOperations.Instance, buffers.Source, buffers.ActualDest); + action(buffers.Source, buffers.ActualDest); buffers.Verify(); } } - private static Vector4[] CreateVector4TestData(int length) + internal static Vector4[] CreateVector4TestData(int length) { Vector4[] result = new Vector4[length]; Random rnd = new Random(42); // Deterministic random values @@ -300,7 +351,7 @@ return result; } - private static TColor[] CreatePixelTestData(int length) + internal static TColor[] CreatePixelTestData(int length) { TColor[] result = new TColor[length]; @@ -315,7 +366,7 @@ return result; } - private static byte[] CreateByteTestData(int length) + internal static byte[] CreateByteTestData(int length) { byte[] result = new byte[length]; Random rnd = new Random(42); // Deterministic random values @@ -326,8 +377,8 @@ } return result; } - - private static Vector4 GetVector(Random rnd) + + internal static Vector4 GetVector(Random rnd) { return new Vector4( (float)rnd.NextDouble(), diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs index 736225680..252b01138 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs @@ -4,14 +4,13 @@ // using System.Text; -using ImageSharp.Formats; + using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace ImageSharp.Tests { using System; - using System.Collections.Generic; using System.Diagnostics; using ImageSharp.Formats.Jpg; @@ -99,28 +98,6 @@ namespace ImageSharp.Tests this.Output.WriteLine(bld.ToString()); } - internal struct ApproximateFloatComparer : IEqualityComparer - { - private readonly float Eps; - - public ApproximateFloatComparer(float eps = 1f) - { - this.Eps = eps; - } - - public bool Equals(float x, float y) - { - float d = x - y; - - return d > -this.Eps && d < this.Eps; - } - - public int GetHashCode(float obj) - { - throw new InvalidOperationException(); - } - } - protected void Print(string msg) { Debug.WriteLine(msg); diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs new file mode 100644 index 000000000..333b645de --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -0,0 +1,38 @@ +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Numerics; + + internal struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer + { + private readonly float Eps; + + public ApproximateFloatComparer(float eps = 1f) + { + this.Eps = eps; + } + + public bool Equals(float x, float y) + { + float d = x - y; + + return d > -this.Eps && d < this.Eps; + } + + public int GetHashCode(float obj) + { + throw new InvalidOperationException(); + } + + public bool Equals(Vector4 a, Vector4 b) + { + return this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W); + } + + public int GetHashCode(Vector4 obj) + { + throw new InvalidOperationException(); + } + } +} \ No newline at end of file From dfeead95a1c829fa27bad44a5a344e8276f7c1f5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Mar 2017 01:53:46 +0100 Subject: [PATCH 04/18] Color.BulkOperations full implementation --- src/ImageSharp/Colors/Color.BulkOperations.cs | 109 ++++++++++++++++++ src/ImageSharp/Common/Memory/BufferPointer.cs | 19 +-- .../Common/Memory/BufferPointer{T}.cs | 4 +- .../Common/Memory/PinnedBuffer{T}.cs | 22 ++++ .../Common/BufferPointerTests.cs | 59 +++++++++- 5 files changed, 192 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 9d698b8ac..18f9d92a6 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -125,6 +125,115 @@ namespace ImageSharp base.ToVector4(sourceColors, destVectors, remainder); } } + + /// + internal override unsafe void PackFromXyzBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + { + byte* source = (byte*)sourceBytes; + byte* destination = (byte*)destColors; + + for (int x = 0; x < count; x++) + { + Unsafe.Write(destination, (uint)(*source << 0 | *(source + 1) << 8 | *(source + 2) << 16 | 255 << 24)); + + source += 3; + destination += 4; + } + } + + /// + internal override unsafe void ToXyzBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + { + byte* source = (byte*)sourceColors; + byte* destination = (byte*)destBytes; + + for (int x = 0; x < count; x++) + { + *destination = *(source + 0); + *(destination + 1) = *(source + 1); + *(destination + 2) = *(source + 2); + + source += 4; + destination += 3; + } + } + + /// + internal override void PackFromXyzwBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + { + BufferPointer.Copy(sourceBytes, destColors, count); + } + + /// + internal override void ToXyzwBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + { + BufferPointer.Copy(sourceColors, destBytes, count); + } + + /// + internal override unsafe void PackFromZyxBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + { + byte* source = (byte*)sourceBytes; + byte* destination = (byte*)destColors; + + for (int x = 0; x < count; x++) + { + Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24)); + + source += 3; + destination += 4; + } + } + + /// + internal override unsafe void ToZyxBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + { + byte* source = (byte*)sourceColors; + byte* destination = (byte*)destBytes; + + for (int x = 0; x < count; x++) + { + *destination = *(source + 2); + *(destination + 1) = *(source + 1); + *(destination + 2) = *(source + 0); + + source += 4; + destination += 3; + } + } + + /// + internal override unsafe void PackFromZyxwBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + { + byte* source = (byte*)sourceBytes; + byte* destination = (byte*)destColors; + + for (int x = 0; x < count; x++) + { + Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24)); + + source += 4; + destination += 4; + } + } + + /// + internal override unsafe void ToZyxwBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + { + byte* source = (byte*)sourceColors; + byte* destination = (byte*)destBytes; + + for (int x = 0; x < count; x++) + { + *destination = *(source + 2); + *(destination + 1) = *(source + 1); + *(destination + 2) = *(source + 0); + *(destination + 3) = *(source + 3); + + source += 4; + destination += 4; + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index c80e22e21..f8798ba24 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -12,18 +12,7 @@ namespace ImageSharp /// internal static class BufferPointer { - /// - /// Gets a to the beginning of the raw data in 'buffer'. - /// - /// The element type - /// The input - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe BufferPointer Slice(this PinnedBuffer buffer) - where T : struct - { - return new BufferPointer(buffer.Array, (void*)buffer.Pointer); - } + /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' @@ -36,7 +25,7 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { - Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(count)); + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(count)); } /// @@ -50,7 +39,7 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { - Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInSource)); + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(countInSource)); } /// @@ -64,7 +53,7 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) where T : struct { - Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInDest)); + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(countInDest)); } /// diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index fff4e513e..e05650a70 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -79,7 +79,7 @@ namespace ImageSharp } /// - /// Convertes instance to a raw 'byte*' pointer + /// Converts instance to a raw 'byte*' pointer /// /// The to convert [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -87,7 +87,7 @@ namespace ImageSharp { return (byte*)bufferPointer.PointerAtOffset; } - + /// /// Forms a slice out of the given BufferPointer, beginning at 'offset'. /// diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 04217a012..8e5ded048 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Buffers; + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -94,6 +95,27 @@ namespace ImageSharp /// Gets a pointer to the pinned . /// public IntPtr Pointer { get; private set; } + + /// + /// Converts to an . + /// + /// The to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator BufferPointer(PinnedBuffer buffer) + { + return buffer.Slice(); + } + + /// + /// Gets a to the beginning of the raw data in 'buffer'. + /// + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe BufferPointer Slice() + { + return new BufferPointer(this.Array, (void*)this.Pointer); + } /// /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index 4b5847d53..a0b91b4c4 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -15,12 +15,18 @@ namespace ImageSharp.Tests.Common public double B; + public Foo(int a, double b) + { + this.A = a; + this.B = b; + } + internal static Foo[] CreateArray(int size) { Foo[] result = new Foo[size]; for (int i = 0; i < size; i++) { - result[i] = new Foo() { A = i, B = i }; + result[i] = new Foo(i, i); } return result; } @@ -81,6 +87,12 @@ namespace ImageSharp.Tests.Common public class Copy { + private static void AssertNotDefault(T[] data, int idx) + where T : struct + { + Assert.NotEqual(default(T), data[idx]); + } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -98,7 +110,11 @@ namespace ImageSharp.Tests.Common BufferPointer.Copy(apSource, apDest, count); } + AssertNotDefault(source, 1); + AssertNotDefault(dest, 1); + Assert.Equal(source[0], dest[0]); + Assert.Equal(source[1], dest[1]); Assert.Equal(source[count-1], dest[count-1]); Assert.NotEqual(source[count], dest[count]); } @@ -121,19 +137,31 @@ namespace ImageSharp.Tests.Common BufferPointer.Copy(apSource, apDest, count); } + AssertNotDefault(source, 1); + Assert.True(ElementsAreEqual(source, dest, 0)); Assert.True(ElementsAreEqual(source, dest, count - 1)); Assert.False(ElementsAreEqual(source, dest, count)); } + private static byte[] CreateTestBytes(int count) + { + byte[] result = new byte[count]; + for (int i = 0; i < result.Length; i++) + { + result[i] = (byte)(i % 255); + } + return result; + } + [Theory] [InlineData(4)] [InlineData(1500)] public void BytesToGeneric(int count) { - int destCount = count * sizeof(Foo); - byte[] source = new byte[destCount + sizeof(Foo) + 1]; - Foo[] dest = Foo.CreateArray(count + 2); + int srcCount = count * sizeof(Foo); + byte[] source = CreateTestBytes(srcCount); + Foo[] dest = new Foo[count + 2]; fixed(byte* pSource = source) fixed (Foo* pDest = dest) @@ -144,10 +172,33 @@ namespace ImageSharp.Tests.Common BufferPointer.Copy(apSource, apDest, count); } + AssertNotDefault(source, sizeof(Foo) + 1); + AssertNotDefault(dest, 1); + Assert.True(ElementsAreEqual(dest, source, 0)); + Assert.True(ElementsAreEqual(dest, source, 1)); Assert.True(ElementsAreEqual(dest, source, count - 1)); Assert.False(ElementsAreEqual(dest, source, count)); } + + [Fact] + public void ColorToBytes() + { + Color[] colors = { new Color(0, 1, 2, 3), new Color(4, 5, 6, 7), new Color(8, 9, 10, 11), }; + + using (PinnedBuffer colorBuf = new PinnedBuffer(colors)) + using (PinnedBuffer byteBuf = new PinnedBuffer(colors.Length*4)) + { + BufferPointer.Copy(colorBuf, byteBuf, colorBuf.Count); + + byte[] a = byteBuf.Array; + + for (int i = 0; i < byteBuf.Count; i++) + { + Assert.Equal((byte)i, a[i]); + } + } + } private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { From ef34b11bca64d4c11935d277f009e7addc07cf68 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Mar 2017 03:20:07 +0100 Subject: [PATCH 05/18] optimized BufferPointer.Copy() implementations --- src/ImageSharp/Colors/Color.BulkOperations.cs | 14 +- src/ImageSharp/Common/Memory/BufferPointer.cs | 45 +++++- .../Common/Memory/BufferPointer{T}.cs | 17 ++- .../Common/Memory/PinnedBuffer{T}.cs | 12 ++ .../ImageSharp.Sandbox46.csproj | 3 + .../Colors/BulkPixelOperationsTests.cs | 2 +- .../Common/BufferPointerTests.cs | 136 +++++++++++++++--- 7 files changed, 191 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 18f9d92a6..8ec332a91 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -59,8 +59,8 @@ namespace ImageSharp ); Vector bVec = new Vector(256.0f / 255.0f); - Vector magicInt = new Vector(1191182336); Vector magicFloat = new Vector(32768.0f); + Vector magicInt = new Vector(1191182336); // reinterpreded value of 32768.0f Vector mask = new Vector(255); int rawInputSize = count * 4; @@ -92,17 +92,7 @@ namespace ImageSharp vf.CopyTo(fTemp, i); } - // TODO: Replace this with an optimized ArrayPointer.Copy() implementation: - uint byteCount = (uint)rawInputSize * sizeof(float); - - if (byteCount > 1024u) - { - Marshal.Copy(fTemp, 0, destVectors.PointerAtOffset, rawInputSize); - } - else - { - Unsafe.CopyBlock((void*)destVectors, tPtr, byteCount); - } + BufferPointer.Copy(tempBuf, (BufferPointer) destVectors, rawInputSize); } } diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index f8798ba24..cc544341e 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -6,13 +6,17 @@ namespace ImageSharp { using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// /// Utility methods for /// internal static class BufferPointer { - + /// + /// It's worth to use Marshal.Copy() over this size. + /// + private const uint ByteCountThreshold = 1024u; /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' @@ -25,7 +29,19 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(count)); + int elementSize = Unsafe.SizeOf(); + uint byteCount = (uint) (count * elementSize); + + if (byteCount > ByteCountThreshold && elementSize == sizeof(int)) + { + // TODO: Add the optimized path for non int-compatible types + int[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + } + else + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + } } /// @@ -39,7 +55,19 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(countInSource)); + int elementSize = Unsafe.SizeOf(); + uint byteCount = (uint)(countInSource * elementSize); + + if (byteCount > ByteCountThreshold && elementSize == sizeof(int)) + { + // TODO: Add the optimized path for non int-compatible types + int[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, countInSource); + } + else + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + } } /// @@ -53,7 +81,16 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) where T : struct { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(countInDest)); + int byteCount = SizeOf(countInDest); + + if (byteCount > (int)ByteCountThreshold) + { + Marshal.Copy(source.Array, source.Offset, destination.PointerAtOffset, byteCount); + } + else + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); + } } /// diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index e05650a70..a9935c5dd 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -87,7 +87,22 @@ namespace ImageSharp { return (byte*)bufferPointer.PointerAtOffset; } - + + /// + /// Converts instance to + /// setting it's and to correct values. + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator BufferPointer(BufferPointer source) + { + BufferPointer result = default(BufferPointer); + result.Array = Unsafe.As(source.Array); + result.Offset = source.Offset * Unsafe.SizeOf(); + result.PointerAtOffset = source.PointerAtOffset; + return result; + } + /// /// Forms a slice out of the given BufferPointer, beginning at 'offset'. /// diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 8e5ded048..ea76252c5 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -117,6 +117,18 @@ namespace ImageSharp return new BufferPointer(this.Array, (void*)this.Pointer); } + /// + /// Gets a to the beginning of the raw data in 'buffer'. + /// + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe BufferPointer Slice(int offset) + { + return new BufferPointer(this.Array, (void*)this.Pointer, offset); + } + + /// /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. /// diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index a8b7ceb33..4d3548c81 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -212,6 +212,9 @@ Tests\Colors\BulkPixelOperationsTests.cs + + Tests\Common\BufferPointerTests.cs + Tests\Common\PinnedBufferTests.cs diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 41abd9d4a..fa1b536f3 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -17,7 +17,7 @@ { Assert.IsType(BulkPixelOperations.Instance); } - + [Fact] public void ToVector4SimdAligned() { diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index a0b91b4c4..fc26bf097 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -26,11 +26,26 @@ namespace ImageSharp.Tests.Common Foo[] result = new Foo[size]; for (int i = 0; i < size; i++) { - result[i] = new Foo(i, i); + result[i] = new Foo(i+1, i+1); } return result; } } + + [Fact] + public void AsBytes() + { + Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; + + using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) + { + BufferPointer orig = colorBuf.Slice(1); + BufferPointer asBytes = (BufferPointer < byte > )orig; + + Assert.Equal(asBytes.Offset, sizeof(Foo)); + Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); + } + } [Fact] public void ConstructWithoutOffset() @@ -93,6 +108,27 @@ namespace ImageSharp.Tests.Common Assert.NotEqual(default(T), data[idx]); } + + private static byte[] CreateTestBytes(int count) + { + byte[] result = new byte[count]; + for (int i = 0; i < result.Length; i++) + { + result[i] = (byte)((i % 200) + 1); + } + return result; + } + + private static int[] CreateTestInts(int count) + { + int[] result = new int[count]; + for (int i = 0; i < result.Length; i++) + { + result[i] = i + 1; + } + return result; + } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -104,56 +140,102 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (Foo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count); + BufferPointer.Copy(apSource, apDest, count-1); } AssertNotDefault(source, 1); AssertNotDefault(dest, 1); - Assert.Equal(source[0], dest[0]); + Assert.NotEqual(source[0], dest[0]); Assert.Equal(source[1], dest[1]); + Assert.Equal(source[2], dest[2]); Assert.Equal(source[count-1], dest[count-1]); Assert.NotEqual(source[count], dest[count]); } - + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void IntToInt(int count) + { + int[] source = CreateTestInts(count+2); + int[] dest = new int[count + 5]; + + fixed (int* pSource = source) + fixed (int* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, 1); + + BufferPointer.Copy(apSource, apDest, count -1); + } + + AssertNotDefault(source, 1); + AssertNotDefault(dest, 1); + + Assert.NotEqual(source[0], dest[0]); + Assert.Equal(source[1], dest[1]); + Assert.Equal(source[2], dest[2]); + Assert.Equal(source[count - 1], dest[count - 1]); + Assert.NotEqual(source[count], dest[count]); + } + [Theory] [InlineData(4)] [InlineData(1500)] public void GenericToBytes(int count) { int destCount = count * sizeof(Foo); - Foo[] source = Foo.CreateArray(count + 2); - byte[] dest = new byte[destCount + sizeof(Foo) + 1]; + Foo[] source = Foo.CreateArray(count+2); + byte[] dest = new byte[destCount + sizeof(Foo)*2]; fixed (Foo* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(Foo)); - BufferPointer.Copy(apSource, apDest, count); + BufferPointer.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); - Assert.True(ElementsAreEqual(source, dest, 0)); + Assert.False(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, 1)); + Assert.True(ElementsAreEqual(source, dest, 2)); Assert.True(ElementsAreEqual(source, dest, count - 1)); Assert.False(ElementsAreEqual(source, dest, count)); } - - private static byte[] CreateTestBytes(int count) + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void IntToBytes(int count) { - byte[] result = new byte[count]; - for (int i = 0; i < result.Length; i++) + int destCount = count * sizeof(int); + int[] source = CreateTestInts(count+2); + byte[] dest = new byte[destCount + sizeof(int) + 1]; + + fixed (int* pSource = source) + fixed (byte* pDest = dest) { - result[i] = (byte)(i % 255); + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); + + BufferPointer.Copy(apSource, apDest, count); } - return result; + + AssertNotDefault(source, 1); + + Assert.True(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, count - 1)); + Assert.False(ElementsAreEqual(source, dest, count)); } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -199,8 +281,8 @@ namespace ImageSharp.Tests.Common } } } - - private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) + + internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { fixed (Foo* pArray = array) fixed (byte* pRaw = rawArray) @@ -213,6 +295,20 @@ namespace ImageSharp.Tests.Common return val1.Equals(val2); } } + + internal static bool ElementsAreEqual(int[] array, byte[] rawArray, int index) + { + fixed (int* pArray = array) + fixed (byte* pRaw = rawArray) + { + int* pCasted = (int*)pRaw; + + int val1 = pArray[index]; + int val2 = pCasted[index]; + + return val1.Equals(val2); + } + } } } } \ No newline at end of file From 6357b9403415ec910b9e1935606f6957488b85bc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Mar 2017 03:46:42 +0100 Subject: [PATCH 06/18] even better BufferPointer.Copy() --- src/ImageSharp/Common/Memory/BufferPointer.cs | 95 +++++++++++++++---- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index cc544341e..10a5c2db2 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -29,19 +30,17 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { - int elementSize = Unsafe.SizeOf(); - uint byteCount = (uint) (count * elementSize); + uint byteCount = USizeOf(count); - if (byteCount > ByteCountThreshold && elementSize == sizeof(int)) + if (byteCount > ByteCountThreshold) { - // TODO: Add the optimized path for non int-compatible types - int[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - } - else - { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + if (TryMarshalCopy(source, destination, count)) + { + return; + } } + + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); } /// @@ -55,19 +54,17 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { - int elementSize = Unsafe.SizeOf(); - uint byteCount = (uint)(countInSource * elementSize); + uint byteCount = USizeOf(countInSource); - if (byteCount > ByteCountThreshold && elementSize == sizeof(int)) - { - // TODO: Add the optimized path for non int-compatible types - int[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, countInSource); - } - else + if (byteCount > ByteCountThreshold) { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + if (TryMarshalCopy(source, destination, countInSource)) + { + return; + } } + + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); } /// @@ -113,5 +110,63 @@ namespace ImageSharp public static uint USizeOf(int count) where T : struct => (uint)SizeOf(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryMarshalCopy(BufferPointer source, BufferPointer destination, int count) + where TSource : struct + where TDest : struct + { + // Pattern Based On: + // https://github.com/dotnet/corefx/blob/master/src/System.Numerics.Vectors/src/System/Numerics/Vector.cs#L12 + // + // Note: The following patterns are used throughout the code here and are described here + // + // PATTERN: + // if (typeof(T) == typeof(Int32)) { ... } + // else if (typeof(T) == typeof(Single)) { ... } + // EXPLANATION: + // At runtime, each instantiation of BufferPointer will be type-specific, and each of these typeof blocks will be eliminated, + // as typeof(T) is a (JIT) compile-time constant for each instantiation. This design was chosen to eliminate any overhead from + // delegates and other patterns. + + if (typeof(TSource) == typeof(long)) + { + long[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + return true; + } + else if (typeof(TSource) == typeof(int)) + { + int[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + return true; + } + else if (typeof(TSource) == typeof(uint)) + { + int[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + return true; + } + else if (typeof(TSource) == typeof(short)) + { + short[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + return true; + } + else if (typeof(TSource) == typeof(ushort)) + { + short[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + return true; + } + else if (typeof(TSource) == typeof(byte)) + { + byte[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + return true; + } + + return false; + } } } \ No newline at end of file From e8b094505204d1d7a1ffae3f62e1b8c69bbdabc0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Mar 2017 03:51:25 +0100 Subject: [PATCH 07/18] naming --- src/ImageSharp/Colors/Color.BulkOperations.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 8ec332a91..2488c643f 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -15,7 +15,7 @@ namespace ImageSharp /// /// Value type to store -s unpacked into multiple -s. /// - private struct RGBAUint + private struct UnpackedRGBA { private uint r; private uint g; @@ -63,24 +63,24 @@ namespace ImageSharp Vector magicInt = new Vector(1191182336); // reinterpreded value of 32768.0f Vector mask = new Vector(255); - int rawInputSize = count * 4; + int unpackedRawCount = count * 4; uint* src = (uint*)sourceColors.PointerAtOffset; uint* srcEnd = src + count; - using (PinnedBuffer tempBuf = new PinnedBuffer(rawInputSize + Vector.Count)) + using (PinnedBuffer tempBuf = new PinnedBuffer(unpackedRawCount + Vector.Count)) { uint* tPtr = (uint*)tempBuf.Pointer; uint[] temp = tempBuf.Array; float[] fTemp = Unsafe.As(temp); - RGBAUint* dst = (RGBAUint*)tPtr; + UnpackedRGBA* dst = (UnpackedRGBA*)tPtr; for (; src < srcEnd; src++, dst++) { dst->Load(*src); } - for (int i = 0; i < rawInputSize; i += vecSize) + for (int i = 0; i < unpackedRawCount; i += vecSize) { Vector vi = new Vector(temp, i); @@ -92,7 +92,7 @@ namespace ImageSharp vf.CopyTo(fTemp, i); } - BufferPointer.Copy(tempBuf, (BufferPointer) destVectors, rawInputSize); + BufferPointer.Copy(tempBuf, (BufferPointer) destVectors, unpackedRawCount); } } From 6fbd5e1bbccad7466075cc2d72be6f1018f2b249 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 01:13:14 +0100 Subject: [PATCH 08/18] StyleStalin --- src/ImageSharp/Colors/Color.BulkOperations.cs | 59 +++++++++++-------- src/ImageSharp/Common/Helpers/DebugGuard.cs | 2 - src/ImageSharp/Common/Memory/BufferPointer.cs | 9 ++- .../Common/Memory/BufferPointer{T}.cs | 2 +- .../Common/Memory/PinnedBuffer{T}.cs | 10 ++-- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 2488c643f..3a1d66752 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp { using System; @@ -5,6 +10,9 @@ namespace ImageSharp using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + /// + /// Conains the definition of + /// public partial struct Color { /// @@ -13,27 +21,7 @@ namespace ImageSharp internal class BulkOperations : BulkPixelOperations { /// - /// Value type to store -s unpacked into multiple -s. - /// - private struct UnpackedRGBA - { - private uint r; - private uint g; - private uint b; - private uint a; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Load(uint p) - { - this.r = p; - this.g = p >> Color.GreenShift; - this.b = p >> Color.BlueShift; - this.a = p >> Color.AlphaShift; - } - } - - /// - /// SIMD optimized bulk implementation of + /// SIMD optimized bulk implementation of /// that works only with `count` divisible by . /// /// The to the source colors. @@ -55,8 +43,7 @@ namespace ImageSharp DebugGuard.IsTrue( count % vecSize == 0, nameof(count), - "Argument 'count' should divisible by Vector.Count!" - ); + "Argument 'count' should divisible by Vector.Count!"); Vector bVec = new Vector(256.0f / 255.0f); Vector magicFloat = new Vector(32768.0f); @@ -64,7 +51,7 @@ namespace ImageSharp Vector mask = new Vector(255); int unpackedRawCount = count * 4; - + uint* src = (uint*)sourceColors.PointerAtOffset; uint* srcEnd = src + count; @@ -92,7 +79,7 @@ namespace ImageSharp vf.CopyTo(fTemp, i); } - BufferPointer.Copy(tempBuf, (BufferPointer) destVectors, unpackedRawCount); + BufferPointer.Copy(tempBuf, (BufferPointer)destVectors, unpackedRawCount); } } @@ -120,7 +107,7 @@ namespace ImageSharp internal override unsafe void PackFromXyzBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) { byte* source = (byte*)sourceBytes; - byte* destination = (byte*)destColors; + byte* destination = (byte*)destColors; for (int x = 0; x < count; x++) { @@ -224,6 +211,26 @@ namespace ImageSharp destination += 4; } } + + /// + /// Value type to store -s unpacked into multiple -s. + /// + private struct UnpackedRGBA + { + private uint r; + private uint g; + private uint b; + private uint a; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Load(uint p) + { + this.r = p; + this.g = p >> Color.GreenShift; + this.b = p >> Color.BlueShift; + this.a = p >> Color.AlphaShift; + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 1375636f4..c1fa46191 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -30,7 +30,6 @@ namespace ImageSharp } } - /// /// Verifies that the specified value is less than a maximum value /// and throws an exception if it is not. @@ -117,7 +116,6 @@ namespace ImageSharp } } - /// /// Verifies, that the method parameter with specified target value is true /// and throws an exception if it is found to be so. diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index 10a5c2db2..e600f4188 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -63,7 +63,7 @@ namespace ImageSharp return; } } - + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); } @@ -110,13 +110,13 @@ namespace ImageSharp public static uint USizeOf(int count) where T : struct => (uint)SizeOf(count); - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryMarshalCopy(BufferPointer source, BufferPointer destination, int count) - where TSource : struct + where TSource : struct where TDest : struct { - // Pattern Based On: + // Pattern Based On: // https://github.com/dotnet/corefx/blob/master/src/System.Numerics.Vectors/src/System/Numerics/Vector.cs#L12 // // Note: The following patterns are used throughout the code here and are described here @@ -128,7 +128,6 @@ namespace ImageSharp // At runtime, each instantiation of BufferPointer will be type-specific, and each of these typeof blocks will be eliminated, // as typeof(T) is a (JIT) compile-time constant for each instantiation. This design was chosen to eliminate any overhead from // delegates and other patterns. - if (typeof(TSource) == typeof(long)) { long[] srcArray = Unsafe.As(source.Array); diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index a9935c5dd..fe79e064e 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -89,7 +89,7 @@ namespace ImageSharp } /// - /// Converts instance to + /// Converts instance to /// setting it's and to correct values. /// /// The to convert diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index ea76252c5..e90238888 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -95,7 +95,7 @@ namespace ImageSharp /// Gets a pointer to the pinned . /// public IntPtr Pointer { get; private set; } - + /// /// Converts to an . /// @@ -107,9 +107,8 @@ namespace ImageSharp } /// - /// Gets a to the beginning of the raw data in 'buffer'. + /// Gets a to the beginning of the raw data of the buffer. /// - /// The element type /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe BufferPointer Slice() @@ -118,9 +117,9 @@ namespace ImageSharp } /// - /// Gets a to the beginning of the raw data in 'buffer'. + /// Gets a to an offseted position inside the buffer. /// - /// The element type + /// The offset /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe BufferPointer Slice(int offset) @@ -128,7 +127,6 @@ namespace ImageSharp return new BufferPointer(this.Array, (void*)this.Pointer, offset); } - /// /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. /// From 13ab7a4631a323c3aeab673e6af76f4b80469e99 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 01:50:31 +0100 Subject: [PATCH 09/18] Using BulkPixelOperations in PixelAccessor --- src/ImageSharp/Image.cs | 12 -- src/ImageSharp/Image/ImageBase{TColor}.cs | 2 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 155 +++++++----------- src/ImageSharp/Image/PixelArea{TColor}.cs | 10 ++ src/ImageSharp/ImageFrame.cs | 52 ------ src/ImageSharp/PixelAccessor.cs | 153 ----------------- .../Color/Bulk/PixelAccessorVirtualCopy.cs | 2 +- .../Image/PixelAccessorTests.cs | 44 +---- 8 files changed, 71 insertions(+), 359 deletions(-) delete mode 100644 src/ImageSharp/ImageFrame.cs delete mode 100644 src/ImageSharp/PixelAccessor.cs diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index af31eff79..8bfd8ee1a 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -223,17 +223,5 @@ namespace ImageSharp : base(other) { } - - /// - public override PixelAccessor Lock() - { - return new PixelAccessor(this); - } - - /// - internal override ImageFrame ToFrame() - { - return new ImageFrame(this); - } } } diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 2badc008a..830318b32 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -150,7 +150,7 @@ namespace ImageSharp } /// - public virtual PixelAccessor Lock() + public PixelAccessor Lock() { return new PixelAccessor(this); } diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index e104b8ae7..a10676565 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp /// Provides per-pixel access to generic pixels. /// /// The pixel format. - public unsafe class PixelAccessor : IDisposable + public sealed unsafe class PixelAccessor : IDisposable where TColor : struct, IPixel { /// @@ -91,7 +91,7 @@ namespace ImageSharp /// /// Gets the pixel buffer array. /// - public TColor[] PixelBuffer => this.pixelBuffer.Array; + public TColor[] PixelArray => this.pixelBuffer.Array; /// /// Gets the pointer to the pixel buffer. @@ -123,6 +123,8 @@ namespace ImageSharp /// public ParallelOptions ParallelOptions { get; } + private static BulkPixelOperations Operations => BulkPixelOperations.Instance; + /// /// Gets or sets the pixel at the specified position. /// @@ -236,6 +238,17 @@ namespace ImageSharp Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); } + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The x coordinate + /// The y coordinate + /// The + internal BufferPointer GetRowPointer(int x, int y) + { + return this.pixelBuffer.Slice((y * this.Width) + x); + } + /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! /// @@ -270,24 +283,15 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromZyx(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromZyx(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - for (int y = 0; y < height; y++) { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - for (int x = 0; x < width; x++) - { - packed.PackFromBytes(*(source + 2), *(source + 1), *source, 255); - Unsafe.Write(destination, packed); + BufferPointer source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); - source += 3; - destination += size; - } + Operations.PackFromZyxBytes(source, destination, width); } } @@ -299,24 +303,15 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromZyxw(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromZyxw(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - for (int y = 0; y < height; y++) { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - for (int x = 0; x < width; x++) - { - packed.PackFromBytes(*(source + 2), *(source + 1), *source, *(source + 3)); - Unsafe.Write(destination, packed); + BufferPointer source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); - source += 4; - destination += size; - } + Operations.PackFromZyxwBytes(source, destination, width); } } @@ -328,24 +323,15 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromXyz(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromXyz(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - for (int y = 0; y < height; y++) { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - for (int x = 0; x < width; x++) - { - packed.PackFromBytes(*source, *(source + 1), *(source + 2), 255); - Unsafe.Write(destination, packed); + BufferPointer source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); - source += 3; - destination += size; - } + Operations.PackFromXyzBytes(source, destination, width); } } @@ -357,24 +343,14 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromXyzw(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromXyzw(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - for (int y = 0; y < height; y++) { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - for (int x = 0; x < width; x++) - { - packed.PackFromBytes(*source, *(source + 1), *(source + 2), *(source + 3)); - Unsafe.Write(destination, packed); - - source += 4; - destination += size; - } + BufferPointer source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); + Operations.PackFromXyzwBytes(source, destination, width); } } @@ -386,16 +362,14 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToZyx(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToZyx(PixelArea area, int sourceX, int sourceY, int width, int height) { for (int y = 0; y < height; y++) { - int offset = y * area.RowStride; - for (int x = 0; x < width; x++) - { - this[sourceX + x, sourceY + y].ToZyxBytes(area.Bytes, offset); - offset += 3; - } + BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToZyxBytes(source, destination, width); } } @@ -407,16 +381,14 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToZyxw(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToZyxw(PixelArea area, int sourceX, int sourceY, int width, int height) { for (int y = 0; y < height; y++) { - int offset = y * area.RowStride; - for (int x = 0; x < width; x++) - { - this[sourceX + x, sourceY + y].ToZyxwBytes(area.Bytes, offset); - offset += 4; - } + BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToZyxwBytes(source, destination, width); } } @@ -428,16 +400,14 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToXyz(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToXyz(PixelArea area, int sourceX, int sourceY, int width, int height) { for (int y = 0; y < height; y++) { - int offset = y * area.RowStride; - for (int x = 0; x < width; x++) - { - this[sourceX + x, sourceY + y].ToXyzBytes(area.Bytes, offset); - offset += 3; - } + BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToXyzBytes(source, destination, width); } } @@ -449,32 +419,17 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToXyzw(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToXyzw(PixelArea area, int sourceX, int sourceY, int width, int height) { for (int y = 0; y < height; y++) { - int offset = y * area.RowStride; - for (int x = 0; x < width; x++) - { - this[sourceX + x, sourceY + y].ToXyzwBytes(area.Bytes, offset); - offset += 4; - } + BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToXyzwBytes(source, destination, width); } } - /// - /// Gets the pointer at the specified row. - /// - /// The column index. - /// The row index. - /// - /// The . - /// - protected byte* GetRowPointer(int x, int y) - { - return this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf()); - } - private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels) { this.SetPixelBufferUnsafe(width, height, new PinnedBuffer(width * height, pixels)); diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index c54de12d6..be6debba2 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -203,6 +203,16 @@ namespace ImageSharp Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowStride * this.Height)); } + /// + /// Gets a to the row y. + /// + /// The y coordinate + /// The + internal BufferPointer GetRowPointer(int y) + { + return this.byteBuffer.Slice(y * this.RowStride); + } + /// /// Gets component count for the given order. /// diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs deleted file mode 100644 index be654111d..000000000 --- a/src/ImageSharp/ImageFrame.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System.Diagnostics; - - /// - /// An optimized frame for the class. - /// - [DebuggerDisplay("ImageFrame: {Width}x{Height}")] - public sealed class ImageFrame : ImageFrame - { - /// - /// Initializes a new instance of the class. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - /// - /// The configuration providing initialization code which allows extending the library. - /// - public ImageFrame(int width, int height, Configuration configuration = null) - : base(width, height, configuration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The image to create the frame from. - /// - public ImageFrame(ImageBase image) - : base(image) - { - } - - /// - public override PixelAccessor Lock() - { - return new PixelAccessor(this); - } - - /// - internal override ImageFrame Clone() - { - return new ImageFrame(this); - } - } -} diff --git a/src/ImageSharp/PixelAccessor.cs b/src/ImageSharp/PixelAccessor.cs deleted file mode 100644 index 7827e7b47..000000000 --- a/src/ImageSharp/PixelAccessor.cs +++ /dev/null @@ -1,153 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System.Runtime.CompilerServices; - - /// - /// An optimized pixel accessor for the class. - /// - public sealed unsafe class PixelAccessor : PixelAccessor - { - /// - /// Initializes a new instance of the class. - /// - /// The image to provide pixel access for. - public PixelAccessor(ImageBase image) - : base(image) - { - } - - /// - protected override void CopyFromXyzw(PixelArea area, int targetX, int targetY, int width, int height) - { - uint byteCount = (uint)width * 4; - - for (int y = 0; y < height; y++) - { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - Unsafe.CopyBlock(destination, source, byteCount); - } - } - - /// - protected override void CopyFromXyz(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - for (int x = 0; x < width; x++) - { - Unsafe.Write(destination, (uint)(*source << 0 | *(source + 1) << 8 | *(source + 2) << 16 | 255 << 24)); - - source += 3; - destination += 4; - } - } - } - - /// - protected override void CopyFromZyx(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - for (int x = 0; x < width; x++) - { - Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24)); - - source += 3; - destination += 4; - } - } - } - - /// - protected override void CopyFromZyxw(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - byte* source = area.PixelBase + (y * area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - for (int x = 0; x < width; x++) - { - Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24)); - - source += 4; - destination += 4; - } - } - } - - /// - protected override void CopyToZyx(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - byte* source = this.GetRowPointer(sourceX, sourceY + y); - byte* destination = area.PixelBase + (y * area.RowStride); - - for (int x = 0; x < width; x++) - { - *destination = *(source + 2); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 0); - - source += 4; - destination += 3; - } - } - } - - /// - protected override void CopyToXyz(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - byte* source = this.GetRowPointer(sourceX, sourceY + y); - byte* destination = area.PixelBase + (y * area.RowStride); - - for (int x = 0; x < width; x++) - { - *destination = *(source + 0); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 2); - - source += 4; - destination += 3; - } - } - } - - /// - protected override void CopyToZyxw(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - byte* source = this.GetRowPointer(sourceX, sourceY + y); - byte* destination = area.PixelBase + (y * area.RowStride); - - for (int x = 0; x < width; x++) - { - *destination = *(source + 2); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 0); - *(destination + 3) = *(source + 3); - - source += 4; - destination += 4; - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs index 694a26f3d..3df688972 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs @@ -114,7 +114,7 @@ namespace ImageSharp.Benchmarks.Color.Bulk private BufferPointer GetPixelAccessorRow(int x, int y) { return new BufferPointer( - this.pixelAccessor.PixelBuffer, + this.pixelAccessor.PixelArray, (void*)this.pixelAccessor.DataPointer, (y * this.pixelAccessor.Width) + x ); diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs index f3cd20f45..cd9cd04b7 100644 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs @@ -130,16 +130,7 @@ namespace ImageSharp.Tests CopyFromZYX(image); } } - - [Fact] - public void CopyFromZYXOptimized() - { - using (Image image = new Image(1, 1)) - { - CopyFromZYX(image); - } - } - + [Fact] public void CopyFromZYXW() { @@ -148,16 +139,7 @@ namespace ImageSharp.Tests CopyFromZYXW(image); } } - - [Fact] - public void CopyFromZYXWOptimized() - { - using (Image image = new Image(1, 1)) - { - CopyFromZYXW(image); - } - } - + [Fact] public void CopyToZYX() { @@ -166,16 +148,7 @@ namespace ImageSharp.Tests CopyToZYX(image); } } - - [Fact] - public void CopyToZYXOptimized() - { - using (Image image = new Image(1, 1)) - { - CopyToZYX(image); - } - } - + [Fact] public void CopyToZYXW() { @@ -184,16 +157,7 @@ namespace ImageSharp.Tests CopyToZYXW(image); } } - - [Fact] - public void CopyToZYXWOptimized() - { - using (Image image = new Image(1, 1)) - { - CopyToZYXW(image); - } - } - + private static void CopyFromZYX(Image image) where TColor : struct, IPixel { From 0e526eeeb1d674d4abb9d73c638881cdf275a1ff Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 03:26:27 +0100 Subject: [PATCH 10/18] benchmarks --- .../Common/Memory/BufferPointer{T}.cs | 5 + src/ImageSharp/project.json | 1 + .../Color/Bulk/PackFromXyzw.cs | 63 +++++++++ .../Color/Bulk/PixelAccessorVirtualCopy.cs | 129 ------------------ .../ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs | 61 +++++++++ .../Color/Bulk/ToXyzw.cs | 70 ++++++++++ .../ImageSharp.Benchmarks/Image/EncodePng.cs | 23 +++- .../ImageSharp.Sandbox46.csproj | 7 +- .../Colors/BulkPixelOperationsTests.cs | 4 +- 9 files changed, 226 insertions(+), 137 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs delete mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs create mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs create mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index fe79e064e..cfdd8e6de 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -63,6 +63,11 @@ namespace ImageSharp /// public int Offset { get; private set; } + /// + /// Gets the offset inside in bytes. + /// + public int ByteOffset => this.Offset * Unsafe.SizeOf(); + /// /// Gets the pointer to the offseted array position /// diff --git a/src/ImageSharp/project.json b/src/ImageSharp/project.json index 639773377..6519a3f6b 100644 --- a/src/ImageSharp/project.json +++ b/src/ImageSharp/project.json @@ -98,6 +98,7 @@ }, "net461": { "dependencies": { + "System.Numerics.Vectors": "4.1.1", "System.Threading.Tasks.Parallel": "4.0.0" }, "frameworkAssemblies": { diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs new file mode 100644 index 000000000..1c541d28b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs @@ -0,0 +1,63 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using BenchmarkDotNet.Attributes; + + using Color = ImageSharp.Color; + + public abstract class PackFromXyzw + where TColor : struct, IPixel + { + private PinnedBuffer destination; + + private PinnedBuffer source; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.destination = new PinnedBuffer(this.Count); + this.source = new PinnedBuffer(this.Count * 4); + } + + [Cleanup] + public void Cleanup() + { + this.destination.Dispose(); + this.source.Dispose(); + } + + [Benchmark(Baseline = true)] + public void PerElement() + { + byte[] s = this.source.Array; + TColor[] d = this.destination.Array; + + for (int i = 0; i < this.Count; i++) + { + int i4 = i * 4; + TColor c = default(TColor); + c.PackFromBytes(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3]); + d[i] = c; + } + } + + [Benchmark] + public void CommonBulk() + { + new BulkPixelOperations().PackFromXyzwBytes(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count); + } + } + + public class PackFromXyzw_Color : PackFromXyzw + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs deleted file mode 100644 index 3df688972..000000000 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace ImageSharp.Benchmarks.Color.Bulk -{ - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - using BenchmarkDotNet.Attributes; - - using Color = ImageSharp.Color; - - /// - /// Benchmark to measure the effect of using virtual bulk-copy calls inside PixelAccessor methods - /// - public unsafe class PixelAccessorVirtualCopy - { - abstract class CopyExecutor - { - internal abstract void VirtualCopy(BufferPointer destination, BufferPointer source, int count); - } - - class UnsafeCopyExecutor : CopyExecutor - { - [MethodImpl(MethodImplOptions.NoInlining)] - internal override unsafe void VirtualCopy(BufferPointer destination, BufferPointer source, int count) - { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4); - } - } - - private PixelAccessor pixelAccessor; - - private PixelArea area; - - private CopyExecutor executor; - - [Params(64, 256, 512)] - public int Width { get; set; } - - public int Height { get; set; } = 256; - - - [Setup] - public void Setup() - { - this.pixelAccessor = new PixelAccessor(this.Width, this.Height); - this.area = new PixelArea(this.Width / 2, this.Height, ComponentOrder.Xyzw); - this.executor = new UnsafeCopyExecutor(); - } - - [Cleanup] - public void Cleanup() - { - this.pixelAccessor.Dispose(); - this.area.Dispose(); - } - - [Benchmark(Baseline = true)] - public void CopyRawUnsafeInlined() - { - uint byteCount = (uint)this.area.Width * 4; - - int targetX = this.Width / 4; - int targetY = 0; - - for (int y = 0; y < this.Height; y++) - { - byte* source = this.area.PixelBase + (y * this.area.RowStride); - byte* destination = this.GetRowPointer(targetX, targetY + y); - - Unsafe.CopyBlock(destination, source, byteCount); - } - } - - [Benchmark] - public void CopyBufferPointerUnsafeInlined() - { - uint byteCount = (uint)this.area.Width * 4; - - int targetX = this.Width / 4; - int targetY = 0; - - for (int y = 0; y < this.Height; y++) - { - BufferPointer source = this.GetAreaRow(y); - BufferPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); - } - } - - [Benchmark] - public void CopyBufferPointerUnsafeVirtual() - { - int targetX = this.Width / 4; - int targetY = 0; - - for (int y = 0; y < this.Height; y++) - { - BufferPointer source = this.GetAreaRow(y); - BufferPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); - this.executor.VirtualCopy(destination, source, this.area.Width); - } - } - - private byte* GetRowPointer(int x, int y) - { - return (byte*)this.pixelAccessor.DataPointer + (((y * this.pixelAccessor.Width) + x) * Unsafe.SizeOf()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private BufferPointer GetPixelAccessorRow(int x, int y) - { - return new BufferPointer( - this.pixelAccessor.PixelArray, - (void*)this.pixelAccessor.DataPointer, - (y * this.pixelAccessor.Width) + x - ); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private BufferPointer GetAreaRow(int y) - { - return new BufferPointer(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs new file mode 100644 index 000000000..bc59dba4e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs @@ -0,0 +1,61 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using BenchmarkDotNet.Attributes; + + using Color = ImageSharp.Color; + + public abstract class ToXyz + where TColor : struct, IPixel + { + private PinnedBuffer source; + + private PinnedBuffer destination; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.source = new PinnedBuffer(this.Count); + this.destination = new PinnedBuffer(this.Count * 3); + } + + [Cleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + [Benchmark(Baseline = true)] + public void PerElement() + { + TColor[] s = this.source.Array; + byte[] d = this.destination.Array; + + for (int i = 0; i < this.Count; i++) + { + TColor c = s[i]; + c.ToXyzBytes(d, i * 4); + } + } + + [Benchmark] + public void CommonBulk() + { + new BulkPixelOperations().ToXyzBytes(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.ToXyzBytes(this.source, this.destination, this.Count); + } + } + + public class ToXyz_Color : ToXyz + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs new file mode 100644 index 000000000..a4ec6f6dc --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +// ReSharper disable InconsistentNaming + +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using BenchmarkDotNet.Attributes; + + using Color = ImageSharp.Color; + + public abstract class ToXyzw + where TColor : struct, IPixel + { + private PinnedBuffer source; + + private PinnedBuffer destination; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.source = new PinnedBuffer(this.Count); + this.destination = new PinnedBuffer(this.Count * 4); + } + + [Cleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + [Benchmark(Baseline = true)] + public void PerElement() + { + TColor[] s = this.source.Array; + byte[] d = this.destination.Array; + + for (int i = 0; i < this.Count; i++) + { + TColor c = s[i]; + c.ToXyzwBytes(d, i * 4); + } + } + + [Benchmark] + public void CommonBulk() + { + new BulkPixelOperations().ToXyzwBytes(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.ToXyzwBytes(this.source, this.destination, this.Count); + } + } + + public class ToXyzw_Color : ToXyzw + { + } + + public class ToXyzw_Argb : ToXyzw + { + } +} diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index dd1882c80..a182ccd88 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -10,6 +10,10 @@ namespace ImageSharp.Benchmarks.Image using System.IO; using BenchmarkDotNet.Attributes; + + using ImageSharp.Formats; + using ImageSharp.Quantizers; + using CoreImage = ImageSharp.Image; public class EncodePng : BenchmarkBase @@ -19,12 +23,21 @@ namespace ImageSharp.Benchmarks.Image private Image bmpDrawing; private CoreImage bmpCore; + [Params(false, true)] + public bool LargeImage { get; set; } + + [Params(false, true)] + public bool UseOctreeQuantizer { get; set; } + [Setup] public void ReadImages() { if (this.bmpStream == null) { - this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"); + string path = this.LargeImage + ? "../ImageSharp.Tests/TestImages/Formats/Jpg/baseline/jpeg420exif.jpg" + : "../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"; + this.bmpStream = File.OpenRead(path); this.bmpCore = new CoreImage(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = Image.FromStream(this.bmpStream); @@ -53,7 +66,13 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - this.bmpCore.SaveAsPng(memoryStream); + Quantizer quantizer = this.UseOctreeQuantizer + ? (Quantizer) + new OctreeQuantizer() + : new PaletteQuantizer(); + + PngEncoderOptions options = new PngEncoderOptions() { Quantizer = quantizer }; + this.bmpCore.SaveAsPng(memoryStream, options); } } } diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 4d3548c81..02318d0b4 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -206,9 +206,6 @@ - - Benchmarks\PixelAccessorVirtualCopy.cs - Tests\Colors\BulkPixelOperationsTests.cs @@ -346,7 +343,9 @@ - + + + diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index fa1b536f3..88bc4a9be 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -10,7 +10,7 @@ public class Color : BulkPixelOperationsTests { // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: - public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; [Fact] public void IsSpecialImplementation() @@ -35,7 +35,7 @@ public class Argb : BulkPixelOperationsTests { // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: - public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; } [Theory] From 973be3c4ac4da1a9a56cac6a2dffc774f19dda35 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 04:38:30 +0100 Subject: [PATCH 11/18] PixelDataPool is no longer static having PixelDataPool.Clean + PixelDataPool.Dirty instead --- src/ImageSharp/Colors/Color.BulkOperations.cs | 4 +- .../Common/Memory/PinnedBuffer{T}.cs | 29 ++++++--- .../Common/Memory/PixelDataPool{T}.cs | 50 ++++++++++++--- src/ImageSharp/Image/ImageBase{TColor}.cs | 4 +- .../Color/Bulk/ToVector4.cs | 61 +++++++++++++++++++ .../ImageSharp.Benchmarks/Image/EncodePng.cs | 4 +- tests/ImageSharp.Sandbox46/Program.cs | 21 +++---- .../Colors/BulkPixelOperationsTests.cs | 19 +++++- .../Common/PixelDataPoolTests.cs | 40 +++++++----- 9 files changed, 179 insertions(+), 53 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 3a1d66752..aadca236a 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -55,7 +55,9 @@ namespace ImageSharp uint* src = (uint*)sourceColors.PointerAtOffset; uint* srcEnd = src + count; - using (PinnedBuffer tempBuf = new PinnedBuffer(unpackedRawCount + Vector.Count)) + using (PinnedBuffer tempBuf = new PinnedBuffer( + unpackedRawCount + Vector.Count, + PixelDataPool.Dirty)) { uint* tPtr = (uint*)tempBuf.Pointer; uint[] temp = tempBuf.Array; diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index e90238888..8c81a3206 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -24,22 +24,32 @@ namespace ImageSharp private GCHandle handle; /// - /// A value indicating whether this instance should return the array to the pool. + /// The if the is pooled. /// - private bool isPoolingOwner; + private PixelDataPool pool; /// /// Initializes a new instance of the class. /// /// The desired count of elements. (Minimum size for ) - public PinnedBuffer(int count) + /// The to be used to rent the data. + public PinnedBuffer(int count, PixelDataPool pool) { this.Count = count; - this.Array = PixelDataPool.Rent(count); - this.isPoolingOwner = true; + this.pool = pool; + this.Array = this.pool.Rent(count); this.Pin(); } + /// + /// Initializes a new instance of the class. + /// + /// The desired count of elements. (Minimum size for ) + public PinnedBuffer(int count) + : this(count, PixelDataPool.Clean) + { + } + /// /// Initializes a new instance of the class. /// @@ -48,6 +58,7 @@ namespace ImageSharp { this.Count = array.Length; this.Array = array; + this.pool = null; this.Pin(); } @@ -65,6 +76,7 @@ namespace ImageSharp this.Count = count; this.Array = array; + this.pool = null; this.Pin(); } @@ -140,11 +152,8 @@ namespace ImageSharp this.IsDisposedOrLostArrayOwnership = true; this.UnPin(); - if (this.isPoolingOwner) - { - PixelDataPool.Return(this.Array); - } - + this.pool?.Return(this.Array); + this.pool = null; this.Array = null; this.Count = 0; diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs index a97d17fdb..a01a941bc 100644 --- a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -9,39 +9,69 @@ namespace ImageSharp using System.Buffers; /// - /// Provides a resource pool that enables reusing instances of value type arrays . - /// will always return arrays initialized with 'default(T)' + /// Provides a resource pool that enables reusing instances of value type arrays for image data . /// /// The value type. - public static class PixelDataPool + public class PixelDataPool where T : struct { /// - /// The used to pool data. + /// The which will be always cleared. /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + private static readonly ArrayPool CleanPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + + /// + /// The which is not kept clean. + /// + private static readonly ArrayPool DirtyPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + + /// + /// The backing + /// + private ArrayPool arrayPool; + + /// + /// A value indicating whether clearArray is requested on . + /// + private bool clearArray; + + private PixelDataPool(ArrayPool arrayPool, bool clearArray) + { + this.clearArray = clearArray; + this.arrayPool = arrayPool; + } + + /// + /// Gets the which will always return arrays initialized to default(T) + /// + public static PixelDataPool Clean { get; } = new PixelDataPool(CleanPool, true); + + /// + /// Gets the which does not keep the arrays clean on Rent/Return. + /// + public static PixelDataPool Dirty { get; } = new PixelDataPool(DirtyPool, false); /// /// Rents the pixel array from the pool. /// /// The minimum length of the array to return. /// The - public static T[] Rent(int minimumLength) + public T[] Rent(int minimumLength) { - return ArrayPool.Rent(minimumLength); + return CleanPool.Rent(minimumLength); } /// /// Returns the rented pixel array back to the pool. /// /// The array to return to the buffer pool. - public static void Return(T[] array) + public void Return(T[] array) { - ArrayPool.Return(array, true); + CleanPool.Return(array, this.clearArray); } /// - /// Heuristically calculates a reasonable maxArrayLength value for the backing . + /// Heuristically calculates a reasonable maxArrayLength value for the backing . /// /// The maxArrayLength value internal static int CalculateMaxArrayLength() diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 830318b32..7d7fc843f 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -221,7 +221,7 @@ namespace ImageSharp /// private void RentPixels() { - this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); + this.pixelBuffer = PixelDataPool.Clean.Rent(this.Width * this.Height); } /// @@ -229,7 +229,7 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelDataPool.Return(this.pixelBuffer); + PixelDataPool.Clean.Return(this.pixelBuffer); this.pixelBuffer = null; } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs new file mode 100644 index 000000000..130091386 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -0,0 +1,61 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public abstract class ToVector4 + where TColor : struct, IPixel + { + private PinnedBuffer source; + + private PinnedBuffer destination; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.source = new PinnedBuffer(this.Count); + this.destination = new PinnedBuffer(this.Count); + } + + [Cleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + [Benchmark(Baseline = true)] + public void PerElement() + { + TColor[] s = this.source.Array; + Vector4[] d = this.destination.Array; + + for (int i = 0; i < this.Count; i++) + { + TColor c = s[i]; + d[i] = c.ToVector4(); + } + } + + [Benchmark] + public void CommonBulk() + { + new BulkPixelOperations().ToVector4(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); + } + } + + public class ToVector4_Color : ToVector4 + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index a182ccd88..0bb0e922c 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -23,10 +23,10 @@ namespace ImageSharp.Benchmarks.Image private Image bmpDrawing; private CoreImage bmpCore; - [Params(false, true)] + [Params(false)] public bool LargeImage { get; set; } - [Params(false, true)] + [Params(false)] public bool UseOctreeQuantizer { get; set; } [Setup] diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 4d6d15925..c361fd6a1 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -7,9 +7,9 @@ namespace ImageSharp.Sandbox46 { using System; using System.Runtime.DesignerServices; - - using ImageSharp.Benchmarks.Color.Bulk; + using ImageSharp.Tests; + using ImageSharp.Tests.Colors; using Xunit.Abstractions; @@ -38,21 +38,18 @@ namespace ImageSharp.Sandbox46 public static void Main(string[] args) { // RunDecodeJpegProfilingTests(); - TestPixelAccessorCopyFromXyzw(); + + RunToVector4ProfilingTest(); + Console.ReadLine(); } - private static void TestPixelAccessorCopyFromXyzw() + private static void RunToVector4ProfilingTest() { - PixelAccessorVirtualCopy benchmark = new PixelAccessorVirtualCopy(); - benchmark.Width = 64; - benchmark.Setup(); - - benchmark.CopyRawUnsafeInlined(); - - benchmark.Cleanup(); + BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(); + tests.Benchmark_ToVector4(); } - + private static void RunDecodeJpegProfilingTests() { Console.WriteLine("RunDecodeJpegProfilingTests..."); diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 88bc4a9be..786c77246 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -1,4 +1,5 @@ -namespace ImageSharp.Tests.Colors +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Colors { using System; using System.Numerics; @@ -30,6 +31,22 @@ (s, d) => ImageSharp.Color.BulkOperations.ToVector4SimdAligned(s, d, 64) ); } + + [Fact] + public void Benchmark_ToVector4() + { + int times = 150000; + int count = 1024; + + using (PinnedBuffer source = new PinnedBuffer(count)) + using (PinnedBuffer dest = new PinnedBuffer(count)) + { + for (int i = 0; i < times; i++) + { + BulkPixelOperations.Instance.ToVector4(source, dest, count); + } + } + } } public class Argb : BulkPixelOperationsTests diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs index 001785d60..db560ba6b 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -3,6 +3,7 @@ // Licensed under the Apache License, Version 2.0. // +// ReSharper disable InconsistentNaming namespace ImageSharp.Tests { using System.Linq; @@ -14,50 +15,59 @@ namespace ImageSharp.Tests /// public class PixelDataPoolTests { - [Fact] - public void PixelDataPoolRentsMinimumSize() + private static PixelDataPool GetPool(bool clean) { - Color[] pixels = PixelDataPool.Rent(1024); + return clean ? PixelDataPool.Clean : PixelDataPool.Dirty; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void PixelDataPoolRentsMinimumSize(bool clean) + { + Color[] pixels = GetPool(clean).Rent(1024); Assert.True(pixels.Length >= 1024); } [Fact] - public void PixelDataPoolRentsEmptyArray() + public void PixelDataPool_Clean_RentsCleanArray() { for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelDataPool.Rent(i); + Color[] pixels = PixelDataPool.Clean.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelDataPool.Return(pixels); + PixelDataPool.Clean.Return(pixels); } for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelDataPool.Rent(i); + Color[] pixels = PixelDataPool.Clean.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelDataPool.Return(pixels); + PixelDataPool.Clean.Return(pixels); } } - [Fact] - public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void PixelDataPoolDoesNotThrowWhenReturningNonPooled(bool clean) { Color[] pixels = new Color[1024]; - PixelDataPool.Return(pixels); + GetPool(clean).Return(pixels); Assert.True(pixels.Length >= 1024); } [Fact] - public void PixelDataPoolCleansRentedArray() + public void PixelDataPool_Clean_CleansRentedArray() { - Color[] pixels = PixelDataPool.Rent(256); + Color[] pixels = PixelDataPool.Clean.Rent(256); for (int i = 0; i < pixels.Length; i++) { @@ -66,7 +76,7 @@ namespace ImageSharp.Tests Assert.True(pixels.All(p => p == Color.Azure)); - PixelDataPool.Return(pixels); + PixelDataPool.Clean.Return(pixels); Assert.True(pixels.All(p => p == default(Color))); } @@ -85,7 +95,7 @@ namespace ImageSharp.Tests [Fact] public void RentNonIPixelData() { - byte[] data = PixelDataPool.Rent(16384); + byte[] data = PixelDataPool.Clean.Rent(16384); Assert.True(data.Length >= 16384); } From ebfd6ce89946d5b9948c8da0ea576e8f330989f0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 12:24:22 +0100 Subject: [PATCH 12/18] if (count < 256) { no SIMD } --- src/ImageSharp/Colors/Color.BulkOperations.cs | 7 ++++ .../Color/Bulk/ToVector4.cs | 2 +- .../Colors/BulkPixelOperationsTests.cs | 33 +++++++++++++++---- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index aadca236a..cdbfdfcd7 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -88,6 +88,13 @@ namespace ImageSharp /// internal override void ToVector4(BufferPointer sourceColors, BufferPointer destVectors, int count) { + if (count < 256) + { + // Doesn't worth to bother with SIMD: + base.ToVector4(sourceColors, destVectors, count); + return; + } + int remainder = count % Vector.Count; int alignedCount = count - remainder; diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 130091386..b48eaa35a 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Benchmarks.Color.Bulk private PinnedBuffer destination; - [Params(16, 128, 1024)] + [Params(64, 300, 1024)] public int Count { get; set; } [Setup] diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 786c77246..6621292ec 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -1,15 +1,22 @@ // ReSharper disable InconsistentNaming +// ReSharper disable AccessToDisposedClosure namespace ImageSharp.Tests.Colors { using System; using System.Numerics; using Xunit; + using Xunit.Abstractions; public class BulkPixelOperationsTests { public class Color : BulkPixelOperationsTests { + public Color(ITestOutputHelper output) + : base(output) + { + } + // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; @@ -32,19 +39,21 @@ namespace ImageSharp.Tests.Colors ); } - [Fact] + // [Fact] // Profiling benchmark - enable manually! public void Benchmark_ToVector4() { - int times = 150000; + int times = 200000; int count = 1024; using (PinnedBuffer source = new PinnedBuffer(count)) using (PinnedBuffer dest = new PinnedBuffer(count)) { - for (int i = 0; i < times; i++) - { - BulkPixelOperations.Instance.ToVector4(source, dest, count); - } + this.Measure( + times, + () => + { + BulkPixelOperations.Instance.ToVector4(source, dest, count); + }); } } } @@ -52,6 +61,11 @@ namespace ImageSharp.Tests.Colors public class Argb : BulkPixelOperationsTests { // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: + public Argb(ITestOutputHelper output) + : base(output) + { + } + public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; } @@ -64,9 +78,14 @@ namespace ImageSharp.Tests.Colors } } - public abstract class BulkPixelOperationsTests + public abstract class BulkPixelOperationsTests : MeasureFixture where TColor : struct, IPixel { + protected BulkPixelOperationsTests(ITestOutputHelper output) + : base(output) + { + } + public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; private static BulkPixelOperations Operations => BulkPixelOperations.Instance; From 3064b2ba20fcc6ecddd112d210acb233d7bf13c4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 14:11:23 +0100 Subject: [PATCH 13/18] better BufferPointer.Copy() --- src/ImageSharp/Colors/Color.BulkOperations.cs | 1 + src/ImageSharp/Common/Memory/BufferPointer.cs | 106 ++++++------------ .../Common/BufferPointerTests.cs | 102 ++++++++++++++++- 3 files changed, 133 insertions(+), 76 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index cdbfdfcd7..617114dff 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -66,6 +66,7 @@ namespace ImageSharp for (; src < srcEnd; src++, dst++) { + // TODO: We can benefit a lot of future Vector API-s here (https://github.com/dotnet/corefx/issues/15957) dst->Load(*src); } diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index e600f4188..b470b4db5 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -17,7 +18,7 @@ namespace ImageSharp /// /// It's worth to use Marshal.Copy() over this size. /// - private const uint ByteCountThreshold = 1024u; + private const int ByteCountThreshold = 1024; /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' @@ -27,20 +28,10 @@ namespace ImageSharp /// The destination . /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) + public static void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { - uint byteCount = USizeOf(count); - - if (byteCount > ByteCountThreshold) - { - if (TryMarshalCopy(source, destination, count)) - { - return; - } - } - - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + CopyImpl(source, destination, count); } /// @@ -51,20 +42,10 @@ namespace ImageSharp /// The destination buffer. /// The number of elements to copy from 'source' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) + public static void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { - uint byteCount = USizeOf(countInSource); - - if (byteCount > ByteCountThreshold) - { - if (TryMarshalCopy(source, destination, countInSource)) - { - return; - } - } - - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + CopyImpl(source, destination, countInSource); } /// @@ -112,60 +93,37 @@ namespace ImageSharp => (uint)SizeOf(count); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryMarshalCopy(BufferPointer source, BufferPointer destination, int count) - where TSource : struct + private static unsafe void CopyImpl(BufferPointer source, BufferPointer destination, int count) + where T : struct where TDest : struct { - // Pattern Based On: - // https://github.com/dotnet/corefx/blob/master/src/System.Numerics.Vectors/src/System/Numerics/Vector.cs#L12 - // - // Note: The following patterns are used throughout the code here and are described here - // - // PATTERN: - // if (typeof(T) == typeof(Int32)) { ... } - // else if (typeof(T) == typeof(Single)) { ... } - // EXPLANATION: - // At runtime, each instantiation of BufferPointer will be type-specific, and each of these typeof blocks will be eliminated, - // as typeof(T) is a (JIT) compile-time constant for each instantiation. This design was chosen to eliminate any overhead from - // delegates and other patterns. - if (typeof(TSource) == typeof(long)) - { - long[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(int)) - { - int[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(uint)) - { - int[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(short)) - { - short[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(ushort)) - { - short[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(byte)) + int byteCount = SizeOf(count); + + if (byteCount > ByteCountThreshold) { - byte[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; + if (Unsafe.SizeOf() == sizeof(long)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(byte)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } } - return false; + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index fc26bf097..58fe2a199 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -32,6 +32,37 @@ namespace ImageSharp.Tests.Common } } + /// + /// sizeof(AlignedFoo) == sizeof(long) + /// + public struct AlignedFoo + { + public int A; + + public int B; + + static AlignedFoo() + { + Assert.Equal(sizeof(AlignedFoo), sizeof(long)); + } + + public AlignedFoo(int a, int b) + { + this.A = a; + this.B = b; + } + + internal static AlignedFoo[] CreateArray(int size) + { + AlignedFoo[] result = new AlignedFoo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new AlignedFoo(i + 1, i + 1); + } + return result; + } + } + [Fact] public void AsBytes() { @@ -108,7 +139,6 @@ namespace ImageSharp.Tests.Common Assert.NotEqual(default(T), data[idx]); } - private static byte[] CreateTestBytes(int count) { byte[] result = new byte[count]; @@ -156,6 +186,33 @@ namespace ImageSharp.Tests.Common Assert.NotEqual(source[count], dest[count]); } + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToOwnType_Aligned(int count) + { + AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); + AlignedFoo[] dest = new AlignedFoo[count + 5]; + + fixed (AlignedFoo* pSource = source) + fixed (AlignedFoo* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, 1); + + BufferPointer.Copy(apSource, apDest, count - 1); + } + + AssertNotDefault(source, 1); + AssertNotDefault(dest, 1); + + Assert.NotEqual(source[0], dest[0]); + Assert.Equal(source[1], dest[1]); + Assert.Equal(source[2], dest[2]); + Assert.Equal(source[count - 1], dest[count - 1]); + Assert.NotEqual(source[count], dest[count]); + } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -209,7 +266,34 @@ namespace ImageSharp.Tests.Common Assert.True(ElementsAreEqual(source, dest, count - 1)); Assert.False(ElementsAreEqual(source, dest, count)); } - + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToBytes_Aligned(int count) + { + int destCount = count * sizeof(Foo); + AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); + byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2]; + + fixed (AlignedFoo* pSource = source) + fixed (byte* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(AlignedFoo)); + + BufferPointer.Copy(apSource, apDest, count - 1); + } + + AssertNotDefault(source, 1); + + Assert.False(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, 1)); + Assert.True(ElementsAreEqual(source, dest, 2)); + Assert.True(ElementsAreEqual(source, dest, count - 1)); + Assert.False(ElementsAreEqual(source, dest, count)); + } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -296,6 +380,20 @@ namespace ImageSharp.Tests.Common } } + internal static bool ElementsAreEqual(AlignedFoo[] array, byte[] rawArray, int index) + { + fixed (AlignedFoo* pArray = array) + fixed (byte* pRaw = rawArray) + { + AlignedFoo* pCasted = (AlignedFoo*)pRaw; + + AlignedFoo val1 = pArray[index]; + AlignedFoo val2 = pCasted[index]; + + return val1.Equals(val2); + } + } + internal static bool ElementsAreEqual(int[] array, byte[] rawArray, int index) { fixed (int* pArray = array) From 81ce9f59778d84f8a7cf91525565bf539b8b8567 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 18:42:04 +0100 Subject: [PATCH 14/18] inlining --- src/ImageSharp/Colors/Color.BulkOperations.cs | 2 +- src/ImageSharp/Common/Memory/BufferPointer.cs | 2 +- src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 617114dff..154cd531d 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -66,7 +66,7 @@ namespace ImageSharp for (; src < srcEnd; src++, dst++) { - // TODO: We can benefit a lot of future Vector API-s here (https://github.com/dotnet/corefx/issues/15957) + // TODO: This is the bottleneck now. We can improve it with future Vector API-s (https://github.com/dotnet/corefx/issues/15957) dst->Load(*src); } diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index b470b4db5..523889611 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -16,7 +16,7 @@ namespace ImageSharp internal static class BufferPointer { /// - /// It's worth to use Marshal.Copy() over this size. + /// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size. /// private const int ByteCountThreshold = 1024; diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 8c81a3206..6e0d1fcc6 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -142,6 +142,7 @@ namespace ImageSharp /// /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { if (this.IsDisposedOrLostArrayOwnership) @@ -165,6 +166,7 @@ namespace ImageSharp /// If is rented, it's the callers responsibility to return it to it's pool. (Most likely ) /// /// The unpinned + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T[] UnPinAndTakeArrayOwnership() { if (this.IsDisposedOrLostArrayOwnership) @@ -182,6 +184,7 @@ namespace ImageSharp /// /// Pins . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Pin() { this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); @@ -191,6 +194,7 @@ namespace ImageSharp /// /// Unpins . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UnPin() { if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) From bf92d664e4dcb492ec37d30d3143593a11feb036 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 19:15:09 +0100 Subject: [PATCH 15/18] BufferPointer.Clear() --- .../Common/Memory/BufferPointer{T}.cs | 10 +++++ .../General/ClearBuffer.cs | 43 +++++++++++++++++++ .../Common/BufferPointerTests.cs | 20 ++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Benchmarks/General/ClearBuffer.cs diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index cfdd8e6de..4e7a40078 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -122,5 +122,15 @@ namespace ImageSharp result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); return result; } + + /// + /// Clears `count` elements beginning from the pointed position. + /// + /// The number of elements to clear + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int count) + { + + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs b/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs new file mode 100644 index 000000000..9aa836de5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs @@ -0,0 +1,43 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Benchmarks.General +{ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + using Color = ImageSharp.Color; + + public unsafe class ClearBuffer + { + private PinnedBuffer buffer; + + [Params(32, 128, 512)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.buffer = new PinnedBuffer(this.Count); + } + + [Cleanup] + public void Cleanup() + { + this.buffer.Dispose(); + } + + [Benchmark(Baseline = true)] + public void Array_Clear() + { + Array.Clear(this.buffer.Array, 0, this.Count); + } + + [Benchmark] + public void Unsafe_InitBlock() + { + Unsafe.InitBlock((void*)this.buffer.Pointer, default(byte), (uint)this.Count*sizeof(uint)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index 58fe2a199..39d76d937 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -131,6 +131,25 @@ namespace ImageSharp.Tests.Common } } + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void Clear(int count) + { + Foo[] array = Foo.CreateArray(count + 42); + + int offset = 2; + fixed (Foo* p = array) + { + BufferPointer ap = new BufferPointer(array, p, offset); + + // Act: + ap.Clear(count); + } + } + + public class Copy { private static void AssertNotDefault(T[] data, int idx) @@ -319,7 +338,6 @@ namespace ImageSharp.Tests.Common Assert.False(ElementsAreEqual(source, dest, count)); } - [Theory] [InlineData(4)] [InlineData(1500)] From 43a482dc71ce191bf238099e2e6a4fcdbc5ce251 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 21:03:47 +0100 Subject: [PATCH 16/18] PixelDataPool is static again, it no longer clears it's arrays. --- src/ImageSharp/Colors/Color.BulkOperations.cs | 9 ++-- .../Common/Memory/BufferPointer{T}.cs | 9 +++- .../Common/Memory/PinnedBuffer{T}.cs | 43 +++++++++++-------- .../Common/Memory/PixelDataPool{T}.cs | 43 +++---------------- src/ImageSharp/Image/ImageBase{TColor}.cs | 10 ++++- tests/ImageSharp.Sandbox46/Program.cs | 2 +- .../Common/BufferPointerTests.cs | 5 +++ .../Common/PinnedBufferTests.cs | 15 +++++++ .../Common/PixelDataPoolTests.cs | 35 ++++++--------- 9 files changed, 86 insertions(+), 85 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 154cd531d..5c040e04c 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -32,6 +32,10 @@ namespace ImageSharp /// /// http://stackoverflow.com/a/5362789 /// + /// TODO: We can replace this implementation in the future using new Vector API-s: + /// + /// https://github.com/dotnet/corefx/issues/15957 + /// /// internal static unsafe void ToVector4SimdAligned( BufferPointer sourceColors, @@ -56,8 +60,7 @@ namespace ImageSharp uint* srcEnd = src + count; using (PinnedBuffer tempBuf = new PinnedBuffer( - unpackedRawCount + Vector.Count, - PixelDataPool.Dirty)) + unpackedRawCount + Vector.Count)) { uint* tPtr = (uint*)tempBuf.Pointer; uint[] temp = tempBuf.Array; @@ -66,7 +69,7 @@ namespace ImageSharp for (; src < srcEnd; src++, dst++) { - // TODO: This is the bottleneck now. We can improve it with future Vector API-s (https://github.com/dotnet/corefx/issues/15957) + // This call is the bottleneck now: dst->Load(*src); } diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index 4e7a40078..441f6b8ce 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -130,7 +130,14 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear(int count) { - + if (count < 256) + { + Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferPointer.USizeOf(count)); + } + else + { + System.Array.Clear(this.Array, this.Offset, count); + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 6e0d1fcc6..2d3d44dda 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -24,32 +24,23 @@ namespace ImageSharp private GCHandle handle; /// - /// The if the is pooled. + /// A value indicating wheter should be returned to + /// when disposing this instance. /// - private PixelDataPool pool; + private bool isPoolingOwner; /// /// Initializes a new instance of the class. /// /// The desired count of elements. (Minimum size for ) - /// The to be used to rent the data. - public PinnedBuffer(int count, PixelDataPool pool) + public PinnedBuffer(int count) { this.Count = count; - this.pool = pool; - this.Array = this.pool.Rent(count); + this.Array = PixelDataPool.Rent(count); + this.isPoolingOwner = true; this.Pin(); } - /// - /// Initializes a new instance of the class. - /// - /// The desired count of elements. (Minimum size for ) - public PinnedBuffer(int count) - : this(count, PixelDataPool.Clean) - { - } - /// /// Initializes a new instance of the class. /// @@ -58,7 +49,7 @@ namespace ImageSharp { this.Count = array.Length; this.Array = array; - this.pool = null; + this.isPoolingOwner = false; this.Pin(); } @@ -76,7 +67,7 @@ namespace ImageSharp this.Count = count; this.Array = array; - this.pool = null; + this.isPoolingOwner = false; this.Pin(); } @@ -153,8 +144,12 @@ namespace ImageSharp this.IsDisposedOrLostArrayOwnership = true; this.UnPin(); - this.pool?.Return(this.Array); - this.pool = null; + if (this.isPoolingOwner) + { + PixelDataPool.Return(this.Array); + } + + this.isPoolingOwner = false; this.Array = null; this.Count = 0; @@ -178,9 +173,19 @@ namespace ImageSharp this.UnPin(); T[] array = this.Array; this.Array = null; + this.isPoolingOwner = false; return array; } + /// + /// Clears the buffer, filling elements between 0 and -1 with default(T) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + this.Slice().Clear(this.Count); + } + /// /// Pins . /// diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs index a01a941bc..dcd031f6e 100644 --- a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -15,63 +15,32 @@ namespace ImageSharp public class PixelDataPool where T : struct { - /// - /// The which will be always cleared. - /// - private static readonly ArrayPool CleanPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); - /// /// The which is not kept clean. /// - private static readonly ArrayPool DirtyPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); - - /// - /// The backing - /// - private ArrayPool arrayPool; - - /// - /// A value indicating whether clearArray is requested on . - /// - private bool clearArray; - - private PixelDataPool(ArrayPool arrayPool, bool clearArray) - { - this.clearArray = clearArray; - this.arrayPool = arrayPool; - } - - /// - /// Gets the which will always return arrays initialized to default(T) - /// - public static PixelDataPool Clean { get; } = new PixelDataPool(CleanPool, true); - - /// - /// Gets the which does not keep the arrays clean on Rent/Return. - /// - public static PixelDataPool Dirty { get; } = new PixelDataPool(DirtyPool, false); + private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); /// /// Rents the pixel array from the pool. /// /// The minimum length of the array to return. /// The - public T[] Rent(int minimumLength) + public static T[] Rent(int minimumLength) { - return CleanPool.Rent(minimumLength); + return ArrayPool.Rent(minimumLength); } /// /// Returns the rented pixel array back to the pool. /// /// The array to return to the buffer pool. - public void Return(T[] array) + public static void Return(T[] array) { - CleanPool.Return(array, this.clearArray); + ArrayPool.Return(array); } /// - /// Heuristically calculates a reasonable maxArrayLength value for the backing . + /// Heuristically calculates a reasonable maxArrayLength value for the backing . /// /// The maxArrayLength value internal static int CalculateMaxArrayLength() diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 7d7fc843f..7afc27a6d 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -60,6 +60,7 @@ namespace ImageSharp { this.Configuration = configuration ?? Configuration.Default; this.InitPixels(width, height); + this.ClearPixels(); } /// @@ -221,7 +222,7 @@ namespace ImageSharp /// private void RentPixels() { - this.pixelBuffer = PixelDataPool.Clean.Rent(this.Width * this.Height); + this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); } /// @@ -229,8 +230,13 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelDataPool.Clean.Return(this.pixelBuffer); + PixelDataPool.Return(this.pixelBuffer); this.pixelBuffer = null; } + + private void ClearPixels() + { + Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index c361fd6a1..467663a53 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Sandbox46 private static void RunToVector4ProfilingTest() { - BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(); + BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(new ConsoleOutput()); tests.Benchmark_ToVector4(); } diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index 39d76d937..c82b63f11 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -146,6 +146,11 @@ namespace ImageSharp.Tests.Common // Act: ap.Clear(count); + + Assert.NotEqual(default(Foo), array[offset-1]); + Assert.Equal(default(Foo), array[offset]); + Assert.Equal(default(Foo), array[offset + count-1]); + Assert.NotEqual(default(Foo), array[offset + count]); } } diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index 65077ae7f..3688763b9 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -47,6 +47,21 @@ } } + [Theory] + [InlineData(42)] + [InlineData(1111)] + public void Clear(int count) + { + Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + buffer.Clear(); + + Assert.Equal(default(Foo), a[0]); + Assert.Equal(default(Foo), a[1]); + } + } + [Fact] public void Dispose() { diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs index db560ba6b..ea747af7d 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -15,17 +15,10 @@ namespace ImageSharp.Tests /// public class PixelDataPoolTests { - private static PixelDataPool GetPool(bool clean) - { - return clean ? PixelDataPool.Clean : PixelDataPool.Dirty; - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void PixelDataPoolRentsMinimumSize(bool clean) + [Fact] + public void PixelDataPoolRentsMinimumSize() { - Color[] pixels = GetPool(clean).Rent(1024); + Color[] pixels = PixelDataPool.Rent(1024); Assert.True(pixels.Length >= 1024); } @@ -35,31 +28,29 @@ namespace ImageSharp.Tests { for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelDataPool.Clean.Rent(i); + Color[] pixels = PixelDataPool.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelDataPool.Clean.Return(pixels); + PixelDataPool.Return(pixels); } for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelDataPool.Clean.Rent(i); + Color[] pixels = PixelDataPool.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelDataPool.Clean.Return(pixels); + PixelDataPool.Return(pixels); } } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void PixelDataPoolDoesNotThrowWhenReturningNonPooled(bool clean) + [Fact] + public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() { Color[] pixels = new Color[1024]; - GetPool(clean).Return(pixels); + PixelDataPool.Return(pixels); Assert.True(pixels.Length >= 1024); } @@ -67,7 +58,7 @@ namespace ImageSharp.Tests [Fact] public void PixelDataPool_Clean_CleansRentedArray() { - Color[] pixels = PixelDataPool.Clean.Rent(256); + Color[] pixels = PixelDataPool.Rent(256); for (int i = 0; i < pixels.Length; i++) { @@ -76,7 +67,7 @@ namespace ImageSharp.Tests Assert.True(pixels.All(p => p == Color.Azure)); - PixelDataPool.Clean.Return(pixels); + PixelDataPool.Return(pixels); Assert.True(pixels.All(p => p == default(Color))); } @@ -95,7 +86,7 @@ namespace ImageSharp.Tests [Fact] public void RentNonIPixelData() { - byte[] data = PixelDataPool.Clean.Rent(16384); + byte[] data = PixelDataPool.Rent(16384); Assert.True(data.Length >= 16384); } From a1db28fbaef300362ff9fe435fcd1b5d3a5ac331 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 21:15:44 +0100 Subject: [PATCH 17/18] docs for ClearPixels() --- src/ImageSharp/Image/ImageBase{TColor}.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 7afc27a6d..878ba09b3 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -234,6 +234,9 @@ namespace ImageSharp this.pixelBuffer = null; } + /// + /// Clears the pixel array. + /// private void ClearPixels() { Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); From 9f2127081252d704418ce571122ef22842f14850 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 23:37:37 +0100 Subject: [PATCH 18/18] remove outdated tests --- .../Common/PixelDataPoolTests.cs | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs index ea747af7d..403dffba9 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -23,28 +23,6 @@ namespace ImageSharp.Tests Assert.True(pixels.Length >= 1024); } - [Fact] - public void PixelDataPool_Clean_RentsCleanArray() - { - for (int i = 16; i < 1024; i += 16) - { - Color[] pixels = PixelDataPool.Rent(i); - - Assert.True(pixels.All(p => p == default(Color))); - - PixelDataPool.Return(pixels); - } - - for (int i = 16; i < 1024; i += 16) - { - Color[] pixels = PixelDataPool.Rent(i); - - Assert.True(pixels.All(p => p == default(Color))); - - PixelDataPool.Return(pixels); - } - } - [Fact] public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() { @@ -55,23 +33,6 @@ namespace ImageSharp.Tests Assert.True(pixels.Length >= 1024); } - [Fact] - public void PixelDataPool_Clean_CleansRentedArray() - { - Color[] pixels = PixelDataPool.Rent(256); - - for (int i = 0; i < pixels.Length; i++) - { - pixels[i] = Color.Azure; - } - - Assert.True(pixels.All(p => p == Color.Azure)); - - PixelDataPool.Return(pixels); - - Assert.True(pixels.All(p => p == default(Color))); - } - [Theory] [InlineData(false)] [InlineData(true)]