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