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