From fa04bf0c3ac0a7f03e352435d91c8d43e3763eca Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Feb 2020 01:56:03 +0100 Subject: [PATCH] replace MemorySource with MemoryGroup --- shared-infrastructure | 2 +- .../Advanced/AdvancedImageExtensions.cs | 2 +- src/ImageSharp/Image.Decode.cs | 2 +- src/ImageSharp/Image.WrapMemory.cs | 6 +- .../ImageFrameCollection{TPixel}.cs | 2 +- src/ImageSharp/ImageFrame{TPixel}.cs | 4 +- src/ImageSharp/Image{TPixel}.cs | 8 +- src/ImageSharp/Memory/Buffer2DExtensions.cs | 10 +- src/ImageSharp/Memory/Buffer2D{T}.cs | 24 ++- .../MemoryGroup{T}.Consumed.cs | 10 +- .../MemoryGroup{T}.Owned.cs | 5 +- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 38 ++++- .../Memory/MemoryAllocatorExtensions.cs | 5 +- src/ImageSharp/Memory/MemorySource.cs | 101 ----------- .../Formats/Jpg/JpegColorConverterTests.cs | 2 +- .../Formats/Jpg/SpectralJpegTests.cs | 3 +- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 67 +++++--- .../Memory/MemorySourceTests.cs | 159 ------------------ 18 files changed, 117 insertions(+), 333 deletions(-) delete mode 100644 src/ImageSharp/Memory/MemorySource.cs delete mode 100644 tests/ImageSharp.Tests/Memory/MemorySourceTests.cs diff --git a/shared-infrastructure b/shared-infrastructure index a75469fdb..36b2d55f5 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a75469fdb93fb89b39a5b0b7c01cb7432ceef98f +Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5 diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index d810296d6..cfaffbbb2 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Advanced internal static Memory GetPixelMemory(this ImageFrame source) where TPixel : struct, IPixel { - return source.PixelBuffer.MemorySource.Memory; + return source.PixelBuffer.GetMemory(); } /// diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index e1376b4a2..e6bcae45c 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp { Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - return new Image(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata); + return new Image(configuration, uninitializedMemoryBuffer.MemoryGroup, width, height, metadata); } /// diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index 095991b07..9bb40a78b 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp ImageMetadata metadata) where TPixel : struct, IPixel { - var memorySource = new MemorySource(pixelMemory); + var memorySource = MemoryGroup.Wrap(pixelMemory); return new Image(config, memorySource, width, height, metadata); } @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp ImageMetadata metadata) where TPixel : struct, IPixel { - var memorySource = new MemorySource(pixelMemoryOwner, false); + var memorySource = MemoryGroup.Wrap(pixelMemoryOwner); return new Image(config, memorySource, width, height, metadata); } @@ -147,4 +147,4 @@ namespace SixLabors.ImageSharp return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index 722a4ddea..b11c74958 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); } - internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) + internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) { this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e1112c017..421cd1032 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. /// The memory source. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource) : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { } @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp /// The height of the image in pixels. /// The memory source. /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetadata metadata) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 87bdf90a1..c60f6638c 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -74,22 +74,22 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class - /// wrapping an external . + /// wrapping an external . /// /// The configuration providing initialization code which allows extending the library. - /// The memory source. + /// The memory source. /// The width of the image in pixels. /// The height of the image in pixels. /// The images metadata. internal Image( Configuration configuration, - MemorySource memorySource, + MemoryGroup memoryGroup, int width, int height, ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.Frames = new ImageFrameCollection(this, width, height, memorySource); + this.Frames = new ImageFrameCollection(this, width, height, memoryGroup); } /// diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index ba4f9c925..959ad94bb 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.GetSpan(); + return buffer.MemoryGroup.Single().Span; } /// @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.Memory; + return buffer.MemoryGroup.Single(); } /// @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width); + return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width).Span; } /// @@ -66,10 +67,11 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width); + return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width); } /// + /// TODO: Does not work with multi-buffer groups, should be specific to Resize. /// Copy columns of inplace, /// from positions starting at to positions at . /// diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 6b7f3bf42..439a5bde2 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -14,21 +14,18 @@ namespace SixLabors.ImageSharp.Memory /// Before RC1, this class might be target of API changes, use it on your own risk! /// /// The value type. - // TODO: Consider moving this type to the SixLabors.ImageSharp.Memory namespace (SixLabors.Core). public sealed class Buffer2D : IDisposable where T : struct { - private MemorySource memorySource; - /// /// Initializes a new instance of the class. /// - /// The buffer to wrap - /// The number of elements in a row - /// The number of rows - internal Buffer2D(MemorySource memorySource, int width, int height) + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height) { - this.memorySource = memorySource; + this.MemoryGroup = memoryGroup; this.Width = width; this.Height = height; } @@ -44,9 +41,9 @@ namespace SixLabors.ImageSharp.Memory public int Height { get; private set; } /// - /// Gets the backing + /// Gets the backing . /// - internal MemorySource MemorySource => this.memorySource; + internal MemoryGroup MemoryGroup { get; } /// /// Gets a reference to the element at the specified position. @@ -62,8 +59,7 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - Span span = this.GetSpan(); - return ref span[(this.Width * y) + x]; + return ref this.GetRowSpan(y)[x]; } } @@ -72,7 +68,7 @@ namespace SixLabors.ImageSharp.Memory /// public void Dispose() { - this.MemorySource.Dispose(); + this.MemoryGroup.Dispose(); } /// @@ -81,7 +77,7 @@ namespace SixLabors.ImageSharp.Memory /// internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - MemorySource.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource); + MemoryGroup.SwapOrCopyContent(destination.MemoryGroup, source.MemoryGroup); SwapDimensionData(destination, source); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index b16692bd5..50987d2cd 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; +using System.Linq; namespace SixLabors.ImageSharp.Memory { @@ -11,9 +13,9 @@ namespace SixLabors.ImageSharp.Memory // Analogous to the "consumed" variant of MemorySource private class Consumed : MemoryGroup { - private readonly ReadOnlyMemory> source; + private readonly Memory[] source; - public Consumed(ReadOnlyMemory> source, int bufferLength, long totalLength) + public Consumed(Memory[] source, int bufferLength, long totalLength) : base(bufferLength, totalLength) { this.source = source; @@ -22,13 +24,13 @@ namespace SixLabors.ImageSharp.Memory public override int Count => this.source.Length; - public override Memory this[int index] => this.source.Span[index]; + public override Memory this[int index] => this.source[index]; public override IEnumerator> GetEnumerator() { for (int i = 0; i < this.source.Length; i++) { - yield return this.source.Span[i]; + yield return this.source[i]; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 6f325d0b2..d7d484ceb 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -15,13 +15,16 @@ namespace SixLabors.ImageSharp.Memory { private IMemoryOwner[] memoryOwners; - public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength) + public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) : base(bufferLength, totalLength) { this.memoryOwners = memoryOwners; + this.Swappable = swappable; this.View = new MemoryGroupView(this); } + public bool Swappable { get; } + public override int Count { get diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 072b7e3e7..d9c384b55 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -108,32 +108,51 @@ namespace SixLabors.ImageSharp.Memory buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); } - return new Owned(buffers, bufferLength, totalLength); + return new Owned(buffers, bufferLength, totalLength, true); } - public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); - - public static MemoryGroup Wrap(ReadOnlyMemory> source) + public static MemoryGroup Wrap(params Memory[] source) { - int bufferLength = source.Length > 0 ? source.Span[0].Length : 0; + int bufferLength = source.Length > 0 ? source[0].Length : 0; for (int i = 1; i < source.Length - 1; i++) { - if (source.Span[i].Length != bufferLength) + if (source[i].Length != bufferLength) { throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); } } - if (source.Length > 0 && source.Span[^1].Length > bufferLength) + if (source.Length > 0 && source[^1].Length > bufferLength) { throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); } - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source.Span[^1].Length : 0; + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Length : 0; return new Consumed(source, bufferLength, totalLength); } + public static MemoryGroup Wrap(params IMemoryOwner[] source) + { + int bufferLength = source.Length > 0 ? source[0].Memory.Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source[i].Memory.Length != bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source[^1].Memory.Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Memory.Length : 0; + + return new Owned(source, bufferLength, totalLength, false); + } + /// /// Swaps the contents of 'target' with 'source' if the buffers are allocated (1), /// copies the contents of 'source' to 'target' otherwise (2). @@ -141,7 +160,8 @@ namespace SixLabors.ImageSharp.Memory /// public static void SwapOrCopyContent(MemoryGroup target, MemoryGroup source) { - if (source is Owned ownedSrc && target is Owned ownedTarget) + if (source is Owned ownedSrc && ownedSrc.Swappable && + target is Owned ownedTarget && ownedTarget.Swappable) { Owned.SwapContents(ownedTarget, ownedSrc); } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index b9a0d2536..1f1ded9a0 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -27,9 +27,8 @@ namespace SixLabors.ImageSharp.Memory AllocationOptions options = AllocationOptions.None) where T : struct { - IMemoryOwner buffer = memoryAllocator.Allocate(width * height, options); - var memorySource = new MemorySource(buffer, true); - + long groupLength = (long)width * height; + MemoryGroup memorySource = memoryAllocator.AllocateGroup(groupLength, width, options); return new Buffer2D(memorySource, width, height); } diff --git a/src/ImageSharp/Memory/MemorySource.cs b/src/ImageSharp/Memory/MemorySource.cs deleted file mode 100644 index 54f1bb0d1..000000000 --- a/src/ImageSharp/Memory/MemorySource.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Holds a that is either OWNED or CONSUMED. - /// When the memory is being owned, the instance is also known. - /// Implements content transfer logic in that depends on the ownership status. - /// This is needed to transfer the contents of a temporary - /// to a persistent without copying the buffer. - /// - /// - /// For a deeper understanding of the owner/consumer model, check out the following docs:
- /// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6 - /// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T - ///
- internal struct MemorySource : IDisposable - { - /// - /// Initializes a new instance of the struct - /// by wrapping an existing . - /// - /// The to wrap - /// - /// A value indicating whether is an internal memory source managed by ImageSharp. - /// Eg. allocated by a . - /// - public MemorySource(IMemoryOwner memoryOwner, bool isInternalMemorySource) - { - this.MemoryOwner = memoryOwner; - this.Memory = memoryOwner.Memory; - this.HasSwappableContents = isInternalMemorySource; - } - - public MemorySource(Memory memory) - { - this.Memory = memory; - this.MemoryOwner = null; - this.HasSwappableContents = false; - } - - public IMemoryOwner MemoryOwner { get; private set; } - - public Memory Memory { get; private set; } - - /// - /// Gets a value indicating whether we are allowed to swap the contents of this buffer - /// with an other instance. - /// The value is true only and only if is present, - /// and it's coming from an internal source managed by ImageSharp (). - /// - public bool HasSwappableContents { get; } - - public Span GetSpan() => this.Memory.Span; - - public void Clear() => this.Memory.Span.Clear(); - - /// - /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), - /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! - /// - public static void SwapOrCopyContent(ref MemorySource destination, ref MemorySource source) - { - if (source.HasSwappableContents && destination.HasSwappableContents) - { - SwapContents(ref destination, ref source); - } - else - { - if (destination.Memory.Length != source.Memory.Length) - { - throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!"); - } - - source.Memory.CopyTo(destination.Memory); - } - } - - /// - public void Dispose() - { - this.MemoryOwner?.Dispose(); - } - - private static void SwapContents(ref MemorySource a, ref MemorySource b) - { - IMemoryOwner tempOwner = a.MemoryOwner; - Memory tempMemory = a.Memory; - - a.MemoryOwner = b.MemoryOwner; - a.Memory = b.Memory; - - b.MemoryOwner = tempOwner; - b.Memory = tempMemory; - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 146b07d05..877571425 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // no need to dispose when buffer is not array owner var memory = new Memory(values); - var source = new MemorySource(memory); + var source = MemoryGroup.Wrap(memory); buffers[i] = new Buffer2D(source, values.Length, 1); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 8d7dda2fe..75e6da5d0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; @@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.GetSpan().Length; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 02b59825b..a11602280 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -18,28 +19,34 @@ namespace SixLabors.ImageSharp.Tests.Memory // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert { - public static void SpanPointsTo(Span span, IMemoryOwner buffer, int bufferOffset = 0) + public static void SpanPointsTo(Span span, Memory buffer, int bufferOffset = 0) where T : struct { ref T actual = ref MemoryMarshal.GetReference(span); - ref T expected = ref Unsafe.Add(ref buffer.GetReference(), bufferOffset); + ref T expected = ref buffer.Span[bufferOffset]; True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } } - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + + private const int Big = 99999; [Theory] - [InlineData(7, 42)] - [InlineData(1025, 17)] - public void Construct(int width, int height) + [InlineData(Big, 7, 42)] + [InlineData(Big, 1025, 17)] + [InlineData(300, 42, 777)] + public unsafe void Construct(int bufferCapacity, int width, int height) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.GetMemory().Length); + Assert.Equal(width * height, buffer.MemoryGroup.TotalLength); + Assert.True(buffer.MemoryGroup.BufferLength % width == 0); } } @@ -57,34 +64,48 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(7, 42, 0)] - [InlineData(7, 42, 10)] - [InlineData(17, 42, 41)] - public void GetRowSpanY(int width, int height, int y) + [InlineData(Big, 7, 42, 0, 0)] + [InlineData(Big, 7, 42, 10, 0)] + [InlineData(Big, 17, 42, 41, 0)] + [InlineData(500, 17, 42, 41, 1)] + [InlineData(200, 100, 30, 1, 0)] + [InlineData(200, 100, 30, 2, 1)] + [InlineData(200, 100, 30, 4, 2)] + public unsafe void GetRowSpanY(int bufferCapacity, int width, int height, int y, int expectedBufferIndex) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(y); - // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y); + + int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.MemoryGroup.BufferLength); + Assert.SpanPointsTo(span, buffer.MemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } } [Theory] - [InlineData(42, 8, 0, 0)] - [InlineData(400, 1000, 20, 10)] - [InlineData(99, 88, 98, 87)] - public void Indexer(int width, int height, int x, int y) + [InlineData(Big, 42, 8, 0, 0)] + [InlineData(Big, 400, 1000, 20, 10)] + [InlineData(Big, 99, 88, 98, 87)] + [InlineData(500, 200, 30, 42, 13)] + [InlineData(500, 200, 30, 199, 29)] + public unsafe void Indexer(int bufferCapacity, int width, int height, int x, int y) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.MemorySource.GetSpan(); + int bufferIndex = (width * y) / buffer.MemoryGroup.BufferLength; + int subBufferStart = (width * y) - (bufferIndex * buffer.MemoryGroup.BufferLength); + + Span span = buffer.MemoryGroup[bufferIndex].Span.Slice(subBufferStart); ref TestStructs.Foo actual = ref buffer[x, y]; - ref TestStructs.Foo expected = ref span[(y * width) + x]; + ref TestStructs.Foo expected = ref span[x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } @@ -96,13 +117,13 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5)) using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7)) { - IMemoryOwner aa = a.MemorySource.MemoryOwner; - IMemoryOwner bb = b.MemorySource.MemoryOwner; + Memory aa = a.MemoryGroup.Single(); + Memory bb = b.MemoryGroup.Single(); Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.MemorySource.MemoryOwner); - Assert.Equal(aa, b.MemorySource.MemoryOwner); + Assert.Equal(bb, a.MemoryGroup.Single()); + Assert.Equal(aa, b.MemoryGroup.Single()); Assert.Equal(new Size(3, 7), a.Size()); Assert.Equal(new Size(10, 5), b.Size()); diff --git a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs b/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs deleted file mode 100644 index d0f8c6f91..000000000 --- a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory -{ - public class MemorySourceTests - { - public class Construction - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void InitializeAsOwner(bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg, isInternalMemorySource); - - Assert.Equal(mmg, a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.Equal(isInternalMemorySource, a.HasSwappableContents); - } - - [Fact] - public void InitializeAsObserver_MemoryOwner_IsNull() - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg.Memory); - - Assert.Null(a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.False(a.HasSwappableContents); - } - } - - public class Dispose - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenOwnershipIsTransferred_ShouldDisposeMemoryOwner(bool isInternalMemorySource) - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg, isInternalMemorySource); - - bmg.Dispose(); - Assert.True(mmg.IsDisposed); - } - - [Fact] - public void WhenMemoryObserver_ShouldNotDisposeAnything() - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg.Memory); - - bmg.Dispose(); - Assert.False(mmg.IsDisposed); - } - } - - public class SwapOrCopyContent - { - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); - - private MemorySource AllocateMemorySource(int length, AllocationOptions options = AllocationOptions.None) - where T : struct - { - IMemoryOwner owner = this.MemoryAllocator.Allocate(length, options); - return new MemorySource(owner, true); - } - - [Fact] - public void WhenBothAreMemoryOwners_ShouldSwap() - { - MemorySource a = this.AllocateMemorySource(13); - MemorySource b = this.AllocateMemorySource(17); - - IMemoryOwner aa = a.MemoryOwner; - IMemoryOwner bb = b.MemoryOwner; - - Memory aaa = a.Memory; - Memory bbb = b.Memory; - - MemorySource.SwapOrCopyContent(ref a, ref b); - - Assert.Equal(bb, a.MemoryOwner); - Assert.Equal(aa, b.MemoryOwner); - - Assert.Equal(bbb, a.Memory); - Assert.Equal(aaa, b.Memory); - Assert.NotEqual(a.Memory, b.Memory); - } - - [Theory] - [InlineData(false, false)] - [InlineData(true, true)] - [InlineData(true, false)] - public void WhenDestIsNotMemoryOwner_SameSize_ShouldCopy(bool sourceIsOwner, bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(21); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, isInternalMemorySource) - : new MemorySource(sourceOwner.Memory); - - sourceOwner.Memory.Span[10] = color; - - // Act: - MemorySource.SwapOrCopyContent(ref dest, ref source); - - // Assert: - Assert.Equal(color, dest.Memory.Span[10]); - Assert.NotEqual(sourceOwner, dest.MemoryOwner); - Assert.NotEqual(destOwner, source.MemoryOwner); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(22); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, true) - : new MemorySource(sourceOwner.Memory); - sourceOwner.Memory.Span[10] = color; - - // Act: - Assert.ThrowsAny(() => MemorySource.SwapOrCopyContent(ref dest, ref source)); - - Assert.Equal(color, source.Memory.Span[10]); - Assert.NotEqual(color, dest.Memory.Span[10]); - } - } - } -}