diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index e53929c5c..d341e93af 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Memory public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue; /// - protected internal override int GetBlockCapacity() => this.MaximumContiguousBufferLength; + protected internal override int GetBufferCapacity() => this.MaximumContiguousBufferLength; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index ccb5bf2e8..9ed322c9c 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Memory /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// /// The length of the largest contiguous buffer that can be handled by this allocator instance. - protected internal abstract int GetBlockCapacity(); + protected internal abstract int GetBufferCapacity(); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 88830c551..293e807ef 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - protected internal override int GetBlockCapacity() => int.MaxValue; + protected internal override int GetBufferCapacity() => int.MaxValue; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index eaacef713..a2eafb160 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Memory /// /// Gets the number of elements per contiguous sub-block. /// - public int BlockSize { get; } + public int BufferSize { get; } bool IsValid { get; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs index b211a13f3..df30c2ee2 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs @@ -2,7 +2,25 @@ using System; namespace SixLabors.ImageSharp.Memory { + /// + /// Exception thrown on invalid memory (allocation) requests. + /// public class InvalidMemoryOperationException : InvalidOperationException { + /// + /// Initializes a new instance of the class. + /// + /// The exception message text. + public InvalidMemoryOperationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + public InvalidMemoryOperationException() + { + } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs index a2d5c0579..b1077e254 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Memory public Memory this[int index] => throw new NotImplementedException(); - public int BlockSize => this.owner.BlockSize; + public int BufferSize => this.owner.BufferSize; public bool IsValid { get; internal set; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 03f61b75b..20afb2d57 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -10,13 +10,16 @@ namespace SixLabors.ImageSharp.Memory { private readonly ReadOnlyMemory> source; - public Consumed(ReadOnlyMemory> source) + public Consumed(ReadOnlyMemory> source, int bufferSize) + : base(bufferSize) { - // TODO: sizes should be uniform, validate! - this.source = source; } + public override int Count => this.source.Length; + + public override Memory this[int index] => this.source.Span[index]; + public override IEnumerator> GetEnumerator() { for (int i = 0; i < this.source.Length; i++) @@ -25,10 +28,6 @@ namespace SixLabors.ImageSharp.Memory } } - public override int Count => this.source.Length; - - public override Memory this[int index] => this.source.Span[index]; - public override void Dispose() { // No ownership nothing to dispose diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index b15d7d676..c90a24376 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -12,7 +12,8 @@ namespace SixLabors.ImageSharp.Memory { private IMemoryOwner[] memoryOwners; - public Owned(IMemoryOwner[] memoryOwners) + public Owned(IMemoryOwner[] memoryOwners, int bufferSize) + : base(bufferSize) { this.memoryOwners = memoryOwners; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index e9c0be02d..9d36cc8dc 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -1,6 +1,8 @@ using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Memory { @@ -10,39 +12,96 @@ namespace SixLabors.ImageSharp.Memory /// and . /// /// The element type. - internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable where T : struct + internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable + where T : struct { - public abstract IEnumerator> GetEnumerator(); + private static readonly int ElementSize = Unsafe.SizeOf(); - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + private MemoryGroup(int bufferSize) => this.BufferSize = bufferSize; public abstract int Count { get; } + public int BufferSize { get; } + + public bool IsValid { get; private set; } = true; + public abstract Memory this[int index] { get; } public abstract void Dispose(); - public int BlockSize { get; } + public abstract IEnumerator> GetEnumerator(); - public bool IsValid { get; protected set; } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); // bufferLengthAlignment == image.Width in row-major images - public static MemoryGroup Allocate(MemoryAllocator allocator, + public static MemoryGroup Allocate( + MemoryAllocator allocator, long totalLength, int blockAlignment, AllocationOptions allocationOptions = AllocationOptions.None) { - long bufferCount = totalLength / allocator.GetBlockCapacity(); + Guard.NotNull(allocator, nameof(allocator)); + Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); + Guard.MustBeGreaterThan(blockAlignment, 0, nameof(blockAlignment)); - // TODO: Adjust bufferCount, and calculate the uniform buffer length with respect to bufferLengthAlignment, and allocate bufferCount buffers - throw new NotImplementedException(); + int blockCapacityInElements = allocator.GetBufferCapacity() / ElementSize; + if (blockAlignment > blockCapacityInElements) + { + throw new InvalidMemoryOperationException(); + } + + int numberOfAlignedSegments = blockCapacityInElements / blockAlignment; + int bufferSize = numberOfAlignedSegments * blockAlignment; + if (totalLength > 0 && totalLength < bufferSize) + { + bufferSize = (int)totalLength; + } + + int sizeOfLastBuffer = (int)(totalLength % bufferSize); + long bufferCount = totalLength / bufferSize; + + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferSize; + } + else + { + bufferCount++; + } + + var buffers = new IMemoryOwner[bufferCount]; + for (int i = 0; i < buffers.Length - 1; i++) + { + buffers[i] = allocator.Allocate(bufferSize, allocationOptions); + } + + if (bufferCount > 0) + { + buffers[^1] = allocator.Allocate(sizeOfLastBuffer, allocationOptions); + } + + return new Owned(buffers, bufferSize); } public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); public static MemoryGroup Wrap(ReadOnlyMemory> source) { - return new Consumed(source); + int bufferSize = source.Length > 0 ? source.Span[0].Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source.Span[i].Length != bufferSize) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source.Span[^1].Length > bufferSize) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + return new Consumed(source, bufferSize); } // Analogous to current MemorySource.SwapOrCopyContent() diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index 85aebb874..c12c09b7a 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -3,11 +3,14 @@ using System.Linq; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { public class MemoryGroupTests { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + public class Allocate { private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); @@ -19,12 +22,12 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { default(S5), 22, 4, 4, 1, 4, 4 }, { default(S5), 22, 4, 7, 2, 4, 3 }, { default(S5), 22, 4, 8, 2, 4, 4 }, - { default(S5), 22, 4, 21, 5, 4, 1 }, - { default(S5), 22, 4, 0, 0, -1, -1 }, + { default(S5), 22, 4, 21, 6, 4, 1 }, + { default(S5), 22, 4, 0, 0, 4, -1 }, { default(S4), 50, 12, 12, 1, 12, 12 }, { default(S4), 50, 7, 12, 2, 7, 5 }, - { default(S4), 50, 6, 12, 2, 6, 6 }, + { default(S4), 50, 6, 12, 1, 12, 12 }, { default(S4), 50, 5, 12, 2, 10, 2 }, { default(S4), 50, 4, 12, 1, 12, 12 }, { default(S4), 50, 3, 12, 1, 12, 12 }, @@ -33,31 +36,34 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { default(S4), 50, 12, 13, 2, 12, 1 }, { default(S4), 50, 7, 21, 3, 7, 7 }, - { default(S4), 50, 7, 23, 3, 7, 2 }, + { default(S4), 50, 7, 23, 4, 7, 2 }, + { default(S4), 50, 6, 13, 2, 12, 1 }, + { default(short), 200, 50, 49, 1, 49, 49 }, + { default(short), 200, 50, 1, 1, 1, 1 }, { default(byte), 1000, 512, 2047, 4, 512, 511 } }; [Theory] [MemberData(nameof(AllocateData))] - public void CreatesBlocksOfCorrectSizes( + public void BufferSizesAreCorrect( T dummy, - int blockCapacity, - int blockAlignment, + int bufferCapacity, + int bufferAlignment, long totalLength, - int expectedNumberOfBlocks, - int expectedBlockSize, - int expectedSizeOfLastBlock) + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) where T : struct { - this.memoryAllocator.BlockCapacity = blockCapacity; + this.memoryAllocator.BufferCapacity = bufferCapacity; // Act: - using var g = MemoryGroup.Allocate(this.memoryAllocator, totalLength, blockAlignment); + using var g = MemoryGroup.Allocate(this.memoryAllocator, totalLength, bufferAlignment); // Assert: - Assert.Equal(expectedNumberOfBlocks, g.Count); - Assert.Equal(expectedBlockSize, g.BlockSize); + Assert.Equal(expectedNumberOfBuffers, g.Count); + Assert.Equal(expectedBufferSize, g.BufferSize); if (g.Count == 0) { return; @@ -65,29 +71,29 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers for (int i = 0; i < g.Count - 1; i++) { - Assert.Equal(g[i].Length, expectedBlockSize); + Assert.Equal(g[i].Length, expectedBufferSize); } - Assert.Equal(g.Last().Length, expectedSizeOfLastBlock); + Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); } [Fact] public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() { - this.memoryAllocator.BlockCapacity = 42; + this.memoryAllocator.BufferCapacity = 84; // 42 * Int16 Assert.Throws(() => { - MemoryGroup.Allocate(this.memoryAllocator, 50, 43); + MemoryGroup.Allocate(this.memoryAllocator, 50, 43); }); } [Theory] [InlineData(AllocationOptions.None)] [InlineData(AllocationOptions.Clean)] - public void MemoryAllocator_IsUtilizedCorrectly(AllocationOptions allocationOptions) + public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) { - this.memoryAllocator.BlockCapacity = 200; + this.memoryAllocator.BufferCapacity = 200; HashSet bufferHashes; @@ -113,15 +119,33 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers } } + [Fact] + public void IsValid_TrueAfterCreation() + { + using var g = MemoryGroup.Allocate(this.memoryAllocator, 10, 100); + + Assert.True(g.IsValid); + } + + [Fact] + public void IsValid_FalseAfterDisposal() + { + using var g = MemoryGroup.Allocate(this.memoryAllocator, 10, 100); + + g.Dispose(); + Assert.False(g.IsValid); + } [StructLayout(LayoutKind.Sequential, Size = 5)] private struct S5 { + public override string ToString() => "S5"; } [StructLayout(LayoutKind.Sequential, Size = 4)] private struct S4 { + public override string ToString() => "S4"; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index c49a68990..a77e7f2f2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -26,13 +26,13 @@ namespace SixLabors.ImageSharp.Tests.Memory /// public byte DirtyValue { get; } - public int BlockCapacity { get; set; } = int.MaxValue; + public int BufferCapacity { get; set; } = int.MaxValue; public IReadOnlyList AllocationLog => this.allocationLog; public IReadOnlyList ReturnLog => this.returnLog; - protected internal override int GetBlockCapacity() => this.BlockCapacity; + protected internal override int GetBufferCapacity() => this.BufferCapacity; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) {