From 0cb54dd7d41863826949e28245ec9e9f9d883ed9 Mon Sep 17 00:00:00 2001 From: Marc <67200910+marcb152@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:20:56 +0100 Subject: [PATCH 1/4] Start of ImGui DirectX11 Implementation From ef5c66cfcb3e0b643c45fd8bc55379a869b57541 Mon Sep 17 00:00:00 2001 From: Marc <67200910+marcb152@users.noreply.github.com> Date: Thu, 9 Jan 2025 23:14:06 +0100 Subject: [PATCH 2/4] Added code files. FontConfig and controller from OpenGL impl. ImGui_Impl_DX11 ported from Cpp file. WARNING: never compiled inside Silk.NET solution --- .../Experiments/ImGuiDX11/ImGuiDX11.csproj | 21 + .../ImGuiDX11/ImGuiDX11Controller.cs | 393 +++++++++ .../Experiments/ImGuiDX11/ImGuiFontConfig.cs | 21 + .../Experiments/ImGuiDX11/ImGui_Impl_DX11.cs | 778 ++++++++++++++++++ src/Lab/Experiments/ImGuiDX11/README.txt | 3 + 5 files changed, 1216 insertions(+) create mode 100644 src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj create mode 100644 src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs create mode 100644 src/Lab/Experiments/ImGuiDX11/ImGuiFontConfig.cs create mode 100644 src/Lab/Experiments/ImGuiDX11/ImGui_Impl_DX11.cs create mode 100644 src/Lab/Experiments/ImGuiDX11/README.txt diff --git a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj new file mode 100644 index 0000000000..6e526f41a0 --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj @@ -0,0 +1,21 @@ + + + + net5.0;net6.0 + enable + true + + + + + + + + + + + + + + + diff --git a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs new file mode 100644 index 0000000000..a113e6d630 --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs @@ -0,0 +1,393 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Drawing; +using System.Numerics; +using ImGuiNET; +using Silk.NET.Core.Native; +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; + + 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); + } +} \ No newline at end of file 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..1a7b0a739c --- /dev/null +++ b/src/Lab/Experiments/ImGuiDX11/ImGui_Impl_DX11.cs @@ -0,0 +1,778 @@ +// 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 == IntPtr.Zero) + return false; + if ((long)pFontSampler.Handle != IntPtr.Zero) + 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 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 == IntPtr.Zero || vertexBufferSize < drawDataPtr.TotalVtxCount) + { + // Looks like it is never called, but there's an OR gate right above + if ((long)pVB.Handle != IntPtr.Zero) + { + 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 == IntPtr.Zero || indexBufferSize < drawDataPtr.TotalIdxCount) + { + // Looks like it is never called, but there's an OR gate right above + if ((long)pIB.Handle != IntPtr.Zero) + { + 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 != IntPtr.Zero) + { + 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 != IntPtr.Zero) + { + pFontTextureView.Release(); + pFontTextureView = null; + ImGui.GetIO().Fonts.SetTexID(0); // 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 == IntPtr.Zero) + 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 +} \ No newline at end of file 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 From c221a86c25f7a9829d2bcd3a2da115eb143d6dad Mon Sep 17 00:00:00 2001 From: Marc <67200910+marcb152@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:20:48 +0100 Subject: [PATCH 3/4] Refactor to follow .NET 6.0 csharp10 rules (removed implicit long to IntPtr conversion). csharp12 is required to pass readonly ref to dx wrapper. Still wasn't able to build inside the solution --- .../Experiments/ImGuiDX11/ImGuiDX11.csproj | 1 + .../ImGuiDX11/ImGuiDX11Controller.cs | 7 +++-- .../Experiments/ImGuiDX11/ImGui_Impl_DX11.cs | 29 +++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj index 6e526f41a0..cae0f93e24 100644 --- a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj +++ b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11.csproj @@ -4,6 +4,7 @@ net5.0;net6.0 enable true + 12 diff --git a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs index a113e6d630..41dd15f5ed 100644 --- a/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs +++ b/src/Lab/Experiments/ImGuiDX11/ImGuiDX11Controller.cs @@ -1,10 +1,11 @@ // 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.Core.Native; using Silk.NET.Direct3D11; using Silk.NET.Input; using Silk.NET.Maths; @@ -40,7 +41,7 @@ public unsafe ImGuiDX11Controller(ID3D11Device* device, ID3D11DeviceContext* dev ImGuiIOPtr io = ImGui.GetIO(); if (imGuiFontConfig is not null) { - IntPtr glyphRange = imGuiFontConfig.Value.GetGlyphRange?.Invoke(io) ?? default; + IntPtr glyphRange = imGuiFontConfig.Value.GetGlyphRange?.Invoke(io) ?? default(IntPtr); io.Fonts.AddFontFromFileTTF(imGuiFontConfig.Value.FontPath, imGuiFontConfig.Value.FontSize, null, glyphRange); } @@ -390,4 +391,4 @@ public void Dispose() ImGui.DestroyContext(ImGuiContext); } -} \ 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 index 1a7b0a739c..55a03f3da9 100644 --- a/src/Lab/Experiments/ImGuiDX11/ImGui_Impl_DX11.cs +++ b/src/Lab/Experiments/ImGuiDX11/ImGui_Impl_DX11.cs @@ -102,9 +102,9 @@ public unsafe bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext /// internal unsafe bool ImGui_ImplDX11_CreateDeviceObjects() { - if ((long)pd3dDevice.Handle == IntPtr.Zero) + if ((long)pd3dDevice.Handle == 0) return false; - if ((long)pFontSampler.Handle != IntPtr.Zero) + 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) @@ -184,8 +184,7 @@ ref Unsafe.NullRef(), fixed (byte* col = SilkMarshal.StringToMemory("COLOR")) { // Create the input layout - ReadOnlySpan local_layout = - [ + ReadOnlySpan local_layout = new[]{ new InputElementDesc() { SemanticName = pos, @@ -216,7 +215,7 @@ ref Unsafe.NullRef(), InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, - ]; + }; try { SilkMarshal.ThrowHResult( @@ -418,7 +417,7 @@ private unsafe void ImGui_ImplDX11_SetupRenderState(Vector2 displaySize) 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]; + float[] blendFactor = {0f, 0f, 0f, 0f}; fixed (float* blendFactorPtr = blendFactor) { pd3dDeviceContext.OMSetBlendState(pBlendState, blendFactorPtr, 0xffffffff); @@ -444,10 +443,10 @@ public unsafe void ImGui_ImplDX11_RenderDrawData(ImDrawDataPtr drawDataPtr) return; // Create and grow vertex/index buffers if needed - if ((long)pVB.Handle == IntPtr.Zero || vertexBufferSize < drawDataPtr.TotalVtxCount) + 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 != IntPtr.Zero) + if ((long)pVB.Handle != 0) { pVB.Release(); pVB = null; @@ -464,10 +463,10 @@ public unsafe void ImGui_ImplDX11_RenderDrawData(ImDrawDataPtr drawDataPtr) SilkMarshal.ThrowHResult( pd3dDevice.CreateBuffer(in desc, null, ref pVB)); } - if ((long)pIB.Handle == IntPtr.Zero || indexBufferSize < drawDataPtr.TotalIdxCount) + 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 != IntPtr.Zero) + if ((long)pIB.Handle != 0) { pIB.Release(); pIB = null; @@ -559,7 +558,7 @@ public unsafe void ImGui_ImplDX11_RenderDrawData(ImDrawDataPtr drawDataPtr) for (int cmd_i = 0; cmd_i < drawListPtr.CmdBuffer.Size; cmd_i++) { ImDrawCmdPtr drawCmdPtr = drawListPtr.CmdBuffer[cmd_i]; - if ((long)drawCmdPtr.UserCallback != IntPtr.Zero) + if ((long)drawCmdPtr.UserCallback != 0) { throw new NotImplementedException(); } @@ -685,11 +684,11 @@ private unsafe void ImGui_ImplDX11_CreateFontsTexture() /// private unsafe void ImGui_ImplDX11_DestroyFontsTexture() { - if ((long)pFontTextureView.Handle != IntPtr.Zero) + if ((long)pFontTextureView.Handle != 0) { pFontTextureView.Release(); pFontTextureView = null; - ImGui.GetIO().Fonts.SetTexID(0); // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well. + ImGui.GetIO().Fonts.SetTexID(IntPtr.Zero); // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well. } } @@ -698,7 +697,7 @@ private unsafe void ImGui_ImplDX11_DestroyFontsTexture() /// private unsafe void ImGui_ImplDX11_InvalidateDeviceObjects() { - if ((long)pd3dDevice.Handle == IntPtr.Zero) + if ((long)pd3dDevice.Handle == 0) return; ImGui_ImplDX11_DestroyFontsTexture(); @@ -775,4 +774,4 @@ unsafe struct BACKUP_DX11_STATE } #endregion -} \ No newline at end of file +} From 65639e2f9294a290b1064cae8ef100390311e8ba Mon Sep 17 00:00:00 2001 From: Marc <67200910+marcb152@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:22:47 +0100 Subject: [PATCH 4/4] Added ImGuiDX11 Project to solution file --- Silk.NET.sln | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Silk.NET.sln b/Silk.NET.sln index 14f6557734..10051e5751 100644 --- a/Silk.NET.sln +++ b/Silk.NET.sln @@ -620,6 +620,8 @@ 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -3785,6 +3787,18 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -4087,6 +4101,7 @@ 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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F5273D7F-3334-48DF-94E3-41AE6816CD4D}