diff --git a/Silk.NET.sln b/Silk.NET.sln index 14f6557734..1ed5f3b7b2 100644 --- a/Silk.NET.sln +++ b/Silk.NET.sln @@ -620,6 +620,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Assimp.Tests", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Core.Tests", "src\Core\Silk.NET.Core.Tests\Silk.NET.Core.Tests.csproj", "{4D871493-0B88-477A-99A1-3E05561CFAD9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGuiDX11", "src\Lab\Experiments\ImGuiDX11\ImGuiDX11.csproj", "{19EF071F-25D0-482D-9B81-31A2D037AD7A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Direct3D11 Demos", "Direct3D11 Demos", "{10CFA2B9-6453-42EB-A89E-796CED80EFA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGuiDX11Demo", "examples\CSharp\Direct3D11 Demos\ImGuiDX11Demo\ImGuiDX11Demo.csproj", "{2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -3785,6 +3791,30 @@ Global {4D871493-0B88-477A-99A1-3E05561CFAD9}.Release|x64.Build.0 = Release|Any CPU {4D871493-0B88-477A-99A1-3E05561CFAD9}.Release|x86.ActiveCfg = Release|Any CPU {4D871493-0B88-477A-99A1-3E05561CFAD9}.Release|x86.Build.0 = Release|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Debug|x64.ActiveCfg = Debug|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Debug|x64.Build.0 = Debug|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Debug|x86.ActiveCfg = Debug|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Debug|x86.Build.0 = Debug|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Release|Any CPU.Build.0 = Release|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Release|x64.ActiveCfg = Release|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Release|x64.Build.0 = Release|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Release|x86.ActiveCfg = Release|Any CPU + {19EF071F-25D0-482D-9B81-31A2D037AD7A}.Release|x86.Build.0 = Release|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Debug|x64.Build.0 = Debug|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Debug|x86.Build.0 = Debug|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Release|Any CPU.Build.0 = Release|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Release|x64.ActiveCfg = Release|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Release|x64.Build.0 = Release|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Release|x86.ActiveCfg = Release|Any CPU + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -4087,6 +4117,9 @@ Global {01B6FFA0-5B37-44EA-ABDF-7BABD05874C5} = {90471225-AC23-424E-B62E-F6EC4C6ECAC0} {12D0A556-7DDF-4902-8911-1DA3F6331149} = {6EADA376-E83F-40B7-9539-71DD17AEF7A4} {4D871493-0B88-477A-99A1-3E05561CFAD9} = {0651C5EF-50AA-4598-8D9C-8F210ADD8490} + {19EF071F-25D0-482D-9B81-31A2D037AD7A} = {39B598E9-44BA-4A61-A1BB-7C543734DBA6} + {10CFA2B9-6453-42EB-A89E-796CED80EFA5} = {6842A2C6-5C7B-42DD-9825-0EDE91BFEBF7} + {2F97E314-5EC7-4FDF-9E89-E3BA19F64EA1} = {10CFA2B9-6453-42EB-A89E-796CED80EFA5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F5273D7F-3334-48DF-94E3-41AE6816CD4D} diff --git a/examples/CSharp/Direct3D11 Demos/ImGuiDX11Demo/ImGuiDX11Demo.csproj b/examples/CSharp/Direct3D11 Demos/ImGuiDX11Demo/ImGuiDX11Demo.csproj new file mode 100644 index 0000000000..3fdde34df2 --- /dev/null +++ b/examples/CSharp/Direct3D11 Demos/ImGuiDX11Demo/ImGuiDX11Demo.csproj @@ -0,0 +1,21 @@ + + + + Exe + net6.0 + enable + enable + true + 12 + + + + + + + + + + + + diff --git a/examples/CSharp/Direct3D11 Demos/ImGuiDX11Demo/Program.cs b/examples/CSharp/Direct3D11 Demos/ImGuiDX11Demo/Program.cs new file mode 100644 index 0000000000..7fbcadf28b --- /dev/null +++ b/examples/CSharp/Direct3D11 Demos/ImGuiDX11Demo/Program.cs @@ -0,0 +1,196 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/////////////////////////////////////////////////////// PLEASE READ! /////////////////////////////////////////////////// +// This provides a basic example of using our Direct3D 11 bindings in their current form. These bindings are still // +// improving over time, and as a result the content of this example may change. // +// Notably: // +// TODO remove Unsafe.NullRef once we've updated the bindings to not require it // +// TODO investigate making the D3DPrimitiveTopology enum more user friendly // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System.Runtime.CompilerServices; +using ImGuiNET; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; +using Silk.NET.Input; +using Silk.NET.Lab.Experiments.ImGuiDX11; +using Silk.NET.Maths; +using Silk.NET.Windowing; + +float[] backgroundColour = [0.0f, 0.0f, 0.0f, 1.0f]; + +// Load the DXGI and Direct3D11 libraries for later use. +// Given this is not tied to the window, this doesn't need to be done in the OnLoad event. +DXGI dxgi = null!; +D3D11 d3d11 = null!; + +// These variables are initialized within the Load event. +ComPtr factory = default; +ComPtr swapchain = default; +ComPtr device = default; +ComPtr deviceContext = default; + +ImGuiDX11Controller controller = null; + +// Create a window. +WindowOptions options = WindowOptions.Default with +{ + Size = new Vector2D(800, 600), + Title = "Learn Direct3D11 with Silk.NET", + API = GraphicsAPI.None, // <-- This bit is important, as your window will be configured for OpenGL by default. +}; +var window = Window.Create(options); + +// Assign events. +window.Load += OnLoad; +window.Render += OnRender; +window.FramebufferResize += OnFramebufferResize; + +// Run the window. +window.Run(); + +// Dispose of the ImGui context. +controller?.Dispose(); + +// Clean up any resources. +factory.Dispose(); +swapchain.Dispose(); +device.Dispose(); +deviceContext.Dispose(); +d3d11.Dispose(); +dxgi.Dispose(); + +//dispose the window, and its internal resources +window.Dispose(); + + +unsafe void OnLoad() +{ + //Whether or not to force use of DXVK on platforms where native DirectX implementations are available + const bool forceDxvk = false; + + dxgi = DXGI.GetApi(window, forceDxvk); + d3d11 = D3D11.GetApi(window, forceDxvk); + + // Set-up input context. + var input = window.CreateInput(); + foreach (var keyboard in input.Keyboards) + { + keyboard.KeyDown += OnKeyDown; + } + + // Create our D3D11 logical device. + SilkMarshal.ThrowHResult + ( + d3d11.CreateDevice + ( + default(ComPtr), + D3DDriverType.Hardware, + Software: default, + (uint) CreateDeviceFlag.Debug, + null, + 0, + D3D11.SdkVersion, + ref device, + null, + ref deviceContext + ) + ); + + //This is not supported under DXVK + //TODO: PR a stub into DXVK for this maybe? + if (OperatingSystem.IsWindows()) + { + // Log debug messages for this device (given that we've enabled the debug flag). Don't do this in release code! + device.SetInfoQueueCallback(msg => Console.WriteLine(SilkMarshal.PtrToString((nint) msg.PDescription))); + } + + // Create our swapchain. + var swapChainDesc = new SwapChainDesc1 + { + BufferCount = 2, // double buffered + Format = Format.FormatB8G8R8A8Unorm, + BufferUsage = DXGI.UsageRenderTargetOutput, + SwapEffect = SwapEffect.FlipDiscard, + SampleDesc = new SampleDesc(1, 0) + }; + + // Create our DXGI factory to allow us to create a swapchain. + factory = dxgi.CreateDXGIFactory(); + + // Create the swapchain. + SilkMarshal.ThrowHResult + ( + factory.CreateSwapChainForHwnd + ( + device, + window.Native!.DXHandle!.Value, + in swapChainDesc, + null, + ref Unsafe.NullRef(), + ref swapchain + ) + ); + + // Init ImGui. + // This is where you can set a custom font for ImGui. + //ImGuiFontConfig fontConfig = new ImGuiFontConfig("C:\\Windows\\Fonts\\arial.ttf", 16, ptr => ptr.Fonts.GetGlyphRangesDefault()); + controller = new ImGuiDX11Controller(device, deviceContext, window, input/*, fontConfig*/); +} + +void OnFramebufferResize(Vector2D newSize) +{ + // If the window resizes, we need to be sure to update the swapchain's back buffers. + // Good starting point for this function: + // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/d3d10-graphics-programming-guide-dxgi#handling-window-resizing + SilkMarshal.ThrowHResult + ( + swapchain.ResizeBuffers(0, (uint) newSize.X, (uint) newSize.Y, Format.FormatB8G8R8A8Unorm, 0) + ); +} + +unsafe void OnRender(double deltaSeconds) +{ + // Update the ImGui controller. + controller.Update((float)deltaSeconds); + + // Obtain the framebuffer for the swapchain's backbuffer. + using ComPtr framebuffer = swapchain.GetBuffer(0); + + // Create a view over the render target. + ComPtr renderTargetView = default; + SilkMarshal.ThrowHResult(device.CreateRenderTargetView(framebuffer, null, ref renderTargetView)); + + // Clear the render target to be all black ahead of rendering. + deviceContext.ClearRenderTargetView(renderTargetView, ref backgroundColour[0]); + + // Update the rasterizer state with the current viewport. + Viewport viewport = new Viewport(0, 0, window.FramebufferSize.X, window.FramebufferSize.Y, 0, 1); + deviceContext.RSSetViewports(1, in viewport); + + // Tell the output merger about our render target view. + deviceContext.OMSetRenderTargets(1, ref renderTargetView, ref Unsafe.NullRef()); + + // Show everything you want before calling ImGui.Render(). + ImGui.ShowDemoWindow(); + + // Then render ImGui. + controller.Render(); + + // Present the drawn image. + swapchain.Present(1, 0); + + // Clean up any resources created in this method. + renderTargetView.Dispose(); +} + +void OnKeyDown(IKeyboard keyboard, Key key, int scancode) +{ + // Check to close the window on escape. + if (key == Key.Escape) + { + window.Close(); + } +} diff --git a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj new file mode 100644 index 0000000000..cae0f93e24 --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj @@ -0,0 +1,22 @@ + + + + net5.0;net6.0 + enable + true + 12 + + + + + + + + + + + + + + + diff --git a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs new file mode 100644 index 0000000000..41dd15f5ed --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs @@ -0,0 +1,394 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Numerics; +using ImGuiNET; +using Silk.NET.Direct3D11; +using Silk.NET.Input; +using Silk.NET.Maths; +using Silk.NET.Windowing; + +namespace Silk.NET.Lab.Experiments.ImGuiDX11; + +public class ImGuiDX11Controller +{ + private ImGui_Impl_DX11 instance; + private IView _view; + private IInputContext _input; + private bool _frameBegun; + private readonly List _pressedChars = new(); + private IKeyboard _keyboard; + + private int _windowWidth; + private int _windowHeight; + + public IntPtr ImGuiContext; + + // TODO: Implement overloads for all numbered types of ID3D11Device and ID3D11DeviceContext + /// + /// Constructs a new ImGuiController with font configuration and onConfigure Action. + /// + public unsafe ImGuiDX11Controller(ID3D11Device* device, ID3D11DeviceContext* deviceContext, + IView view, IInputContext input, + ImGuiFontConfig? imGuiFontConfig = null, Action onConfigureIO = null) + { + instance = new ImGui_Impl_DX11(); + Init(view, input); + + ImGuiIOPtr io = ImGui.GetIO(); + if (imGuiFontConfig is not null) + { + IntPtr glyphRange = imGuiFontConfig.Value.GetGlyphRange?.Invoke(io) ?? default(IntPtr); + + io.Fonts.AddFontFromFileTTF(imGuiFontConfig.Value.FontPath, imGuiFontConfig.Value.FontSize, null, glyphRange); + } + + onConfigureIO?.Invoke(); + + io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; + + // From ImGui_Impl_win32 + ImGuiViewportPtr mainViewport = ImGui.GetMainViewport(); + mainViewport.PlatformHandle = view.Native!.DXHandle!.Value; + mainViewport.PlatformHandleRaw = view.Native!.DXHandle!.Value; + + if (!instance.ImGui_ImplDX11_Init(device, deviceContext)) + { + throw new Exception("Failed to initialize ImGui DX11"); + } + if (!instance.ImGui_ImplDX11_CreateDeviceObjects()) + { + throw new Exception("Failed to create ImGui DX11 device objects"); + } + + SetPerFrameImGuiData(1f / 60f); + + BeginFrame(); + } + + private void Init(IView view, IInputContext input) + { + _view = view; + _input = input; + _windowWidth = view.FramebufferSize.X; + _windowHeight = view.FramebufferSize.Y; + + ImGuiContext = ImGui.CreateContext(); + ImGui.SetCurrentContext(ImGuiContext); + ImGui.StyleColorsDark(); + } + + private void BeginFrame() + { + ImGui.NewFrame(); + _frameBegun = true; + _keyboard = _input.Keyboards[0]; + _view.Resize += WindowResized; + _keyboard.KeyDown += OnKeyDown; + _keyboard.KeyUp += OnKeyUp; + _keyboard.KeyChar += OnKeyChar; + } + + /// + /// Delegate to receive keyboard key down events. + /// + /// The keyboard context generating the event. + /// The native keycode of the pressed key. + /// The native scancode of the pressed key. + private static void OnKeyDown(IKeyboard keyboard, Key keycode, int scancode) => + OnKeyEvent(keyboard, keycode, scancode, down: true); + + /// + /// Delegate to receive keyboard key up events. + /// + /// The keyboard context generating the event. + /// The native keycode of the released key. + /// The native scancode of the released key. + private static void OnKeyUp(IKeyboard keyboard, Key keycode, int scancode) => + OnKeyEvent(keyboard, keycode, scancode, down: false); + + /// + /// Delegate to receive keyboard key events. + /// + /// The keyboard context generating the event. + /// The native keycode of the key generating the event. + /// The native scancode of the key generating the event. + /// True if the event is a key down event, otherwise False + private static void OnKeyEvent(IKeyboard keyboard, Key keycode, int scancode, bool down) + { + ImGuiIOPtr io = ImGui.GetIO(); + ImGuiKey imGuiKey = TranslateInputKeyToImGuiKey(keycode); + io.AddKeyEvent(imGuiKey, down); + io.SetKeyEventNativeData(imGuiKey, (int) keycode, scancode); + } + + private void OnKeyChar(IKeyboard arg1, char arg2) + { + _pressedChars.Add(arg2); + } + + private void WindowResized(Vector2D size) + { + _windowWidth = size.X; + _windowHeight = size.Y; + } + + /// + /// Renders the ImGui draw list data. + /// This method requires a because it may create new DeviceBuffers if the size of vertex + /// or index data has increased beyond the capacity of the existing buffers. + /// A is needed to submit drawing and resource update commands. + /// + public void Render() + { + if (_frameBegun) + { + IntPtr oldCtx = ImGui.GetCurrentContext(); + + if (oldCtx != ImGuiContext) + { + ImGui.SetCurrentContext(ImGuiContext); + } + + _frameBegun = false; + ImGui.Render(); + instance.ImGui_ImplDX11_RenderDrawData(ImGui.GetDrawData()); + + if (oldCtx != ImGuiContext) + { + ImGui.SetCurrentContext(oldCtx); + } + } + } + + /// + /// Updates ImGui input and IO configuration state. + /// + public void Update(float deltaSeconds) + { + IntPtr oldCtx = ImGui.GetCurrentContext(); + + if (oldCtx != ImGuiContext) + { + ImGui.SetCurrentContext(ImGuiContext); + } + + if (_frameBegun) + { + ImGui.Render(); + } + + SetPerFrameImGuiData(deltaSeconds); + UpdateImGuiInput(); + + _frameBegun = true; + ImGui.NewFrame(); + + if (oldCtx != ImGuiContext) + { + ImGui.SetCurrentContext(oldCtx); + } + } + + /// + /// Sets per-frame data based on the associated window. + /// This is called by Update(float). + /// + private void SetPerFrameImGuiData(float deltaSeconds) + { + var io = ImGuiNET.ImGui.GetIO(); + io.DisplaySize = new Vector2(_windowWidth, _windowHeight); + + if (_windowWidth > 0 && _windowHeight > 0) + { + io.DisplayFramebufferScale = new Vector2( + _view.FramebufferSize.X / (float)_windowWidth, + _view.FramebufferSize.Y / (float)_windowHeight); + } + + io.DeltaTime = deltaSeconds; // DeltaTime is in seconds. + } + + private void UpdateImGuiInput() + { + ImGuiIOPtr io = ImGui.GetIO(); + + // TODO: Add support for multiple mice and keyboards + IMouse mouse = _input.Mice[0]; + + io.MouseDown[0] = mouse.IsButtonPressed(MouseButton.Left); + io.MouseDown[1] = mouse.IsButtonPressed(MouseButton.Right); + io.MouseDown[2] = mouse.IsButtonPressed(MouseButton.Middle); + + var point = new Point((int) mouse.Position.X, (int) mouse.Position.Y); + io.MousePos = new Vector2(point.X, point.Y); + + ScrollWheel wheel = mouse.ScrollWheels[0]; + io.MouseWheel = wheel.Y; + io.MouseWheelH = wheel.X; + + foreach (char c in _pressedChars) + { + io.AddInputCharacter(c); + } + + _pressedChars.Clear(); + + io.KeyCtrl = _keyboard.IsKeyPressed(Key.ControlLeft) || _keyboard.IsKeyPressed(Key.ControlRight); + io.KeyAlt = _keyboard.IsKeyPressed(Key.AltLeft) || _keyboard.IsKeyPressed(Key.AltRight); + io.KeyShift = _keyboard.IsKeyPressed(Key.ShiftLeft) || _keyboard.IsKeyPressed(Key.ShiftRight); + io.KeySuper = _keyboard.IsKeyPressed(Key.SuperLeft) || _keyboard.IsKeyPressed(Key.SuperRight); + } + + internal void PressChar(char keyChar) + { + _pressedChars.Add(keyChar); + } + + /// + /// Translates a Silk.NET.Input.Key to an ImGuiKey. + /// + /// The Silk.NET.Input.Key to translate. + /// The corresponding ImGuiKey. + /// When the key has not been implemented yet. + private static ImGuiKey TranslateInputKeyToImGuiKey(Key key) + { + return key switch + { + Key.Tab => ImGuiKey.Tab, + Key.Left => ImGuiKey.LeftArrow, + Key.Right => ImGuiKey.RightArrow, + Key.Up => ImGuiKey.UpArrow, + Key.Down => ImGuiKey.DownArrow, + Key.PageUp => ImGuiKey.PageUp, + Key.PageDown => ImGuiKey.PageDown, + Key.Home => ImGuiKey.Home, + Key.End => ImGuiKey.End, + Key.Insert => ImGuiKey.Insert, + Key.Delete => ImGuiKey.Delete, + Key.Backspace => ImGuiKey.Backspace, + Key.Space => ImGuiKey.Space, + Key.Enter => ImGuiKey.Enter, + Key.Escape => ImGuiKey.Escape, + Key.Apostrophe => ImGuiKey.Apostrophe, + Key.Comma => ImGuiKey.Comma, + Key.Minus => ImGuiKey.Minus, + Key.Period => ImGuiKey.Period, + Key.Slash => ImGuiKey.Slash, + Key.Semicolon => ImGuiKey.Semicolon, + Key.Equal => ImGuiKey.Equal, + Key.LeftBracket => ImGuiKey.LeftBracket, + Key.BackSlash => ImGuiKey.Backslash, + Key.RightBracket => ImGuiKey.RightBracket, + Key.GraveAccent => ImGuiKey.GraveAccent, + Key.CapsLock => ImGuiKey.CapsLock, + Key.ScrollLock => ImGuiKey.ScrollLock, + Key.NumLock => ImGuiKey.NumLock, + Key.PrintScreen => ImGuiKey.PrintScreen, + Key.Pause => ImGuiKey.Pause, + Key.Keypad0 => ImGuiKey.Keypad0, + Key.Keypad1 => ImGuiKey.Keypad1, + Key.Keypad2 => ImGuiKey.Keypad2, + Key.Keypad3 => ImGuiKey.Keypad3, + Key.Keypad4 => ImGuiKey.Keypad4, + Key.Keypad5 => ImGuiKey.Keypad5, + Key.Keypad6 => ImGuiKey.Keypad6, + Key.Keypad7 => ImGuiKey.Keypad7, + Key.Keypad8 => ImGuiKey.Keypad8, + Key.Keypad9 => ImGuiKey.Keypad9, + Key.KeypadDecimal => ImGuiKey.KeypadDecimal, + Key.KeypadDivide => ImGuiKey.KeypadDivide, + Key.KeypadMultiply => ImGuiKey.KeypadMultiply, + Key.KeypadSubtract => ImGuiKey.KeypadSubtract, + Key.KeypadAdd => ImGuiKey.KeypadAdd, + Key.KeypadEnter => ImGuiKey.KeypadEnter, + Key.KeypadEqual => ImGuiKey.KeypadEqual, + Key.ShiftLeft => ImGuiKey.LeftShift, + Key.ControlLeft => ImGuiKey.LeftCtrl, + Key.AltLeft => ImGuiKey.LeftAlt, + Key.SuperLeft => ImGuiKey.LeftSuper, + Key.ShiftRight => ImGuiKey.RightShift, + Key.ControlRight => ImGuiKey.RightCtrl, + Key.AltRight => ImGuiKey.RightAlt, + Key.SuperRight => ImGuiKey.RightSuper, + Key.Menu => ImGuiKey.Menu, + Key.Number0 => ImGuiKey._0, + Key.Number1 => ImGuiKey._1, + Key.Number2 => ImGuiKey._2, + Key.Number3 => ImGuiKey._3, + Key.Number4 => ImGuiKey._4, + Key.Number5 => ImGuiKey._5, + Key.Number6 => ImGuiKey._6, + Key.Number7 => ImGuiKey._7, + Key.Number8 => ImGuiKey._8, + Key.Number9 => ImGuiKey._9, + Key.A => ImGuiKey.A, + Key.B => ImGuiKey.B, + Key.C => ImGuiKey.C, + Key.D => ImGuiKey.D, + Key.E => ImGuiKey.E, + Key.F => ImGuiKey.F, + Key.G => ImGuiKey.G, + Key.H => ImGuiKey.H, + Key.I => ImGuiKey.I, + Key.J => ImGuiKey.J, + Key.K => ImGuiKey.K, + Key.L => ImGuiKey.L, + Key.M => ImGuiKey.M, + Key.N => ImGuiKey.N, + Key.O => ImGuiKey.O, + Key.P => ImGuiKey.P, + Key.Q => ImGuiKey.Q, + Key.R => ImGuiKey.R, + Key.S => ImGuiKey.S, + Key.T => ImGuiKey.T, + Key.U => ImGuiKey.U, + Key.V => ImGuiKey.V, + Key.W => ImGuiKey.W, + Key.X => ImGuiKey.X, + Key.Y => ImGuiKey.Y, + Key.Z => ImGuiKey.Z, + Key.F1 => ImGuiKey.F1, + Key.F2 => ImGuiKey.F2, + Key.F3 => ImGuiKey.F3, + Key.F4 => ImGuiKey.F4, + Key.F5 => ImGuiKey.F5, + Key.F6 => ImGuiKey.F6, + Key.F7 => ImGuiKey.F7, + Key.F8 => ImGuiKey.F8, + Key.F9 => ImGuiKey.F9, + Key.F10 => ImGuiKey.F10, + Key.F11 => ImGuiKey.F11, + Key.F12 => ImGuiKey.F12, + Key.F13 => ImGuiKey.F13, + Key.F14 => ImGuiKey.F14, + Key.F15 => ImGuiKey.F15, + Key.F16 => ImGuiKey.F16, + Key.F17 => ImGuiKey.F17, + Key.F18 => ImGuiKey.F18, + Key.F19 => ImGuiKey.F19, + Key.F20 => ImGuiKey.F20, + Key.F21 => ImGuiKey.F21, + Key.F22 => ImGuiKey.F22, + Key.F23 => ImGuiKey.F23, + Key.F24 => ImGuiKey.F24, + _ => throw new NotImplementedException(), + }; + } + + /// + /// Frees all graphics resources used by the renderer. + /// + public void Dispose() + { + _view.Resize -= WindowResized; + _keyboard.KeyChar -= OnKeyChar; + + instance.ImGui_ImplDX11_Shutdown(); + + ImGui.DestroyContext(ImGuiContext); + } +} diff --git a/src/Lab/Experiments/ImGuiDX11/ImGuiFontConfig.cs b/src/Lab/Experiments/ImGuiDX11/ImGuiFontConfig.cs new file mode 100644 index 0000000000..4b13b06e44 --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/ImGuiFontConfig.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ImGuiNET; + +namespace Silk.NET.Lab.Experiments.ImGuiDX11; + +public readonly struct ImGuiFontConfig +{ + public ImGuiFontConfig(string fontPath, int fontSize, Func getGlyphRange = null) + { + if (fontSize <= 0) throw new ArgumentOutOfRangeException(nameof(fontSize)); + FontPath = fontPath ?? throw new ArgumentNullException(nameof(fontPath)); + FontSize = fontSize; + GetGlyphRange = getGlyphRange; + } + + public string FontPath { get; } + public int FontSize { get; } + public Func GetGlyphRange { get; } +} \ No newline at end of file diff --git a/src/Lab/Experiments/ImGuiDX11/ImGui_Impl_DX11.cs b/src/Lab/Experiments/ImGuiDX11/ImGui_Impl_DX11.cs new file mode 100644 index 0000000000..55a03f3da9 --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/ImGui_Impl_DX11.cs @@ -0,0 +1,777 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using ImGuiNET; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D.Compilers; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; +using Silk.NET.Maths; + +namespace Silk.NET.Lab.Experiments.ImGuiDX11; + +public sealed class ImGui_Impl_DX11 +{ + private const uint D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE = 16u; + + // DirectX11 data + // Released in ImGui_ImplDX11_Shutdown() + private ComPtr pd3dDevice = default; + private ComPtr pd3dDeviceContext = default; + private ComPtr pFactory = default; + // Released in ImGui_ImplDX11_InvalidateDeviceObjects() + private ComPtr pVB = default; + private ComPtr pIB = default; + private ComPtr pVertexShader = default; + private ComPtr pInputLayout = default; + private ComPtr pVertexConstantBuffer=default; + private ComPtr pPixelShader = default; + private ComPtr pRasterizerState = default; + private ComPtr pBlendState = default; + private ComPtr pDepthStencilState = default; + private ComPtr pFontSampler = default; + // Released in ImGui_ImplDX11_DestroyFontsTexture() + private ComPtr pFontTextureView = default; + + private int vertexBufferSize = 5_000; + private int indexBufferSize = 10_000; + + // Do not use BackendRendererUserData, it messed up my pointers randomly + + #region Functions + + /// + /// Initializes the DirectX11 backend for ImGui. + /// + /// + /// Pointer to the already initialized D3D11 device. + /// + /// + /// Pointer to the already initialized D3D11 device context. + /// + /// + /// True if the initialization was successful, throws otherwise. + /// + public unsafe bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* deviceContext) + { + ImGuiIOPtr io = ImGui.GetIO(); + + // TODO: Check version macro + // IMGUI_CHECKVERSION(); + // Debug.Assert(io.BackendRendererUserData == IntPtr.Zero, "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + // Do not use BackendRendererUserData it messed my pointers randomly + io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + + // Get factory from device + ComPtr pDXGIDevice = default; + ComPtr pDXGIAdapter = default; + ComPtr pFactory = default; + + if (device->QueryInterface(out pDXGIDevice) == 0) + { + if (pDXGIDevice.GetParent(out pDXGIAdapter) == 0) + { + if (pDXGIAdapter.GetParent(out pFactory) == 0) + { + pd3dDevice = device; + pd3dDeviceContext = deviceContext; + this.pFactory = pFactory; + } + } + } + pDXGIDevice.Release(); + pDXGIAdapter.Release(); + pd3dDevice.AddRef(); + pd3dDeviceContext.AddRef(); + + return true; + } + + /// + /// Creates all required DX11 resources for ImGui. + /// + /// + /// True if the resources were created successfully, false otherwise. + /// + internal unsafe bool ImGui_ImplDX11_CreateDeviceObjects() + { + if ((long)pd3dDevice.Handle == 0) + return false; + if ((long)pFontSampler.Handle != 0) + ImGui_ImplDX11_InvalidateDeviceObjects(); + + // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) + // If you would like to use this DX11 sample code but remove this dependency you can: + // 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution] + // 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL. + // See https://github.com/ocornut/imgui/pull/638 for sources and details. + D3DCompiler compiler = D3DCompiler.GetApi(); + + // Create the vertex shader + string vertexShader = + @"cbuffer vertexBuffer : register(b0) + { + float4x4 ProjectionMatrix; + }; + struct VS_INPUT + { + float2 pos : POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; + }; + + struct PS_INPUT + { + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; + }; + + PS_INPUT main(VS_INPUT input) + { + PS_INPUT output; + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f)); + output.col = input.col; + output.uv = input.uv; + return output; + }"; + + ComPtr vertexShaderBlob = default; + ComPtr errorBlob = default; + byte[] shaderBytes = Encoding.ASCII.GetBytes(vertexShader); + HResult hr = compiler.Compile( + in shaderBytes[0], + (nuint) shaderBytes.Length, + nameof(vertexShader), + null, + ref Unsafe.NullRef(), + "main", + "vs_4_0", + 0, + 0, + ref vertexShaderBlob, + ref errorBlob); + if (hr.IsFailure) + { + if (errorBlob.Handle is not null) + { + Console.WriteLine(SilkMarshal.PtrToString((nint) errorBlob.GetBufferPointer())); + } + hr.Throw(); + return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! + } + try + { + SilkMarshal.ThrowHResult( + pd3dDevice.CreateVertexShader(vertexShaderBlob.GetBufferPointer(), vertexShaderBlob.GetBufferSize(), + ref Unsafe.NullRef(), ref pVertexShader)); + } + catch + { + vertexShaderBlob.Release(); + throw; + } + + fixed (byte* pos = SilkMarshal.StringToMemory("POSITION")) + fixed (byte* uv = SilkMarshal.StringToMemory("TEXCOORD")) + fixed (byte* col = SilkMarshal.StringToMemory("COLOR")) + { + // Create the input layout + ReadOnlySpan local_layout = new[]{ + new InputElementDesc() + { + SemanticName = pos, + SemanticIndex = 0, + Format = Format.FormatR32G32Float, + InputSlot = 0, + AlignedByteOffset = (uint)Marshal.OffsetOf(nameof(ImDrawVert.pos)), + InputSlotClass = InputClassification.PerVertexData, + InstanceDataStepRate = 0 + }, + new InputElementDesc() + { + SemanticName = uv, + SemanticIndex = 0, + Format = Format.FormatR32G32Float, + InputSlot = 0, + AlignedByteOffset = (uint)Marshal.OffsetOf(nameof(ImDrawVert.uv)), + InputSlotClass = InputClassification.PerVertexData, + InstanceDataStepRate = 0 + }, + new InputElementDesc() + { + SemanticName = col, + SemanticIndex = 0, + Format = Format.FormatR8G8B8A8Unorm, + InputSlot = 0, + AlignedByteOffset = (uint)Marshal.OffsetOf(nameof(ImDrawVert.col)), + InputSlotClass = InputClassification.PerVertexData, + InstanceDataStepRate = 0 + }, + }; + try + { + SilkMarshal.ThrowHResult( + pd3dDevice.CreateInputLayout(local_layout, 3, + vertexShaderBlob.GetBufferPointer(), (uint)vertexShaderBlob.GetBufferSize(), + pInputLayout.GetAddressOf())); + } + catch + { + vertexShaderBlob.Release(); + throw; + } + } + vertexShaderBlob.Release(); + // Release the error blob + errorBlob.Release(); + + // Create the constant buffer + BufferDesc desc = new BufferDesc + { + ByteWidth = (uint)sizeof(VERTEX_CONSTANT_BUFFER_DX11), + Usage = Usage.Dynamic, + BindFlags = (uint)BindFlag.ConstantBuffer, + CPUAccessFlags = (uint)CpuAccessFlag.Write, + MiscFlags = 0 + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateBuffer(in desc, null, ref pVertexConstantBuffer)); + + // Create the pixel shader + string pixelShader = + @"struct PS_INPUT + { + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; + }; + sampler sampler0; + Texture2D texture0; + + float4 main(PS_INPUT input) : SV_Target + { + float4 out_col = input.col * texture0.Sample(sampler0, input.uv); + return out_col; + }"; + + ComPtr pixelShaderBlob = default; + errorBlob = null; + shaderBytes = Encoding.ASCII.GetBytes(pixelShader); + hr = compiler.Compile( + in shaderBytes[0], + (nuint) shaderBytes.Length, + nameof(pixelShader), + null, + ref Unsafe.NullRef(), + "main", + "ps_4_0", + 0, + 0, + ref pixelShaderBlob, + ref errorBlob); + if (hr.IsFailure) + { + if (errorBlob.Handle is not null) + { + Console.WriteLine(SilkMarshal.PtrToString((nint) errorBlob.GetBufferPointer())); + } + hr.Throw(); + return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! + } + + try + { + SilkMarshal.ThrowHResult( + pd3dDevice.CreatePixelShader(pixelShaderBlob.GetBufferPointer(), pixelShaderBlob.GetBufferSize(), + ref Unsafe.NullRef(), ref pPixelShader)); + } + catch + { + pixelShaderBlob.Release(); + throw; + } + // TODO: Can be improved by using a try-finally block + pixelShaderBlob.Release(); + // Release the error blob + errorBlob.Release(); + + // Create the blending setup + BlendDesc blendDesc = new BlendDesc + { + AlphaToCoverageEnable = false, + RenderTarget = new BlendDesc.RenderTargetBuffer() + { + Element0 = new RenderTargetBlendDesc() + { + BlendEnable = true, + SrcBlend = Blend.SrcAlpha, + DestBlend = Blend.InvSrcAlpha, + BlendOp = BlendOp.Add, + SrcBlendAlpha = Blend.One, + DestBlendAlpha = Blend.InvSrcAlpha, + BlendOpAlpha = BlendOp.Add, + RenderTargetWriteMask = (byte)ColorWriteEnable.All, + } + } + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateBlendState(in blendDesc, ref pBlendState)); + + // Create the rasterizer state + RasterizerDesc rasterizerDesc = new RasterizerDesc + { + FillMode = FillMode.Solid, + CullMode = CullMode.None, + ScissorEnable = true, + DepthClipEnable = true + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateRasterizerState(in rasterizerDesc, ref pRasterizerState)); + + // Create depth-stencil State + DepthStencilDesc depthStencilDesc = new DepthStencilDesc + { + DepthEnable = false, + DepthWriteMask = DepthWriteMask.All, + DepthFunc = ComparisonFunc.Always, + StencilEnable = false, + FrontFace = new DepthStencilopDesc() + { + StencilFailOp = StencilOp.Keep, + StencilDepthFailOp = StencilOp.Keep, + StencilPassOp = StencilOp.Keep, + StencilFunc = ComparisonFunc.Always + }, + }; + depthStencilDesc.BackFace = depthStencilDesc.FrontFace; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateDepthStencilState(in depthStencilDesc, ref pDepthStencilState)); + + // Create texture sampler + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + SamplerDesc samplerDesc = new SamplerDesc + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Clamp, + AddressV = TextureAddressMode.Clamp, + AddressW = TextureAddressMode.Clamp, + MipLODBias = 0f, + ComparisonFunc = ComparisonFunc.Always, + MinLOD = 0f, + MaxLOD = 0f + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateSamplerState(in samplerDesc, ref pFontSampler)); + + ImGui_ImplDX11_CreateFontsTexture(); + + return true; + } + + /// + /// Setups basic/defaults DX11 bindings/state. + /// + /// + /// Size of the display for viewport setup. + /// + private unsafe void ImGui_ImplDX11_SetupRenderState(Vector2 displaySize) + { + // Setup viewport + Viewport vp = new Viewport + { + Width = displaySize.X, + Height = displaySize.Y, + MinDepth = 0.0f, + MaxDepth = 1.0f + }; + vp.TopLeftX = vp.TopLeftY = 0; + pd3dDeviceContext.RSSetViewports(1, in vp); + + // Setup shader and vertex buffers + uint stride = (uint)sizeof(ImDrawVert); + uint offset = 0; + + pd3dDeviceContext.IASetInputLayout(pInputLayout); + pd3dDeviceContext.IASetVertexBuffers(0, 1, ref pVB, in stride, in offset); + // WARNING there, ImDrawIdx is not defined by ImGui.NET, and uses R16_UINT by default (16-bit indices, aka ushort) + // Using R32_UINT would require recompiling cimgui with the correct define + // See https://github.com/ImGuiNET/ImGui.NET/issues/248 + // Original code: https://github.com/ocornut/imgui/blob/v1.91.6/imgui.h#L259 + pd3dDeviceContext.IASetIndexBuffer(pIB, Format.FormatR16Uint, 0); + pd3dDeviceContext.IASetPrimitiveTopology(D3DPrimitiveTopology.D3D11PrimitiveTopologyTrianglelist); + pd3dDeviceContext.VSSetShader(pVertexShader, null, 0); + pd3dDeviceContext.VSSetConstantBuffers(0, 1, ref pVertexConstantBuffer); + pd3dDeviceContext.PSSetShader(pPixelShader, null, 0); + pd3dDeviceContext.PSSetSamplers(0, 1, ref pFontSampler); + pd3dDeviceContext.GSSetShader(ref Unsafe.NullRef(), null, 0); + pd3dDeviceContext.HSSetShader(ref Unsafe.NullRef(), null, 0); // In theory we should backup and restore this as well.. very infrequently used.. + pd3dDeviceContext.DSSetShader(ref Unsafe.NullRef(), null, 0); // In theory we should backup and restore this as well.. very infrequently used.. + pd3dDeviceContext.CSSetShader(ref Unsafe.NullRef(), null, 0); // In theory we should backup and restore this as well.. very infrequently used.. + + // Setup blend state + float[] blendFactor = {0f, 0f, 0f, 0f}; + fixed (float* blendFactorPtr = blendFactor) + { + pd3dDeviceContext.OMSetBlendState(pBlendState, blendFactorPtr, 0xffffffff); + } + pd3dDeviceContext.OMSetDepthStencilState(pDepthStencilState, 0); + pd3dDeviceContext.RSSetState(pRasterizerState); + } + + /// + /// Renders the raw ImGui draw data. + /// and must have been called first. + /// + /// + /// ImGui draw data to send to the graphics pipeline to render. + /// + /// + /// User callbacks are not implemented and will throw. + /// + public unsafe void ImGui_ImplDX11_RenderDrawData(ImDrawDataPtr drawDataPtr) + { + // Avoid rendering when minimized + if (drawDataPtr.DisplaySize.X <= 0.0f || drawDataPtr.DisplaySize.Y <= 0.0f) + return; + + // Create and grow vertex/index buffers if needed + if ((long)pVB.Handle == 0 || vertexBufferSize < drawDataPtr.TotalVtxCount) + { + // Looks like it is never called, but there's an OR gate right above + if ((long)pVB.Handle != 0) + { + pVB.Release(); + pVB = null; + } + vertexBufferSize = drawDataPtr.TotalVtxCount + 5000; + BufferDesc desc = new BufferDesc + { + Usage = Usage.Dynamic, + ByteWidth = (uint)(vertexBufferSize * sizeof(ImDrawVert)), + BindFlags = (uint)BindFlag.VertexBuffer, + CPUAccessFlags = (uint)CpuAccessFlag.Write, + MiscFlags = 0 + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateBuffer(in desc, null, ref pVB)); + } + if ((long)pIB.Handle == 0 || indexBufferSize < drawDataPtr.TotalIdxCount) + { + // Looks like it is never called, but there's an OR gate right above + if ((long)pIB.Handle != 0) + { + pIB.Release(); + pIB = null; + } + indexBufferSize = drawDataPtr.TotalIdxCount + 10000; + BufferDesc desc = new BufferDesc + { + Usage = Usage.Dynamic, + ByteWidth = (uint)(indexBufferSize * sizeof(ushort)), + BindFlags = (uint)BindFlag.IndexBuffer, + CPUAccessFlags = (uint)CpuAccessFlag.Write + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateBuffer(in desc, null, ref pIB)); + } + + // Upload vertex/index data into a single contiguous GPU buffer + MappedSubresource vtxResource = default; + MappedSubresource idxResource = default; + SilkMarshal.ThrowHResult( + pd3dDeviceContext.Map(pVB, 0, Map.WriteDiscard, 0, ref vtxResource)); + SilkMarshal.ThrowHResult( + pd3dDeviceContext.Map(pIB, 0, Map.WriteDiscard, 0, ref idxResource)); + // TODO: Check those casts, should be fine but idk + ImDrawVert* vtxDstResource = (ImDrawVert*)vtxResource.PData; + ushort* idxDstResource = (ushort*)idxResource.PData; + for (int n = 0; n < drawDataPtr.CmdListsCount; n++) + { + ImDrawListPtr drawListPtr = drawDataPtr.CmdLists[n]; + Unsafe.CopyBlock(vtxDstResource, drawListPtr.VtxBuffer.Data.ToPointer(), (uint)(drawListPtr.VtxBuffer.Size * sizeof(ImDrawVert))); + Unsafe.CopyBlock(idxDstResource, drawListPtr.IdxBuffer.Data.ToPointer(), (uint)(drawListPtr.IdxBuffer.Size * sizeof(ushort))); + vtxDstResource += drawListPtr.VtxBuffer.Size; + idxDstResource += drawListPtr.IdxBuffer.Size; + } + pd3dDeviceContext.Unmap(pVB, 0); + pd3dDeviceContext.Unmap(pIB, 0); + + // Setup orthographic projection matrix into our constant buffer + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + MappedSubresource mappedResource = default; + SilkMarshal.ThrowHResult( + pd3dDeviceContext.Map(pVertexConstantBuffer, 0, Map.WriteDiscard, 0, ref mappedResource)); + VERTEX_CONSTANT_BUFFER_DX11* constantBuffer = (VERTEX_CONSTANT_BUFFER_DX11*)mappedResource.PData; + float L = drawDataPtr.DisplayPos.X; + float R = drawDataPtr.DisplayPos.X + drawDataPtr.DisplaySize.X; + float T = drawDataPtr.DisplayPos.Y; + float B = drawDataPtr.DisplayPos.Y + drawDataPtr.DisplaySize.Y; + Matrix4X4 mvp = new Matrix4X4( + 2.0f / (R - L), 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f / (T - B), 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + (R + L) / (L - R), (T + B) / (B - T), 0.5f, 1.0f + ); + Unsafe.CopyBlock(Unsafe.AsPointer(ref constantBuffer->mvp), Unsafe.AsPointer(ref mvp), (uint)sizeof(Matrix4X4)); + pd3dDeviceContext.Unmap(pVertexConstantBuffer, 0); + + // Backup previous DX11 state + BACKUP_DX11_STATE old = new BACKUP_DX11_STATE(); + old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE; + pd3dDeviceContext.RSGetScissorRects(ref old.ScissorRectsCount, old.ScissorRects); + pd3dDeviceContext.RSGetViewports(ref old.ViewportsCount, old.Viewports); + pd3dDeviceContext.RSGetState(ref old.RS); + pd3dDeviceContext.OMGetBlendState(ref old.BlendState, old.BlendFactor, ref old.SampleMask); + pd3dDeviceContext.OMGetDepthStencilState(ref old.DepthStencilState, ref old.StencilRef); + pd3dDeviceContext.PSGetShaderResources(0, 1, ref old.PSShaderResource); + pd3dDeviceContext.PSGetSamplers(0, 1, ref old.PSSampler); + old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256; + pd3dDeviceContext.PSGetShader(ref old.PS, ref old.PSInstances, ref old.PSInstancesCount); + pd3dDeviceContext.VSGetShader(ref old.VS, ref old.VSInstances, ref old.VSInstancesCount); + pd3dDeviceContext.VSGetConstantBuffers(0, 1, ref old.VSConstantBuffer); + pd3dDeviceContext.GSGetShader(ref old.GS, ref old.GSInstances, ref old.GSInstancesCount); + + pd3dDeviceContext.IAGetPrimitiveTopology(ref old.PrimitiveTopology); + pd3dDeviceContext.IAGetIndexBuffer(ref old.IndexBuffer, ref old.IndexBufferFormat, ref old.IndexBufferOffset); + pd3dDeviceContext.IAGetVertexBuffers(0, 1, ref old.VertexBuffer, ref old.VertexBufferStride, ref old.VertexBufferOffset); + pd3dDeviceContext.IAGetInputLayout(ref old.InputLayout); + + // Setup desired DX state + ImGui_ImplDX11_SetupRenderState(drawDataPtr.DisplaySize); + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int globalIdxOffset = 0; + int globalVtxOffset = 0; + Vector2 clipOff = drawDataPtr.DisplayPos; + for (int n = 0; n < drawDataPtr.CmdListsCount; n++) + { + ImDrawListPtr drawListPtr = drawDataPtr.CmdLists[n]; + for (int cmd_i = 0; cmd_i < drawListPtr.CmdBuffer.Size; cmd_i++) + { + ImDrawCmdPtr drawCmdPtr = drawListPtr.CmdBuffer[cmd_i]; + if ((long)drawCmdPtr.UserCallback != 0) + { + throw new NotImplementedException(); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + Vector2D clipMin = new Vector2D(drawCmdPtr.ClipRect.X - clipOff.X, drawCmdPtr.ClipRect.Y - clipOff.Y); + Vector2D clipMax = new Vector2D(drawCmdPtr.ClipRect.Z - clipOff.X, drawCmdPtr.ClipRect.W - clipOff.Y); + if (clipMax.X <= clipMin.X || clipMax.Y <= clipMin.Y) + continue; + + // Apply scissor/clipping rectangle + Box2D r = new Box2D((int)clipMin.X, (int)clipMin.Y, (int)clipMax.X, (int)clipMax.Y); + pd3dDeviceContext.RSSetScissorRects(1, in r); + + // Bind texture, Draw + // Moved this line out of the for loop since it caused issues + // ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd.TextureId; + // pd3dDeviceContext.PSSetShaderResources(0, 1, &texture_srv); + pd3dDeviceContext.DrawIndexed(drawCmdPtr.ElemCount, (uint)(drawCmdPtr.IdxOffset + globalIdxOffset), (int)(drawCmdPtr.VtxOffset + globalVtxOffset)); + } + } + globalIdxOffset += drawListPtr.IdxBuffer.Size; + globalVtxOffset += drawListPtr.VtxBuffer.Size; + } + // Moved font texture binding out of the for loop + pd3dDeviceContext.PSSetShaderResources(0u, 1u, ref pFontTextureView); + + // Restore modified DX state + pd3dDeviceContext.RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects); + // TODO: Solve viewports issue, throws nullptrException + // pd3dDeviceContext.RSSetViewports(old.ViewportsCount, old.Viewports); + pd3dDeviceContext.RSSetState(old.RS); + if (old.RS != null) old.RS->Release(); + pd3dDeviceContext.OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); + if (old.BlendState != null) old.BlendState->Release(); + pd3dDeviceContext.OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); + if (old.DepthStencilState != null) old.DepthStencilState->Release(); + pd3dDeviceContext.PSSetShaderResources(0, 1, in old.PSShaderResource); + if (old.PSShaderResource != null) old.PSShaderResource->Release(); + pd3dDeviceContext.PSSetSamplers(0, 1, in old.PSSampler); + if (old.PSSampler != null) old.PSSampler->Release(); + pd3dDeviceContext.PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); + if (old.PS != null) old.PS->Release(); + if (old.PSInstances.Handle != null) old.PSInstances.Release(); + pd3dDeviceContext.VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); + if (old.VS != null) old.VS->Release(); + if (old.VSInstances.Handle != null) old.VSInstances.Release(); + pd3dDeviceContext.VSSetConstantBuffers(0, 1, in old.VSConstantBuffer); + if (old.VSConstantBuffer != null) old.VSConstantBuffer->Release(); + pd3dDeviceContext.GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); + if (old.GS != null) old.GS->Release(); + if (old.GSInstances.Handle != null) old.GSInstances.Release(); + pd3dDeviceContext.IASetPrimitiveTopology(old.PrimitiveTopology); + pd3dDeviceContext.IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); + if (old.IndexBuffer != null) old.IndexBuffer->Release(); + pd3dDeviceContext.IASetVertexBuffers(0, 1, in old.VertexBuffer, in old.VertexBufferStride, in old.VertexBufferOffset); + if (old.VertexBuffer != null) old.VertexBuffer->Release(); + pd3dDeviceContext.IASetInputLayout(old.InputLayout); + if (old.InputLayout != null) old.InputLayout->Release(); + } + + /// + /// Creates the texture for the font atlas, retrieves it from ImGui and uploads it to the graphics system + /// + private unsafe void ImGui_ImplDX11_CreateFontsTexture() + { + // Build texture atlas + ImGuiIOPtr io = ImGui.GetIO(); + io.Fonts.GetTexDataAsRGBA32(out byte* pixels,out int width, out int height); + + // Upload texture to graphics system + Texture2DDesc desc = new Texture2DDesc + { + Width = (uint)width, + Height = (uint)height, + MipLevels = 1, + ArraySize = 1, + Format = Format.FormatR8G8B8A8Unorm, + SampleDesc = new SampleDesc() + { + Count = 1, + }, + Usage = Usage.Default, + BindFlags = (uint)BindFlag.ShaderResource, + CPUAccessFlags = 0, + }; + + ComPtr pTexture = default; + SubresourceData subResource = new SubresourceData + { + PSysMem = pixels, + SysMemPitch = desc.Width * 4, + SysMemSlicePitch = 0 + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateTexture2D(in desc, in subResource, ref pTexture)); + Debug.Assert(pTexture.Handle != null); + + // Create texture view + ShaderResourceViewDesc srvDesc = new ShaderResourceViewDesc + { + Format = Format.FormatR8G8B8A8Unorm, + ViewDimension = D3DSrvDimension.D3D11SrvDimensionTexture2D, + Texture2D = new Tex2DSrv() + { + MipLevels = desc.MipLevels, + MostDetailedMip = 0, + }, + }; + SilkMarshal.ThrowHResult( + pd3dDevice.CreateShaderResourceView(pTexture, in srvDesc, ref pFontTextureView)); + pTexture.Release(); + + // Store our identifier, not useful, but we keep it anyway + io.Fonts.SetTexID((IntPtr)pFontTextureView.Handle); + // Not sure where to put it, removed it from cmd buffers + pd3dDeviceContext.PSSetShaderResources(0, 1, ref pFontTextureView); + } + + /// + /// Destroys the font texture and clears the font texture view. + /// + private unsafe void ImGui_ImplDX11_DestroyFontsTexture() + { + if ((long)pFontTextureView.Handle != 0) + { + pFontTextureView.Release(); + pFontTextureView = null; + ImGui.GetIO().Fonts.SetTexID(IntPtr.Zero); // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well. + } + } + + /// + /// Releases D3D11 resources created by . + /// + private unsafe void ImGui_ImplDX11_InvalidateDeviceObjects() + { + if ((long)pd3dDevice.Handle == 0) + return; + + ImGui_ImplDX11_DestroyFontsTexture(); + + pFontSampler.Release(); + pFontSampler = null; + pIB.Release(); + pIB = null; + pVB.Release(); + pVB = null; + pBlendState.Release(); + pBlendState = null; + pDepthStencilState.Release(); + pDepthStencilState = null; + pRasterizerState.Release(); + pRasterizerState = null; + pPixelShader.Release(); + pPixelShader = null; + pVertexConstantBuffer.Release(); + pVertexConstantBuffer = null; + pInputLayout.Release(); + pInputLayout = null; + pVertexShader.Release(); + pVertexShader = null; + } + + public void ImGui_ImplDX11_Shutdown() + { + ImGuiIOPtr io = ImGui.GetIO(); + + ImGui_ImplDX11_InvalidateDeviceObjects(); + pFactory.Release(); + pd3dDevice.Release(); + pd3dDeviceContext.Release(); + + io.BackendRendererUserData = IntPtr.Zero; + io.BackendFlags &= ~ImGuiBackendFlags.RendererHasVtxOffset; + } + + #endregion + + #region Structs + + struct VERTEX_CONSTANT_BUFFER_DX11 + { + public Matrix4X4 mvp; + } + + // Backup DX state that will be modified to restore it afterward (unfortunately this is very ugly looking and verbose. Close your eyes!) + // I added some pointers to pass arrays of instances where I thought it was needed + unsafe struct BACKUP_DX11_STATE + { + public uint ScissorRectsCount, ViewportsCount; + public Box2D* ScissorRects; + public Viewport* Viewports; + public ID3D11RasterizerState* RS; + public ID3D11BlendState* BlendState; + public float* BlendFactor; + public uint SampleMask; + public uint StencilRef; + public ID3D11DepthStencilState* DepthStencilState; + public ID3D11ShaderResourceView* PSShaderResource; + public ID3D11SamplerState* PSSampler; + public ID3D11PixelShader* PS; + public ID3D11VertexShader* VS; + public ID3D11GeometryShader* GS; + public uint PSInstancesCount, VSInstancesCount, GSInstancesCount; + public ComPtr PSInstances, VSInstances, GSInstances; // 256 is max according to PSSetShader documentation + public D3DPrimitiveTopology PrimitiveTopology; + public ID3D11Buffer* IndexBuffer, VertexBuffer, VSConstantBuffer; + public uint IndexBufferOffset, VertexBufferStride, VertexBufferOffset; + public Format IndexBufferFormat; + public ID3D11InputLayout* InputLayout; + } + + #endregion +} diff --git a/src/Lab/Experiments/ImGuiDX11/README.txt b/src/Lab/Experiments/ImGuiDX11/README.txt new file mode 100644 index 0000000000..0b0e739259 --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/README.txt @@ -0,0 +1,3 @@ +Adapted from the DirectX11 reference implementation of ImGui under the provisions of the MIT license at: +https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_dx11.h +https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_dx11.cpp