mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
71 changed files with 912 additions and 486 deletions
@ -1,34 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// A buffer implementation that consumes an existing <see cref="Memory{T}"/> instance.
|
|||
/// The ownership of the memory remains external.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type</typeparam>
|
|||
internal sealed class ConsumedBuffer<T> : IBuffer<T> |
|||
where T : struct |
|||
{ |
|||
public ConsumedBuffer(Memory<T> memory) |
|||
{ |
|||
this.Memory = memory; |
|||
} |
|||
|
|||
public Memory<T> Memory { get; } |
|||
|
|||
public bool IsMemoryOwner => false; |
|||
|
|||
public Span<T> GetSpan() |
|||
{ |
|||
return this.Memory.Span; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a contigous memory buffer of value-type items.
|
|||
/// Depending on it's implementation, an <see cref="IBuffer{T}"/> can (1) OWN or (2) CONSUME the <see cref="Memory{T}"/> instance it wraps.
|
|||
/// For a deeper understanding of the owner/consumer model, read the following docs: <br/>
|
|||
/// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6
|
|||
/// TODO: We need more SOC here! For owned buffers we should use <see cref="IMemoryOwner{T}"/>.
|
|||
/// For the consumption case we should not use buffers at all. We need to refactor Buffer2D{T} for this.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type</typeparam>
|
|||
internal interface IBuffer<T> : IDisposable |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the <see cref="Memory{T}"/> ownerd/consumed by this buffer.
|
|||
/// </summary>
|
|||
Memory<T> Memory { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this instance is owning the <see cref="Memory"/>.
|
|||
/// </summary>
|
|||
bool IsMemoryOwner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the span to the memory "promised" by this buffer when it's OWNED (1).
|
|||
/// Gets `this.Memory.Span` when the buffer CONSUMED (2).
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Span{T}"/></returns>
|
|||
Span<T> GetSpan(); |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.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,163 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
|
|||
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 WhenOwnershipIsTransfered_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]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory |
|||
{ |
|||
internal class TestMemoryAllocator : MemoryAllocator |
|||
{ |
|||
public TestMemoryAllocator(byte dirtyValue = 42) |
|||
{ |
|||
this.DirtyValue = dirtyValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The value to initilazie the result buffer with, with non-clean options (<see cref="AllocationOptions.None"/>)
|
|||
/// </summary>
|
|||
public byte DirtyValue { get; } |
|||
|
|||
internal override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
T[] array = this.AllocateArray<T>(length, options); |
|||
|
|||
return new BasicArrayBuffer<T>(array, length); |
|||
} |
|||
|
|||
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
byte[] array = this.AllocateArray<byte>(length, options); |
|||
return new ManagedByteBuffer(array); |
|||
} |
|||
|
|||
private T[] AllocateArray<T>(int length, AllocationOptions options) |
|||
where T : struct |
|||
{ |
|||
var array = new T[length + 42]; |
|||
|
|||
if (options == AllocationOptions.None) |
|||
{ |
|||
Span<byte> data = MemoryMarshal.Cast<T, byte>(array.AsSpan()); |
|||
data.Fill(this.DirtyValue); |
|||
} |
|||
|
|||
return array; |
|||
} |
|||
|
|||
private class ManagedByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer |
|||
{ |
|||
public ManagedByteBuffer(byte[] array) |
|||
: base(array) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue