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)
{