Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using ImDrawList to render from another Thread #5776

Closed
xetru opened this issue Oct 14, 2022 · 6 comments
Closed

Using ImDrawList to render from another Thread #5776

xetru opened this issue Oct 14, 2022 · 6 comments

Comments

@xetru
Copy link

xetru commented Oct 14, 2022

Hi,

i am trying what title says, using d3d11, but even doing it in the main imgui thread won't work for me. Creating the ImDrawList, setting _Data to ImGui::GetSharedData() and then calling _ResetForNewFrame(), after that I was expecting to be able to just draw on the list, and then replicate what ImGui::Render() does / append to draw_data, or call ImGui_ImplDX11_RenderDrawData(my_draw_data);

However, just nothing is being drawn. I couldn't find much info about this, any help appreciated.

@ocornut
Copy link
Owner

ocornut commented Oct 14, 2022

You'll probably need to dig further and provide more details.
Note that multi-threading isn't supported without a lock, but as posted your issue isn't a threading issue.
You can call ImGui::DebugNodeDrawList() from imgui_internal.h near the end of the frame (or before calling _ResetForNewFrame()) to visualize its content.

@thedemons
Copy link
Contributor

thedemons commented Oct 15, 2022

What a coincidence, I just did exactly this yesterday, here's my code, it creates separated draw lists and won't be clear by the main thread so you won't have syncing/flashing issues, and it uses a simple std::mutex for locking, it's not pretty nor optimized so I don't recommend using this for performance-critical tasks

class MThreadRenderer
{
private:
    std::mutex m_mutex;
    ImDrawData m_drawData;

    std::vector<ImDrawList> m_drawLists;    // the thread render to these draw lists
    std::vector<ImDrawList> m_copyList;     // calling EndFrame() will copy the m_drawLists to this
    std::vector<ImDrawList*> m_copyPointer; // pointer to each drawlist in m_copyList, needed for m_drawData

    ImGuiViewportP* m_viewport;
public:

    // numDrawList = number of draw lists needed, 2 for background and foreground
    MThreadRenderer(size_t numDrawList = 2, ImGuiViewport* viewport = nullptr)
    {
        m_viewport = static_cast<ImGuiViewportP*>(viewport ? viewport : ImGui::GetMainViewport());

        m_copyList.resize(numDrawList, &GImGui->DrawListSharedData);

        for (size_t i = 0; i < numDrawList; i++)
        {
           m_drawLists.emplace_back(&GImGui->DrawListSharedData);
           m_copyPointer.emplace_back(&m_copyList[i]);
        }

        m_drawData.Valid = true;
        m_drawData.CmdLists = m_copyPointer.data();
        m_drawData.CmdListsCount = m_copyPointer.size();
    }
    
    // index=0 for the background drawlist, index=1 for the foreground, basically the z-index
    inline ImDrawList& GetDrawList(size_t index = 0) noexcept
    {
        return m_drawLists[index];
    }

    inline void BeginFrame()
    {
        for (auto& drawList : m_drawLists)
        {
            drawList._ResetForNewFrame();
            drawList.PushTextureID(GImGui->IO.Fonts->TexID);
            drawList.PushClipRect(m_viewport->Pos, m_viewport->Pos + m_viewport->Size, false);
        }
    }

    inline void EndFrame()
    {
        m_mutex.lock();

        m_copyList = m_drawLists;

        m_drawData.TotalVtxCount = m_drawData.TotalIdxCount = 0;
        m_drawData.DisplayPos = m_viewport->Pos;
        m_drawData.DisplaySize = m_viewport->Size;
        m_drawData.FramebufferScale = GImGui->IO.DisplayFramebufferScale;

        for (auto& drawList : m_copyList)
        {
            drawList._PopUnusedDrawCmd();
            m_drawData.TotalVtxCount += drawList.VtxBuffer.Size;
            m_drawData.TotalIdxCount += drawList.IdxBuffer.Size;
        }

        m_mutex.unlock();
    }

    inline void Render() noexcept
    {
        m_mutex.lock();
        ImGui_ImplDX11_RenderDrawData(&m_drawData);
        m_mutex.unlock();
    }
};

A test thread:

void threadTest(MThreadRenderer& renderer)
{
    while (true)
    {
        renderer.BeginFrame();

        ImVec2 pos = ImGui::GetMousePos();

        renderer.GetDrawList(1).AddRectFilled({ 200,200 }, { 500, 500 }, 0xff0000ff);
        renderer.GetDrawList(0).AddRectFilled(pos, pos + ImVec2(300, 300), 0xff00ff00);

        renderer.EndFrame();

        Sleep(100);
    }
}

You need to create an instance of MThreadRenderer and pass it to your thread:

    MThreadRenderer renderer;
    auto thread = std::thread(threadTest, std::ref(renderer));
    thread.detach();

On the main thread you need to call MThreadRenderer::Render

        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
        renderer.Render();

@ocornut I wonder if this code has any issues with it, I didn't look into what the GImGui->DrawListSharedData does but it doesn't seem to cause any race-conditions.

It'd be nice to have a way to render an entire Begin->End window in another thread but I guess that would be so much more work to do with minor benefits

@xetru
Copy link
Author

xetru commented Oct 15, 2022

    inline void BeginFrame()
    {
        for (auto& drawList : m_drawLists)
        {
            drawList._ResetForNewFrame();
            drawList.PushTextureID(GImGui->IO.Fonts->TexID);
            drawList.PushClipRect(m_viewport->Pos, m_viewport->Pos + m_viewport->Size, false);
        }
    }

isn't that a problem that you reset the drawlist without locking your mutex?

@thedemons
Copy link
Contributor

thedemons commented Oct 16, 2022

@xetru No, the m_drawLists is only accessed by one thread, only m_copyList and m_drawData are accessed by both

@ocornut
Copy link
Owner

ocornut commented Oct 21, 2022

@xetru Let us know if that's solved for you.

@ocornut ocornut closed this as completed Nov 24, 2022
ocornut added a commit that referenced this issue Jul 12, 2023
…DrawData itself. Faclitate user-manipulation of the array (#6406, #4879, #1878) + deep swap. (#6597, #6475, #6167, #5776, #5109, #4763, #3515, #1860)

+ Metrics: avoid misleadingly iterating all layers of DrawDataBuilder as everything is flattened into Layers[0] at this point.

# Conflicts:
#	imgui.cpp
#	imgui_internal.h
ocornut added a commit that referenced this issue Aug 6, 2023
@ocornut
Copy link
Owner

ocornut commented Feb 5, 2024

I have posted a ImDrawDataSnapshot class which allows efficiently taking a snapshot without full copy and with amortized allocations, you can find it there: (feedback welcome in that other thread)
#1860 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants
@ocornut @thedemons @xetru and others