imgui
imgui copied to clipboard
Can ImDrawLists be reused in a later Render()?
Version/Branch of Dear ImGui:
Version: 1.75
Back-ends: imgui_impl_glfw.cpp
Operating System: Windows
My Issue/Question: This may not be possible, and may not fit the immediate mode paradigm, but I'm still curious:
Can ImDrawLists be stored, and reused in a later Render() call?
XXX (please provide as much context as possible)
I want to do some cpu-intensive stuff that informs the drawing, that I wouldn't want to update every frame. I could create my own drawing command queue that only updates after user-input, and then add drawlist commands based on my queue on every ImGui call.
But then I thought, why the extra abstraction: the drawlist itself is a list of commands. Could a particular drawlist be stored and reused at a later time?
It tried the following, but it crashes:
static ImDrawListSharedData dummy_data;
static ImDrawList stored_draw_list = ImDrawList(&dummy_data);
if (IsHovered || FirstTimeRendered)
{
//Do cpu intensive logic that informs drawing here
ImGui::GetCurrentWindow()->DrawList->AddLine(ImVec2(100*KA::Frame, 100), ImVec2(300, 1200), linescol, 5.0);
stored_draw_list = ImDrawList(ImGui::GetCurrentWindow()->DrawList->_Data);
FirstTimeRendered = false;
}
else
{
ImGui::GetCurrentWindow()->DrawList = &stored_draw_list;
}
Would this at all be possible? Thanks in advance!
Hello,
but it crashes:
As suggested in the guidelines, if you ever state "it crashes" please provide details and evidence in the form of a call-stack.
There's no copy constructor but you may currently use CloneOutput()
to duplicate ImDrawList's output.
More specifically for what you seem to be trying to do (swap window's draw list) I believe we could do it more easily by flagging a window as Active but not clearing its ImDrawList contents, nor calling Begin(). I would like to use that technique to allow for windows with variable refresh rate, but it's not done yet.
Sorry, there's the assertion: Assertion failed: _ClipRectStack.Size > 0 imgui_draw.cpp, line 516
stored_draw_list = *ImGui::GetCurrentWindow()->DrawList->CloneOutput(); Gives the same assertion.
It was a long shot anyway. The approach you describe sounds better. It sounds like, with that possibility, you could create a retained UI on top of the Immediate UI, to keep expensive calculations in the retained model and only use the immediate UI for drawing.
Here's a nice article about that approach, in case you are interested: https://www.gamasutra.com/blogs/NiklasGray/20170719/301963/One_Draw_Call_UI.php
Thanks for the quick response!
to keep expensive calculations in the retained model and only use the immediate UI for drawing.
You are free to store expensive calculations however you want, I don't understand which problem you are trying to solve.
It's just that now, I have the calculations of what to draw in a particular state, combined with the actual drawlist commands in my code. generic example:
if(ExpensiveCalculationA() && ExpensiveCalculationB() )
{
x = ExpensiveCalculationC();
y = ExpensiveCalculationD();
size = ExpensiveCalculationE();
draw_list->AddCircleFilled(ImVec2(x, y), size, col, 6);
}
I could separate the logic from the actual drawlist commands, storing x,y and size somewhere but I like the directness of ImGui to keep logic and draw commands together. By flagging a window as active but not clearing the drawlist as you say, that would be a very simple bloat-free approach to gain performance, whilst not complicating the code IMO. Then we would just redraw the previous state of a window, except when there's a user input that triggers the drawlist to be cleared and refilled. If that makes sense.
For example, otherwise, I would need something like:
struct Circle
{
float x, y
float size,
ImU32 col
void Draw()
{
draw_list->AddCircleFilled(ImVec2(x, y), size, col, 6);
}
};
std::vector<Circle> circles;
If(UpdateWindow)
{
circles.clear();
if (ExpensiveCalculationA() && ExpensiveCalculationB())
{
x = ExpensiveCalculationC();
y = ExpensiveCalculationD();
size = ExpensiveCalculationE();
circles.emplace_back(x,y,size, col);
}
}
for (Circle c : circles)
c.Draw();
This related to #2391/#1878 right ?
To clarify the situation, correct me if I am wrong:
- As explained by #2391, there is no such thing as
ImGui::AddDrawList(&drawList)
- Using
GetBackgroundDrawList()
still requires to copy vertices/indices data every frame - As stated in #1878 a way to save computation and copies, is by creating your own
ImDrawData
and reusing aImDrawList
without clearing its content to often and use a secondImGui_Impl*_RenderDrawData()
call.
I am curious wether there are other methods ?
Is there any update on this? @WildRackoon have you figured out anything?
I in particular would like to "cache" certain areas of a window. So, my idea is
- Call something like
cacheBegin
- Create a new ImDrawList
cacheData = new ImDrawListSharedData();
cacheDrawList = new ImDrawList(cacheData);
- Replace current windows drawlist (store the original it somewhere to replace it back) to this one (so that any subsequent drawing happens to the
cacheDrawList
- Call
cacheEnd
- this should merge the
cacheDrawList
back to theoriginalDrawList
I was just trying to make this work, reached a point where it's not crashing, but what I'm drawing in the cache
layer is not shown (and neither some draws after)
I roughly tried to follow ImDrawListSplitter::Merge
, but have failed
Generally for the merging I'm doing
for (int i = 0; i < cacheDrawList->CmdBuffer.Size; i++) {
draw_list->CmdBuffer.push_back(cmd);
}
draw_list->PrimReserve(cacheDrawList->IdxBuffer.Size, cacheDrawList->VtxBuffer.Size);
for (int i = 0; i < cacheDrawList->VtxBuffer.Size; i++) {
const ImDrawVert vert = cacheDrawList->VtxBuffer[i];
draw_list->PrimWriteVtx(vert.pos, vert.uv, vert.col);
}
for (int i = 0; i < cacheDrawList->IdxBuffer.Size; i++) {
draw_list->PrimWriteIdx(cacheDrawList->IdxBuffer[i]);
}
My guess is that it has something to do with the IdxOffset
but I'm incredibly vague of vertex/textures etc.
Is what I'm trying to do possible? And if so,.. how?
As far as I understand what @ocornut has suggested is caching a whole window (by not clearing it's drawlist). I'm yet to try this actually, but another level of control (to be able to cache small areas of a window) would be useful I reckon.
Thanks in advance!
(edit: using PrimReserve & PrimWrite*)
FYI, tangential: I have posted a ImDrawDataSnapshot
class which allows efficiently taking a snapshot of a ImDrawData instance without full copy and with amortized allocations, you can find it there: (feedback welcome in that other thread)
https://github.com/ocornut/imgui/issues/1860#issuecomment-1927630727
Also note that since 1.89.8 it became easier to add draw lists to an existing ImDrawData.
I think it should be possible already.
When you want to update the ImDrawList:
- Call
_ResetForNewFrame()
, followed (most likely) byPushClipRect()
,PushTextureID()
. - Draw your stuff.
- For convenience, you can add this draw list to an existing ImDrawData with
ImDrawData::AddDrawList()
.
When you want to simply re-render:
- Call
ImDrawData::AddDrawList()
only.
I think this should work. I realize this issue/request is old and it may not matter to do specially, but I'm happy to help further if needed.
I also realize ImDrawData::AddDrawList()
as a limitation that it always push_back to the end of ImDrawData, when in theory we could perfectly allow users to inject this ImDrawList anywhere in the list. For now it may be easy to call the function and reorder if desirable.
By flagging a window as active but not clearing the drawlist as you say, that would be a very simple bloat-free approach to gain performance, whilst not complicating the code IMO. Then we would just redraw the previous state of a window, except when there's a user input that triggers the drawlist to be cleared and refilled. If that makes sense.
That's planned and desirable but I think it may need to wait until we change the Begin()
api (right now we allow drawing even when returning false).
However it may be interesting for stuff like #7556 to experiment with the feature early on... even if it means using an internal API temporarily. I am going to toy with that.
By flagging a window as active but not clearing the drawlist as you say, that would be a very simple bloat-free approach to gain performance, whilst not complicating the code IMO. Then we would just redraw the previous state of a window, except when there's a user input that triggers the drawlist to be cleared and refilled. If that makes sense.
That's planned and desirable but I think it may need to wait until we change the Begin() api (right now we allow drawing even when returning false).
I pushed an experiment: d449544
You can toy with it as ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags_TryToAvoidRefresh);
(require imgui_internal.h)
Note that this behave at the Window level, not on a per-DrawList basis. So it doesn't exactly full-fill the title of this topic, but it goes in the direction suggested by that paragraph of yours. See comments/caveats in the commit description.
I think it'll be a good tool for reduce costs of "normally heavy" UI traversal, but may be an inadequate tool to reduce cost of "abnormally heavy" computation, as for that later you'll want very precise control and guaranteed avoidance that e.g. something is not done two frames in a row.
Thank you! I will play with it when I have the time. Does the refreshPolicy only apply to the window's drawlist, or entire content, e.g. buttons as well?
Currently the idea is that when not refreshed Begin() returns false and you can’t submit items anyhow. So everything will look frozen. There are flag to eg always refresh when hovered etc and the key will be to improve this design.
Looking good, though what I'd ideally want for my use case are two more things (which can both be easily substituted but would make for a more complete API):
First is to be able to force an update for a specific window, since my code can notify the UI that it wants a UI update. I would like this to be a property of the window so the next update respects that and then clears it. Currently I could imagine a workaround that clears ImGuiNextWindowDataFlags_HasRefreshPolicy and then, after each Render(), sets it up again.
Second is to more easily know if the window UI was updated last frame - though this one is trivial to implement on my own since it's equivalent to checking if Begin returns true. I'd personally also set this flag to false myself since I already call RenderDrawData more than I call NewFrame (for GL views that need constant updates). This one is only really useful since I modified my OpenGL3 backend to be able to only update a small rectangle of the screen. This is because in my case, the rendering cost for the GPU is vastly outstripping the minimal CPU use of the UI code - so I'd actually just hijack this code to make GPU rendering less costly.
Thanks for the work on this front!