pkdawson / imgui-godot

Dear ImGui plugin for Godot 4
MIT License
448 stars 26 forks source link

Additional third-party modules for ImGui #75

Open JekSun97 opened 3 months ago

JekSun97 commented 3 months ago

Previously, when I used ImGui in C++, I used various libraries for ImGui, here are some of them https://github.com/thedmd/imgui-node-editor https://github.com/Nelarius/imnodes https://github.com/epezent/implot https://github.com/BalazsJako/ImGuiColorTextEdit https://github.com/aiekick/ImGuiFileDialog https://github.com/enkisoftware/imgui_markdown https://github.com/thennequin/ImWindow

How are things going with this? Should I add everything manually or could I consider adding the most popular ones to the plugin itself?

pkdawson commented 3 months ago

For the most part, everything should just work in C++. I've been meaning to test this to be sure, so I added ImPlot to the GDExtension example. The important thing is to call ImGui::Godot::SyncImGuiPtrs() first, because that sets up the ImGuiContext. Otherwise it should be pretty simple to add whatever you want to your build.

I have no plans to add C# or GDScript bindings for any third-party libraries, it's just way too much tedious work to write and maintain that sort of thing. The C# bindings are entirely ImGui.NET, and the GDScript bindings only exist because dear_bindings does the heavy lifting specifically for ImGui.

pkdawson commented 3 months ago

Thought of an important extra detail while looking at imgui_markdown, which is that fonts can only be accessed after the scene tree is loaded, eg in your node's _ready() method. So something like imgui_markdown should be initialized there.

Let me know if you have any specific difficulties. ImWindow would be very complicated to support, but its functionality now exists natively in ImGui with its docking and multi-viewport features.

https://github.com/pkdawson/imgui-godot/wiki/CPlusPlus#notes

pkdawson commented 3 months ago

I added an example of using imgui_markdown, because that's a good way to show how to setup something a little more complex. It's still pretty straightforward, except maybe the few extra lines of code to handle atlas textures properly.

Subtixx commented 2 months ago

I have no plans to add C# or GDScript bindings for any third-party libraries, it's just way too much tedious work to write and maintain that sort of thing. The C# bindings are entirely ImGui.NET,

One could just use the bindings from the ImGui.NET repo it does contain ImPlot / ImNodes etc. bindings for C#. I just don't know how to tell Godot where to find cimplot.. (btw cimplot and all other C Bindings can be obtained here)

EDIT: New link to my forks for cimplot since the mentioned one couldn't be built and needed to be updated too.

EDIT2: Been struggling for hours now.... Got to the point where a hack between the src from you and hacked in cimgui, cimplot from me runs. HOWEVER.. I always get a nullptr exception here:

ImGuiWindow* Window = G.CurrentWindow;

No clue why.. because commenting out ImPlot::BeginPlot etc. and Godot does not crash and runs without issues....

Thread 1 "Godot_v4.2.2-st" received signal SIGSEGV, Segmentation fault.
0x00007fff49e2d4f2 in ImPlot::BeginPlot (title_id=0x7fffffff93f0 "FPS", size=..., flags=0x0)
    at /home/subtixx/Godot/TruckSim/Game/modules/imgui/ImGui.NET-nativebuild/cimplot/implot/implot.cpp:2384
2384        ImGuiWindow* Window      = G.CurrentWindow;
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0
$rbx   : 0x00007fffffff9990  →  0x3fa3333300000002
$rcx   : 0x00007fffffff92f0  →  0x00000000bf800000
$rdx   : 0x0
$rsp   : 0x00007fffffff9250  →  0x00007fffc9a6f888  →  0x00007fffc947e0f0  →   push rbp
$rbp   : 0x00007fffffff92d0  →  0x00007fffffff9300  →  0x00007fffffff93e0  →  0x00007fffffff9470  →  0x00007fffffff94f0  →  0x00007fffffff9790  →  0x00007fffffff9810  →  0x00007fffffff9900
$rsi   : 0x00007fffffff92f0  →  0x00000000bf800000
$rdi   : 0x00007fffffff93f0  →  0x0000000000535046  →   ss add al, 0x4c
$rip   : 0x00007fff49e2d4f2  →  <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00d0> mov rax, QWORD PTR [rax+0x41b0]
$r8    : 0x00007fffffff93f0  →  0x0000000000535046  →   ss add al, 0x4c
$r9    : 0x3
$r10   : 0x0
$r11   : 0x00007fff49e2d422  →  <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+0000> push rbp
$r12   : 0x11
$r13   : 0x000000000b698330  →  0x0000000005817de8  →  0x000000000211e130  →   cmp BYTE PTR [rip+0x430a2e7], 0x0        # 0x642841e
$r14   : 0x0
$r15   : 0xd
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffff9250│+0x0000: 0x00007fffc9a6f888  →  0x00007fffc947e0f0  →   push rbp      ← $rsp
0x00007fffffff9258│+0x0008: 0x000000000b0f15e0  →  0x0000000000000000
0x00007fffffff9260│+0x0010: 0x00007fffffff92f0  →  0x00000000bf800000
0x00007fffffff9268│+0x0018: 0x00007fffffff93f0  →  0x0000000000535046  →   ss add al, 0x4c
0x00007fffffff9270│+0x0020: 0x0000000000000000
0x00007fffffff9278│+0x0028: 0x00007fffc9a6f888  →  0x00007fffc947e0f0  →   push rbp
0x00007fffffff9280│+0x0030: 0x000000000bd3f490  →  0x0000000000000000
0x00007fffffff9288│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7fff49e2d4e7 <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00c5> mov    rax, QWORD PTR [rax]
   0x7fff49e2d4ea <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00c8> mov    QWORD PTR [rbp-0x48], rax
   0x7fff49e2d4ee <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00cc> mov    rax, QWORD PTR [rbp-0x48]
 → 0x7fff49e2d4f2 <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00d0> mov    rax, QWORD PTR [rax+0x41b0]
   0x7fff49e2d4f9 <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00d7> mov    QWORD PTR [rbp-0x40], rax
   0x7fff49e2d4fd <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00db> mov    rax, QWORD PTR [rbp-0x40]
   0x7fff49e2d501 <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00df> movzx  eax, BYTE PTR [rax+0x104]
   0x7fff49e2d508 <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00e6> test   al, al
   0x7fff49e2d50a <ImPlot::BeginPlot(char const*, ImVec2 const&, int)+00e8> je     0x7fff49e2d535 <_ZN6ImPlot9BeginPlotEPKcRK6ImVec2i+275>
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── source:/home/subtixx/G[...]implot.cpp+2384 ────
   2379      if (gp.CurrentSubplot != nullptr)
   2380          ImGui::PushID(gp.CurrentSubplot->CurrentIdx);
   2381
   2382      // get globals
   2383      ImGuiContext &G          = *GImGui;
             // Window=0x00007fffffff9290  →  [...]  →   push rbp
 → 2384      ImGuiWindow* Window      = G.CurrentWindow;
   2385
   2386      // skip if needed
   2387      if (Window->SkipItems && !gp.CurrentSubplot) {
   2388          ResetCtxForNextPlot(GImPlot);
   2389          return false;
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "Godot_v4.2.2-st", stopped 0x7fff49e2d4f2 in ImPlot::BeginPlot (), reason: SIGSEGV
[#1] Id 2, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#2] Id 22, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#3] Id 30, Name: "[vkps] Update", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#4] Id 3, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#5] Id 21, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#6] Id 26, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#7] Id 4, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#8] Id 34, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d5d733 in clock_nanosleep (), reason: SIGSEGV
[#9] Id 40, Name: ".NET DebugPipe", stopped 0x7ffff7d87355 in open64 (), reason: SIGSEGV
[#10] Id 5, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#11] Id 6, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#12] Id 14, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#13] Id 7, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#14] Id 16, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#15] Id 8, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#16] Id 29, Name: "[vkcf] Analysis", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#17] Id 9, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#18] Id 10, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#19] Id 11, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#20] Id 12, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#21] Id 13, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#22] Id 15, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#23] Id 27, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#24] Id 32, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d92c57 in select (), reason: SIGSEGV
[#25] Id 17, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#26] Id 18, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#27] Id 19, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#28] Id 20, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#29] Id 23, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#30] Id 24, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#31] Id 25, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#32] Id 28, Name: "[vkrt] Analysis", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#33] Id 31, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#34] Id 37, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#35] Id 33, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d5d733 in clock_nanosleep (), reason: SIGSEGV
[#36] Id 35, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d5d733 in clock_nanosleep (), reason: SIGSEGV
[#37] Id 36, Name: "Godot_v4.2.2-st", stopped 0x7ffff7d5d733 in clock_nanosleep (), reason: SIGSEGV
[#38] Id 38, Name: ".NET SynchManag", stopped 0x7ffff7d8763d in poll (), reason: SIGSEGV
[#39] Id 39, Name: ".NET EventPipe", stopped 0x7ffff7d8763d in poll (), reason: SIGSEGV
[#40] Id 41, Name: ".NET Debugger", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#41] Id 42, Name: ".NET Finalizer", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
[#42] Id 43, Name: ".NET Tiered Com", stopped 0x7ffff7d0ca19 in ?? (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7fff49e2d4f2 → ImPlot::BeginPlot(title_id=0x7fffffff93f0 "FPS", size=@0x7fffffff92f0, flags=0x0)
[#1] 0x7fff49e0bd18 → ImPlot_BeginPlot(title_id=0x7fffffff93f0 "FPS", size={
  x = -1,
  y = 0
}, flags=0x0)
[#2] 0x7fff4b16b2f7 → mov rcx, QWORD PTR [rbp-0x78]

This is how I "run it":

    private readonly List<double> _fps = new List<double>(64);

    private double _elapsedTime = 0;

    public override void _Ready()
    {
        base._Ready();

        ImGuiSync.SyncPtrs();

        ImPlot.CreateContext();
    }

    public override void _Process(double delta)
    {
        _elapsedTime += delta;
        if (_elapsedTime >= 1.0)
        {
            _fps.Add(Performance.GetMonitor(Performance.Monitor.TimeFps));
            _elapsedTime = 0.0;
        }

        if (ImGui.Begin("Monitor"))
        {
            //Performance.TIME_FPS
            if (ImPlot.BeginPlot("FPS"))
            {
                Span<double> fpsSpan = CollectionsMarshal.AsSpan(_fps);
                ImPlot.PlotLine("FPS", ref fpsSpan[0], fpsSpan.Length);
                ImPlot.EndPlot();
            } 
        }

        ImGui.End();

        base._Process(delta);
    }
pkdawson commented 2 months ago

@Subtixx You shouldn't need to call SyncPtrs if you have the plugin installed and enabled, it does that automatically.

So the key problem here is that the ImGuiContext is not shared between different DLLs / shared libraries. It needs to be manually synchronized.

https://github.com/ocornut/imgui/blob/29fadad1939ab62adcc5cd9339db7a784de90120/imgui.cpp#L1204-L1213

Try adding this before creating the ImPlot context. I'm just looking at the ImPlot.NET code, haven't tested.

ImPlot.SetImGuiContext(ImGui.GetCurrentContext());
Subtixx commented 2 months ago

Thank you very much! Didn't know that about Shared Libraries thanks!

ImPlot.SetImGuiContext(ImGui.GetCurrentContext());

This does not fully solve the issue since now I'm crashing here:

if (Window->SkipItems && !gp.CurrentSubplot) {

now Window is null.. But I had a hacky solution for me and for the time being, compiling cimgui with cimplot embedded does not result in a crash anymore.