Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(ReadOnly)SpanView<T> and (ReadOnly)MemoryView<T> #24

Open
DaZombieKiller opened this issue Jan 12, 2021 · 2 comments
Open

(ReadOnly)SpanView<T> and (ReadOnly)MemoryView<T> #24

DaZombieKiller opened this issue Jan 12, 2021 · 2 comments
Labels
feature request 📬 A request for new changes to improve functionality high-performance 🚂 Issues/PRs for the HighPerformance package

Comments

@DaZombieKiller
Copy link

Describe the problem this feature would solve

Currently you cannot conveniently manipulate strided memory via Memory<T> and Span<T>, as they have no support for stride. While it may soon be possible to manually create instances of (ReadOnly)RefEnumerable<T> (via CommunityToolkit/WindowsCommunityToolkit#3645), they cannot always be used for this purpose as they operate in terms of T and not byte.

Describe the solution

These four new types (SpanView<T>, ReadOnlySpanView<T>, MemoryView<T> and ReadOnlyMemoryView<T>) provide a solution to this problem by providing a strided "view" over an existing (ReadOnly)Span<T> or (ReadOnly)Memory<T>'s data. This allows safe and convenient access to strided data, such as interleaved mesh data buffers:

// ReadOnly variants omitted for brevity.
namespace Microsoft.Toolkit.HighPerformance.Memory
{
    public readonly ref struct SpanView<T>
        where T : unmanaged
    {
        public int Length { get; }
        public int Stride { get; }
        public bool IsEmpty { get; }
        public ref T this[int index] { get; }

        public static SpanView<T> Empty { get; }

        public static SpanView<T> DangerousCreate<TBuffer>(Span<TBuffer> buffer, ref T field) where TBuffer : unmanaged;
        public static SpanView<T> DangerousCreate<TBuffer>(Span<TBuffer> buffer, int offset) where TBuffer : unmanaged;

        public static implicit operator SpanView<T>(Span<T> span);
        public static bool operator ==(SpanView<T> left, SpanView<T> right);
        public static bool operator !=(SpanView<T> left, SpanView<T> right);

        public unsafe SpanView(void* pointer, int stride, int length);
        public SpanView(Span<byte> span, int offset, int stride);

        public Enumerator GetEnumerator();
        public SpanView<T> Slice(int start);
        public SpanView<T> Slice(int start, int length);
        public void Clear();
        public void Fill(T value);
        public void CopyFrom(ReadOnlySpan<T> source);
        public bool TryCopyFrom(ReadOnlySpan<T> source);
        public void CopyFrom(ReadOnlySpanView<T> source);
        public bool TryCopyFrom(ReadOnlySpanView<T> source);
        public void CopyTo(SpanView<T> destination);
        public bool TryCopyTo(SpanView<T> destination);
        public void CopyTo(Span<T> destination);
        public bool TryCopyTo(Span<T> destination);
        public ref T DangerousGetReference();
        public ref T DangerousGetReferenceAt(int index);
        public ref T GetPinnableReference();
        public bool Equals(SpanView<T> other);
        public override bool Equals(object obj); // NotSupportedException
        public override int GetHashCode(); // NotSupportedException
        public override string ToString();
        public T[] ToArray();

        public ref struct Enumerator
        {
            public ref T Current { get; }
            public bool MoveNext();
        }
    }

    public readonly struct MemoryView<T> : IEquatable<MemoryView<T>>
        where T : unmanaged
    {
        public int Length { get; }
        public int Stride { get; }
        public bool IsEmpty { get; }
        public SpanView<T> SpanView { get; }

        public static MemoryView<T> Empty { get; }

        public static MemoryView<T> DangerousCreate<TBuffer>(Memory<TBuffer> buffer, ref T field) where TBuffer : unmanaged;
        public static MemoryView<T> DangerousCreate<TBuffer>(Memory<TBuffer> buffer, int offset) where TBuffer : unmanaged;

        public static implicit operator MemoryView<T>(Memory<T> span);
        public static bool operator ==(MemoryView<T> left, MemoryView<T> right);
        public static bool operator !=(MemoryView<T> left, MemoryView<T> right);

        public MemoryView(Memory<byte> memory, int offset, int stride);

        public MemoryView<T> Slice(int start);
        public MemoryView<T> Slice(int start, int length);
        public void CopyTo(MemoryView<T> destination);
        public bool TryCopyTo(MemoryView<T> destination);
        public void CopyTo(Memory<T> destination);
        public bool TryCopyTo(Memory<T> destination);
        public bool Equals(MemoryView<T> other);
        public override bool Equals(object obj);
        public override int GetHashCode();
        public override string ToString();
        public T[] ToArray();
        public MemoryHandle Pin();
    }

    public static class MemoryViewMarshal
    {
        public static Memory<byte> GetMemory<T>(MemoryView<T> view) where T : unmanaged;
        public static ReadOnlyMemory<byte> GetMemory<T>(ReadOnlyMemoryView<T> view) where T : unmanaged;
        public static Span<byte> GetSpan<T>(SpanView<T> view) where T : unmanaged;
        public static ReadOnlySpan<byte> GetSpan<T>(ReadOnlySpanView<T> view) where T : unmanaged;
        
        public static MemoryView<TTo> Cast<TFrom, TTo>(MemoryView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;

        public static ReadOnlyMemoryView<TTo> Cast<TFrom, TTo>(ReadOnlyMemoryView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;

        public static SpanView<TTo> Cast<TFrom, TTo>(SpanView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;

        public static ReadOnlySpanView<TTo> Cast<TFrom, TTo>(ReadOnlySpanView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;
    }
}

Using the proposed API, the following code can be written:

// An interleaved vertex buffer for uploading mesh data to the GPU
struct Vertex
{
    public Vector3 Position;
    public Vector3 Normal;
    public Vector2 TexCoord;
}

// Allocate the vertex buffer
var vertices = new Span<Vertex>(new Vertex[100]);

// Create views over the fields
var positions = SpanView<Vector3>.DangerousCreate(vertices, ref vertices[0].Position);
var normals   = SpanView<Vector3>.DangerousCreate(vertices, ref vertices[0].Normal);
var coords    = SpanView<Vector2>.DangerousCreate(vertices, ref vertices[0].TexCoord);

// The interleaved data can now be iterated over field-wise
foreach (ref Vector3 position in positions)
{
    position += new Vector3(0, 1, 0);
}

A sample implementation of these types can be found here. Note that there are several optimizations and improvements that can be made (especially as this was written for .NET Standard 2.0). These are only intended to serve as a reference.

Unanswered questions

  • Can/should the unmanaged constraint be removed from the API? Currently it is necessitated by the use of (ReadOnly)Span<byte>.
  • How beneficial is the existence of MemoryViewMarshal?

Describe alternatives you've considered

Previously I proposed CommunityToolkit/WindowsCommunityToolkit#3641 to allow manual creation of (ReadOnly)RefEnumerable<T>, but it was found to be unsuitable for many of the scenarios these types are intended to solve.

@ghost
Copy link

ghost commented Jan 12, 2021

Hello, 'DaZombieKiller! Thanks for submitting a new feature request. I've automatically added a vote 👍 reaction to help get things started. Other community members can vote to help us prioritize this feature in the future!

@michael-hawker
Copy link
Member

FYI @Sergio0694

@Sergio0694 Sergio0694 transferred this issue from CommunityToolkit/WindowsCommunityToolkit Nov 11, 2021
@Sergio0694 Sergio0694 changed the title [Feature, HighPerformance] (ReadOnly)SpanView<T> and (ReadOnly)MemoryView<T> (ReadOnly)SpanView<T> and (ReadOnly)MemoryView<T> Nov 11, 2021
@Sergio0694 Sergio0694 added feature request 📬 A request for new changes to improve functionality high-performance 🚂 Issues/PRs for the HighPerformance package labels Nov 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request 📬 A request for new changes to improve functionality high-performance 🚂 Issues/PRs for the HighPerformance package
Projects
None yet
Development

No branches or pull requests

3 participants