mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Introducing PinnedBuffer<T> to manage pinned (and optionally pooled) arrays as disposable resources + Introducing BulkedPixelOperations API.pull/112/merge
committed by
GitHub
52 changed files with 1927 additions and 423 deletions
@ -0,0 +1,256 @@ |
|||
// <copyright file="BulkPixelOperations{TColor}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <summary>
|
|||
/// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations
|
|||
/// for pixel buffers of type <typeparamref name="TColor"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TColor">The pixel format.</typeparam>
|
|||
public unsafe class BulkPixelOperations<TColor> |
|||
where TColor : struct, IPixel<TColor> |
|||
{ |
|||
/// <summary>
|
|||
/// The size of <typeparamref name="TColor"/> in bytes
|
|||
/// </summary>
|
|||
private static readonly int ColorSize = Unsafe.SizeOf<TColor>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the global <see cref="BulkPixelOperations{TColor}"/> instance for the pixel type <typeparamref name="TColor"/>
|
|||
/// </summary>
|
|||
public static BulkPixelOperations<TColor> Instance { get; } = default(TColor).CreateBulkOperations(); |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.PackFromVector4(Vector4)"/>
|
|||
/// </summary>
|
|||
/// <param name="sourceVectors">The <see cref="BufferPointer{T}"/> to the source vectors.</param>
|
|||
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void PackFromVector4( |
|||
BufferPointer<Vector4> sourceVectors, |
|||
BufferPointer<TColor> destColors, |
|||
int count) |
|||
{ |
|||
Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; |
|||
byte* dp = (byte*)destColors; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
Vector4 v = Unsafe.Read<Vector4>(sp); |
|||
TColor c = default(TColor); |
|||
c.PackFromVector4(v); |
|||
Unsafe.Write(dp, c); |
|||
|
|||
sp++; |
|||
dp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.ToVector4()"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
|
|||
/// <param name="destVectors">The <see cref="BufferPointer{T}"/> to the destination vectors.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void ToVector4( |
|||
BufferPointer<TColor> sourceColors, |
|||
BufferPointer<Vector4> destVectors, |
|||
int count) |
|||
{ |
|||
byte* sp = (byte*)sourceColors; |
|||
Vector4* dp = (Vector4*)destVectors.PointerAtOffset; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
TColor c = Unsafe.Read<TColor>(sp); |
|||
*dp = c.ToVector4(); |
|||
sp += ColorSize; |
|||
dp++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Xyz"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
|
|||
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void PackFromXyzBytes( |
|||
BufferPointer<byte> sourceBytes, |
|||
BufferPointer<TColor> destColors, |
|||
int count) |
|||
{ |
|||
byte* sp = (byte*)sourceBytes; |
|||
byte* dp = (byte*)destColors.PointerAtOffset; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
TColor c = default(TColor); |
|||
c.PackFromBytes(sp[0], sp[1], sp[2], 255); |
|||
Unsafe.Write(dp, c); |
|||
sp += 3; |
|||
dp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.ToXyzBytes(byte[], int)"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
|
|||
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void ToXyzBytes(BufferPointer<TColor> sourceColors, BufferPointer<byte> destBytes, int count) |
|||
{ |
|||
byte* sp = (byte*)sourceColors; |
|||
byte[] dest = destBytes.Array; |
|||
|
|||
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) |
|||
{ |
|||
TColor c = Unsafe.Read<TColor>(sp); |
|||
c.ToXyzBytes(dest, i); |
|||
sp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Xyzw"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
|
|||
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void PackFromXyzwBytes( |
|||
BufferPointer<byte> sourceBytes, |
|||
BufferPointer<TColor> destColors, |
|||
int count) |
|||
{ |
|||
byte* sp = (byte*)sourceBytes; |
|||
byte* dp = (byte*)destColors.PointerAtOffset; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
TColor c = default(TColor); |
|||
c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); |
|||
Unsafe.Write(dp, c); |
|||
sp += 4; |
|||
dp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.ToXyzwBytes(byte[], int)"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
|
|||
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void ToXyzwBytes( |
|||
BufferPointer<TColor> sourceColors, |
|||
BufferPointer<byte> destBytes, |
|||
int count) |
|||
{ |
|||
byte* sp = (byte*)sourceColors; |
|||
byte[] dest = destBytes.Array; |
|||
|
|||
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) |
|||
{ |
|||
TColor c = Unsafe.Read<TColor>(sp); |
|||
c.ToXyzwBytes(dest, i); |
|||
sp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Zyx"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
|
|||
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void PackFromZyxBytes( |
|||
BufferPointer<byte> sourceBytes, |
|||
BufferPointer<TColor> destColors, |
|||
int count) |
|||
{ |
|||
byte* sp = (byte*)sourceBytes; |
|||
byte* dp = (byte*)destColors.PointerAtOffset; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
TColor c = default(TColor); |
|||
c.PackFromBytes(sp[2], sp[1], sp[0], 255); |
|||
Unsafe.Write(dp, c); |
|||
sp += 3; |
|||
dp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.ToZyxBytes(byte[], int)"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
|
|||
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void ToZyxBytes(BufferPointer<TColor> sourceColors, BufferPointer<byte> destBytes, int count) |
|||
{ |
|||
byte* sp = (byte*)sourceColors; |
|||
byte[] dest = destBytes.Array; |
|||
|
|||
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) |
|||
{ |
|||
TColor c = Unsafe.Read<TColor>(sp); |
|||
c.ToZyxBytes(dest, i); |
|||
sp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Zyxw"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
|
|||
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void PackFromZyxwBytes( |
|||
BufferPointer<byte> sourceBytes, |
|||
BufferPointer<TColor> destColors, |
|||
int count) |
|||
{ |
|||
byte* sp = (byte*)sourceBytes; |
|||
byte* dp = (byte*)destColors.PointerAtOffset; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
TColor c = default(TColor); |
|||
c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); |
|||
Unsafe.Write(dp, c); |
|||
sp += 4; |
|||
dp += ColorSize; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Bulk version of <see cref="IPixel.ToZyxwBytes(byte[], int)"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
|
|||
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
|
|||
/// <param name="count">The number of pixels to convert.</param>
|
|||
internal virtual void ToZyxwBytes( |
|||
BufferPointer<TColor> sourceColors, |
|||
BufferPointer<byte> destBytes, |
|||
int count) |
|||
{ |
|||
byte* sp = (byte*)sourceColors; |
|||
byte[] dest = destBytes.Array; |
|||
|
|||
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) |
|||
{ |
|||
TColor c = Unsafe.Read<TColor>(sp); |
|||
c.ToZyxwBytes(dest, i); |
|||
sp += ColorSize; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
// <copyright file="ArrayExtensions.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Extension methods for arrays.
|
|||
/// </summary>
|
|||
public static class ArrayExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Locks the pixel buffer providing access to the pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TColor">The pixel format.</typeparam>
|
|||
/// <param name="pixels">The pixel buffer.</param>
|
|||
/// <param name="width">Gets the width of the image represented by the pixel buffer.</param>
|
|||
/// <param name="height">The height of the image represented by the pixel buffer.</param>
|
|||
/// <returns>The <see cref="PixelAccessor{TColor}"/></returns>
|
|||
public static PixelAccessor<TColor> Lock<TColor>(this TColor[] pixels, int width, int height) |
|||
where TColor : struct, IPixel<TColor> |
|||
{ |
|||
return new PixelAccessor<TColor>(width, height, pixels); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// <copyright file="BufferPointer.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <summary>
|
|||
/// Utility methods for <see cref="BufferPointer{T}"/>
|
|||
/// </summary>
|
|||
internal static class BufferPointer |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a <see cref="BufferPointer{T}"/> to the beginning of the raw data in 'buffer'.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type</typeparam>
|
|||
/// <param name="buffer">The input <see cref="PinnedBuffer{T}"/></param>
|
|||
/// <returns>The <see cref="BufferPointer{T}"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static unsafe BufferPointer<T> Slice<T>(this PinnedBuffer<T> buffer) |
|||
where T : struct |
|||
{ |
|||
return new BufferPointer<T>(buffer.Array, (void*)buffer.Pointer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copy 'count' number of elements of the same type from 'source' to 'dest'
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="source">The input <see cref="BufferPointer{T}"/></param>
|
|||
/// <param name="destination">The destination <see cref="BufferPointer{T}"/>.</param>
|
|||
/// <param name="count">The number of elements to copy</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static unsafe void Copy<T>(BufferPointer<T> source, BufferPointer<T> destination, int count) |
|||
where T : struct |
|||
{ |
|||
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(count)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copy 'countInSource' elements of <typeparamref name="T"/> from 'source' into the raw byte buffer 'destination'.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="source">The source buffer of <typeparamref name="T"/> elements to copy from.</param>
|
|||
/// <param name="destination">The destination buffer.</param>
|
|||
/// <param name="countInSource">The number of elements to copy from 'source'</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static unsafe void Copy<T>(BufferPointer<T> source, BufferPointer<byte> destination, int countInSource) |
|||
where T : struct |
|||
{ |
|||
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(countInSource)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copy 'countInDest' number of <typeparamref name="T"/> elements into 'dest' from a raw byte buffer defined by 'source'.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="source">The raw source buffer to copy from"/></param>
|
|||
/// <param name="destination">The destination buffer"/></param>
|
|||
/// <param name="countInDest">The number of <typeparamref name="T"/> elements to copy.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static unsafe void Copy<T>(BufferPointer<byte> source, BufferPointer<T> destination, int countInDest) |
|||
where T : struct |
|||
{ |
|||
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(countInDest)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of `count` elements in bytes.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="count">The count of the elements</param>
|
|||
/// <returns>The size in bytes as int</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int SizeOf<T>(int count) |
|||
where T : struct => Unsafe.SizeOf<T>() * count; |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of `count` elements in bytes as UInt32
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="count">The count of the elements</param>
|
|||
/// <returns>The size in bytes as UInt32</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static uint USizeOf<T>(int count) |
|||
where T : struct |
|||
=> (uint)SizeOf<T>(count); |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
// <copyright file="PinnedBuffer{T}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
/// <summary>
|
|||
/// Manages a pinned buffer of value type data 'T' as a Disposable resource.
|
|||
/// The backing array is either pooled or comes from the outside.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type.</typeparam>
|
|||
internal class PinnedBuffer<T> : IDisposable |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// A handle that allows to access the managed <see cref="Array"/> as an unmanaged memory by pinning.
|
|||
/// </summary>
|
|||
private GCHandle handle; |
|||
|
|||
/// <summary>
|
|||
/// A value indicating whether this <see cref="PinnedBuffer{T}"/> instance should return the array to the pool.
|
|||
/// </summary>
|
|||
private bool isPoolingOwner; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="count">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
|
|||
public PinnedBuffer(int count) |
|||
{ |
|||
this.Count = count; |
|||
this.Array = PixelDataPool<T>.Rent(count); |
|||
this.isPoolingOwner = true; |
|||
this.Pin(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="array">The array to pin.</param>
|
|||
public PinnedBuffer(T[] array) |
|||
{ |
|||
this.Count = array.Length; |
|||
this.Array = array; |
|||
this.Pin(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="count">The count of "relevant" elements in 'array'.</param>
|
|||
/// <param name="array">The array to pin.</param>
|
|||
public PinnedBuffer(int count, T[] array) |
|||
{ |
|||
if (array.Length < count) |
|||
{ |
|||
throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); |
|||
} |
|||
|
|||
this.Count = count; |
|||
this.Array = array; |
|||
this.Pin(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finalizes an instance of the <see cref="PinnedBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
~PinnedBuffer() |
|||
{ |
|||
this.UnPin(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="PinnedBuffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>.
|
|||
/// </summary>
|
|||
public bool IsDisposedOrLostArrayOwnership { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the count of "relevant" elements. Usually be smaller than 'Array.Length' when <see cref="Array"/> is pooled.
|
|||
/// </summary>
|
|||
public int Count { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the backing pinned array.
|
|||
/// </summary>
|
|||
public T[] Array { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a pointer to the pinned <see cref="Array"/>.
|
|||
/// </summary>
|
|||
public IntPtr Pointer { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Disposes the <see cref="PinnedBuffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
if (this.IsDisposedOrLostArrayOwnership) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.IsDisposedOrLostArrayOwnership = true; |
|||
this.UnPin(); |
|||
|
|||
if (this.isPoolingOwner) |
|||
{ |
|||
PixelDataPool<T>.Return(this.Array); |
|||
} |
|||
|
|||
this.Array = null; |
|||
this.Count = 0; |
|||
|
|||
GC.SuppressFinalize(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unpins <see cref="Array"/> and makes the object "quasi-disposed" so the array is no longer owned by this object.
|
|||
/// If <see cref="Array"/> is rented, it's the callers responsibility to return it to it's pool. (Most likely <see cref="PixelDataPool{T}"/>)
|
|||
/// </summary>
|
|||
/// <returns>The unpinned <see cref="Array"/></returns>
|
|||
public T[] UnPinAndTakeArrayOwnership() |
|||
{ |
|||
if (this.IsDisposedOrLostArrayOwnership) |
|||
{ |
|||
throw new InvalidOperationException("UnPinAndTakeArrayOwnership() is invalid: either PinnedBuffer<T> is disposed or UnPinAndTakeArrayOwnership() has been called multiple times!"); |
|||
} |
|||
|
|||
this.IsDisposedOrLostArrayOwnership = true; |
|||
this.UnPin(); |
|||
T[] array = this.Array; |
|||
this.Array = null; |
|||
return array; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Pins <see cref="Array"/>.
|
|||
/// </summary>
|
|||
private void Pin() |
|||
{ |
|||
this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); |
|||
this.Pointer = this.handle.AddrOfPinnedObject(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unpins <see cref="Array"/>.
|
|||
/// </summary>
|
|||
private void UnPin() |
|||
{ |
|||
if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.handle.Free(); |
|||
this.Pointer = IntPtr.Zero; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// <copyright file="PixelDataPool{T}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
/// <summary>
|
|||
/// Provides a resource pool that enables reusing instances of value type arrays <see cref="T:T[]"/>.
|
|||
/// <see cref="Rent(int)"/> will always return arrays initialized with 'default(T)'
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type.</typeparam>
|
|||
public static class PixelDataPool<T> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// The <see cref="ArrayPool{T}"/> used to pool data.
|
|||
/// </summary>
|
|||
private static readonly ArrayPool<T> ArrayPool = ArrayPool<T>.Create(CalculateMaxArrayLength(), 50); |
|||
|
|||
/// <summary>
|
|||
/// Rents the pixel array from the pool.
|
|||
/// </summary>
|
|||
/// <param name="minimumLength">The minimum length of the array to return.</param>
|
|||
/// <returns>The <see cref="T:TColor[]"/></returns>
|
|||
public static T[] Rent(int minimumLength) |
|||
{ |
|||
return ArrayPool.Rent(minimumLength); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the rented pixel array back to the pool.
|
|||
/// </summary>
|
|||
/// <param name="array">The array to return to the buffer pool.</param>
|
|||
public static void Return(T[] array) |
|||
{ |
|||
ArrayPool.Return(array, true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Heuristically calculates a reasonable maxArrayLength value for the backing <see cref="ArrayPool"/>.
|
|||
/// </summary>
|
|||
/// <returns>The maxArrayLength value</returns>
|
|||
internal static int CalculateMaxArrayLength() |
|||
{ |
|||
// ReSharper disable once SuspiciousTypeConversion.Global
|
|||
if (default(T) is IPixel) |
|||
{ |
|||
const int MaximumExpectedImageSize = 16384; |
|||
return MaximumExpectedImageSize * MaximumExpectedImageSize; |
|||
} |
|||
else |
|||
{ |
|||
return int.MaxValue; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
// <copyright file="PixelPool{TColor}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
/// <summary>
|
|||
/// Provides a resource pool that enables reusing instances of type <see cref="T:TColor[]"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TColor">The pixel format.</typeparam>
|
|||
public static class PixelPool<TColor> |
|||
where TColor : struct, IPixel<TColor> |
|||
{ |
|||
/// <summary>
|
|||
/// The <see cref="ArrayPool{T}"/> used to pool data. TODO: Choose sensible default size and count
|
|||
/// </summary>
|
|||
private static readonly ArrayPool<TColor> ArrayPool = ArrayPool<TColor>.Create(int.MaxValue, 50); |
|||
|
|||
/// <summary>
|
|||
/// Rents the pixel array from the pool.
|
|||
/// </summary>
|
|||
/// <param name="minimumLength">The minimum length of the array to return.</param>
|
|||
/// <returns>The <see cref="T:TColor[]"/></returns>
|
|||
public static TColor[] RentPixels(int minimumLength) |
|||
{ |
|||
return ArrayPool.Rent(minimumLength); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the rented pixel array back to the pool.
|
|||
/// </summary>
|
|||
/// <param name="array">The array to return to the buffer pool.</param>
|
|||
public static void ReturnPixels(TColor[] array) |
|||
{ |
|||
ArrayPool.Return(array, true); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace ImageSharp.Benchmarks.Color.Bulk |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using Color = ImageSharp.Color; |
|||
|
|||
/// <summary>
|
|||
/// Benchmark to measure the effect of using virtual bulk-copy calls inside PixelAccessor methods
|
|||
/// </summary>
|
|||
public unsafe class PixelAccessorVirtualCopy |
|||
{ |
|||
abstract class CopyExecutor |
|||
{ |
|||
internal abstract void VirtualCopy(BufferPointer<Color> destination, BufferPointer<byte> source, int count); |
|||
} |
|||
|
|||
class UnsafeCopyExecutor : CopyExecutor |
|||
{ |
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
internal override unsafe void VirtualCopy(BufferPointer<Color> destination, BufferPointer<byte> source, int count) |
|||
{ |
|||
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4); |
|||
} |
|||
} |
|||
|
|||
private PixelAccessor<Color> pixelAccessor; |
|||
|
|||
private PixelArea<Color> area; |
|||
|
|||
private CopyExecutor executor; |
|||
|
|||
[Params(64, 256, 512)] |
|||
public int Width { get; set; } |
|||
|
|||
public int Height { get; set; } = 256; |
|||
|
|||
|
|||
[Setup] |
|||
public void Setup() |
|||
{ |
|||
this.pixelAccessor = new PixelAccessor<ImageSharp.Color>(this.Width, this.Height); |
|||
this.area = new PixelArea<Color>(this.Width / 2, this.Height, ComponentOrder.Xyzw); |
|||
this.executor = new UnsafeCopyExecutor(); |
|||
} |
|||
|
|||
[Cleanup] |
|||
public void Cleanup() |
|||
{ |
|||
this.pixelAccessor.Dispose(); |
|||
this.area.Dispose(); |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void CopyRawUnsafeInlined() |
|||
{ |
|||
uint byteCount = (uint)this.area.Width * 4; |
|||
|
|||
int targetX = this.Width / 4; |
|||
int targetY = 0; |
|||
|
|||
for (int y = 0; y < this.Height; y++) |
|||
{ |
|||
byte* source = this.area.PixelBase + (y * this.area.RowStride); |
|||
byte* destination = this.GetRowPointer(targetX, targetY + y); |
|||
|
|||
Unsafe.CopyBlock(destination, source, byteCount); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void CopyBufferPointerUnsafeInlined() |
|||
{ |
|||
uint byteCount = (uint)this.area.Width * 4; |
|||
|
|||
int targetX = this.Width / 4; |
|||
int targetY = 0; |
|||
|
|||
for (int y = 0; y < this.Height; y++) |
|||
{ |
|||
BufferPointer<byte> source = this.GetAreaRow(y); |
|||
BufferPointer<Color> destination = this.GetPixelAccessorRow(targetX, targetY + y); |
|||
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void CopyBufferPointerUnsafeVirtual() |
|||
{ |
|||
int targetX = this.Width / 4; |
|||
int targetY = 0; |
|||
|
|||
for (int y = 0; y < this.Height; y++) |
|||
{ |
|||
BufferPointer<byte> source = this.GetAreaRow(y); |
|||
BufferPointer<Color> destination = this.GetPixelAccessorRow(targetX, targetY + y); |
|||
this.executor.VirtualCopy(destination, source, this.area.Width); |
|||
} |
|||
} |
|||
|
|||
private byte* GetRowPointer(int x, int y) |
|||
{ |
|||
return (byte*)this.pixelAccessor.DataPointer + (((y * this.pixelAccessor.Width) + x) * Unsafe.SizeOf<Color>()); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private BufferPointer<Color> GetPixelAccessorRow(int x, int y) |
|||
{ |
|||
return new BufferPointer<ImageSharp.Color>( |
|||
this.pixelAccessor.PixelBuffer, |
|||
(void*)this.pixelAccessor.DataPointer, |
|||
(y * this.pixelAccessor.Width) + x |
|||
); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private BufferPointer<byte> GetAreaRow(int y) |
|||
{ |
|||
return new BufferPointer<byte>(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
namespace ImageSharp.Benchmarks.General.Vectorization |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
public class BitwiseOrUInt32 |
|||
{ |
|||
private uint[] input; |
|||
|
|||
private uint[] result; |
|||
|
|||
[Params(32)] |
|||
public int InputSize { get; set; } |
|||
|
|||
private uint testValue; |
|||
|
|||
[Setup] |
|||
public void Setup() |
|||
{ |
|||
this.input = new uint[this.InputSize]; |
|||
this.result = new uint[this.InputSize]; |
|||
this.testValue = 42; |
|||
|
|||
for (int i = 0; i < this.InputSize; i++) |
|||
{ |
|||
this.input[i] = (uint) i; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Standard() |
|||
{ |
|||
uint v = this.testValue; |
|||
for (int i = 0; i < this.input.Length; i++) |
|||
{ |
|||
this.result[i] = this.input[i] | v; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Simd() |
|||
{ |
|||
Vector<uint> v = new Vector<uint>(this.testValue); |
|||
|
|||
for (int i = 0; i < this.input.Length; i+=Vector<uint>.Count) |
|||
{ |
|||
Vector<uint> a = new Vector<uint>(this.input, i); |
|||
a = Vector.BitwiseOr(a, v); |
|||
a.CopyTo(this.result, i); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
namespace ImageSharp.Benchmarks.General.Vectorization |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
public class DivFloat |
|||
{ |
|||
private float[] input; |
|||
|
|||
private float[] result; |
|||
|
|||
[Params(32)] |
|||
public int InputSize { get; set; } |
|||
|
|||
private float testValue; |
|||
|
|||
[Setup] |
|||
public void Setup() |
|||
{ |
|||
this.input = new float[this.InputSize]; |
|||
this.result = new float[this.InputSize]; |
|||
this.testValue = 42; |
|||
|
|||
for (int i = 0; i < this.InputSize; i++) |
|||
{ |
|||
this.input[i] = (uint)i; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Standard() |
|||
{ |
|||
float v = this.testValue; |
|||
for (int i = 0; i < this.input.Length; i++) |
|||
{ |
|||
this.result[i] = this.input[i] / v; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Simd() |
|||
{ |
|||
Vector<float> v = new Vector<float>(this.testValue); |
|||
|
|||
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count) |
|||
{ |
|||
Vector<float> a = new Vector<float>(this.input, i); |
|||
a = a / v; |
|||
a.CopyTo(this.result, i); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
namespace ImageSharp.Benchmarks.General.Vectorization |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
public class DivUInt32 |
|||
{ |
|||
private uint[] input; |
|||
|
|||
private uint[] result; |
|||
|
|||
[Params(32)] |
|||
public int InputSize { get; set; } |
|||
|
|||
private uint testValue; |
|||
|
|||
[Setup] |
|||
public void Setup() |
|||
{ |
|||
this.input = new uint[this.InputSize]; |
|||
this.result = new uint[this.InputSize]; |
|||
this.testValue = 42; |
|||
|
|||
for (int i = 0; i < this.InputSize; i++) |
|||
{ |
|||
this.input[i] = (uint)i; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Standard() |
|||
{ |
|||
uint v = this.testValue; |
|||
for (int i = 0; i < this.input.Length; i++) |
|||
{ |
|||
this.result[i] = this.input[i] / v; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Simd() |
|||
{ |
|||
Vector<uint> v = new Vector<uint>(this.testValue); |
|||
|
|||
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count) |
|||
{ |
|||
Vector<uint> a = new Vector<uint>(this.input, i); |
|||
a = a / v; |
|||
a.CopyTo(this.result, i); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
namespace ImageSharp.Benchmarks.General.Vectorization |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
public class MulFloat |
|||
{ |
|||
private float[] input; |
|||
|
|||
private float[] result; |
|||
|
|||
[Params(32)] |
|||
public int InputSize { get; set; } |
|||
|
|||
private float testValue; |
|||
|
|||
[Setup] |
|||
public void Setup() |
|||
{ |
|||
this.input = new float[this.InputSize]; |
|||
this.result = new float[this.InputSize]; |
|||
this.testValue = 42; |
|||
|
|||
for (int i = 0; i < this.InputSize; i++) |
|||
{ |
|||
this.input[i] = (uint)i; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Standard() |
|||
{ |
|||
float v = this.testValue; |
|||
for (int i = 0; i < this.input.Length; i++) |
|||
{ |
|||
this.result[i] = this.input[i] * v; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Simd() |
|||
{ |
|||
Vector<float> v = new Vector<float>(this.testValue); |
|||
|
|||
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count) |
|||
{ |
|||
Vector<float> a = new Vector<float>(this.input, i); |
|||
a = a * v; |
|||
a.CopyTo(this.result, i); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
namespace ImageSharp.Benchmarks.General.Vectorization |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
public class MulUInt32 |
|||
{ |
|||
private uint[] input; |
|||
|
|||
private uint[] result; |
|||
|
|||
[Params(32)] |
|||
public int InputSize { get; set; } |
|||
|
|||
private uint testValue; |
|||
|
|||
[Setup] |
|||
public void Setup() |
|||
{ |
|||
this.input = new uint[this.InputSize]; |
|||
this.result = new uint[this.InputSize]; |
|||
this.testValue = 42; |
|||
|
|||
for (int i = 0; i < this.InputSize; i++) |
|||
{ |
|||
this.input[i] = (uint)i; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Standard() |
|||
{ |
|||
uint v = this.testValue; |
|||
for (int i = 0; i < this.input.Length; i++) |
|||
{ |
|||
this.result[i] = this.input[i] * v; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Simd() |
|||
{ |
|||
Vector<uint> v = new Vector<uint>(this.testValue); |
|||
|
|||
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count) |
|||
{ |
|||
Vector<uint> a = new Vector<uint>(this.input, i); |
|||
a = a * v; |
|||
a.CopyTo(this.result, i); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
namespace ImageSharp.Benchmarks.General.Vectorization |
|||
{ |
|||
using System.Numerics; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
public class ReinterpretUInt32AsFloat |
|||
{ |
|||
private uint[] input; |
|||
|
|||
private float[] result; |
|||
|
|||
[Params(32)] |
|||
public int InputSize { get; set; } |
|||
|
|||
[StructLayout(LayoutKind.Explicit)] |
|||
struct UIntFloatUnion |
|||
{ |
|||
[FieldOffset(0)] |
|||
public float f; |
|||
|
|||
[FieldOffset(0)] |
|||
public uint i; |
|||
} |
|||
|
|||
|
|||
[Setup] |
|||
public void Setup() |
|||
{ |
|||
this.input = new uint[this.InputSize]; |
|||
this.result = new float[this.InputSize]; |
|||
|
|||
for (int i = 0; i < this.InputSize; i++) |
|||
{ |
|||
this.input[i] = (uint)i; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Standard() |
|||
{ |
|||
UIntFloatUnion u = default(UIntFloatUnion); |
|||
for (int i = 0; i < this.input.Length; i++) |
|||
{ |
|||
u.i = this.input[i]; |
|||
this.result[i] = u.f; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Simd() |
|||
{ |
|||
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count) |
|||
{ |
|||
Vector<uint> a = new Vector<uint>(this.input, i); |
|||
Vector<float> b = Vector.AsVectorSingle(a); |
|||
b.CopyTo(this.result, i); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,340 @@ |
|||
namespace ImageSharp.Tests.Colors |
|||
{ |
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
using Xunit; |
|||
|
|||
public class BulkPixelOperationsTests |
|||
{ |
|||
public class Color : BulkPixelOperationsTests<ImageSharp.Color> |
|||
{ |
|||
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class:
|
|||
public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 }; |
|||
} |
|||
|
|||
public class Argb : BulkPixelOperationsTests<ImageSharp.Argb> |
|||
{ |
|||
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class:
|
|||
public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 }; |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(1, 1, PixelTypes.All)] |
|||
public void GetGlobalInstance<TColor>(TestImageProvider<TColor> dummy) |
|||
where TColor:struct, IPixel<TColor> |
|||
{ |
|||
Assert.NotNull(BulkPixelOperations<TColor>.Instance); |
|||
} |
|||
} |
|||
|
|||
public abstract class BulkPixelOperationsTests<TColor> |
|||
where TColor : struct, IPixel<TColor> |
|||
{ |
|||
public static TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 }; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackFromVector4(int count) |
|||
{ |
|||
Vector4[] source = CreateVector4TestData(count); |
|||
TColor[] expected = new TColor[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
expected[i].PackFromVector4(source[i]); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.PackFromVector4(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackToVector4(int count) |
|||
{ |
|||
TColor[] source = CreatePixelTestData(count); |
|||
Vector4[] expected = new Vector4[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
expected[i] = source[i].ToVector4(); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.ToVector4(s, d, count) |
|||
); |
|||
} |
|||
|
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackFromXyzBytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count * 3); |
|||
TColor[] expected = new TColor[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i3 = i * 3; |
|||
|
|||
expected[i].PackFromBytes(source[i3 + 0], source[i3 + 1], source[i3 + 2], 255); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.PackFromXyzBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackToXyzBytes(int count) |
|||
{ |
|||
TColor[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count * 3]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i3 = i * 3; |
|||
source[i].ToXyzBytes(expected, i3); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.ToXyzBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackFromXyzwBytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count * 4); |
|||
TColor[] expected = new TColor[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i4 = i * 4; |
|||
|
|||
expected[i].PackFromBytes(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3]); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.PackFromXyzwBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackToXyzwBytes(int count) |
|||
{ |
|||
TColor[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count * 4]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i4 = i * 4; |
|||
source[i].ToXyzwBytes(expected, i4); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.ToXyzwBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackFromZyxBytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count * 3); |
|||
TColor[] expected = new TColor[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i3 = i * 3; |
|||
|
|||
expected[i].PackFromBytes(source[i3 + 2], source[i3 + 1], source[i3 + 0], 255); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.PackFromZyxBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackToZyxBytes(int count) |
|||
{ |
|||
TColor[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count * 3]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i3 = i * 3; |
|||
source[i].ToZyxBytes(expected, i3); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.ToZyxBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackFromZyxwBytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count * 4); |
|||
TColor[] expected = new TColor[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i4 = i * 4; |
|||
|
|||
expected[i].PackFromBytes(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3]); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.PackFromZyxwBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void PackToZyxwBytes(int count) |
|||
{ |
|||
TColor[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count * 4]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i4 = i * 4; |
|||
source[i].ToZyxwBytes(expected, i4); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(ops, s, d) => ops.ToZyxwBytes(s, d, count) |
|||
); |
|||
} |
|||
|
|||
|
|||
private class TestBuffers<TSource, TDest> : IDisposable |
|||
where TSource : struct |
|||
where TDest : struct |
|||
{ |
|||
public PinnedBuffer<TSource> SourceBuffer { get; } |
|||
public PinnedBuffer<TDest> ActualDestBuffer { get; } |
|||
public PinnedBuffer<TDest> ExpectedDestBuffer { get; } |
|||
|
|||
public BufferPointer<TSource> Source => this.SourceBuffer.Slice(); |
|||
public BufferPointer<TDest> ActualDest => this.ActualDestBuffer.Slice(); |
|||
|
|||
public TestBuffers(TSource[] source, TDest[] expectedDest) |
|||
{ |
|||
this.SourceBuffer = new PinnedBuffer<TSource>(source); |
|||
this.ExpectedDestBuffer = new PinnedBuffer<TDest>(expectedDest); |
|||
this.ActualDestBuffer = new PinnedBuffer<TDest>(expectedDest.Length); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.SourceBuffer.Dispose(); |
|||
this.ActualDestBuffer.Dispose(); |
|||
this.ExpectedDestBuffer.Dispose(); |
|||
} |
|||
|
|||
public void Verify() |
|||
{ |
|||
int count = this.ExpectedDestBuffer.Count; |
|||
TDest[] expected = this.ExpectedDestBuffer.Array; |
|||
TDest[] actual = this.ActualDestBuffer.Array; |
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
Assert.Equal(expected[i], actual[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void TestOperation<TSource, TDest>( |
|||
TSource[] source, |
|||
TDest[] expected, |
|||
Action<BulkPixelOperations<TColor>, BufferPointer<TSource>, BufferPointer<TDest>> action) |
|||
where TSource : struct |
|||
where TDest : struct |
|||
{ |
|||
using (var buffers = new TestBuffers<TSource, TDest>(source, expected)) |
|||
{ |
|||
action(BulkPixelOperations<TColor>.Instance, buffers.Source, buffers.ActualDest); |
|||
buffers.Verify(); |
|||
} |
|||
} |
|||
|
|||
private static Vector4[] CreateVector4TestData(int length) |
|||
{ |
|||
Vector4[] result = new Vector4[length]; |
|||
Random rnd = new Random(42); // Deterministic random values
|
|||
|
|||
for (int i = 0; i < result.Length; i++) |
|||
{ |
|||
result[i] = GetVector(rnd); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private static TColor[] CreatePixelTestData(int length) |
|||
{ |
|||
TColor[] result = new TColor[length]; |
|||
|
|||
Random rnd = new Random(42); // Deterministic random values
|
|||
|
|||
for (int i = 0; i < result.Length; i++) |
|||
{ |
|||
Vector4 v = GetVector(rnd); |
|||
result[i].PackFromVector4(v); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static byte[] CreateByteTestData(int length) |
|||
{ |
|||
byte[] result = new byte[length]; |
|||
Random rnd = new Random(42); // Deterministic random values
|
|||
|
|||
for (int i = 0; i < result.Length; i++) |
|||
{ |
|||
result[i] = (byte)rnd.Next(255); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private static Vector4 GetVector(Random rnd) |
|||
{ |
|||
return new Vector4( |
|||
(float)rnd.NextDouble(), |
|||
(float)rnd.NextDouble(), |
|||
(float)rnd.NextDouble(), |
|||
(float)rnd.NextDouble() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -1,83 +0,0 @@ |
|||
// ReSharper disable ObjectCreationAsStatement
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace ImageSharp.Tests.Common |
|||
{ |
|||
using System; |
|||
|
|||
using Xunit; |
|||
|
|||
public unsafe class ArrayPointerTests |
|||
{ |
|||
public struct Foo |
|||
{ |
|||
#pragma warning disable CS0414
|
|||
private int a; |
|||
|
|||
private double b; |
|||
#pragma warning restore CS0414
|
|||
|
|||
internal static Foo[] CreateArray(int size) |
|||
{ |
|||
Foo[] result = new Foo[size]; |
|||
for (int i = 0; i < size; i++) |
|||
{ |
|||
result[i] = new Foo() { a = i, b = i }; |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructWithoutOffset() |
|||
{ |
|||
Foo[] array = Foo.CreateArray(3); |
|||
fixed (Foo* p = array) |
|||
{ |
|||
// Act:
|
|||
ArrayPointer<Foo> ap = new ArrayPointer<Foo>(array, p); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(array, ap.Array); |
|||
Assert.Equal((IntPtr)p, ap.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructWithOffset() |
|||
{ |
|||
Foo[] array = Foo.CreateArray(3); |
|||
int offset = 2; |
|||
fixed (Foo* p = array) |
|||
{ |
|||
// Act:
|
|||
ArrayPointer<Foo> ap = new ArrayPointer<Foo>(array, p, offset); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(array, ap.Array); |
|||
Assert.Equal(offset, ap.Offset); |
|||
Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Slice() |
|||
{ |
|||
Foo[] array = Foo.CreateArray(5); |
|||
int offset0 = 2; |
|||
int offset1 = 2; |
|||
int totalOffset = offset0 + offset1; |
|||
fixed (Foo* p = array) |
|||
{ |
|||
ArrayPointer<Foo> ap = new ArrayPointer<Foo>(array, p, offset0); |
|||
|
|||
// Act:
|
|||
ap = ap.Slice(offset1); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(array, ap.Array); |
|||
Assert.Equal(totalOffset, ap.Offset); |
|||
Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
// ReSharper disable ObjectCreationAsStatement
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace ImageSharp.Tests.Common |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using Xunit; |
|||
|
|||
public unsafe class BufferPointerTests |
|||
{ |
|||
public struct Foo |
|||
{ |
|||
public int A; |
|||
|
|||
public double B; |
|||
|
|||
internal static Foo[] CreateArray(int size) |
|||
{ |
|||
Foo[] result = new Foo[size]; |
|||
for (int i = 0; i < size; i++) |
|||
{ |
|||
result[i] = new Foo() { A = i, B = i }; |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructWithoutOffset() |
|||
{ |
|||
Foo[] array = Foo.CreateArray(3); |
|||
fixed (Foo* p = array) |
|||
{ |
|||
// Act:
|
|||
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(array, ap.Array); |
|||
Assert.Equal((IntPtr)p, ap.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructWithOffset() |
|||
{ |
|||
Foo[] array = Foo.CreateArray(3); |
|||
int offset = 2; |
|||
fixed (Foo* p = array) |
|||
{ |
|||
// Act:
|
|||
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(array, ap.Array); |
|||
Assert.Equal(offset, ap.Offset); |
|||
Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Slice() |
|||
{ |
|||
Foo[] array = Foo.CreateArray(5); |
|||
int offset0 = 2; |
|||
int offset1 = 2; |
|||
int totalOffset = offset0 + offset1; |
|||
fixed (Foo* p = array) |
|||
{ |
|||
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset0); |
|||
|
|||
// Act:
|
|||
ap = ap.Slice(offset1); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(array, ap.Array); |
|||
Assert.Equal(totalOffset, ap.Offset); |
|||
Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
public class Copy |
|||
{ |
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void GenericToOwnType(int count) |
|||
{ |
|||
Foo[] source = Foo.CreateArray(count + 2); |
|||
Foo[] dest = new Foo[count + 5]; |
|||
|
|||
fixed (Foo* pSource = source) |
|||
fixed (Foo* pDest = dest) |
|||
{ |
|||
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource); |
|||
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest); |
|||
|
|||
BufferPointer.Copy(apSource, apDest, count); |
|||
} |
|||
|
|||
Assert.Equal(source[0], dest[0]); |
|||
Assert.Equal(source[count-1], dest[count-1]); |
|||
Assert.NotEqual(source[count], dest[count]); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void GenericToBytes(int count) |
|||
{ |
|||
int destCount = count * sizeof(Foo); |
|||
Foo[] source = Foo.CreateArray(count + 2); |
|||
byte[] dest = new byte[destCount + sizeof(Foo) + 1]; |
|||
|
|||
fixed (Foo* pSource = source) |
|||
fixed (byte* pDest = dest) |
|||
{ |
|||
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource); |
|||
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest); |
|||
|
|||
BufferPointer.Copy(apSource, apDest, count); |
|||
} |
|||
|
|||
Assert.True(ElementsAreEqual(source, dest, 0)); |
|||
Assert.True(ElementsAreEqual(source, dest, count - 1)); |
|||
Assert.False(ElementsAreEqual(source, dest, count)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void BytesToGeneric(int count) |
|||
{ |
|||
int destCount = count * sizeof(Foo); |
|||
byte[] source = new byte[destCount + sizeof(Foo) + 1]; |
|||
Foo[] dest = Foo.CreateArray(count + 2); |
|||
|
|||
fixed(byte* pSource = source) |
|||
fixed (Foo* pDest = dest) |
|||
{ |
|||
BufferPointer<byte> apSource = new BufferPointer<byte>(source, pSource); |
|||
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest); |
|||
|
|||
BufferPointer.Copy(apSource, apDest, count); |
|||
} |
|||
|
|||
Assert.True(ElementsAreEqual(dest, source, 0)); |
|||
Assert.True(ElementsAreEqual(dest, source, count - 1)); |
|||
Assert.False(ElementsAreEqual(dest, source, count)); |
|||
} |
|||
|
|||
private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) |
|||
{ |
|||
fixed (Foo* pArray = array) |
|||
fixed (byte* pRaw = rawArray) |
|||
{ |
|||
Foo* pCasted = (Foo*)pRaw; |
|||
|
|||
Foo val1 = pArray[index]; |
|||
Foo val2 = pCasted[index]; |
|||
|
|||
return val1.Equals(val2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
namespace ImageSharp.Tests.Common |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using Xunit; |
|||
|
|||
public unsafe class PinnedBufferTests |
|||
{ |
|||
public struct Foo |
|||
{ |
|||
public int A; |
|||
|
|||
public double B; |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(42)] |
|||
[InlineData(1111)] |
|||
public void ConstructWithOwnArray(int count) |
|||
{ |
|||
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(count)) |
|||
{ |
|||
Assert.False(buffer.IsDisposedOrLostArrayOwnership); |
|||
Assert.NotNull(buffer.Array); |
|||
Assert.Equal(count, buffer.Count); |
|||
Assert.True(buffer.Array.Length >= count); |
|||
|
|||
VerifyPointer(buffer); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(42)] |
|||
[InlineData(1111)] |
|||
public void ConstructWithExistingArray(int count) |
|||
{ |
|||
Foo[] array = new Foo[count]; |
|||
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(array)) |
|||
{ |
|||
Assert.False(buffer.IsDisposedOrLostArrayOwnership); |
|||
Assert.Equal(array, buffer.Array); |
|||
Assert.Equal(count, buffer.Count); |
|||
|
|||
VerifyPointer(buffer); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Dispose() |
|||
{ |
|||
PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42); |
|||
buffer.Dispose(); |
|||
|
|||
Assert.True(buffer.IsDisposedOrLostArrayOwnership); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Slice() |
|||
{ |
|||
Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; |
|||
|
|||
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a)) |
|||
{ |
|||
var arrayPtr = buffer.Slice(); |
|||
|
|||
Assert.Equal(a, arrayPtr.Array); |
|||
Assert.Equal(0, arrayPtr.Offset); |
|||
Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void UnPinAndTakeArrayOwnership() |
|||
{ |
|||
Foo[] data = null; |
|||
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42)) |
|||
{ |
|||
data = buffer.UnPinAndTakeArrayOwnership(); |
|||
Assert.True(buffer.IsDisposedOrLostArrayOwnership); |
|||
} |
|||
|
|||
Assert.NotNull(data); |
|||
Assert.True(data.Length >= 42); |
|||
} |
|||
|
|||
private static void VerifyPointer(PinnedBuffer<Foo> buffer) |
|||
{ |
|||
IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]); |
|||
Assert.Equal(ptr, buffer.Pointer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
// <copyright file="PixelDataPoolTests.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Tests |
|||
{ |
|||
using System.Linq; |
|||
|
|||
using Xunit; |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="PixelDataPool{T}"/> class.
|
|||
/// </summary>
|
|||
public class PixelDataPoolTests |
|||
{ |
|||
[Fact] |
|||
public void PixelDataPoolRentsMinimumSize() |
|||
{ |
|||
Color[] pixels = PixelDataPool<Color>.Rent(1024); |
|||
|
|||
Assert.True(pixels.Length >= 1024); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PixelDataPoolRentsEmptyArray() |
|||
{ |
|||
for (int i = 16; i < 1024; i += 16) |
|||
{ |
|||
Color[] pixels = PixelDataPool<Color>.Rent(i); |
|||
|
|||
Assert.True(pixels.All(p => p == default(Color))); |
|||
|
|||
PixelDataPool<Color>.Return(pixels); |
|||
} |
|||
|
|||
for (int i = 16; i < 1024; i += 16) |
|||
{ |
|||
Color[] pixels = PixelDataPool<Color>.Rent(i); |
|||
|
|||
Assert.True(pixels.All(p => p == default(Color))); |
|||
|
|||
PixelDataPool<Color>.Return(pixels); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() |
|||
{ |
|||
Color[] pixels = new Color[1024]; |
|||
|
|||
PixelDataPool<Color>.Return(pixels); |
|||
|
|||
Assert.True(pixels.Length >= 1024); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PixelDataPoolCleansRentedArray() |
|||
{ |
|||
Color[] pixels = PixelDataPool<Color>.Rent(256); |
|||
|
|||
for (int i = 0; i < pixels.Length; i++) |
|||
{ |
|||
pixels[i] = Color.Azure; |
|||
} |
|||
|
|||
Assert.True(pixels.All(p => p == Color.Azure)); |
|||
|
|||
PixelDataPool<Color>.Return(pixels); |
|||
|
|||
Assert.True(pixels.All(p => p == default(Color))); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false)] |
|||
[InlineData(true)] |
|||
public void CalculateMaxArrayLength(bool isRawData) |
|||
{ |
|||
int max = isRawData ? PixelDataPool<int>.CalculateMaxArrayLength() |
|||
: PixelDataPool<Color>.CalculateMaxArrayLength(); |
|||
|
|||
Assert.Equal(max < int.MaxValue, !isRawData); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RentNonIPixelData() |
|||
{ |
|||
byte[] data = PixelDataPool<byte>.Rent(16384); |
|||
|
|||
Assert.True(data.Length >= 16384); |
|||
} |
|||
} |
|||
} |
|||
@ -1,74 +0,0 @@ |
|||
// <copyright file="PixelPoolTests.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Tests |
|||
{ |
|||
using System.Linq; |
|||
|
|||
using Xunit; |
|||
|
|||
/// <summary>
|
|||
/// Tests the <see cref="PixelAccessor"/> class.
|
|||
/// </summary>
|
|||
public class PixelPoolTests |
|||
{ |
|||
[Fact] |
|||
public void PixelPoolRentsMinimumSize() |
|||
{ |
|||
Color[] pixels = PixelPool<Color>.RentPixels(1024); |
|||
|
|||
Assert.True(pixels.Length >= 1024); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PixelPoolRentsEmptyArray() |
|||
{ |
|||
for (int i = 16; i < 1024; i += 16) |
|||
{ |
|||
Color[] pixels = PixelPool<Color>.RentPixels(i); |
|||
|
|||
Assert.True(pixels.All(p => p == default(Color))); |
|||
|
|||
PixelPool<Color>.ReturnPixels(pixels); |
|||
} |
|||
|
|||
for (int i = 16; i < 1024; i += 16) |
|||
{ |
|||
Color[] pixels = PixelPool<Color>.RentPixels(i); |
|||
|
|||
Assert.True(pixels.All(p => p == default(Color))); |
|||
|
|||
PixelPool<Color>.ReturnPixels(pixels); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void PixelPoolDoesNotThrowWhenReturningNonPooled() |
|||
{ |
|||
Color[] pixels = new Color[1024]; |
|||
|
|||
PixelPool<Color>.ReturnPixels(pixels); |
|||
|
|||
Assert.True(pixels.Length >= 1024); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PixelPoolCleansRentedArray() |
|||
{ |
|||
Color[] pixels = PixelPool<Color>.RentPixels(256); |
|||
|
|||
for (int i = 0; i < pixels.Length; i++) |
|||
{ |
|||
pixels[i] = Color.Azure; |
|||
} |
|||
|
|||
Assert.True(pixels.All(p => p == Color.Azure)); |
|||
|
|||
PixelPool<Color>.ReturnPixels(pixels); |
|||
|
|||
Assert.True(pixels.All(p => p == default(Color))); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue