ImGuiNET / ImGui.NET

An ImGui wrapper for .NET.
MIT License
1.89k stars 306 forks source link

ImVector types #48

Closed xposure closed 6 years ago

xposure commented 6 years ago

Since as far as I know this isn't possible...

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct ImVector<T>
        where T: struct
    {
        public int Size;
        public int Capacity;
        ///<summary>T* Data</summary>
        public T* Data;
    }

Would there be consideration to clean some of these up with something like T4 Text Templates and auto generate a series of ImVectors as something like ...

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct ImVector
    {
        public int Size;
        public int Capacity;
        ///<summary>DrawVert* Data</summary>
        public DrawVert* Data;
    }

Could even implement something like...

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct ImVector
    {
        public int Size;
        public int Capacity;
        ///<summary>T* Data</summary>
        public DrawVert* Data;

        public ref DrawVert this[int index]
        {
            get
            {
                ////could save a lot of protected memory errors
                //if(index <= 0 || index >= Size)
                //    throw new ArgumentOutOfRangeException(nameof(index));
                return ref Data[index];
            }
        }
    }

I'm not sure on the level of where you want to take this project if its simply just "its good enough" then thats fine and I can start work in my own fork to tidy things up. I think this sort of goes with this issue https://github.com/mellinoe/ImGui.NET/issues/23 though and would be the start in implementing more friendly things.

EDIT: I had a bit of a hassle using this project (and some others last time) due to inconsistencies between the projects and in the end I ported ImGui to straight .NET here https://github.com/xposure/ImGuiSharp this didn't feel well received so I haven't expanded on it. Now I'm on a new project and circling back around to this.

mellinoe commented 6 years ago

I'd rather do something like this:

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct ImVector<T> where T : struct
    {
        private static readonly int s_sizeofT = Unsafe.SizeOf<T>();

        public int Size;
        public int Capacity;
        ///<summary>T* Data</summary>
        public void* Data;

        public ref T this[int index]
        {
            get
            {
                if (index < 0 || index >= Size)
                {
                    throw new ArgumentOutOfRangeException(nameof(index));
                }
                return ref Unsafe.AsRef<T>((byte*)Data + (s_sizeofT * index));
            }
        }
    }

When I originally wrote this stuff, I don't think ref returns, or the Unsafe library existed yet.

This gets you typed access to the contents, and we don't need to blow up the number of types in the library.

xposure commented 6 years ago

I'm not familiar with Unsafe so I'll look in to that. My only concern here is the extra level of indirection being added. I assume the JIT in release mode would inline the indexer out but if I remember right this can be tricky with generics and adding a function call inside here would add to the overhead of the call.

PS Yes, return ref has given some of my code syntactical sugar while keeping performance up, I love it. It also has a lot of other benefits as well.

mellinoe commented 6 years ago

I wouldn't worry too much about the overhead of it. I don't think there's much that can be done to reduce it, and essentially all that is being done is re-interpreting a pointer as a managed reference. It doesn't get much simpler than that. I think the indexer call is unavoidable.

BTW, what are you interested in using this for? Generally the stuff contained in ImVectors is internal or otherwise not that interesting. Usually you don't need to poke around in them.

xposure commented 6 years ago

I've been working on an editor of sorts and seeing how you have recently started bringing in some of the docking code from imgui, it brought me back to here. I'm not directly trying to use it as of now, but this project can be a bit of a head ache to get setup outside of the example in another ecosystem. I have experience in this area but I know some don't and I thought if I could help simplify some of this stuff it would help others get setup quicker.

For example, creating a DrawVert ImVector would ease the readability of the sample code for people not experienced with pointers. DrawVert is something you really have to access here because you need to push something to the GPU. I know its a one time setup, but was just looking to lower the entry for some.

mellinoe commented 6 years ago

You just need the pointer and the data size to push to the GPU, though -- you don't need to iterate through individual elements. We can definitely add this since it's not going to hurt anything, but I think it will still be an advanced scenario to read individual elements of an ImVector.

mellinoe commented 6 years ago

you have recently started bringing in some of the docking code from imgui

I'm using the docking code from Lumix Engine in the editor for my own engine and it is working well. I've been meaning to revamp the sample code here and to provide the docking stuff as an extra helper, but lots of stuff has gotten in the way of that...

xposure commented 6 years ago

You just need the pointer and the data size to push to the GPU, though -- you don't need to iterate through individual elements. We can definitely add this since it's not going to hurt anything, but I think it will still be an advanced scenario to read individual elements of an ImVector.

There is currently no way to submit a pointer of data in MonoGame, it expects an array of T. Now that you have shown me Unsafe, I imagine there might be a way. Going to mess with this later tonight.

I'm using the docking code from Lumix Engine in the editor for my own engine and it is working well. I've been meaning to revamp the sample code here and to provide the docking stuff as an extra helper, but lots of stuff has gotten in the way of that...

Are you using this in .NET? I seen the project but never considered to check for .NET bindings.

mellinoe commented 6 years ago

There is currently no way to submit a pointer of data in MonoGame, it expects an array of T. Now that you have shown me Unsafe, I imagine there might be a way. Going to mess with this later tonight.

Seems like a major hole in the API to me, but I'm not familiar with MonoGame. There might be another way to do it.

Are you using this in .NET? I seen the project but never considered to check for .NET bindings.

Yes, I've converted the code to C#. https://gist.github.com/mellinoe/4b8f8886d864d1d1eb00ec5f69c465a5

dmitsuki commented 6 years ago

Hi Mellinoe, do you have a basic usage example of that imguidock class? If not that's fine, I will just have to figure it out myself.

mellinoe commented 6 years ago

@dmitsuki I don't have anything public, no. It's fairly simple, though:

DockContext.RootDock(Vector2.Zero, ImGui.GetIO().DisplaySize);
if (DockContext.BeginDock("Panel A", WindowFlags.Default, new Vector2(500, 300)))
{
    // Draw Stuff
    DockContext.EndDock();
}
if (DockContext.BeginDock("Panel B", WindowFlags.Default, new Vector2(500, 300)))
{
    // Draw Other Stuff
    DockContext.EndDock();
}
hypeartist commented 6 years ago

@xposure

Since as far as I know this isn't possible...

Anything like this, maybe?

    public unsafe struct PodPointer<T> where T : struct
    {
        // ReSharper disable once InconsistentNaming
        private static readonly int TSize = Unsafe.SizeOf<T>();

        private readonly byte* _pointer;

        public PodPointer(void* unsafePtr)
        {
            _pointer = (byte*) unsafePtr;
        }

        public ref T this[int pos]
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => ref Unsafe.AsRef<T>(_pointer + pos * TSize);
        }

        public ref T Ref
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => ref Unsafe.AsRef<T>(_pointer);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Fill(T value, int length)
        {
            var l = length;
            var p = _pointer;
            do
            {
                Unsafe.Write(p, value);
                p += TSize;
            } while (--l != 0);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ref TOther AsRef<TOther>() => ref Unsafe.AsRef<TOther>(_pointer);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public PodPointer<TOther> AsPtr<TOther>() where TOther : struct => new PodPointer<TOther>(_pointer);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static PodPointer<T> operator ++(PodPointer<T> p) => new PodPointer<T>(p._pointer + TSize);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static PodPointer<T> operator --(PodPointer<T> p) => new PodPointer<T>(p._pointer - TSize);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static PodPointer<T> operator +(PodPointer<T> p, int o) => new PodPointer<T>(p._pointer + o * TSize);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static PodPointer<T> operator -(PodPointer<T> p, int o) => new PodPointer<T>(p._pointer - o * TSize);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator <(PodPointer<T> p1, PodPointer<T> p2) => p1._pointer < p2._pointer;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator <=(PodPointer<T> p1, PodPointer<T> p2) => p1._pointer <= p2._pointer;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator >=(PodPointer<T> p1, PodPointer<T> p2) => p1._pointer >= p2._pointer;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator >(PodPointer<T> p1, PodPointer<T> p2) => p1._pointer > p2._pointer;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator ==(PodPointer<T> p1, PodPointer<T> p2) => p1._pointer == p2._pointer;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator !=(PodPointer<T> p1, PodPointer<T> p2) => !(p1 == p2);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static long operator -(PodPointer<T> p1, PodPointer<T> p2) => (p1._pointer - p2._pointer) / TSize;

        public static implicit operator void*(PodPointer<T> p) => p._pointer;

        public static implicit operator IntPtr(PodPointer<T> p) => (IntPtr) p._pointer;
    }
mellinoe commented 6 years ago

I will likely make a breaking change for this improvement at the next native version bump.

mellinoe commented 6 years ago

The new 1.65.0 version has a significantly better abstraction for ImVector which gives you type-safe by-ref access to native structures.

https://github.com/mellinoe/ImGui.NET/blob/master/src/ImGui.NET/ImVector.cs