From 354dfccf3db6da02a5f75d6af56bc3aa4dc2bd6d Mon Sep 17 00:00:00 2001 From: Patrick Dawson Date: Fri, 21 Jul 2023 03:38:40 +0100 Subject: [PATCH] refactor multi-threaded renderer --- addons/imgui-godot/ImGuiGodot/ImGuiGD.cs | 1 + .../Internal/RdRendererThreadSafe.cs | 110 +++++++++++------- 2 files changed, 68 insertions(+), 43 deletions(-) diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs b/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs index e3269dc..7fc9e22 100644 --- a/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs +++ b/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs @@ -68,6 +68,7 @@ public static void Init(Window mainWindow, Rid mainSubViewport, float? scale = n renderer = RendererType.Canvas; } + // there's no way to get the actual current thread model, eg if --render-thread is used int threadModel = (int)ProjectSettings.GetSetting("rendering/driver/threads/thread_model"); Internal.State.Instance = new(mainWindow, mainSubViewport, renderer switch diff --git a/addons/imgui-godot/ImGuiGodot/Internal/RdRendererThreadSafe.cs b/addons/imgui-godot/ImGuiGodot/Internal/RdRendererThreadSafe.cs index 248d4d0..4a768b1 100644 --- a/addons/imgui-godot/ImGuiGodot/Internal/RdRendererThreadSafe.cs +++ b/addons/imgui-godot/ImGuiGodot/Internal/RdRendererThreadSafe.cs @@ -1,102 +1,126 @@ using Godot; using ImGuiNET; using System; +using System.Collections.Generic; using System.Runtime.InteropServices; +using SharedList = ImGuiGodot.Internal.DisposableList; + namespace ImGuiGodot.Internal; -internal sealed class RdRendererThreadSafe : RdRenderer, IRenderer +internal sealed class ClonedDrawData : IDisposable { - public new string Name => "imgui_impl_godot4_rd_mt"; + public ImDrawDataPtr Data { get; private set; } - private readonly object _sharedDataLock = new(); - private Tuple[] _dataToDraw = null; - - public RdRendererThreadSafe() : base() + public unsafe ClonedDrawData(ImDrawDataPtr inp) { - // draw on the renderer thread to avoid conflicts - RenderingServer.FramePreDraw += OnFramePreDraw; - } + long ddsize = Marshal.SizeOf(); - ~RdRendererThreadSafe() - { - RenderingServer.FramePreDraw -= OnFramePreDraw; + // start with a shallow copy + Data = new(ImGui.MemAlloc((uint)ddsize)); + Buffer.MemoryCopy(inp.NativePtr, Data.NativePtr, ddsize, ddsize); + + // clone the draw data + Data.NativePtr->CmdLists = (ImDrawList**)ImGui.MemAlloc((uint)(Marshal.SizeOf() * inp.CmdListsCount)); + for (int i = 0; i < inp.CmdListsCount; ++i) + { + Data.NativePtr->CmdLists[i] = inp.CmdListsRange[i].CloneOutput().NativePtr; + } } - private static unsafe ImDrawDataPtr CopyDrawData(ImDrawDataPtr drawData) + public unsafe void Dispose() { - long ddsize = Marshal.SizeOf(); - ImDrawDataPtr rv = new(ImGui.MemAlloc((uint)ddsize)); - Buffer.MemoryCopy(drawData.NativePtr, rv.NativePtr, ddsize, ddsize); - rv.CmdLists = ImGui.MemAlloc((uint)(Marshal.SizeOf() * drawData.CmdListsCount)); + if (Data.NativePtr == null) + return; - for (int i = 0; i < drawData.CmdListsCount; ++i) + for (int i = 0; i < Data.CmdListsCount; ++i) { - rv.NativePtr->CmdLists[i] = drawData.CmdListsRange[i].CloneOutput().NativePtr; + Data.CmdListsRange[i].Destroy(); } - return rv; + ImGui.MemFree(Data.CmdLists); + Data.Destroy(); + Data = new(null); } +} + +internal sealed class DisposableList : List>, IDisposable where U : IDisposable +{ + public DisposableList() : base() { } + public DisposableList(int capacity) : base(capacity) { } - private static unsafe void FreeDrawData(ImDrawDataPtr drawData) + public void Dispose() { - for (int i = 0; i < drawData.CmdListsCount; ++i) + foreach (var tuple in this) { - drawData.CmdListsRange[i].Destroy(); + tuple.Item2.Dispose(); } - ImGui.MemFree(drawData.CmdLists); - ImGui.MemFree((IntPtr)drawData.NativePtr); + Clear(); } +} - private static void FreeAll(Tuple[] array) +internal sealed class RdRendererThreadSafe : RdRenderer, IRenderer +{ + public new string Name => "imgui_impl_godot4_rd_mt"; + + private readonly object _sharedDataLock = new(); + private SharedList _dataToDraw; + + public RdRendererThreadSafe() : base() { - foreach (var kv in array) - { - FreeDrawData(kv.Item2); - } + // draw on the renderer thread to avoid conflicts + RenderingServer.FramePreDraw += OnFramePreDraw; + } + + ~RdRendererThreadSafe() + { + RenderingServer.FramePreDraw -= OnFramePreDraw; } public new void RenderDrawData() { var pio = ImGui.GetPlatformIO(); - var newData = new Tuple[pio.Viewports.Size]; + var newData = new SharedList(pio.Viewports.Size); for (int i = 0; i < pio.Viewports.Size; ++i) { - // TODO: skip minimized windows var vp = pio.Viewports[i]; + if (vp.Flags.HasFlag(ImGuiViewportFlags.IsMinimized)) + continue; + ReplaceTextureRids(vp.DrawData); Rid vprid = Util.ConstructRid((ulong)vp.RendererUserData); - newData[i] = new(GetFramebuffer(vprid), CopyDrawData(vp.DrawData)); + newData.Add(new(GetFramebuffer(vprid), new(vp.DrawData))); } lock (_sharedDataLock) { // if a frame was skipped, free old data - if (_dataToDraw != null) - FreeAll(_dataToDraw); + _dataToDraw?.Dispose(); _dataToDraw = newData; } } - private void OnFramePreDraw() + private SharedList TakeSharedData() { - Tuple[] dataArray = null; lock (_sharedDataLock) { - // take ownership of shared data - dataArray = _dataToDraw; + var rv = _dataToDraw; _dataToDraw = null; + return rv ?? new(); } + } - if (dataArray == null) - return; + private void OnFramePreDraw() + { + // take ownership of shared data + using SharedList dataArray = TakeSharedData(); foreach (var kv in dataArray) { if (RD.FramebufferIsValid(kv.Item1)) - RenderOne(kv.Item1, kv.Item2); + RenderOne(kv.Item1, kv.Item2.Data); } - FreeAll(dataArray); + FreeUnusedTextures(); } }