Skip to content

Commit

Permalink
refactor multi-threaded renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
pkdawson committed Jul 21, 2023
1 parent ed904f6 commit 354dfcc
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 43 deletions.
1 change: 1 addition & 0 deletions addons/imgui-godot/ImGuiGodot/ImGuiGD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 67 additions & 43 deletions addons/imgui-godot/ImGuiGodot/Internal/RdRendererThreadSafe.cs
Original file line number Diff line number Diff line change
@@ -1,102 +1,126 @@
using Godot;
using ImGuiNET;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using SharedList = ImGuiGodot.Internal.DisposableList<Godot.Rid, ImGuiGodot.Internal.ClonedDrawData>;

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<Rid, ImDrawDataPtr>[] _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<ImDrawData>();

~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<IntPtr>() * 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<ImDrawData>();
ImDrawDataPtr rv = new(ImGui.MemAlloc((uint)ddsize));
Buffer.MemoryCopy(drawData.NativePtr, rv.NativePtr, ddsize, ddsize);
rv.CmdLists = ImGui.MemAlloc((uint)(Marshal.SizeOf<IntPtr>() * 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<T, U> : List<Tuple<T, U>>, 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<Rid, ImDrawDataPtr>[] 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<Rid, ImDrawDataPtr>[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<Rid, ImDrawDataPtr>[] 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();
}
}

0 comments on commit 354dfcc

Please sign in to comment.