-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
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
Double-buffered ImDrawData #1860
Comments
Hello,
It’s not supported yet but there is a CloneOutput function you can use (a little slower) to duplicate an ImDrawList.
For this and other features I would like to support double buffered ImDrawList per window, which would lead to double buffered ImDrawData.
|
Hey there, I wanted to share a few things:
The NULL argument causes a crash when the constructor calls Clear so I changed it to
|
…sary risk from ImDrawList::Clear(), draw lists are being clear before use each frame anyway. (#1860)
@gviot For now I have pushed the fix for the issue described in (3). |
…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
Here's a suggested more optimal way to handle double buffering of ImDrawData. I wrote this on a whim and haven't tested it thoroughly. Would be interested in feedback. Code // Usage:
// static ImDrawDataSnapshot snapshot; // Important: make persistent accross frames to reuse buffers.
// snapshot.SnapUsingSwap(ImGui::GetDrawData(), ImGui::GetTime());
// [...]
// ImGui_ImplDX11_RenderDrawData(&snapshot.DrawData);
// FIXME: Could store an ID in ImDrawList to make this easier for user.
#include "imgui_internal.h" // ImPool<>, ImHashData
struct ImDrawDataSnapshotEntry
{
ImDrawList* SrcCopy = NULL; // Drawlist owned by main context
ImDrawList* OurCopy = NULL; // Our copy
double LastUsedTime = 0.0;
};
struct ImDrawDataSnapshot
{
// Members
ImDrawData DrawData;
ImPool<ImDrawDataSnapshotEntry> Cache;
float MemoryCompactTimer = 20.0f; // Discard unused data after 20 seconds
// Functions
~ImDrawDataSnapshot() { Clear(); }
void Clear();
void SnapUsingSwap(ImDrawData* src, double current_time); // Efficient snapshot by swapping data, meaning "src_list" is unusable.
//void SnapUsingCopy(ImDrawData* src, double current_time); // Deep-copy snapshop
// Internals
ImGuiID GetDrawListID(ImDrawList* src_list) { return ImHashData(&src_list, sizeof(src_list)); } // Hash pointer
ImDrawDataSnapshotEntry* GetOrAddEntry(ImDrawList* src_list) { return Cache.GetOrAddByKey(GetDrawListID(src_list)); }
};
void ImDrawDataSnapshot::Clear()
{
for (int n = 0; n < Cache.GetMapSize(); n++)
if (ImDrawDataSnapshotEntry* entry = Cache.TryGetMapData(n))
IM_DELETE(entry->OurCopy);
Cache.Clear();
DrawData.Clear();
}
void ImDrawDataSnapshot::SnapUsingSwap(ImDrawData* src, double current_time)
{
ImDrawData* dst = &DrawData;
IM_ASSERT(src != dst && src->Valid);
// Copy all fields except CmdLists[]
ImVector<ImDrawList*> backup_draw_list;
backup_draw_list.swap(src->CmdLists);
IM_ASSERT(src->CmdLists.Data == NULL);
*dst = *src;
backup_draw_list.swap(src->CmdLists);
// Swap and mark as used
for (ImDrawList* src_list : src->CmdLists)
{
ImDrawDataSnapshotEntry* entry = GetOrAddEntry(src_list);
if (entry->OurCopy == NULL)
{
entry->SrcCopy = src_list;
entry->OurCopy = IM_NEW(ImDrawList)(src_list->_Data);
}
IM_ASSERT(entry->SrcCopy == src_list);
entry->SrcCopy->CmdBuffer.swap(entry->OurCopy->CmdBuffer); // Cheap swap
entry->SrcCopy->IdxBuffer.swap(entry->OurCopy->IdxBuffer);
entry->SrcCopy->VtxBuffer.swap(entry->OurCopy->VtxBuffer);
entry->SrcCopy->CmdBuffer.reserve(entry->OurCopy->CmdBuffer.Capacity); // Preserve bigger size to avoid reallocs for two consecutive frames
entry->SrcCopy->IdxBuffer.reserve(entry->OurCopy->IdxBuffer.Capacity);
entry->SrcCopy->VtxBuffer.reserve(entry->OurCopy->VtxBuffer.Capacity);
entry->LastUsedTime = current_time;
dst->CmdLists.push_back(entry->OurCopy);
}
// Cleanup unused data
const double gc_threshold = current_time - MemoryCompactTimer;
for (int n = 0; n < Cache.GetMapSize(); n++)
if (ImDrawDataSnapshotEntry* entry = Cache.TryGetMapData(n))
{
if (entry->LastUsedTime > gc_threshold)
continue;
IM_DELETE(entry->OurCopy);
Cache.Remove(GetDrawListID(entry->SrcCopy), entry);
}
}; Test bed, press 1 to capture current output, hold 2 to display it: static ImDrawData snapshot;
ImDrawData* draw_data_to_render = ImGui::GetDrawData();
if (ImGui::IsKeyPressed(ImGuiKey_1))
{
snapshot.SnapUsingSwap(ImGui::GetDrawData(), ImGui::GetTime());
draw_data_to_render = &snapshot.DrawData;
}
else if (ImGui::IsKeyDown(ImGuiKey_2))
{
draw_data_to_render = &snapshot.DrawData;
}
ImGui_ImplDX11_RenderDrawData(draw_data_to_render); |
Isn't sizeof supposed to be called on the structure instead of the pointer? return ImHashData(&src_list, sizeof(*src_list)); |
I guess that's why that line has a comment "hash pointer", to make clear that it is indeed the pointer that is to be hashed. That also makes sense since you want an ID for the draw list, that shouldn't change if the contents of the draw list change. The pointer stays the same. Btw, dangerous bug in your one-liner. You would be hashing the pointer and unspecified memory after where the pointer is stored, not the structure itself, but with the size of the structure. Could be an access violation, could be hashing the stack, could be hashing anything. |
I run two primary threads in my application; one handles world update at 60hz while another renders frames at however fast it's able. The world update thread keeps two versions of the graphics state that the graphics thread interpolates. Is there support for this mode of operation now? If not, would it be simple to add? (Interpolation isn't important for the GUI side of things. I'd be fine having double-buffered DrawData.)
The text was updated successfully, but these errors were encountered: