diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs index 794339fb..448d0368 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics.Contracts; using System.IO; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Memory; using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream; namespace Microsoft.Toolkit.HighPerformance.Extensions @@ -15,6 +16,50 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions /// public static class MemoryExtensions { + /// + /// Returns a instance wrapping the underlying data for the given instance. + /// + /// The type of items in the input instance. + /// The input instance. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// The resulting instance. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory2D AsMemory2D(this Memory memory, int height, int width) + { + return new Memory2D(memory, height, width); + } + + /// + /// Returns a instance wrapping the underlying data for the given instance. + /// + /// The type of items in the input instance. + /// The input instance. + /// The initial offset within . + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// The pitch in the resulting 2D area. + /// The resulting instance. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory2D AsMemory2D(this Memory memory, int offset, int height, int width, int pitch) + { + return new Memory2D(memory, offset, height, width, pitch); + } + /// /// Returns a wrapping the contents of the given of instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 3b77481e..1b6f8bff 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -382,6 +382,118 @@ namespace Microsoft.Toolkit.HighPerformance.Memory this.width = width; this.pitch = pitch; } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. + /// + /// The total area must match the lenght of . + internal Memory2D(Memory memory, int height, int width) + : this(memory, 0, height, width, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The initial offset within . + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + internal Memory2D(Memory memory, int offset, int height, int width, int pitch) + { + if ((uint)offset > (uint)memory.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = memory.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + // Check if the Memory instance wraps a string. This is possible in case + // consumers do an unsafe cast for the entire Memory object, and while not + // really safe it is still supported in CoreCLR too, so we're following suit here. + if (typeof(T) == typeof(char) && + MemoryMarshal.TryGetString(Unsafe.As, Memory>(ref memory), out string? text, out int textStart, out _)) + { + ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset); + + this.instance = text; + this.offset = text.DangerousGetObjectDataByteOffset(ref r0); + } + else if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + // Check if the input Memory instance wraps an array we can access. + // This is fine, since Memory on its own doesn't control the lifetime + // of the underlying array anyway, and this Memory2D type would do the same. + // Using the array directly makes retrieving a Span2D faster down the line, + // as we no longer have to jump through the boxed Memory first anymore. + T[] array = segment.Array!; + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset)); + } + else if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int memoryManagerStart, out _)) + { + this.instance = memoryManager; + + unsafe + { + this.offset = (IntPtr)(void*)(uint)(memoryManagerStart + offset); + } + } + else + { + ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); + + this.instance = null; + this.offset = default; + } + + this.height = height; + this.width = width; + this.pitch = pitch; + } #endif ///