From 7dbaf3ba9fd115b0e8755da1a1c30fe910b90ea6 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sat, 11 Nov 2023 16:43:24 +0900 Subject: [PATCH] Add ImDrawCmd.UserCallback support --- .../ImGui_Impl/Renderers/IImGuiRenderer.cs | 7 +++ .../ImGui_Impl/Renderers/ImGui_Impl_DX11.cs | 52 ++++++++++++++---- .../Renderers/ImGui_Impl_OpenGL3.cs | 53 ++++++++++++++++--- ImGuiScene/RawDX11Scene.cs | 16 +++--- 4 files changed, 106 insertions(+), 22 deletions(-) diff --git a/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs b/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs index 791a5b1..f2c1dca 100644 --- a/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs +++ b/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs @@ -1,4 +1,5 @@ using System; +using ImGuiNET; namespace ImGuiScene { @@ -7,10 +8,16 @@ namespace ImGuiScene /// public interface IImGuiRenderer { + public delegate void DrawCmdUserCallbackDelegate(ImDrawDataPtr drawData, ImDrawCmdPtr drawCmd); + + public nint ResetDrawCmdUserCallback { get; } + // FIXME - probably a better way to do this than params object[] ! void Init(params object[] initParams); void Shutdown(); void NewFrame(); void RenderDrawData(ImGuiNET.ImDrawDataPtr drawData); + public nint AddDrawCmdUserCallback(DrawCmdUserCallbackDelegate @delegate); + public void RemoveDrawCmdUserCallback(DrawCmdUserCallbackDelegate @delegate); } } diff --git a/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs b/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs index 652cd5f..a04eee1 100644 --- a/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs +++ b/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs @@ -6,6 +6,7 @@ using SharpDX.Mathematics.Interop; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -26,6 +27,7 @@ namespace ImGuiScene /// public unsafe class ImGui_Impl_DX11 : IImGuiRenderer { + private readonly Dictionary _drawCallbacks = new(); private IntPtr _renderNamePtr; private Device _device; private DeviceContext _deviceContext; @@ -46,6 +48,8 @@ public unsafe class ImGui_Impl_DX11 : IImGuiRenderer // so we don't make a temporary object every frame private RawColor4 _blendColor = new RawColor4(0, 0, 0, 0); + public nint ResetDrawCmdUserCallback { get; private set; } + // TODO: I'll clean this up better later private class StateBackup : IDisposable { @@ -426,23 +430,31 @@ public void RenderDrawData(ImDrawDataPtr drawData) for (int cmd = 0; cmd < cmdList.CmdBuffer.Size; cmd++) { var pcmd = cmdList.CmdBuffer[cmd]; - if (pcmd.UserCallback != IntPtr.Zero) - { - // TODO - throw new NotImplementedException(); - } - else + + if (pcmd.UserCallback == IntPtr.Zero) { // Apply scissor/clipping rectangle _deviceContext.Rasterizer.SetScissorRectangle((int)(pcmd.ClipRect.X - clipOff.X), (int)(pcmd.ClipRect.Y - clipOff.Y), (int)(pcmd.ClipRect.Z - clipOff.X), (int)(pcmd.ClipRect.W - clipOff.Y)); // Bind texture, Draw - // TODO: might be nice to store samplers for loaded textures so that we can look them up and apply them here - // rather than just always using the font sampler - var textureSrv = ShaderResourceView.FromPointer(pcmd.TextureId); + var textureSrv = CppObject.FromPointer(pcmd.TextureId); _deviceContext.PixelShader.SetShaderResource(0, textureSrv); _deviceContext.DrawIndexed((int)pcmd.ElemCount, (int)(pcmd.IdxOffset + indexOffset), (int)(pcmd.VtxOffset + vertexOffset)); } + else if (_drawCallbacks.TryGetValue(pcmd.UserCallback, out var cb)) + { + // Use custom callback + cb(drawData, pcmd); + } + else + { + Debug.WriteLine( + $"[{nameof(ImGui_Impl_DX11)})] " + + $"((ImDrawData*)0x{(ulong) drawData.NativePtr:X})" + + $"->CmdLists[{n}]" + + $"->CmdBuffer[{cmd}]" + + $".UserCallback (0x{pcmd.UserCallback}:X) is not registered for use."); + } } indexOffset += cmdList.IdxBuffer.Size; @@ -685,6 +697,7 @@ public void Init(params object[] initParams) _device = (Device)initParams[0]; _deviceContext = (DeviceContext)initParams[1]; + ResetDrawCmdUserCallback = AddDrawCmdUserCallback((drawData, _) => SetupRenderState(drawData)); InitPlatformInterface(); @@ -715,6 +728,27 @@ public void NewFrame() } } + public nint AddDrawCmdUserCallback(IImGuiRenderer.DrawCmdUserCallbackDelegate @delegate) + { + if (this._drawCallbacks.FirstOrDefault(x => x.Value == @delegate).Key is not 0 and var key) + return key; + + key = Marshal.GetFunctionPointerForDelegate(@delegate); + this._drawCallbacks.Add(key, @delegate); + return key; + } + + public void RemoveDrawCmdUserCallback(IImGuiRenderer.DrawCmdUserCallbackDelegate @delegate) + { + foreach (var key in this._drawCallbacks + .Where(x => x.Value == @delegate) + .Select(x => x.Key) + .ToArray()) + { + this._drawCallbacks.Remove(key); + } + } + /** Viewport support **/ private struct ImGuiViewportDataDx11 { diff --git a/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_OpenGL3.cs b/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_OpenGL3.cs index 3492dce..62c3359 100644 --- a/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_OpenGL3.cs +++ b/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_OpenGL3.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; @@ -18,6 +21,7 @@ public class ImGui_Impl_OpenGL3 : IImGuiRenderer { private static GL Gl = Util.Gl; + private readonly Dictionary _drawCallbacks = new(); private IntPtr _renderNamePtr; private uint _vertHandle; private uint _fragHandle; @@ -32,6 +36,8 @@ public class ImGui_Impl_OpenGL3 : IImGuiRenderer private uint _fontTexture; private uint _vertexArrayObject; + public nint ResetDrawCmdUserCallback { get; private set; } + public void RenderDrawData(ImDrawDataPtr drawData) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) @@ -98,12 +104,7 @@ public void RenderDrawData(ImDrawDataPtr drawData) for (var i = 0; i < cmdList.CmdBuffer.Size; i++) { var pcmd = cmdList.CmdBuffer[i]; - if (pcmd.UserCallback != IntPtr.Zero) - { - // TODO - throw new NotImplementedException(); - } - else + if (pcmd.UserCallback == IntPtr.Zero) { // Project scissor/clipping rectangles into framebuffer space var clipRect = new Vector4 @@ -124,6 +125,23 @@ public void RenderDrawData(ImDrawDataPtr drawData) Gl.DrawElementsBaseVertex(PrimitiveType.Triangles, pcmd.ElemCount, DrawElementsType.UnsignedShort, (IntPtr)(pcmd.IdxOffset * sizeof(short)), (int)pcmd.VtxOffset); } } + else if (_drawCallbacks.TryGetValue(pcmd.UserCallback, out var cb)) + { + // Use custom callback + cb(drawData, pcmd); + } + else + { + unsafe + { + Debug.WriteLine( + $"[{nameof(ImGui_Impl_DX11)})] " + + $"((ImDrawData*)0x{(ulong) drawData.NativePtr:X})" + + $"->CmdLists[{n}]" + + $"->CmdBuffer[{i}]" + + $".UserCallback (0x{pcmd.UserCallback}:X) is not registered for use."); + } + } } } @@ -188,6 +206,8 @@ public void Init(params object[] initParams) io.NativePtr->BackendRendererName = (byte*)_renderNamePtr.ToPointer(); } + ResetDrawCmdUserCallback = AddDrawCmdUserCallback((drawData, _) => SetupRenderState(drawData)); + // literally nothing else in the source implementation of this function is useful } @@ -210,6 +230,27 @@ public void NewFrame() } } + public nint AddDrawCmdUserCallback(IImGuiRenderer.DrawCmdUserCallbackDelegate @delegate) + { + if (this._drawCallbacks.FirstOrDefault(x => x.Value == @delegate).Key is not 0 and var key) + return key; + + key = Marshal.GetFunctionPointerForDelegate(@delegate); + this._drawCallbacks.Add(key, @delegate); + return key; + } + + public void RemoveDrawCmdUserCallback(IImGuiRenderer.DrawCmdUserCallbackDelegate @delegate) + { + foreach (var key in this._drawCallbacks + .Where(x => x.Value == @delegate) + .Select(x => x.Key) + .ToArray()) + { + this._drawCallbacks.Remove(key); + } + } + private void SetupRenderState(ImDrawDataPtr drawData) { // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill diff --git a/ImGuiScene/RawDX11Scene.cs b/ImGuiScene/RawDX11Scene.cs index b28e7a7..4e3c8e0 100644 --- a/ImGuiScene/RawDX11Scene.cs +++ b/ImGuiScene/RawDX11Scene.cs @@ -19,6 +19,7 @@ namespace ImGuiScene public sealed class RawDX11Scene : IDisposable { public Device Device { get; private set; } + public DeviceContext DeviceContext { get; private set; } public IntPtr WindowHandlePtr { get; private set; } public SwapChain SwapChain { get; private set; } @@ -28,7 +29,6 @@ public bool UpdateCursor set => this.imguiInput.UpdateCursor = value; } - private DeviceContext deviceContext; private RenderTargetView rtv; private int targetWidth; @@ -84,9 +84,11 @@ public RawDX11Scene(IntPtr nativeDevice, IntPtr nativeSwapChain) Initialize(); } + public IImGuiRenderer Renderer => this.imguiRenderer; + private void Initialize() { - this.deviceContext = this.Device.ImmediateContext; + this.DeviceContext = this.Device.ImmediateContext; using (var backbuffer = this.SwapChain.GetBackBuffer(0)) { @@ -113,7 +115,7 @@ private void InitializeImGui() ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable; - this.imguiRenderer.Init(this.Device, this.deviceContext); + this.imguiRenderer.Init(this.Device, this.DeviceContext); this.imguiInput = new ImGui_Input_Impl_Direct(WindowHandlePtr); } @@ -131,7 +133,7 @@ private void InitializeImGui() public void Render() { - this.deviceContext.OutputMerger.SetRenderTargets(this.rtv); + this.DeviceContext.OutputMerger.SetRenderTargets(this.rtv); this.imguiRenderer.NewFrame(); this.OnNewRenderFrame?.Invoke(); @@ -146,14 +148,14 @@ public void Render() ImGui.Render(); this.imguiRenderer.RenderDrawData(ImGui.GetDrawData()); - this.deviceContext.OutputMerger.SetRenderTargets((RenderTargetView)null); + this.DeviceContext.OutputMerger.SetRenderTargets((RenderTargetView)null); ImGui.UpdatePlatformWindows(); ImGui.RenderPlatformWindowsDefault(); } public void OnPreResize() { - this.deviceContext.OutputMerger.SetRenderTargets((RenderTargetView)null); + this.DeviceContext.OutputMerger.SetRenderTargets((RenderTargetView)null); this.rtv?.Dispose(); this.rtv = null; @@ -276,7 +278,7 @@ public byte[] CaptureScreenshot() using (var tex = new Texture2D(this.Device, desc)) { - this.deviceContext.CopyResource(backBuffer, tex); + this.DeviceContext.CopyResource(backBuffer, tex); using (var surf = tex.QueryInterface()) { var map = surf.Map(SharpDX.DXGI.MapFlags.Read, out DataStream dataStream);