ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
60.81k stars 10.27k forks source link

Drawing the Imgui Windows in Thread A but rendering them in Thread B #5109

Closed bulllit closed 2 years ago

bulllit commented 2 years ago

Version: Dear ImGui 1.87 Backend Platform imgui_impl_win32 Backend Renderer imgui_impl_dx111 Operating System: Windows 10 Compiler Msvc 14.2 c++17

Hello I am currently writing an overlay for a game that uses a main Gamethread and a separate Renderthread. In my case, I am deploying hooks in both the game's mainloop and the directxpresent . The reason I do this is because the game's mainthread has alot of vtable functions that access the gamethreads TLS, which makes it very inconvenient to call those functions from the renderthread.

Ideally, my code would look like this:

//mainthread hook
void __fastcall hkGameLoop(int a1, int a2)
{
    if (imguiinit && g_ImGuiResourceMutex.try_lock()) { 
        ImGui::SetCurrentContext(g_imContext);
        ImGui_ImplDX11_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();
        RenderUI::drawMenus();
        ImGui::Render();
        g_ImGuiResourceMutex.unlock();
    }
    origGameLoop(a1,a2);
}

//present hook
HRESULT __stdcall hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags)
{
    if (!dxhookinit)
    {
        if (SUCCEEDED(pSwapChain->GetDevice(__uuidof(ID3D11Device), (void**)&pDevice)))
        {
            pDevice->GetImmediateContext(&pContext);
            DXGI_SWAP_CHAIN_DESC sd;
            pSwapChain->GetDesc(&sd);
            window = sd.OutputWindow;
            ID3D11Texture2D* pBackBuffer;
            pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
            pDevice->CreateRenderTargetView(pBackBuffer, NULL, &mainRenderTargetView);
            pBackBuffer->Release();
            InitImGui();
            RenderUI::init();
            dxhookinit = true;
        }
        else return oPresent(pSwapChain, SyncInterval, Flags);
       }

    std::shared_lock< std::shared_mutex > lock(g_ImGuiResourceMutex);

    ImGui::SetCurrentContext(g_imContext);
    pContext->OMSetRenderTargets(1, &mainRenderTargetView, NULL);
    ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
    lock.unlock();
    return oirgPresent(pSwapChain, SyncInterval, Flags);
}

Well this code crashes when calling ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());. Looking at the debugger, the drawdata seems valid and it crashes when doing things with the backenddata because of Segfault ACCESS_VIOLATION. I assume the backend data gets somehow invalidated during this process.

The way I understand ImGui::Render() is that it it wraps everything up, ends the frame and compiles Everything into an internal ImDrawData struct.

At this point ive also tried storing just the Drawdata in the game thread and adding them into an empty Frame on the renderthread using

        ImGui_ImplDX11_NewFrame();
    ImGui_ImplWin32_NewFrame();
    ImGui::NewFrame();
    ImGui::Render();
    ImDrawData* d = &g_drawdataBuffer;
    ImDrawList** l = new ImDrawList* [d->CmdListsCount];
    for (int i = 0; i < d->CmdListsCount; i++) {
        l[i] = d->CmdLists[i]->CloneOutput();
    }
    ImGui::GetDrawData()->CmdLists = l;
    ImGui::GetDrawData()->CmdListsCount = d->CmdListsCount;
    pContext->OMSetRenderTargets(1, &mainRenderTargetView, NULL);
        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

This renders what I've drawn in the gameloop but is completely unresponsive to interactions (window collision). At this point my question would be what is the best approach to do that?

ocornut commented 2 years ago

You should deep clone the ImDrawData after calling ImGui::Render() and then pass that to your render thread/function. Not sure what else you need to know.

Your last snippet somehow does a clone from the wrong source and then proceed to overwrite the original source that's a little strange and backward.

ocornut commented 2 years ago

Your last snippet somehow does a clone from the wrong source and then proceed to overwrite the original source that's a little strange and backward.

I'll close it as it looks like your ImDrawData cloning code is simply wrong. It works if you write the code correctly.

(Btw if you keep clone every frame as a second step you may want to keep a double buffered version of ImDrawData to avoid reallocating buffers.)

bulllit commented 2 years ago

Yes you were right, at that time i got confused about what went wrong so i started trying out stuff that made no sense whatsoever.. But basically my fault was to initialize Imgui in the renderthread. All what i had to do was to basically move that part to the gamethread and everything worked like a charm.
There was no need to clone anything whatsoever either.