ImGuiNET / ImGui.NET

An ImGui wrapper for .NET.
MIT License
1.91k stars 307 forks source link

Help needed with setting ImGui viewports #236

Open ScottKane opened 3 years ago

ScottKane commented 3 years ago

I'm currently working with Silk.NET, they have extensions to use ImGui.NET with their bindings based on your ImGuiController, I think they based it on the Tkinter version. I'm using an updated version from a PR (https://github.com/Ultz/SilkExtensions/pull/3). Here is what I'm working with (commented out my best guess at what I need to do based on your docking branch):

using ImGuiNET;
using Silk.NET.Input;
using Silk.NET.Input.Extensions;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

public class ImGuiController : IDisposable
{
    private GL _gl;
    private IView _view;
    private IInputContext _input;
    private bool _frameBegun;

    private uint _vertexArray;
    private uint _vertexBuffer;
    private uint _vertexBufferSize;
    private uint _indexBuffer;
    private uint _indexBufferSize;
    private Version _glVersion;

    private Texture _fontTexture;
    private Shader _shader;

    private int _windowWidth;
    private int _windowHeight;

    private Vector2 _scaleFactor = Vector2.One;

    private MouseState _prevMouseState;
    private readonly List<char> _pressedKeys = new List<char>();
    private IKeyboard _keyboard;

    // private readonly Platform_CreateWindow _createWindow;
    // private readonly Platform_DestroyWindow _destroyWindow;
    // private readonly Platform_GetWindowPos _getWindowPos;
    // private readonly Platform_ShowWindow _showWindow;
    // private readonly Platform_SetWindowPos _setWindowPos;
    // private readonly Platform_SetWindowSize _setWindowSize;
    // private readonly Platform_GetWindowSize _getWindowSize;
    // private readonly Platform_SetWindowFocus _setWindowFocus;
    // private readonly Platform_GetWindowFocus _getWindowFocus;
    // private readonly Platform_GetWindowMinimized _getWindowMinimized;
    // private readonly Platform_SetWindowTitle _setWindowTitle;

    public ImGuiController(GL gl, IView view, IInputContext input)
    {
        Init(gl, view, input);

        var io = ImGuiNET.ImGui.GetIO();

        io.Fonts.AddFontDefault();
        // io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
        // io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;

        // var platformIO = ImGuiNET.ImGui.GetPlatformIO();
        // var mainViewport = platformIO.MainViewport;
        // mainViewport.PlatformHandle = view.Handle;

        // io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
        // io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
        // io.BackendFlags |= ImGuiBackendFlags.PlatformHasViewports;
        // io.BackendFlags |= ImGuiBackendFlags.RendererHasViewports;
        io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;

        // var handle = GCHandle.Alloc(view);
        // view.Resize += (size) => mainViewport.PlatformRequestResize = true;
        // //view.Moved += p => mainViewport.PlatformRequestMove = true;
        // view.Closing += () => mainViewport.PlatformRequestClose = true;

        // mainViewport.PlatformUserData = (IntPtr)handle;

        // _createWindow = CreateWindow;
        // _destroyWindow = DestroyWindow;
        // _getWindowPos = GetWindowPos;
        // _showWindow = ShowWindow;
        // _setWindowPos = SetWindowPos;
        // _setWindowSize = SetWindowSize;
        // _getWindowSize = GetWindowSize;
        // _setWindowFocus = SetWindowFocus;
        // _getWindowFocus = GetWindowFocus;
        // _getWindowMinimized = GetWindowMinimized;
        // _setWindowTitle = SetWindowTitle;

        // platformIO.Platform_CreateWindow = Marshal.GetFunctionPointerForDelegate(_createWindow);
        // platformIO.Platform_DestroyWindow = Marshal.GetFunctionPointerForDelegate(_destroyWindow);
        // platformIO.Platform_ShowWindow = Marshal.GetFunctionPointerForDelegate(_showWindow);
        // platformIO.Platform_SetWindowPos = Marshal.GetFunctionPointerForDelegate(_setWindowPos);
        // platformIO.Platform_SetWindowSize = Marshal.GetFunctionPointerForDelegate(_setWindowSize);
        // platformIO.Platform_SetWindowFocus = Marshal.GetFunctionPointerForDelegate(_setWindowFocus);
        // platformIO.Platform_GetWindowFocus = Marshal.GetFunctionPointerForDelegate(_getWindowFocus);
        // platformIO.Platform_GetWindowMinimized = Marshal.GetFunctionPointerForDelegate(_getWindowMinimized);
        // platformIO.Platform_SetWindowTitle = Marshal.GetFunctionPointerForDelegate(_setWindowTitle);

        // ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowPos(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(_getWindowPos));
        // ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowSize(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(_getWindowSize));

        CreateDeviceResources();
        SetKeyMappings();

        SetPerFrameImGuiData(1f / 60f);

        BeginFrame();
    }

    public unsafe ImGuiController(GL gl, IView view, IInputContext input, ImGuiFontConfig imGuiFontConfig)
    {
        Init(gl, view, input);

        var io = ImGuiNET.ImGui.GetIO();

        io.Fonts.AddFontFromFileTTF(imGuiFontConfig.FontPath, imGuiFontConfig.FontSize);
        // io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
        // io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
        // io.BackendFlags |= ImGuiBackendFlags.PlatformHasViewports;
        // io.BackendFlags |= ImGuiBackendFlags.RendererHasViewports;
        io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;

        // io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
        // io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;

        // var platformIO = ImGuiNET.ImGui.GetPlatformIO();
        // var mainViewport = platformIO.MainViewport;
        // mainViewport.PlatformHandle = view.Handle;

        // var handle = GCHandle.Alloc(this);

        // view.Resize += (size) => mainViewport.PlatformRequestResize = true;
        // //view.Moved += p => mainViewport.PlatformRequestMove = true;
        // view.Closing += () => mainViewport.PlatformRequestClose = true;

        // mainViewport.PlatformUserData = (IntPtr)handle;

        // _createWindow = CreateWindow;
        // _destroyWindow = DestroyWindow;
        // _getWindowPos = GetWindowPos;
        // _showWindow = ShowWindow;
        // _setWindowPos = SetWindowPos;
        // _setWindowSize = SetWindowSize;
        // _getWindowSize = GetWindowSize;
        // _setWindowFocus = SetWindowFocus;
        // _getWindowFocus = GetWindowFocus;
        // _getWindowMinimized = GetWindowMinimized;
        // _setWindowTitle = SetWindowTitle;

        // platformIO.Platform_CreateWindow = Marshal.GetFunctionPointerForDelegate(_createWindow);
        // platformIO.Platform_DestroyWindow = Marshal.GetFunctionPointerForDelegate(_destroyWindow);
        // platformIO.Platform_ShowWindow = Marshal.GetFunctionPointerForDelegate(_showWindow);
        // platformIO.Platform_SetWindowPos = Marshal.GetFunctionPointerForDelegate(_setWindowPos);
        // platformIO.Platform_SetWindowSize = Marshal.GetFunctionPointerForDelegate(_setWindowSize);
        // platformIO.Platform_SetWindowFocus = Marshal.GetFunctionPointerForDelegate(_setWindowFocus);
        // platformIO.Platform_GetWindowFocus = Marshal.GetFunctionPointerForDelegate(_getWindowFocus);
        // platformIO.Platform_GetWindowMinimized = Marshal.GetFunctionPointerForDelegate(_getWindowMinimized);
        // platformIO.Platform_SetWindowTitle = Marshal.GetFunctionPointerForDelegate(_setWindowTitle);

        // ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowPos(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(_getWindowPos));
        // ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowSize(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(_getWindowSize));

        CreateDeviceResources();
        SetKeyMappings();

        SetPerFrameImGuiData(1f / 60f);

        BeginFrame();
    }

    private void CreateWindow(ImGuiViewportPtr vp)
    {

    }

    private void DestroyWindow(ImGuiViewportPtr vp)
    {

    }

    private void ShowWindow(ImGuiViewportPtr vp)
    {

    }

    private unsafe void GetWindowPos(ImGuiViewportPtr vp, Vector2* outPos)
    {

    }

    private void SetWindowPos(ImGuiViewportPtr vp, Vector2 pos)
    {

    }

    private void SetWindowSize(ImGuiViewportPtr vp, Vector2 size)
    {

    }

    private unsafe void GetWindowSize(ImGuiViewportPtr vp, Vector2* outSize)
    {

    }

    private void SetWindowFocus(ImGuiViewportPtr vp)
    {

    }

    private byte GetWindowFocus(ImGuiViewportPtr vp)
    {

    }

    private byte GetWindowMinimized(ImGuiViewportPtr vp)
    {

    }

    private unsafe void SetWindowTitle(ImGuiViewportPtr vp, IntPtr title)
    {

    }

    private void Init(GL gl, IView view, IInputContext input)
    {
        _gl = gl;
        _glVersion = new Version(gl.GetInteger(GLEnum.MajorVersion), gl.GetInteger(GLEnum.MinorVersion));
        _view = view;
        _input = input;
        _windowWidth = view.Size.X;
        _windowHeight = view.Size.Y;

        var context = ImGuiNET.ImGui.CreateContext();
        ImGuiNET.ImGui.SetCurrentContext(context);
    }

    private void BeginFrame()
    {
        ImGuiNET.ImGui.NewFrame();
        _frameBegun = true;
        _keyboard = _input.Keyboards[0];
        _view.Resize += WindowResized;
        _keyboard.KeyChar += OnKeyChar;
    }

    private void OnKeyChar(IKeyboard keyboard, char key) => _pressedKeys.Add(key);

    private void WindowResized(Vector2D<int> size)
    {
        _windowWidth = size.X;
        _windowHeight = size.Y;
    }

    public void DestroyDeviceObjects() => Dispose();

    private unsafe void CreateDeviceResources()
    {
        _gl.CreateVertexArray(nameof(ImGui), out _vertexArray);

        _vertexBufferSize = 10000;
        _indexBufferSize = 2000;

        _gl.CreateVertexBuffer(nameof(ImGui), out _vertexBuffer);
        _gl.CreateElementBuffer(nameof(ImGui), out _indexBuffer);
        _gl.NamedBufferData(_vertexBuffer, _vertexBufferSize, null, VertexBufferObjectUsage.DynamicDraw);
        _gl.NamedBufferData(_indexBuffer, _indexBufferSize, null, VertexBufferObjectUsage.DynamicDraw);

        RecreateFontDeviceTexture();

        var vertexSource = @"
            #version 330 core

            uniform mat4 projection_matrix;

            in vec2 in_position;
            in vec2 in_texCoord;
            in vec4 in_color;

            out vec4 color;
            out vec2 texCoord;

            void main()
            {
                gl_Position = projection_matrix * vec4(in_position, 0, 1);
                color = in_color;
                texCoord = in_texCoord;
            }";
        var fragmentSource = @"
            #version 330 core

            uniform sampler2D in_fontTexture;

            in vec4 color;
            in vec2 texCoord;

            out vec4 outputColor;

            void main()
            {
                outputColor = color * texture(in_fontTexture, texCoord);
            }";

        _shader = new Shader(_gl, nameof(ImGui), vertexSource, fragmentSource);

        _gl.VertexArrayVertexBuffer(_vertexArray, 0, _vertexBuffer, IntPtr.Zero, (uint)Unsafe.SizeOf<ImDrawVert>());
        _gl.VertexArrayElementBuffer(_vertexArray, _indexBuffer);

        _gl.EnableVertexArrayAttrib(_vertexArray, 0);
        _gl.VertexArrayAttribBinding(_vertexArray, 0, 0);
        _gl.VertexArrayAttribFormat(_vertexArray, 0, 2, VertexAttribType.Float, false, 0);

        _gl.EnableVertexArrayAttrib(_vertexArray, 1);
        _gl.VertexArrayAttribBinding(_vertexArray, 1, 0);
        _gl.VertexArrayAttribFormat(_vertexArray, 1, 2, VertexAttribType.Float, false, 8);

        _gl.EnableVertexArrayAttrib(_vertexArray, 2);
        _gl.VertexArrayAttribBinding(_vertexArray, 2, 0);
        _gl.VertexArrayAttribFormat(_vertexArray, 2, 4, VertexAttribType.UnsignedByte, true, 16);

        _gl.CheckGlError("End of ImGui setup");
    }

    private void RecreateFontDeviceTexture()
    {
        var io = ImGuiNET.ImGui.GetIO();
        io.Fonts.GetTexDataAsRGBA32(out IntPtr pixels, out int width, out int height, out int bytesPerPixel);

        _fontTexture = new Texture(_gl, "ImGui Text Atlas", width, height, pixels);
        _fontTexture.SetMagFilter(TextureMagFilter.Linear);
        _fontTexture.SetMinFilter(TextureMinFilter.Linear);

        io.Fonts.SetTexID((IntPtr)_fontTexture.GlTexture);

        io.Fonts.ClearTexData();
    }

    public void Render()
    {
        if (_frameBegun)
        {
            _frameBegun = false;
            ImGuiNET.ImGui.Render();
            RenderImDrawData(ImGuiNET.ImGui.GetDrawData());
        }
    }

    public void Update(float deltaSeconds)
    {
        if (_frameBegun) ImGuiNET.ImGui.Render();

        SetPerFrameImGuiData(deltaSeconds);
        UpdateImGuiInput();

        _frameBegun = true;
        ImGuiNET.ImGui.NewFrame();
    }

    private void SetPerFrameImGuiData(float deltaSeconds)
    {
        var io = ImGuiNET.ImGui.GetIO();
        io.DisplaySize = new Vector2(
            _windowWidth / _scaleFactor.X,
            _windowHeight / _scaleFactor.Y);
        io.DisplayFramebufferScale = _scaleFactor;
        io.DeltaTime = deltaSeconds;
    }

    private void UpdateImGuiInput()
    {
        var io = ImGuiNET.ImGui.GetIO();

        var mouseState = _input.Mice[0].CaptureState();
        var keyboardState = _input.Keyboards[0];

        io.MouseDown[0] = mouseState.IsButtonPressed(MouseButton.Left);
        io.MouseDown[1] = mouseState.IsButtonPressed(MouseButton.Right);
        io.MouseDown[2] = mouseState.IsButtonPressed(MouseButton.Middle);

        var point = new Point((int)mouseState.Position.X, (int)mouseState.Position.Y);
        io.MousePos = new Vector2(point.X, point.Y);

        var wheel = mouseState.GetScrollWheels()[0];
        io.MouseWheel = wheel.Y;
        io.MouseWheelH = wheel.X;

        foreach (var key in (Key[])Enum.GetValues(typeof(Key)))
        {
            if (key == Key.Unknown) continue;
            io.KeysDown[(int)key] = keyboardState.IsKeyPressed(key);
        }

        foreach (var c in _pressedKeys) io.AddInputCharacter(c);

        _pressedKeys.Clear();

        io.KeyCtrl = keyboardState.IsKeyPressed(Key.ControlLeft) || keyboardState.IsKeyPressed(Key.ControlRight);
        io.KeyAlt = keyboardState.IsKeyPressed(Key.AltLeft) || keyboardState.IsKeyPressed(Key.AltRight);
        io.KeyShift = keyboardState.IsKeyPressed(Key.ShiftLeft) || keyboardState.IsKeyPressed(Key.ShiftRight);
        io.KeySuper = keyboardState.IsKeyPressed(Key.SuperLeft) || keyboardState.IsKeyPressed(Key.SuperRight);

        _prevMouseState = mouseState;
    }

    internal void PressChar(char key) => _pressedKeys.Add(key);

    private static void SetKeyMappings()
    {
        var io = ImGuiNET.ImGui.GetIO();
        io.KeyMap[(int)ImGuiKey.Tab] = (int)Key.Tab;
        io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Key.Left;
        io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Key.Right;
        io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Key.Up;
        io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Key.Down;
        io.KeyMap[(int)ImGuiKey.PageUp] = (int)Key.PageUp;
        io.KeyMap[(int)ImGuiKey.PageDown] = (int)Key.PageDown;
        io.KeyMap[(int)ImGuiKey.Home] = (int)Key.Home;
        io.KeyMap[(int)ImGuiKey.End] = (int)Key.End;
        io.KeyMap[(int)ImGuiKey.Delete] = (int)Key.Delete;
        io.KeyMap[(int)ImGuiKey.Backspace] = (int)Key.Backspace;
        io.KeyMap[(int)ImGuiKey.Enter] = (int)Key.Enter;
        io.KeyMap[(int)ImGuiKey.Escape] = (int)Key.Escape;
        io.KeyMap[(int)ImGuiKey.A] = (int)Key.A;
        io.KeyMap[(int)ImGuiKey.C] = (int)Key.C;
        io.KeyMap[(int)ImGuiKey.V] = (int)Key.V;
        io.KeyMap[(int)ImGuiKey.X] = (int)Key.X;
        io.KeyMap[(int)ImGuiKey.Y] = (int)Key.Y;
        io.KeyMap[(int)ImGuiKey.Z] = (int)Key.Z;
    }

    private unsafe void RenderImDrawData(ImDrawDataPtr drawData)
    {
        var vertexOffsetInVertices = (uint)0;
        var indexOffsetInElements = (uint)0;

        if (drawData.CmdListsCount == 0) return;

        var totalVbSize = (uint)(drawData.TotalVtxCount * Unsafe.SizeOf<ImDrawVert>());
        if (totalVbSize > _vertexBufferSize)
        {
            var newSize = (int)Math.Max(_vertexBufferSize * 1.5f, totalVbSize);
            _gl.NamedBufferData(_vertexBuffer, (uint)newSize, null, VertexBufferObjectUsage.DynamicDraw);
            _vertexBufferSize = (uint)newSize;

            if (_glVersion >= new Version(4, 3))
            {
                var str = $"Silk.NET ImGui: Resized vertex buffer to new size {_vertexBufferSize}";
                _gl.DebugMessageInsert(DebugSource.DebugSourceApi, DebugType.DontCare, 1879701u, DebugSeverity.DebugSeverityNotification, (uint)str.Length, str);
            }
        }

        var totalIbSize = (uint)(drawData.TotalIdxCount * sizeof(ushort));
        if (totalIbSize > _indexBufferSize)
        {
            var newSize = (int)Math.Max(_indexBufferSize * 1.5f, totalIbSize);
            _gl.NamedBufferData(_indexBuffer, (uint)newSize, null, VertexBufferObjectUsage.DynamicDraw);
            _indexBufferSize = (uint)newSize;

            if (_glVersion >= new Version(4, 3))
            {
                var str = $"Silk.NET ImGui: Resized index buffer to new size {_indexBufferSize}";
                _gl.DebugMessageInsert(DebugSource.DebugSourceApi, DebugType.DontCare, 1879702u, DebugSeverity.DebugSeverityNotification, (uint)str.Length, str);
            }
        }

        for (var i = 0; i < drawData.CmdListsCount; i++)
        {
            var cmdList = drawData.CmdListsRange[i];

            _gl.NamedBufferSubData(_vertexBuffer, (IntPtr)(vertexOffsetInVertices * Unsafe.SizeOf<ImDrawVert>()), (UIntPtr)(cmdList.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>()), (void*)cmdList.VtxBuffer.Data);
            _gl.CheckGlError($"Data Vert {i}");
            _gl.NamedBufferSubData(_indexBuffer, (IntPtr)(indexOffsetInElements * sizeof(ushort)), (UIntPtr)(cmdList.IdxBuffer.Size * sizeof(ushort)), (void*)cmdList.IdxBuffer.Data);

            _gl.CheckGlError($"Data Idx {i}");

            vertexOffsetInVertices += (uint)cmdList.VtxBuffer.Size;
            indexOffsetInElements += (uint)cmdList.IdxBuffer.Size;
        }

        var io = ImGuiNET.ImGui.GetIO();
        var mvp = Matrix4x4.CreateOrthographicOffCenter(-1.0f, io.DisplaySize.X, io.DisplaySize.Y, 0.0f, -1.0f, 1.0f);

        _shader.UseShader();
        _gl.ProgramUniformMatrix4(_shader.Program, _shader.GetUniformLocation("projection_matrix"), 1, false, (float*)Unsafe.AsPointer(ref mvp));
        _gl.ProgramUniform1(_shader.Program, _shader.GetUniformLocation("in_fontTexture"), 0);
        _gl.CheckGlError("Projection");

        _gl.BindVertexArray(_vertexArray);
        _gl.CheckGlError("VAO");

        drawData.ScaleClipRects(io.DisplayFramebufferScale);

        _gl.Enable(EnableCap.Blend);
        _gl.Enable(EnableCap.ScissorTest);
        _gl.BlendEquation(BlendEquationModeEXT.FuncAdd);
        _gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
        _gl.Disable(EnableCap.CullFace);
        _gl.Disable(EnableCap.DepthTest);

        var vtxOffset = 0;
        var idxOffset = 0;
        for (var n = 0; n < drawData.CmdListsCount; n++)
        {
            var cmdList = drawData.CmdListsRange[n];
            for (var cmdI = 0; cmdI < cmdList.CmdBuffer.Size; cmdI++)
            {
                var pcmd = cmdList.CmdBuffer[cmdI];
                if (pcmd.UserCallback != IntPtr.Zero) throw new NotImplementedException();
                else
                {
                    _gl.ActiveTexture(TextureUnit.Texture0);
                    _gl.BindTexture(TextureTarget.Texture2D, (uint)pcmd.TextureId);
                    _gl.CheckGlError("Texture");

                    var clip = pcmd.ClipRect;
                    _gl.Scissor((int)clip.X, _windowHeight - (int)clip.W, (uint)(clip.Z - clip.X), (uint)(clip.W - clip.Y));
                    _gl.CheckGlError("Scissor");

                    _gl.DrawElementsBaseVertex(PrimitiveType.Triangles, pcmd.ElemCount, DrawElementsType.UnsignedShort, (void*)(idxOffset * sizeof(ushort)), vtxOffset);
                    _gl.CheckGlError("Draw");
                }

                idxOffset += (int)pcmd.ElemCount;
            }

            vtxOffset += cmdList.VtxBuffer.Size;
        }

        _gl.Disable(EnableCap.Blend);
        _gl.Disable(EnableCap.ScissorTest);
    }

    public void Dispose()
    {
        _view.Resize -= WindowResized;
        _keyboard.KeyChar -= OnKeyChar;
        _fontTexture.Dispose();
        _shader.Dispose();
    }
}

Is there any chance you can give some advice on how to get this working? Thanks

mellinoe commented 3 years ago

I'm not able to dig through this much code at the moment, but @nogginbops shared an OpenGL-based renderer for ImGui.NET one of the previous times this came up. I'd suggest following along with what he has done there. Even if it's not using the exact same OpenGL and windowing library, it should be helpful as a reference.

By the way, I'm assuming that you are not trying to take advantage of the multi-window, multi-viewport features in the docking branch. While they are fully supported here, it is NOT easy to use them from OpenGL. If you're interested in using multiple viewports, I'd suggest using a different graphics API.