Browse Source

replace MemorySource with MemoryGroup

af/octree-no-pixelmap
Anton Firszov 6 years ago
parent
commit
a4980be72d
  1. 2
      shared-infrastructure
  2. 2
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  3. 2
      src/ImageSharp/Image.Decode.cs
  4. 6
      src/ImageSharp/Image.WrapMemory.cs
  5. 2
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  6. 4
      src/ImageSharp/ImageFrame{TPixel}.cs
  7. 8
      src/ImageSharp/Image{TPixel}.cs
  8. 10
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  9. 24
      src/ImageSharp/Memory/Buffer2D{T}.cs
  10. 10
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
  11. 5
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  12. 38
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  13. 5
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  14. 101
      src/ImageSharp/Memory/MemorySource.cs
  15. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  16. 3
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  17. 67
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
  18. 159
      tests/ImageSharp.Tests/Memory/MemorySourceTests.cs

2
shared-infrastructure

@ -1 +1 @@
Subproject commit a75469fdb93fb89b39a5b0b7c01cb7432ceef98f
Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5

2
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Advanced
internal static Memory<TPixel> GetPixelMemory<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return source.PixelBuffer.MemorySource.Memory;
return source.PixelBuffer.GetMemory();
}
/// <summary>

2
src/ImageSharp/Image.Decode.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp
{
Buffer2D<TPixel> uninitializedMemoryBuffer =
configuration.MemoryAllocator.Allocate2D<TPixel>(width, height);
return new Image<TPixel>(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata);
return new Image<TPixel>(configuration, uninitializedMemoryBuffer.MemoryGroup, width, height, metadata);
}
/// <summary>

6
src/ImageSharp/Image.WrapMemory.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
where TPixel : struct, IPixel<TPixel>
{
var memorySource = new MemorySource<TPixel>(pixelMemory);
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory);
return new Image<TPixel>(config, memorySource, width, height, metadata);
}
@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
where TPixel : struct, IPixel<TPixel>
{
var memorySource = new MemorySource<TPixel>(pixelMemoryOwner, false);
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner);
return new Image<TPixel>(config, memorySource, width, height, metadata);
}
@ -147,4 +147,4 @@ namespace SixLabors.ImageSharp
return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
}
}
}

2
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, backgroundColor));
}
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemorySource<TPixel> memorySource)
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemoryGroup<TPixel> memorySource)
{
this.parent = parent ?? throw new ArgumentNullException(nameof(parent));

4
src/ImageSharp/ImageFrame{TPixel}.cs

@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource)
internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup<TPixel> memorySource)
: this(configuration, width, height, memorySource, new ImageFrameMetadata())
{
}
@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="metadata">The metadata.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource, ImageFrameMetadata metadata)
internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup<TPixel> memorySource, ImageFrameMetadata metadata)
: base(configuration, width, height, metadata)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));

8
src/ImageSharp/Image{TPixel}.cs

@ -74,22 +74,22 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// wrapping an external <see cref="MemorySource{T}"/>.
/// wrapping an external <see cref="MemoryGroup{T}"/>.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="memoryGroup">The memory source.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(
Configuration configuration,
MemorySource<TPixel> memorySource,
MemoryGroup<TPixel> memoryGroup,
int width,
int height,
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
{
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memoryGroup);
}
/// <summary>

10
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.GetSpan();
return buffer.MemoryGroup.Single().Span;
}
/// <summary>
@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Memory
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory;
return buffer.MemoryGroup.Single();
}
/// <summary>
@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Memory
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width).Span;
}
/// <summary>
@ -66,10 +67,11 @@ namespace SixLabors.ImageSharp.Memory
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width);
}
/// <summary>
/// TODO: Does not work with multi-buffer groups, should be specific to Resize.
/// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace,
/// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>.
/// </summary>

24
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -14,21 +14,18 @@ namespace SixLabors.ImageSharp.Memory
/// Before RC1, this class might be target of API changes, use it on your own risk!
/// </remarks>
/// <typeparam name="T">The value type.</typeparam>
// TODO: Consider moving this type to the SixLabors.ImageSharp.Memory namespace (SixLabors.Core).
public sealed class Buffer2D<T> : IDisposable
where T : struct
{
private MemorySource<T> memorySource;
/// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>
/// <param name="memorySource">The buffer to wrap</param>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
internal Buffer2D(MemorySource<T> memorySource, int width, int height)
/// <param name="memoryGroup">The <see cref="MemoryGroup{T}"/> to wrap.</param>
/// <param name="width">The number of elements in a row.</param>
/// <param name="height">The number of rows.</param>
internal Buffer2D(MemoryGroup<T> memoryGroup, int width, int height)
{
this.memorySource = memorySource;
this.MemoryGroup = memoryGroup;
this.Width = width;
this.Height = height;
}
@ -44,9 +41,9 @@ namespace SixLabors.ImageSharp.Memory
public int Height { get; private set; }
/// <summary>
/// Gets the backing <see cref="MemorySource{T}"/>
/// Gets the backing <see cref="MemoryGroup{T}"/>.
/// </summary>
internal MemorySource<T> MemorySource => this.memorySource;
internal MemoryGroup<T> MemoryGroup { get; }
/// <summary>
/// Gets a reference to the element at the specified position.
@ -62,8 +59,7 @@ namespace SixLabors.ImageSharp.Memory
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.GetSpan();
return ref span[(this.Width * y) + x];
return ref this.GetRowSpan(y)[x];
}
}
@ -72,7 +68,7 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public void Dispose()
{
this.MemorySource.Dispose();
this.MemoryGroup.Dispose();
}
/// <summary>
@ -81,7 +77,7 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
internal static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
{
MemorySource<T>.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource);
MemoryGroup<T>.SwapOrCopyContent(destination.MemoryGroup, source.MemoryGroup);
SwapDimensionData(destination, source);
}

10
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
namespace SixLabors.ImageSharp.Memory
{
@ -11,9 +13,9 @@ namespace SixLabors.ImageSharp.Memory
// Analogous to the "consumed" variant of MemorySource
private class Consumed : MemoryGroup<T>
{
private readonly ReadOnlyMemory<Memory<T>> source;
private readonly Memory<T>[] source;
public Consumed(ReadOnlyMemory<Memory<T>> source, int bufferLength, long totalLength)
public Consumed(Memory<T>[] source, int bufferLength, long totalLength)
: base(bufferLength, totalLength)
{
this.source = source;
@ -22,13 +24,13 @@ namespace SixLabors.ImageSharp.Memory
public override int Count => this.source.Length;
public override Memory<T> this[int index] => this.source.Span[index];
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.Span[i];
yield return this.source[i];
}
}

5
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs

@ -15,13 +15,16 @@ namespace SixLabors.ImageSharp.Memory
{
private IMemoryOwner<T>[] memoryOwners;
public Owned(IMemoryOwner<T>[] memoryOwners, int bufferLength, long totalLength)
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; }
public override int Count
{
get

38
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

@ -108,32 +108,51 @@ namespace SixLabors.ImageSharp.Memory
buffers[^1] = allocator.Allocate<T>(sizeOfLastBuffer, options);
}
return new Owned(buffers, bufferLength, totalLength);
return new Owned(buffers, bufferLength, totalLength, true);
}
public static MemoryGroup<T> Wrap(params Memory<T>[] source) => Wrap(source.AsMemory());
public static MemoryGroup<T> Wrap(ReadOnlyMemory<Memory<T>> source)
public static MemoryGroup<T> Wrap(params Memory<T>[] source)
{
int bufferLength = source.Length > 0 ? source.Span[0].Length : 0;
int bufferLength = source.Length > 0 ? source[0].Length : 0;
for (int i = 1; i < source.Length - 1; i++)
{
if (source.Span[i].Length != bufferLength)
if (source[i].Length != bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!");
}
}
if (source.Length > 0 && source.Span[^1].Length > bufferLength)
if (source.Length > 0 && source[^1].Length > bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!");
}
long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source.Span[^1].Length : 0;
long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^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[^1].Memory.Length > bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!");
}
long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^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).
@ -141,7 +160,8 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public static void SwapOrCopyContent(MemoryGroup<T> target, MemoryGroup<T> source)
{
if (source is Owned ownedSrc && target is Owned ownedTarget)
if (source is Owned ownedSrc && ownedSrc.Swappable &&
target is Owned ownedTarget && ownedTarget.Swappable)
{
Owned.SwapContents(ownedTarget, ownedSrc);
}

5
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -27,9 +27,8 @@ namespace SixLabors.ImageSharp.Memory
AllocationOptions options = AllocationOptions.None)
where T : struct
{
IMemoryOwner<T> buffer = memoryAllocator.Allocate<T>(width * height, options);
var memorySource = new MemorySource<T>(buffer, true);
long groupLength = (long)width * height;
MemoryGroup<T> memorySource = memoryAllocator.AllocateGroup<T>(groupLength, width, options);
return new Buffer2D<T>(memorySource, width, height);
}

101
src/ImageSharp/Memory/MemorySource.cs

@ -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;
}
}
}

2
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// no need to dispose when buffer is not array owner
var memory = new Memory<float>(values);
var source = new MemorySource<float>(memory);
var source = MemoryGroup<float>.Wrap(memory);
buffers[i] = new Buffer2D<float>(source, values.Length, 1);
}

3
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine($"Component{i}: {diff}");
averageDifference += diff.average;
totalDifference += diff.total;
tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length;
tolerance += libJpegComponent.SpectralBlocks.GetSpan().Length;
}
averageDifference /= componentCount;

67
tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -18,28 +19,34 @@ namespace SixLabors.ImageSharp.Tests.Memory
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SpanPointsTo<T>(Span<T> span, IMemoryOwner<T> buffer, int bufferOffset = 0)
public static void SpanPointsTo<T>(Span<T> span, Memory<T> buffer, int bufferOffset = 0)
where T : struct
{
ref T actual = ref MemoryMarshal.GetReference(span);
ref T expected = ref Unsafe.Add(ref buffer.GetReference(), bufferOffset);
ref T expected = ref buffer.Span[bufferOffset];
True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position");
}
}
private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator();
private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator();
private const int Big = 99999;
[Theory]
[InlineData(7, 42)]
[InlineData(1025, 17)]
public void Construct(int width, int height)
[InlineData(Big, 7, 42)]
[InlineData(Big, 1025, 17)]
[InlineData(300, 42, 777)]
public unsafe void Construct(int bufferCapacity, int width, int height)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity;
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Assert.Equal(width, buffer.Width);
Assert.Equal(height, buffer.Height);
Assert.Equal(width * height, buffer.GetMemory().Length);
Assert.Equal(width * height, buffer.MemoryGroup.TotalLength);
Assert.True(buffer.MemoryGroup.BufferLength % width == 0);
}
}
@ -57,34 +64,48 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
[Theory]
[InlineData(7, 42, 0)]
[InlineData(7, 42, 10)]
[InlineData(17, 42, 41)]
public void GetRowSpanY(int width, int height, int y)
[InlineData(Big, 7, 42, 0, 0)]
[InlineData(Big, 7, 42, 10, 0)]
[InlineData(Big, 17, 42, 41, 0)]
[InlineData(500, 17, 42, 41, 1)]
[InlineData(200, 100, 30, 1, 0)]
[InlineData(200, 100, 30, 2, 1)]
[InlineData(200, 100, 30, 4, 2)]
public unsafe void GetRowSpanY(int bufferCapacity, int width, int height, int y, int expectedBufferIndex)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity;
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Span<TestStructs.Foo> span = buffer.GetRowSpan(y);
// Assert.Equal(width * y, span.Start);
Assert.Equal(width, span.Length);
Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y);
int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.MemoryGroup.BufferLength);
Assert.SpanPointsTo(span, buffer.MemoryGroup[expectedBufferIndex], expectedSubBufferOffset);
}
}
[Theory]
[InlineData(42, 8, 0, 0)]
[InlineData(400, 1000, 20, 10)]
[InlineData(99, 88, 98, 87)]
public void Indexer(int width, int height, int x, int y)
[InlineData(Big, 42, 8, 0, 0)]
[InlineData(Big, 400, 1000, 20, 10)]
[InlineData(Big, 99, 88, 98, 87)]
[InlineData(500, 200, 30, 42, 13)]
[InlineData(500, 200, 30, 199, 29)]
public unsafe void Indexer(int bufferCapacity, int width, int height, int x, int y)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity;
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Span<TestStructs.Foo> span = buffer.MemorySource.GetSpan();
int bufferIndex = (width * y) / buffer.MemoryGroup.BufferLength;
int subBufferStart = (width * y) - (bufferIndex * buffer.MemoryGroup.BufferLength);
Span<TestStructs.Foo> span = buffer.MemoryGroup[bufferIndex].Span.Slice(subBufferStart);
ref TestStructs.Foo actual = ref buffer[x, y];
ref TestStructs.Foo expected = ref span[(y * width) + x];
ref TestStructs.Foo expected = ref span[x];
Assert.True(Unsafe.AreSame(ref expected, ref actual));
}
@ -96,13 +117,13 @@ namespace SixLabors.ImageSharp.Tests.Memory
using (Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(10, 5))
using (Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(3, 7))
{
IMemoryOwner<int> aa = a.MemorySource.MemoryOwner;
IMemoryOwner<int> bb = b.MemorySource.MemoryOwner;
Memory<int> aa = a.MemoryGroup.Single();
Memory<int> bb = b.MemoryGroup.Single();
Buffer2D<int>.SwapOrCopyContent(a, b);
Assert.Equal(bb, a.MemorySource.MemoryOwner);
Assert.Equal(aa, b.MemorySource.MemoryOwner);
Assert.Equal(bb, a.MemoryGroup.Single());
Assert.Equal(aa, b.MemoryGroup.Single());
Assert.Equal(new Size(3, 7), a.Size());
Assert.Equal(new Size(10, 5), b.Size());

159
tests/ImageSharp.Tests/Memory/MemorySourceTests.cs

@ -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]);
}
}
}
}
Loading…
Cancel
Save