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
///