diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
index d341e93af..883d57851 100644
--- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
+++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
@@ -83,19 +83,19 @@ namespace SixLabors.ImageSharp.Memory
///
public int PoolSelectorThresholdInBytes { get; }
+ ///
+ /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance.
+ ///
+ public int BufferCapacityInBytes { get; set; } = DefaultBufferCapacity;
+
///
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
- ///
- /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance.
- ///
- public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue;
-
///
- protected internal override int GetBufferCapacity() => this.MaximumContiguousBufferLength;
+ protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes;
///
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 9ed322c9c..c6e92f23f 100644
--- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
+++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
@@ -10,11 +10,13 @@ namespace SixLabors.ImageSharp.Memory
///
public abstract class MemoryAllocator
{
+ internal const int DefaultBufferCapacity = int.MaxValue / 2;
+
///
/// 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 GetBufferCapacity();
+ protected internal abstract int GetBufferCapacityInBytes();
///
/// Allocates an , holding a of length .
diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
index 293e807ef..b417df351 100644
--- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
+++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
@@ -7,12 +7,12 @@ using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
///
- /// Implements by newing up arrays by the GC on every allocation requests.
+ /// Implements by newing up managed arrays on every allocation request.
///
public sealed class SimpleGcMemoryAllocator : MemoryAllocator
{
///
- protected internal override int GetBufferCapacity() => int.MaxValue;
+ protected internal override int GetBufferCapacityInBytes() => DefaultBufferCapacity;
///
public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None)
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
index 3e0df15ea..68a1f2e80 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
@@ -10,7 +10,88 @@ namespace SixLabors.ImageSharp.Memory
public static void CopyTo(this IMemoryGroup source, IMemoryGroup target)
where T : struct
{
- throw new NotImplementedException();
+ Guard.NotNull(source, nameof(source));
+ Guard.NotNull(target, nameof(target));
+ Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid.");
+ Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid.");
+ Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!");
+
+ if (source.IsEmpty())
+ {
+ return;
+ }
+
+ long position = 0;
+ var srcCur = new MemoryGroupCursor(source);
+ var trgCur = new MemoryGroupCursor(target);
+
+ while (position < source.TotalLength)
+ {
+ int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead());
+ Span srcSpan = srcCur.GetSpan(fwd);
+ Span trgSpan = trgCur.GetSpan(fwd);
+ srcSpan.CopyTo(trgSpan);
+
+ srcCur.Forward(fwd);
+ trgCur.Forward(fwd);
+ position += fwd;
+ }
+ }
+
+ public static bool IsEmpty(this IMemoryGroup group)
+ where T : struct
+ => group.Count == 0;
+
+ private struct MemoryGroupCursor
+ where T : struct
+ {
+ private readonly IMemoryGroup memoryGroup;
+
+ private int bufferIndex;
+
+ private int elementIndex;
+
+ public MemoryGroupCursor(IMemoryGroup memoryGroup)
+ {
+ this.memoryGroup = memoryGroup;
+ this.bufferIndex = 0;
+ this.elementIndex = 0;
+ }
+
+ private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1;
+
+ private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length;
+
+ public Span GetSpan(int length)
+ {
+ return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length);
+ }
+
+ public int LookAhead()
+ {
+ return this.CurrentBufferLength - this.elementIndex;
+ }
+
+ public void Forward(int steps)
+ {
+ int nextIdx = this.elementIndex + steps;
+ int currentBufferLength = this.CurrentBufferLength;
+
+ if (nextIdx < currentBufferLength)
+ {
+ this.elementIndex = nextIdx;
+ }
+ else if (nextIdx == currentBufferLength)
+ {
+ this.bufferIndex++;
+ this.elementIndex = 0;
+ }
+ else
+ {
+ // If we get here, it indicates a bug in CopyTo:
+ throw new ArgumentException("Can't forward multiple buffers!", nameof(steps));
+ }
+ }
}
}
}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
index ac43d6847..3883a111d 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
@@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Memory
Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength));
Guard.MustBeGreaterThan(blockAlignment, 0, nameof(blockAlignment));
- int blockCapacityInElements = allocator.GetBufferCapacity() / ElementSize;
+ int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize;
if (blockAlignment > blockCapacityInElements)
{
throw new InvalidMemoryOperationException();
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs
index 710f90216..d619aec29 100644
--- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs
index 6a9d322f6..f0cc18f29 100644
--- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs
@@ -1,4 +1,7 @@
-using Xunit;
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using Xunit;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs
index 1a617d396..0c96f3d78 100644
--- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs
@@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
int expectedSizeOfLastBuffer)
where T : struct
{
- this.MemoryAllocator.BufferCapacity = bufferCapacity;
+ this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity;
// Act:
using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment);
@@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
[Fact]
public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException()
{
- this.MemoryAllocator.BufferCapacity = 84; // 42 * Int16
+ this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16
Assert.Throws(() =>
{
@@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
[InlineData(AllocationOptions.Clean)]
public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions)
{
- this.MemoryAllocator.BufferCapacity = 200;
+ this.MemoryAllocator.BufferCapacityInBytes = 200;
HashSet bufferHashes;
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs
index 206d6499b..fd679d071 100644
--- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs
@@ -10,15 +10,20 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public class CopyTo : MemoryGroupTestsBase
{
+#pragma warning disable SA1509
public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data =
new TheoryData()
{
+ { 20, 10, 20, 10 },
{ 20, 5, 20, 4 },
{ 20, 4, 20, 5 },
{ 18, 6, 20, 5 },
{ 19, 10, 20, 10 },
{ 21, 10, 22, 2 },
{ 1, 5, 5, 4 },
+
+ { 30, 12, 40, 5 },
+ { 30, 5, 40, 12 },
};
[Theory]
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
index b9fea3497..f7865b00c 100644
--- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
@@ -26,7 +26,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
Assert.False(g.IsValid);
}
-
[StructLayout(LayoutKind.Sequential, Size = 5)]
private struct S5
{
@@ -39,29 +38,4 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
public override string ToString() => "S4";
}
}
-
- public abstract class MemoryGroupTestsBase
- {
- internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator();
-
- internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false)
- {
- this.MemoryAllocator.BufferCapacity = bufferLength;
- var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength);
-
- if (!fillSequence)
- {
- return g;
- }
-
- int j = 1;
- for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1)
- {
- g.SetElementAt(i, j);
- j++;
- }
-
- return g;
- }
- }
}
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs
new file mode 100644
index 000000000..acd24e343
--- /dev/null
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
+{
+ public abstract class MemoryGroupTestsBase
+ {
+ internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator();
+
+ internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false)
+ {
+ this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int);
+ var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength);
+
+ if (!fillSequence)
+ {
+ return g;
+ }
+
+ int j = 1;
+ for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1)
+ {
+ g.SetElementAt(i, j);
+ j++;
+ }
+
+ return g;
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs
index a77e7f2f2..dd928cb75 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 BufferCapacity { get; set; } = int.MaxValue;
+ public int BufferCapacityInBytes { get; set; } = int.MaxValue;
public IReadOnlyList AllocationLog => this.allocationLog;
public IReadOnlyList ReturnLog => this.returnLog;
- protected internal override int GetBufferCapacity() => this.BufferCapacity;
+ protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes;
public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None)
{