mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
112 changed files with 3731 additions and 1004 deletions
@ -1 +1 @@ |
|||
Subproject commit a75469fdb93fb89b39a5b0b7c01cb7432ceef98f |
|||
Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5 |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
/// <summary>
|
|||
/// Cache 8 pixel rows on the stack, which may originate from different buffers of a <see cref="MemoryGroup{T}"/>.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal readonly ref struct RowOctet<T> |
|||
where T : struct |
|||
{ |
|||
private readonly Span<T> row0; |
|||
private readonly Span<T> row1; |
|||
private readonly Span<T> row2; |
|||
private readonly Span<T> row3; |
|||
private readonly Span<T> row4; |
|||
private readonly Span<T> row5; |
|||
private readonly Span<T> row6; |
|||
private readonly Span<T> row7; |
|||
|
|||
public RowOctet(Buffer2D<T> buffer, int startY) |
|||
{ |
|||
int y = startY; |
|||
int height = buffer.Height; |
|||
this.row0 = y < height ? buffer.GetRowSpan(y++) : default; |
|||
this.row1 = y < height ? buffer.GetRowSpan(y++) : default; |
|||
this.row2 = y < height ? buffer.GetRowSpan(y++) : default; |
|||
this.row3 = y < height ? buffer.GetRowSpan(y++) : default; |
|||
this.row4 = y < height ? buffer.GetRowSpan(y++) : default; |
|||
this.row5 = y < height ? buffer.GetRowSpan(y++) : default; |
|||
this.row6 = y < height ? buffer.GetRowSpan(y++) : default; |
|||
this.row7 = y < height ? buffer.GetRowSpan(y) : default; |
|||
} |
|||
|
|||
public Span<T> this[int y] |
|||
{ |
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
get |
|||
{ |
|||
// No unsafe tricks, since Span<T> can't be used as a generic argument
|
|||
return y switch |
|||
{ |
|||
0 => this.row0, |
|||
1 => this.row1, |
|||
2 => this.row2, |
|||
3 => this.row3, |
|||
4 => this.row4, |
|||
5 => this.row5, |
|||
6 => this.row6, |
|||
7 => this.row7, |
|||
_ => ThrowIndexOutOfRangeException() |
|||
}; |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ColdPath)] |
|||
private static Span<T> ThrowIndexOutOfRangeException() |
|||
{ |
|||
throw new IndexOutOfRangeException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Represents discontiguous group of multiple uniformly-sized memory segments.
|
|||
/// The last segment can be smaller than the preceding ones.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
public interface IMemoryGroup<T> : IReadOnlyList<Memory<T>> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the number of elements per contiguous sub-buffer preceding the last buffer.
|
|||
/// The last buffer is allowed to be smaller.
|
|||
/// </summary>
|
|||
public int BufferLength { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the aggregate number of elements in the group.
|
|||
/// </summary>
|
|||
public long TotalLength { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the group has been invalidated.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces
|
|||
/// the image buffers internally.
|
|||
/// </remarks>
|
|||
bool IsValid { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,232 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
internal static class MemoryGroupExtensions |
|||
{ |
|||
internal static void Fill<T>(this IMemoryGroup<T> group, T value) |
|||
where T : struct |
|||
{ |
|||
foreach (Memory<T> memory in group) |
|||
{ |
|||
memory.Span.Fill(value); |
|||
} |
|||
} |
|||
|
|||
internal static void Clear<T>(this IMemoryGroup<T> group) |
|||
where T : struct |
|||
{ |
|||
foreach (Memory<T> memory in group) |
|||
{ |
|||
memory.Span.Clear(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a slice that is expected to be within the bounds of a single buffer.
|
|||
/// Otherwise <see cref="ArgumentOutOfRangeException"/> is thrown.
|
|||
/// </summary>
|
|||
internal static Memory<T> GetBoundedSlice<T>(this IMemoryGroup<T> group, long start, int length) |
|||
where T : struct |
|||
{ |
|||
Guard.NotNull(group, nameof(group)); |
|||
Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!"); |
|||
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); |
|||
Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); |
|||
|
|||
int bufferIdx = (int)(start / group.BufferLength); |
|||
if (bufferIdx >= group.Count) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(start)); |
|||
} |
|||
|
|||
int bufferStart = (int)(start % group.BufferLength); |
|||
int bufferEnd = bufferStart + length; |
|||
Memory<T> memory = group[bufferIdx]; |
|||
|
|||
if (bufferEnd > memory.Length) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(length)); |
|||
} |
|||
|
|||
return memory.Slice(bufferStart, length); |
|||
} |
|||
|
|||
internal static void CopyTo<T>(this IMemoryGroup<T> source, Span<T> target) |
|||
where T : struct |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target)); |
|||
|
|||
var cur = new MemoryGroupCursor<T>(source); |
|||
long position = 0; |
|||
while (position < source.TotalLength) |
|||
{ |
|||
int fwd = Math.Min(cur.LookAhead(), target.Length); |
|||
cur.GetSpan(fwd).CopyTo(target); |
|||
|
|||
cur.Forward(fwd); |
|||
target = target.Slice(fwd); |
|||
position += fwd; |
|||
} |
|||
} |
|||
|
|||
internal static void CopyTo<T>(this Span<T> source, IMemoryGroup<T> target) |
|||
where T : struct |
|||
=> CopyTo((ReadOnlySpan<T>)source, target); |
|||
|
|||
internal static void CopyTo<T>(this ReadOnlySpan<T> source, IMemoryGroup<T> target) |
|||
where T : struct |
|||
{ |
|||
Guard.NotNull(target, nameof(target)); |
|||
Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target)); |
|||
|
|||
var cur = new MemoryGroupCursor<T>(target); |
|||
|
|||
while (!source.IsEmpty) |
|||
{ |
|||
int fwd = Math.Min(cur.LookAhead(), source.Length); |
|||
source.Slice(0, fwd).CopyTo(cur.GetSpan(fwd)); |
|||
cur.Forward(fwd); |
|||
source = source.Slice(fwd); |
|||
} |
|||
} |
|||
|
|||
internal static void CopyTo<T>(this IMemoryGroup<T> source, IMemoryGroup<T> target) |
|||
where T : struct |
|||
{ |
|||
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<T>(source); |
|||
var trgCur = new MemoryGroupCursor<T>(target); |
|||
|
|||
while (position < source.TotalLength) |
|||
{ |
|||
int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); |
|||
Span<T> srcSpan = srcCur.GetSpan(fwd); |
|||
Span<T> trgSpan = trgCur.GetSpan(fwd); |
|||
srcSpan.CopyTo(trgSpan); |
|||
|
|||
srcCur.Forward(fwd); |
|||
trgCur.Forward(fwd); |
|||
position += fwd; |
|||
} |
|||
} |
|||
|
|||
internal static void TransformTo<TSource, TTarget>( |
|||
this IMemoryGroup<TSource> source, |
|||
IMemoryGroup<TTarget> target, |
|||
TransformItemsDelegate<TSource, TTarget> transform) |
|||
where TSource : struct |
|||
where TTarget : struct |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
Guard.NotNull(target, nameof(target)); |
|||
Guard.NotNull(transform, nameof(transform)); |
|||
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<TSource>(source); |
|||
var trgCur = new MemoryGroupCursor<TTarget>(target); |
|||
|
|||
while (position < source.TotalLength) |
|||
{ |
|||
int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); |
|||
Span<TSource> srcSpan = srcCur.GetSpan(fwd); |
|||
Span<TTarget> trgSpan = trgCur.GetSpan(fwd); |
|||
transform(srcSpan, trgSpan); |
|||
|
|||
srcCur.Forward(fwd); |
|||
trgCur.Forward(fwd); |
|||
position += fwd; |
|||
} |
|||
} |
|||
|
|||
internal static void TransformInplace<T>( |
|||
this IMemoryGroup<T> memoryGroup, |
|||
TransformItemsInplaceDelegate<T> transform) |
|||
where T : struct |
|||
{ |
|||
foreach (Memory<T> memory in memoryGroup) |
|||
{ |
|||
transform(memory.Span); |
|||
} |
|||
} |
|||
|
|||
internal static bool IsEmpty<T>(this IMemoryGroup<T> group) |
|||
where T : struct |
|||
=> group.Count == 0; |
|||
|
|||
private struct MemoryGroupCursor<T> |
|||
where T : struct |
|||
{ |
|||
private readonly IMemoryGroup<T> memoryGroup; |
|||
|
|||
private int bufferIndex; |
|||
|
|||
private int elementIndex; |
|||
|
|||
public MemoryGroupCursor(IMemoryGroup<T> 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<T> 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<T>:
|
|||
throw new ArgumentException("Can't forward multiple buffers!", nameof(steps)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Implements <see cref="IMemoryGroup{T}"/>, defining a view for <see cref="Memory.MemoryGroup{T}"/>
|
|||
/// rather than owning the segments.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This type provides an indirection, protecting the users of publicly exposed memory API-s
|
|||
/// from internal memory-swaps. Whenever an internal swap happens, the <see cref="MemoryGroupView{T}"/>
|
|||
/// instance becomes invalid, throwing an exception on all operations.
|
|||
/// </remarks>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
internal class MemoryGroupView<T> : IMemoryGroup<T> |
|||
where T : struct |
|||
{ |
|||
private MemoryGroup<T> owner; |
|||
private readonly MemoryOwnerWrapper[] memoryWrappers; |
|||
|
|||
public MemoryGroupView(MemoryGroup<T> owner) |
|||
{ |
|||
this.owner = owner; |
|||
this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; |
|||
|
|||
for (int i = 0; i < owner.Count; i++) |
|||
{ |
|||
this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i); |
|||
} |
|||
} |
|||
|
|||
public int Count |
|||
{ |
|||
get |
|||
{ |
|||
this.EnsureIsValid(); |
|||
return this.owner.Count; |
|||
} |
|||
} |
|||
|
|||
public int BufferLength |
|||
{ |
|||
get |
|||
{ |
|||
this.EnsureIsValid(); |
|||
return this.owner.BufferLength; |
|||
} |
|||
} |
|||
|
|||
public long TotalLength |
|||
{ |
|||
get |
|||
{ |
|||
this.EnsureIsValid(); |
|||
return this.owner.TotalLength; |
|||
} |
|||
} |
|||
|
|||
public bool IsValid => this.owner != null; |
|||
|
|||
public Memory<T> this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
this.EnsureIsValid(); |
|||
return this.memoryWrappers[index].Memory; |
|||
} |
|||
} |
|||
|
|||
public IEnumerator<Memory<T>> GetEnumerator() |
|||
{ |
|||
this.EnsureIsValid(); |
|||
for (int i = 0; i < this.Count; i++) |
|||
{ |
|||
yield return this.memoryWrappers[i].Memory; |
|||
} |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); |
|||
|
|||
internal void Invalidate() |
|||
{ |
|||
this.owner = null; |
|||
} |
|||
|
|||
private void EnsureIsValid() |
|||
{ |
|||
if (!this.IsValid) |
|||
{ |
|||
throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!"); |
|||
} |
|||
} |
|||
|
|||
private class MemoryOwnerWrapper : MemoryManager<T> |
|||
{ |
|||
private readonly MemoryGroupView<T> view; |
|||
|
|||
private readonly int index; |
|||
|
|||
public MemoryOwnerWrapper(MemoryGroupView<T> view, int index) |
|||
{ |
|||
this.view = view; |
|||
this.index = index; |
|||
} |
|||
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
|
|||
public override Span<T> GetSpan() |
|||
{ |
|||
this.view.EnsureIsValid(); |
|||
return this.view.owner[this.index].Span; |
|||
} |
|||
|
|||
public override MemoryHandle Pin(int elementIndex = 0) |
|||
{ |
|||
this.view.EnsureIsValid(); |
|||
return this.view.owner[this.index].Pin(); |
|||
} |
|||
|
|||
public override void Unpin() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
internal abstract partial class MemoryGroup<T> |
|||
{ |
|||
// Analogous to the "consumed" variant of MemorySource
|
|||
private sealed class Consumed : MemoryGroup<T> |
|||
{ |
|||
private readonly Memory<T>[] source; |
|||
|
|||
public Consumed(Memory<T>[] source, int bufferLength, long totalLength) |
|||
: base(bufferLength, totalLength) |
|||
{ |
|||
this.source = source; |
|||
this.View = new MemoryGroupView<T>(this); |
|||
} |
|||
|
|||
public override int Count => this.source.Length; |
|||
|
|||
public override Memory<T> this[int index] => this.source[index]; |
|||
|
|||
public override IEnumerator<Memory<T>> GetEnumerator() |
|||
{ |
|||
for (int i = 0; i < this.source.Length; i++) |
|||
{ |
|||
yield return this.source[i]; |
|||
} |
|||
} |
|||
|
|||
public override void Dispose() |
|||
{ |
|||
this.View.Invalidate(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
// Analogous to the "owned" variant of MemorySource
|
|||
internal abstract partial class MemoryGroup<T> |
|||
{ |
|||
private sealed class Owned : MemoryGroup<T> |
|||
{ |
|||
private IMemoryOwner<T>[] memoryOwners; |
|||
|
|||
public Owned(IMemoryOwner<T>[] memoryOwners, int bufferLength, long totalLength, bool swappable) |
|||
: base(bufferLength, totalLength) |
|||
{ |
|||
this.memoryOwners = memoryOwners; |
|||
this.Swappable = swappable; |
|||
this.View = new MemoryGroupView<T>(this); |
|||
} |
|||
|
|||
public bool Swappable { get; } |
|||
|
|||
private bool IsDisposed => this.memoryOwners == null; |
|||
|
|||
public override int Count |
|||
{ |
|||
get |
|||
{ |
|||
this.EnsureNotDisposed(); |
|||
return this.memoryOwners.Length; |
|||
} |
|||
} |
|||
|
|||
public override Memory<T> this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
this.EnsureNotDisposed(); |
|||
return this.memoryOwners[index].Memory; |
|||
} |
|||
} |
|||
|
|||
public override IEnumerator<Memory<T>> GetEnumerator() |
|||
{ |
|||
this.EnsureNotDisposed(); |
|||
return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); |
|||
} |
|||
|
|||
public override void Dispose() |
|||
{ |
|||
if (this.IsDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.View.Invalidate(); |
|||
|
|||
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners) |
|||
{ |
|||
memoryOwner.Dispose(); |
|||
} |
|||
|
|||
this.memoryOwners = null; |
|||
this.IsValid = false; |
|||
} |
|||
|
|||
private void EnsureNotDisposed() |
|||
{ |
|||
if (this.memoryOwners == null) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(MemoryGroup<T>)); |
|||
} |
|||
} |
|||
|
|||
internal static void SwapContents(Owned a, Owned b) |
|||
{ |
|||
a.EnsureNotDisposed(); |
|||
b.EnsureNotDisposed(); |
|||
|
|||
IMemoryOwner<T>[] tempOwners = a.memoryOwners; |
|||
long tempTotalLength = a.TotalLength; |
|||
int tempBufferLength = a.BufferLength; |
|||
|
|||
a.memoryOwners = b.memoryOwners; |
|||
a.TotalLength = b.TotalLength; |
|||
a.BufferLength = b.BufferLength; |
|||
|
|||
b.memoryOwners = tempOwners; |
|||
b.TotalLength = tempTotalLength; |
|||
b.BufferLength = tempBufferLength; |
|||
|
|||
a.View.Invalidate(); |
|||
b.View.Invalidate(); |
|||
a.View = new MemoryGroupView<T>(a); |
|||
b.View = new MemoryGroupView<T>(b); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,190 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Memory.Internals; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Represents discontinuous group of multiple uniformly-sized memory segments.
|
|||
/// The underlying buffers may change with time, therefore it's not safe to expose them directly on
|
|||
/// <see cref="Image{TPixel}"/> and <see cref="ImageFrame{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
internal abstract partial class MemoryGroup<T> : IMemoryGroup<T>, IDisposable |
|||
where T : struct |
|||
{ |
|||
private static readonly int ElementSize = Unsafe.SizeOf<T>(); |
|||
|
|||
private MemoryGroup(int bufferLength, long totalLength) |
|||
{ |
|||
this.BufferLength = bufferLength; |
|||
this.TotalLength = totalLength; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public abstract int Count { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public int BufferLength { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public long TotalLength { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public bool IsValid { get; private set; } = true; |
|||
|
|||
public MemoryGroupView<T> View { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public abstract Memory<T> this[int index] { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public abstract void Dispose(); |
|||
|
|||
/// <inheritdoc />
|
|||
public abstract IEnumerator<Memory<T>> GetEnumerator(); |
|||
|
|||
/// <inheritdoc />
|
|||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); |
|||
|
|||
/// <summary>
|
|||
/// Creates a new memory group, allocating it's buffers with the provided allocator.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The <see cref="MemoryAllocator"/> to use.</param>
|
|||
/// <param name="totalLength">The total length of the buffer.</param>
|
|||
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
|
|||
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
|
|||
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
|
|||
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
|
|||
public static MemoryGroup<T> Allocate( |
|||
MemoryAllocator allocator, |
|||
long totalLength, |
|||
int bufferAlignment, |
|||
AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
Guard.NotNull(allocator, nameof(allocator)); |
|||
Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); |
|||
Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment)); |
|||
|
|||
int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; |
|||
|
|||
if (bufferAlignment > blockCapacityInElements) |
|||
{ |
|||
throw new InvalidMemoryOperationException( |
|||
$"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); |
|||
} |
|||
|
|||
if (totalLength == 0) |
|||
{ |
|||
var buffers0 = new IMemoryOwner<T>[1] { allocator.Allocate<T>(0, options) }; |
|||
return new Owned(buffers0, 0, 0, true); |
|||
} |
|||
|
|||
int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment; |
|||
int bufferLength = numberOfAlignedSegments * bufferAlignment; |
|||
if (totalLength > 0 && totalLength < bufferLength) |
|||
{ |
|||
bufferLength = (int)totalLength; |
|||
} |
|||
|
|||
int sizeOfLastBuffer = (int)(totalLength % bufferLength); |
|||
long bufferCount = totalLength / bufferLength; |
|||
|
|||
if (sizeOfLastBuffer == 0) |
|||
{ |
|||
sizeOfLastBuffer = bufferLength; |
|||
} |
|||
else |
|||
{ |
|||
bufferCount++; |
|||
} |
|||
|
|||
var buffers = new IMemoryOwner<T>[bufferCount]; |
|||
for (int i = 0; i < buffers.Length - 1; i++) |
|||
{ |
|||
buffers[i] = allocator.Allocate<T>(bufferLength, options); |
|||
} |
|||
|
|||
if (bufferCount > 0) |
|||
{ |
|||
buffers[buffers.Length - 1] = allocator.Allocate<T>(sizeOfLastBuffer, options); |
|||
} |
|||
|
|||
return new Owned(buffers, bufferLength, totalLength, true); |
|||
} |
|||
|
|||
public static MemoryGroup<T> Wrap(params Memory<T>[] source) |
|||
{ |
|||
int bufferLength = source.Length > 0 ? source[0].Length : 0; |
|||
for (int i = 1; i < source.Length - 1; i++) |
|||
{ |
|||
if (source[i].Length != bufferLength) |
|||
{ |
|||
throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); |
|||
} |
|||
} |
|||
|
|||
if (source.Length > 0 && source[source.Length - 1].Length > bufferLength) |
|||
{ |
|||
throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); |
|||
} |
|||
|
|||
long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Length : 0; |
|||
|
|||
return new Consumed(source, bufferLength, totalLength); |
|||
} |
|||
|
|||
public static MemoryGroup<T> Wrap(params IMemoryOwner<T>[] 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[source.Length - 1].Memory.Length > bufferLength) |
|||
{ |
|||
throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); |
|||
} |
|||
|
|||
long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Memory.Length : 0; |
|||
|
|||
return new Owned(source, bufferLength, totalLength, false); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Swaps the contents of 'target' with 'source' if the buffers are allocated (1),
|
|||
/// copies the contents of 'source' to 'target' otherwise (2).
|
|||
/// Groups should be of same TotalLength in case 2.
|
|||
/// </summary>
|
|||
public static bool SwapOrCopyContent(MemoryGroup<T> target, MemoryGroup<T> source) |
|||
{ |
|||
if (source is Owned ownedSrc && ownedSrc.Swappable && |
|||
target is Owned ownedTarget && ownedTarget.Swappable) |
|||
{ |
|||
Owned.SwapContents(ownedTarget, ownedSrc); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
if (target.TotalLength != source.TotalLength) |
|||
{ |
|||
throw new InvalidMemoryOperationException( |
|||
"Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); |
|||
} |
|||
|
|||
source.CopyTo(target); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Exception thrown when the library detects an invalid memory allocation request,
|
|||
/// or an attempt has been made to use an invalidated <see cref="IMemoryGroup{T}"/>.
|
|||
/// </summary>
|
|||
public class InvalidMemoryOperationException : InvalidOperationException |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="InvalidMemoryOperationException"/> class.
|
|||
/// </summary>
|
|||
/// <param name="message">The exception message text.</param>
|
|||
public InvalidMemoryOperationException(string message) |
|||
: base(message) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="InvalidMemoryOperationException"/> class.
|
|||
/// </summary>
|
|||
public InvalidMemoryOperationException() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Holds a <see cref="System.Memory{T}"/> that is either OWNED or CONSUMED.
|
|||
/// When the memory is being owned, the <see cref="IMemoryOwner{T}"/> instance is also known.
|
|||
/// Implements content transfer logic in <see cref="SwapOrCopyContent"/> that depends on the ownership status.
|
|||
/// This is needed to transfer the contents of a temporary <see cref="Buffer2D{T}"/>
|
|||
/// to a persistent <see cref="SixLabors.ImageSharp.ImageFrame{T}.PixelBuffer"/> without copying the buffer.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// For a deeper understanding of the owner/consumer model, check out the following docs: <br/>
|
|||
/// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6
|
|||
/// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T
|
|||
/// </remarks>
|
|||
internal struct MemorySource<T> : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="MemorySource{T}"/> struct
|
|||
/// by wrapping an existing <see cref="IMemoryOwner{T}"/>.
|
|||
/// </summary>
|
|||
/// <param name="memoryOwner">The <see cref="IMemoryOwner{T}"/> to wrap</param>
|
|||
/// <param name="isInternalMemorySource">
|
|||
/// A value indicating whether <paramref name="memoryOwner"/> is an internal memory source managed by ImageSharp.
|
|||
/// Eg. allocated by a <see cref="MemoryAllocator"/>.
|
|||
/// </param>
|
|||
public MemorySource(IMemoryOwner<T> memoryOwner, bool isInternalMemorySource) |
|||
{ |
|||
this.MemoryOwner = memoryOwner; |
|||
this.Memory = memoryOwner.Memory; |
|||
this.HasSwappableContents = isInternalMemorySource; |
|||
} |
|||
|
|||
public MemorySource(Memory<T> memory) |
|||
{ |
|||
this.Memory = memory; |
|||
this.MemoryOwner = null; |
|||
this.HasSwappableContents = false; |
|||
} |
|||
|
|||
public IMemoryOwner<T> MemoryOwner { get; private set; } |
|||
|
|||
public Memory<T> Memory { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether we are allowed to swap the contents of this buffer
|
|||
/// with an other <see cref="MemorySource{T}"/> instance.
|
|||
/// The value is true only and only if <see cref="MemoryOwner"/> is present,
|
|||
/// and it's coming from an internal source managed by ImageSharp (<see cref="MemoryAllocator"/>).
|
|||
/// </summary>
|
|||
public bool HasSwappableContents { get; } |
|||
|
|||
public Span<T> GetSpan() => this.Memory.Span; |
|||
|
|||
public void Clear() => this.Memory.Span.Clear(); |
|||
|
|||
/// <summary>
|
|||
/// 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!
|
|||
/// </summary>
|
|||
public static void SwapOrCopyContent(ref MemorySource<T> destination, ref MemorySource<T> 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); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
this.MemoryOwner?.Dispose(); |
|||
} |
|||
|
|||
private static void SwapContents(ref MemorySource<T> a, ref MemorySource<T> b) |
|||
{ |
|||
IMemoryOwner<T> tempOwner = a.MemoryOwner; |
|||
Memory<T> tempMemory = a.Memory; |
|||
|
|||
a.MemoryOwner = b.MemoryOwner; |
|||
a.Memory = b.Memory; |
|||
|
|||
b.MemoryOwner = tempOwner; |
|||
b.Memory = tempMemory; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
internal delegate void TransformItemsDelegate<TSource, TTarget>(ReadOnlySpan<TSource> source, Span<TTarget> target); |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory |
|||
{ |
|||
internal delegate void TransformItemsInplaceDelegate<T>(Span<T> data); |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
public class ImageFrameTests |
|||
{ |
|||
public class Indexer |
|||
{ |
|||
private readonly Configuration configuration = Configuration.CreateDefaultInstance(); |
|||
|
|||
private void LimitBufferCapacity(int bufferCapacityInBytes) |
|||
{ |
|||
var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; |
|||
allocator.BufferCapacityInBytes = bufferCapacityInBytes; |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false)] |
|||
[InlineData(true)] |
|||
public void GetSet(bool enforceDisco) |
|||
{ |
|||
if (enforceDisco) |
|||
{ |
|||
this.LimitBufferCapacity(100); |
|||
} |
|||
|
|||
using var image = new Image<Rgba32>(this.configuration, 10, 10); |
|||
ImageFrame<Rgba32> frame = image.Frames.RootFrame; |
|||
Rgba32 val = frame[3, 4]; |
|||
Assert.Equal(default(Rgba32), val); |
|||
frame[3, 4] = Color.Red; |
|||
val = frame[3, 4]; |
|||
Assert.Equal(Color.Red.ToRgba32(), val); |
|||
} |
|||
|
|||
public static TheoryData<bool, int> OutOfRangeData = new TheoryData<bool, int>() |
|||
{ |
|||
{ false, -1 }, |
|||
{ false, 10 }, |
|||
{ true, -1 }, |
|||
{ true, 10 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(OutOfRangeData))] |
|||
public void Get_OutOfRangeX(bool enforceDisco, int x) |
|||
{ |
|||
if (enforceDisco) |
|||
{ |
|||
this.LimitBufferCapacity(100); |
|||
} |
|||
|
|||
using var image = new Image<Rgba32>(this.configuration, 10, 10); |
|||
ImageFrame<Rgba32> frame = image.Frames.RootFrame; |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => _ = frame[x, 3]); |
|||
Assert.Equal("x", ex.ParamName); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(OutOfRangeData))] |
|||
public void Set_OutOfRangeX(bool enforceDisco, int x) |
|||
{ |
|||
if (enforceDisco) |
|||
{ |
|||
this.LimitBufferCapacity(100); |
|||
} |
|||
|
|||
using var image = new Image<Rgba32>(this.configuration, 10, 10); |
|||
ImageFrame<Rgba32> frame = image.Frames.RootFrame; |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => frame[x, 3] = default); |
|||
Assert.Equal("x", ex.ParamName); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(OutOfRangeData))] |
|||
public void Set_OutOfRangeY(bool enforceDisco, int y) |
|||
{ |
|||
if (enforceDisco) |
|||
{ |
|||
this.LimitBufferCapacity(100); |
|||
} |
|||
|
|||
using var image = new Image<Rgba32>(this.configuration, 10, 10); |
|||
ImageFrame<Rgba32> frame = image.Frames.RootFrame; |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => frame[3, y] = default); |
|||
Assert.Equal("y", ex.ParamName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
public class LargeImageIntegrationTests |
|||
{ |
|||
[Theory(Skip = "For local testing only.")] |
|||
[WithBasicTestPatternImages(width: 30000, height: 30000, PixelTypes.Rgba32)] |
|||
public void CreateAndResize(TestImageProvider<Rgba32> provider) |
|||
{ |
|||
using Image<Rgba32> image = provider.GetImage(); |
|||
image.Mutate(c => c.Resize(1000, 1000)); |
|||
image.DebugSave(provider); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
// 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 |
|||
{ |
|||
public struct MemoryGroupIndex : IEquatable<MemoryGroupIndex> |
|||
{ |
|||
public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other); |
|||
|
|||
public override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex); |
|||
|
|||
public int BufferLength { get; } |
|||
|
|||
public int BufferIndex { get; } |
|||
|
|||
public int ElementIndex { get; } |
|||
|
|||
public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex) |
|||
{ |
|||
this.BufferLength = bufferLength; |
|||
this.BufferIndex = bufferIndex; |
|||
this.ElementIndex = elementIndex; |
|||
} |
|||
|
|||
public static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val) |
|||
{ |
|||
int nextElementIndex = idx.ElementIndex + val; |
|||
return new MemoryGroupIndex( |
|||
idx.BufferLength, |
|||
idx.BufferIndex + (nextElementIndex / idx.BufferLength), |
|||
nextElementIndex % idx.BufferLength); |
|||
} |
|||
|
|||
public bool Equals(MemoryGroupIndex other) |
|||
{ |
|||
if (this.BufferLength != other.BufferLength) |
|||
{ |
|||
throw new InvalidOperationException(); |
|||
} |
|||
|
|||
return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex; |
|||
} |
|||
|
|||
public static bool operator ==(MemoryGroupIndex a, MemoryGroupIndex b) => a.Equals(b); |
|||
|
|||
public static bool operator !=(MemoryGroupIndex a, MemoryGroupIndex b) => !a.Equals(b); |
|||
|
|||
public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b) |
|||
{ |
|||
if (a.BufferLength != b.BufferLength) |
|||
{ |
|||
throw new InvalidOperationException(); |
|||
} |
|||
|
|||
if (a.BufferIndex < b.BufferIndex) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (a.BufferIndex == b.BufferIndex) |
|||
{ |
|||
return a.ElementIndex < b.ElementIndex; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b) |
|||
{ |
|||
if (a.BufferLength != b.BufferLength) |
|||
{ |
|||
throw new InvalidOperationException(); |
|||
} |
|||
|
|||
if (a.BufferIndex > b.BufferIndex) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (a.BufferIndex == b.BufferIndex) |
|||
{ |
|||
return a.ElementIndex > b.ElementIndex; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
internal static class MemoryGroupIndexExtensions |
|||
{ |
|||
public static T GetElementAt<T>(this IMemoryGroup<T> group, MemoryGroupIndex idx) |
|||
where T : struct |
|||
{ |
|||
return group[idx.BufferIndex].Span[idx.ElementIndex]; |
|||
} |
|||
|
|||
public static void SetElementAt<T>(this IMemoryGroup<T> group, MemoryGroupIndex idx, T value) |
|||
where T : struct |
|||
{ |
|||
group[idx.BufferIndex].Span[idx.ElementIndex] = value; |
|||
} |
|||
|
|||
public static MemoryGroupIndex MinIndex<T>(this IMemoryGroup<T> group) |
|||
where T : struct |
|||
{ |
|||
return new MemoryGroupIndex(group.BufferLength, 0, 0); |
|||
} |
|||
|
|||
public static MemoryGroupIndex MaxIndex<T>(this IMemoryGroup<T> group) |
|||
where T : struct |
|||
{ |
|||
return group.Count == 0 |
|||
? new MemoryGroupIndex(group.BufferLength, 0, 0) |
|||
: new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[group.Count - 1].Length); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers |
|||
{ |
|||
public class MemoryGroupIndexTests |
|||
{ |
|||
[Fact] |
|||
public void Equal() |
|||
{ |
|||
var a = new MemoryGroupIndex(10, 1, 3); |
|||
var b = new MemoryGroupIndex(10, 1, 3); |
|||
|
|||
Assert.True(a.Equals(b)); |
|||
Assert.True(a == b); |
|||
Assert.False(a != b); |
|||
Assert.False(a < b); |
|||
Assert.False(a > b); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SmallerBufferIndex() |
|||
{ |
|||
var a = new MemoryGroupIndex(10, 3, 3); |
|||
var b = new MemoryGroupIndex(10, 5, 3); |
|||
|
|||
Assert.False(a == b); |
|||
Assert.True(a != b); |
|||
Assert.True(a < b); |
|||
Assert.False(a > b); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SmallerElementIndex() |
|||
{ |
|||
var a = new MemoryGroupIndex(10, 3, 3); |
|||
var b = new MemoryGroupIndex(10, 3, 9); |
|||
|
|||
Assert.False(a == b); |
|||
Assert.True(a != b); |
|||
Assert.True(a < b); |
|||
Assert.False(a > b); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Increment() |
|||
{ |
|||
var a = new MemoryGroupIndex(10, 3, 3); |
|||
a += 1; |
|||
Assert.Equal(new MemoryGroupIndex(10, 3, 4), a); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Increment_OverflowBuffer() |
|||
{ |
|||
var a = new MemoryGroupIndex(10, 5, 3); |
|||
var b = new MemoryGroupIndex(10, 5, 9); |
|||
a += 8; |
|||
b += 1; |
|||
|
|||
Assert.Equal(new MemoryGroupIndex(10, 6, 1), a); |
|||
Assert.Equal(new MemoryGroupIndex(10, 6, 0), b); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers |
|||
{ |
|||
public partial class MemoryGroupTests |
|||
{ |
|||
public class Allocate : MemoryGroupTestsBase |
|||
{ |
|||
#pragma warning disable SA1509
|
|||
public static TheoryData<object, int, int, long, int, int, int> AllocateData = |
|||
new TheoryData<object, int, int, long, int, int, int>() |
|||
{ |
|||
{ 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, 6, 4, 1 }, |
|||
|
|||
// empty:
|
|||
{ default(S5), 22, 0, 0, 1, -1, 0 }, |
|||
{ default(S5), 22, 4, 0, 1, -1, 0 }, |
|||
|
|||
{ default(S4), 50, 12, 12, 1, 12, 12 }, |
|||
{ default(S4), 50, 7, 12, 2, 7, 5 }, |
|||
{ 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 }, |
|||
{ default(S4), 50, 2, 12, 1, 12, 12 }, |
|||
{ default(S4), 50, 1, 12, 1, 12, 12 }, |
|||
|
|||
{ default(S4), 50, 12, 13, 2, 12, 1 }, |
|||
{ default(S4), 50, 7, 21, 3, 7, 7 }, |
|||
{ 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 BufferSizesAreCorrect<T>( |
|||
T dummy, |
|||
int bufferCapacity, |
|||
int bufferAlignment, |
|||
long totalLength, |
|||
int expectedNumberOfBuffers, |
|||
int expectedBufferSize, |
|||
int expectedSizeOfLastBuffer) |
|||
where T : struct |
|||
{ |
|||
this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; |
|||
|
|||
// Act:
|
|||
using var g = MemoryGroup<T>.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(expectedNumberOfBuffers, g.Count); |
|||
|
|||
if (expectedBufferSize >= 0) |
|||
{ |
|||
Assert.Equal(expectedBufferSize, g.BufferLength); |
|||
} |
|||
|
|||
if (g.Count == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
for (int i = 0; i < g.Count - 1; i++) |
|||
{ |
|||
Assert.Equal(g[i].Length, expectedBufferSize); |
|||
} |
|||
|
|||
Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() |
|||
{ |
|||
this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16
|
|||
|
|||
Assert.Throws<InvalidMemoryOperationException>(() => |
|||
{ |
|||
MemoryGroup<short>.Allocate(this.MemoryAllocator, 50, 43); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(AllocationOptions.None)] |
|||
[InlineData(AllocationOptions.Clean)] |
|||
public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) |
|||
{ |
|||
this.MemoryAllocator.BufferCapacityInBytes = 200; |
|||
|
|||
HashSet<int> bufferHashes; |
|||
|
|||
int expectedBlockCount = 5; |
|||
using (var g = MemoryGroup<short>.Allocate(this.MemoryAllocator, 500, 100, allocationOptions)) |
|||
{ |
|||
IReadOnlyList<TestMemoryAllocator.AllocationRequest> allocationLog = this.MemoryAllocator.AllocationLog; |
|||
Assert.Equal(expectedBlockCount, allocationLog.Count); |
|||
bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); |
|||
Assert.Equal(expectedBlockCount, bufferHashes.Count); |
|||
Assert.Equal(0, this.MemoryAllocator.ReturnLog.Count); |
|||
|
|||
for (int i = 0; i < expectedBlockCount; i++) |
|||
{ |
|||
Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions); |
|||
Assert.Equal(100, allocationLog[i].Length); |
|||
Assert.Equal(200, allocationLog[i].LengthInBytes); |
|||
} |
|||
} |
|||
|
|||
Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); |
|||
Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers |
|||
{ |
|||
public partial class MemoryGroupTests |
|||
{ |
|||
public class CopyTo : MemoryGroupTestsBase |
|||
{ |
|||
public static readonly TheoryData<int, int, int, int> WhenSourceBufferIsShorterOrEqual_Data = |
|||
CopyAndTransformData; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] |
|||
public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) |
|||
{ |
|||
using MemoryGroup<int> src = this.CreateTestGroup(srcTotal, srcBufLen, true); |
|||
using MemoryGroup<int> trg = this.CreateTestGroup(trgTotal, trgBufLen, false); |
|||
|
|||
src.CopyTo(trg); |
|||
|
|||
int pos = 0; |
|||
MemoryGroupIndex i = src.MinIndex(); |
|||
MemoryGroupIndex j = trg.MinIndex(); |
|||
for (; i < src.MaxIndex(); i += 1, j += 1, pos++) |
|||
{ |
|||
int a = src.GetElementAt(i); |
|||
int b = trg.GetElementAt(j); |
|||
|
|||
Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenTargetBufferTooShort_Throws() |
|||
{ |
|||
using MemoryGroup<int> src = this.CreateTestGroup(10, 20, true); |
|||
using MemoryGroup<int> trg = this.CreateTestGroup(5, 20, false); |
|||
|
|||
Assert.Throws<ArgumentOutOfRangeException>(() => src.CopyTo(trg)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(30, 10, 40)] |
|||
[InlineData(42, 23, 42)] |
|||
[InlineData(1, 3, 10)] |
|||
[InlineData(0, 4, 0)] |
|||
public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength) |
|||
{ |
|||
using MemoryGroup<int> src = this.CreateTestGroup(totalLength, bufferLength, true); |
|||
var trg = new int[spanLength]; |
|||
src.CopyTo(trg); |
|||
|
|||
int expected = 1; |
|||
foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) |
|||
{ |
|||
Assert.Equal(expected, val); |
|||
expected++; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(20, 7, 19)] |
|||
[InlineData(2, 1, 1)] |
|||
public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) |
|||
{ |
|||
using MemoryGroup<int> src = this.CreateTestGroup(totalLength, bufferLength, true); |
|||
var trg = new int[spanLength]; |
|||
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => src.CopyTo(trg)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(30, 35, 10)] |
|||
[InlineData(42, 23, 42)] |
|||
[InlineData(10, 3, 1)] |
|||
[InlineData(0, 3, 0)] |
|||
public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength) |
|||
{ |
|||
var src = new int[spanLength]; |
|||
for (int i = 0; i < src.Length; i++) |
|||
{ |
|||
src[i] = i + 1; |
|||
} |
|||
|
|||
using MemoryGroup<int> trg = this.CreateTestGroup(totalLength, bufferLength); |
|||
src.AsSpan().CopyTo(trg); |
|||
|
|||
int position = 0; |
|||
for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) |
|||
{ |
|||
int expected = position + 1; |
|||
Assert.Equal(expected, trg.GetElementAt(i)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(10, 3, 11)] |
|||
[InlineData(0, 3, 1)] |
|||
public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) |
|||
{ |
|||
var src = new int[spanLength]; |
|||
using MemoryGroup<int> trg = this.CreateTestGroup(totalLength, bufferLength, true); |
|||
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => src.AsSpan().CopyTo(trg)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers |
|||
{ |
|||
public partial class MemoryGroupTests |
|||
{ |
|||
public class SwapOrCopyContent : MemoryGroupTestsBase |
|||
{ |
|||
[Fact] |
|||
public void WhenBothAreMemoryOwners_ShouldSwap() |
|||
{ |
|||
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; |
|||
using MemoryGroup<int> a = this.MemoryAllocator.AllocateGroup<int>(100, 50); |
|||
using MemoryGroup<int> b = this.MemoryAllocator.AllocateGroup<int>(120, 50); |
|||
|
|||
Memory<int> a0 = a[0]; |
|||
Memory<int> a1 = a[1]; |
|||
Memory<int> b0 = b[0]; |
|||
Memory<int> b1 = b[1]; |
|||
|
|||
bool swap = MemoryGroup<int>.SwapOrCopyContent(a, b); |
|||
|
|||
Assert.True(swap); |
|||
Assert.Equal(b0, a[0]); |
|||
Assert.Equal(b1, a[1]); |
|||
Assert.Equal(a0, b[0]); |
|||
Assert.Equal(a1, b[1]); |
|||
Assert.NotEqual(a[0], b[0]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenBothAreMemoryOwners_ShouldReplaceViews() |
|||
{ |
|||
using MemoryGroup<int> a = this.MemoryAllocator.AllocateGroup<int>(100, 100); |
|||
using MemoryGroup<int> b = this.MemoryAllocator.AllocateGroup<int>(120, 100); |
|||
|
|||
a[0].Span[42] = 1; |
|||
b[0].Span[33] = 2; |
|||
MemoryGroupView<int> aView0 = a.View; |
|||
MemoryGroupView<int> bView0 = b.View; |
|||
|
|||
MemoryGroup<int>.SwapOrCopyContent(a, b); |
|||
Assert.False(aView0.IsValid); |
|||
Assert.False(bView0.IsValid); |
|||
Assert.ThrowsAny<InvalidOperationException>(() => _ = aView0[0].Span); |
|||
Assert.ThrowsAny<InvalidOperationException>(() => _ = bView0[0].Span); |
|||
|
|||
Assert.True(a.View.IsValid); |
|||
Assert.True(b.View.IsValid); |
|||
Assert.Equal(2, a.View[0].Span[33]); |
|||
Assert.Equal(1, b.View[0].Span[42]); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false)] |
|||
[InlineData(true)] |
|||
public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) |
|||
{ |
|||
var data = new Rgba32[21]; |
|||
var color = new Rgba32(1, 2, 3, 4); |
|||
|
|||
using var destOwner = new TestMemoryManager<Rgba32>(data); |
|||
using var dest = MemoryGroup<Rgba32>.Wrap(destOwner.Memory); |
|||
|
|||
using MemoryGroup<Rgba32> source = this.MemoryAllocator.AllocateGroup<Rgba32>(21, 30); |
|||
|
|||
source[0].Span[10] = color; |
|||
|
|||
// Act:
|
|||
bool swap = MemoryGroup<Rgba32>.SwapOrCopyContent(dest, source); |
|||
|
|||
// Assert:
|
|||
Assert.False(swap); |
|||
Assert.Equal(color, dest[0].Span[10]); |
|||
Assert.NotEqual(source[0], dest[0]); |
|||
} |
|||
|
|||
[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); |
|||
|
|||
using var destOwner = new TestMemoryManager<Rgba32>(data); |
|||
var dest = MemoryGroup<Rgba32>.Wrap(destOwner.Memory); |
|||
|
|||
using MemoryGroup<Rgba32> source = this.MemoryAllocator.AllocateGroup<Rgba32>(22, 30); |
|||
|
|||
source[0].Span[10] = color; |
|||
|
|||
// Act:
|
|||
Assert.ThrowsAny<InvalidOperationException>(() => MemoryGroup<Rgba32>.SwapOrCopyContent(dest, source)); |
|||
|
|||
Assert.Equal(color, source[0].Span[10]); |
|||
Assert.NotEqual(color, dest[0].Span[10]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers |
|||
{ |
|||
public partial class MemoryGroupTests |
|||
{ |
|||
public class View : MemoryGroupTestsBase |
|||
{ |
|||
[Fact] |
|||
public void RefersToOwnerGroupContent() |
|||
{ |
|||
using MemoryGroup<int> group = this.CreateTestGroup(240, 80, true); |
|||
|
|||
MemoryGroupView<int> view = group.View; |
|||
Assert.True(view.IsValid); |
|||
Assert.Equal(group.Count, view.Count); |
|||
Assert.Equal(group.BufferLength, view.BufferLength); |
|||
Assert.Equal(group.TotalLength, view.TotalLength); |
|||
int cnt = 1; |
|||
foreach (Memory<int> memory in view) |
|||
{ |
|||
Span<int> span = memory.Span; |
|||
foreach (int t in span) |
|||
{ |
|||
Assert.Equal(cnt, t); |
|||
cnt++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsInvalidatedOnOwnerGroupDispose() |
|||
{ |
|||
MemoryGroupView<int> view; |
|||
using (MemoryGroup<int> group = this.CreateTestGroup(240, 80, true)) |
|||
{ |
|||
view = group.View; |
|||
} |
|||
|
|||
Assert.False(view.IsValid); |
|||
|
|||
Assert.ThrowsAny<InvalidMemoryOperationException>(() => |
|||
{ |
|||
_ = view.Count; |
|||
}); |
|||
|
|||
Assert.ThrowsAny<InvalidMemoryOperationException>(() => |
|||
{ |
|||
_ = view.BufferLength; |
|||
}); |
|||
|
|||
Assert.ThrowsAny<InvalidMemoryOperationException>(() => |
|||
{ |
|||
_ = view.TotalLength; |
|||
}); |
|||
|
|||
Assert.ThrowsAny<InvalidMemoryOperationException>(() => |
|||
{ |
|||
_ = view[0]; |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenInvalid_CanNotUseMemberMemory() |
|||
{ |
|||
Memory<int> memory; |
|||
using (MemoryGroup<int> group = this.CreateTestGroup(240, 80, true)) |
|||
{ |
|||
memory = group.View[0]; |
|||
} |
|||
|
|||
Assert.ThrowsAny<InvalidMemoryOperationException>(() => |
|||
{ |
|||
_ = memory.Span; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,214 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers |
|||
{ |
|||
public partial class MemoryGroupTests : MemoryGroupTestsBase |
|||
{ |
|||
[Fact] |
|||
public void IsValid_TrueAfterCreation() |
|||
{ |
|||
using var g = MemoryGroup<byte>.Allocate(this.MemoryAllocator, 10, 100); |
|||
|
|||
Assert.True(g.IsValid); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsValid_FalseAfterDisposal() |
|||
{ |
|||
using var g = MemoryGroup<byte>.Allocate(this.MemoryAllocator, 10, 100); |
|||
|
|||
g.Dispose(); |
|||
Assert.False(g.IsValid); |
|||
} |
|||
|
|||
#pragma warning disable SA1509
|
|||
private static readonly TheoryData<int, int, int, int> CopyAndTransformData = |
|||
new TheoryData<int, int, int, int>() |
|||
{ |
|||
{ 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 }, |
|||
}; |
|||
|
|||
public class TransformTo : MemoryGroupTestsBase |
|||
{ |
|||
public static readonly TheoryData<int, int, int, int> WhenSourceBufferIsShorterOrEqual_Data = |
|||
CopyAndTransformData; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] |
|||
public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) |
|||
{ |
|||
using MemoryGroup<int> src = this.CreateTestGroup(srcTotal, srcBufLen, true); |
|||
using MemoryGroup<int> trg = this.CreateTestGroup(trgTotal, trgBufLen, false); |
|||
|
|||
src.TransformTo(trg, MultiplyAllBy2); |
|||
|
|||
int pos = 0; |
|||
MemoryGroupIndex i = src.MinIndex(); |
|||
MemoryGroupIndex j = trg.MinIndex(); |
|||
for (; i < src.MaxIndex(); i += 1, j += 1, pos++) |
|||
{ |
|||
int a = src.GetElementAt(i); |
|||
int b = trg.GetElementAt(j); |
|||
|
|||
Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenTargetBufferTooShort_Throws() |
|||
{ |
|||
using MemoryGroup<int> src = this.CreateTestGroup(10, 20, true); |
|||
using MemoryGroup<int> trg = this.CreateTestGroup(5, 20, false); |
|||
|
|||
Assert.Throws<ArgumentOutOfRangeException>(() => src.TransformTo(trg, MultiplyAllBy2)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(100, 5)] |
|||
[InlineData(100, 101)] |
|||
public void TransformInplace(int totalLength, int bufferLength) |
|||
{ |
|||
using MemoryGroup<int> src = this.CreateTestGroup(10, 20, true); |
|||
|
|||
src.TransformInplace(s => MultiplyAllBy2(s, s)); |
|||
|
|||
int cnt = 1; |
|||
for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1) |
|||
{ |
|||
int val = src.GetElementAt(i); |
|||
Assert.Equal(expected: cnt * 2, val); |
|||
cnt++; |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Wrap() |
|||
{ |
|||
int[] data0 = { 1, 2, 3, 4 }; |
|||
int[] data1 = { 5, 6, 7, 8 }; |
|||
int[] data2 = { 9, 10 }; |
|||
using var mgr0 = new TestMemoryManager<int>(data0); |
|||
using var mgr1 = new TestMemoryManager<int>(data1); |
|||
|
|||
using var group = MemoryGroup<int>.Wrap(mgr0.Memory, mgr1.Memory, data2); |
|||
|
|||
Assert.Equal(3, group.Count); |
|||
Assert.Equal(4, group.BufferLength); |
|||
Assert.Equal(10, group.TotalLength); |
|||
|
|||
Assert.True(group[0].Span.SequenceEqual(data0)); |
|||
Assert.True(group[1].Span.SequenceEqual(data1)); |
|||
Assert.True(group[2].Span.SequenceEqual(data2)); |
|||
} |
|||
|
|||
public static TheoryData<long, int, long, int> GetBoundedSlice_SuccessData = new TheoryData<long, int, long, int>() |
|||
{ |
|||
{ 300, 100, 110, 80 }, |
|||
{ 300, 100, 100, 100 }, |
|||
{ 280, 100, 201, 79 }, |
|||
{ 42, 7, 0, 0 }, |
|||
{ 42, 7, 0, 1 }, |
|||
{ 42, 7, 0, 7 }, |
|||
{ 42, 9, 9, 9 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(GetBoundedSlice_SuccessData))] |
|||
public void GetBoundedSlice_WhenArgsAreCorrect(long totalLength, int bufferLength, long start, int length) |
|||
{ |
|||
using MemoryGroup<int> group = this.CreateTestGroup(totalLength, bufferLength, true); |
|||
|
|||
Memory<int> slice = group.GetBoundedSlice(start, length); |
|||
|
|||
Assert.Equal(length, slice.Length); |
|||
|
|||
int expected = (int)start + 1; |
|||
foreach (int val in slice.Span) |
|||
{ |
|||
Assert.Equal(expected, val); |
|||
expected++; |
|||
} |
|||
} |
|||
|
|||
public static TheoryData<long, int, long, int> GetBoundedSlice_ErrorData = new TheoryData<long, int, long, int>() |
|||
{ |
|||
{ 300, 100, 110, 91 }, |
|||
{ 42, 7, 0, 8 }, |
|||
{ 42, 7, 1, 7 }, |
|||
{ 42, 7, 1, 30 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(GetBoundedSlice_ErrorData))] |
|||
public void GetBoundedSlice_WhenOverlapsBuffers_Throws(long totalLength, int bufferLength, long start, int length) |
|||
{ |
|||
using MemoryGroup<int> group = this.CreateTestGroup(totalLength, bufferLength, true); |
|||
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => group.GetBoundedSlice(start, length)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Fill() |
|||
{ |
|||
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true); |
|||
group.Fill(42); |
|||
|
|||
int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); |
|||
foreach (Memory<int> memory in group) |
|||
{ |
|||
Assert.True(memory.Span.SequenceEqual(expectedRow)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Clear() |
|||
{ |
|||
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true); |
|||
group.Clear(); |
|||
|
|||
var expectedRow = new int[10]; |
|||
foreach (Memory<int> memory in group) |
|||
{ |
|||
Assert.True(memory.Span.SequenceEqual(expectedRow)); |
|||
} |
|||
} |
|||
|
|||
private static void MultiplyAllBy2(ReadOnlySpan<int> source, Span<int> target) |
|||
{ |
|||
Assert.Equal(source.Length, target.Length); |
|||
for (int k = 0; k < source.Length; k++) |
|||
{ |
|||
target[k] = source[k] * 2; |
|||
} |
|||
} |
|||
|
|||
[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"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// 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(); |
|||
|
|||
/// <summary>
|
|||
/// Create a group, either uninitialized or filled with incrementing numbers starting with 1.
|
|||
/// </summary>
|
|||
internal MemoryGroup<int> CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) |
|||
{ |
|||
this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); |
|||
var g = MemoryGroup<int>.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; |
|||
} |
|||
} |
|||
} |
|||
@ -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<Rgba32>(data); |
|||
|
|||
var a = new MemorySource<Rgba32>(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<Rgba32>(data); |
|||
|
|||
var a = new MemorySource<Rgba32>(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<int>(new int[10]); |
|||
var bmg = new MemorySource<int>(mmg, isInternalMemorySource); |
|||
|
|||
bmg.Dispose(); |
|||
Assert.True(mmg.IsDisposed); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenMemoryObserver_ShouldNotDisposeAnything() |
|||
{ |
|||
var mmg = new TestMemoryManager<int>(new int[10]); |
|||
var bmg = new MemorySource<int>(mmg.Memory); |
|||
|
|||
bmg.Dispose(); |
|||
Assert.False(mmg.IsDisposed); |
|||
} |
|||
} |
|||
|
|||
public class SwapOrCopyContent |
|||
{ |
|||
private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); |
|||
|
|||
private MemorySource<T> AllocateMemorySource<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
where T : struct |
|||
{ |
|||
IMemoryOwner<T> owner = this.MemoryAllocator.Allocate<T>(length, options); |
|||
return new MemorySource<T>(owner, true); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenBothAreMemoryOwners_ShouldSwap() |
|||
{ |
|||
MemorySource<int> a = this.AllocateMemorySource<int>(13); |
|||
MemorySource<int> b = this.AllocateMemorySource<int>(17); |
|||
|
|||
IMemoryOwner<int> aa = a.MemoryOwner; |
|||
IMemoryOwner<int> bb = b.MemoryOwner; |
|||
|
|||
Memory<int> aaa = a.Memory; |
|||
Memory<int> bbb = b.Memory; |
|||
|
|||
MemorySource<int>.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<Rgba32>(data); |
|||
var dest = new MemorySource<Rgba32>(destOwner.Memory); |
|||
|
|||
IMemoryOwner<Rgba32> sourceOwner = this.MemoryAllocator.Allocate<Rgba32>(21); |
|||
|
|||
MemorySource<Rgba32> source = sourceIsOwner |
|||
? new MemorySource<Rgba32>(sourceOwner, isInternalMemorySource) |
|||
: new MemorySource<Rgba32>(sourceOwner.Memory); |
|||
|
|||
sourceOwner.Memory.Span[10] = color; |
|||
|
|||
// Act:
|
|||
MemorySource<Rgba32>.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<Rgba32>(data); |
|||
var dest = new MemorySource<Rgba32>(destOwner.Memory); |
|||
|
|||
IMemoryOwner<Rgba32> sourceOwner = this.MemoryAllocator.Allocate<Rgba32>(22); |
|||
|
|||
MemorySource<Rgba32> source = sourceIsOwner |
|||
? new MemorySource<Rgba32>(sourceOwner, true) |
|||
: new MemorySource<Rgba32>(sourceOwner.Memory); |
|||
sourceOwner.Memory.Span[10] = color; |
|||
|
|||
// Act:
|
|||
Assert.ThrowsAny<InvalidOperationException>(() => MemorySource<Rgba32>.SwapOrCopyContent(ref dest, ref source)); |
|||
|
|||
Assert.Equal(color, source.Memory.Span[10]); |
|||
Assert.NotEqual(color, dest.Memory.Span[10]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue