Columns/ImDrawList: dispatch render of each column in a sub-draw list and merge on closure, saving draw calls (#125)

This commit is contained in:
ocornut 2015-07-07 18:19:01 -06:00
parent 3e4841765d
commit 87ebe95fd6
2 changed files with 88 additions and 6 deletions

View File

@ -6,7 +6,6 @@
// ANTI-ALIASED PRIMITIVES BRANCH // ANTI-ALIASED PRIMITIVES BRANCH
// TODO // TODO
// - Redesign parameters passed to RenderFn ImDrawList etc.
// - Support for thickness stroking. recently been added to the ImDrawList API as a convenience. // - Support for thickness stroking. recently been added to the ImDrawList API as a convenience.
/* /*
@ -8668,12 +8667,14 @@ void ImGui::NextColumn()
if (++window->DC.ColumnsCurrent < window->DC.ColumnsCount) if (++window->DC.ColumnsCurrent < window->DC.ColumnsCount)
{ {
window->DC.ColumnsOffsetX = ImGui::GetColumnOffset(window->DC.ColumnsCurrent) - window->DC.ColumnsStartX + g.Style.ItemSpacing.x; window->DC.ColumnsOffsetX = ImGui::GetColumnOffset(window->DC.ColumnsCurrent) - window->DC.ColumnsStartX + g.Style.ItemSpacing.x;
window->DrawList->ChannelsSetCurrent(window->DC.ColumnsCurrent);
} }
else else
{ {
window->DC.ColumnsCurrent = 0; window->DC.ColumnsCurrent = 0;
window->DC.ColumnsOffsetX = 0.0f; window->DC.ColumnsOffsetX = 0.0f;
window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY; window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY;
window->DrawList->ChannelsSetCurrent(0);
} }
window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.ColumnsStartX + window->DC.ColumnsOffsetX); window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.ColumnsStartX + window->DC.ColumnsOffsetX);
window->DC.CursorPos.y = window->DC.ColumnsCellMinY; window->DC.CursorPos.y = window->DC.ColumnsCellMinY;
@ -8785,6 +8786,7 @@ void ImGui::Columns(int columns_count, const char* id, bool border)
ItemSize(ImVec2(0,0)); // Advance to column 0 ItemSize(ImVec2(0,0)); // Advance to column 0
ImGui::PopItemWidth(); ImGui::PopItemWidth();
PopClipRect(); PopClipRect();
window->DrawList->ChannelsMerge(window->DC.ColumnsCount);
window->DC.ColumnsCellMaxY = ImMax(window->DC.ColumnsCellMaxY, window->DC.CursorPos.y); window->DC.ColumnsCellMaxY = ImMax(window->DC.ColumnsCellMaxY, window->DC.CursorPos.y);
window->DC.CursorPos.y = window->DC.ColumnsCellMaxY; window->DC.CursorPos.y = window->DC.ColumnsCellMaxY;
@ -8848,7 +8850,7 @@ void ImGui::Columns(int columns_count, const char* id, bool border)
const float t = window->DC.StateStorage->GetFloat(column_id, default_t); // Cheaply store our floating point value inside the integer (could store an union into the map?) const float t = window->DC.StateStorage->GetFloat(column_id, default_t); // Cheaply store our floating point value inside the integer (could store an union into the map?)
window->DC.ColumnsOffsetsT[column_index] = t; window->DC.ColumnsOffsetsT[column_index] = t;
} }
window->DrawList->ChannelsSplit(window->DC.ColumnsCount);
PushColumnClipRect(); PushColumnClipRect();
ImGui::PushItemWidth(ImGui::GetColumnWidth() * 0.65f); ImGui::PushItemWidth(ImGui::GetColumnWidth() * 0.65f);
} }
@ -8963,8 +8965,10 @@ void ImDrawList::Clear()
vtx_current_idx = 0; vtx_current_idx = 0;
idx_buffer.resize(0); idx_buffer.resize(0);
idx_write = NULL; idx_write = NULL;
channel_current = 0;
clip_rect_stack.resize(0); clip_rect_stack.resize(0);
texture_id_stack.resize(0); texture_id_stack.resize(0);
// NB: Do not clear channels so our allocations are re-used after the first frame.
} }
void ImDrawList::ClearFreeMemory() void ImDrawList::ClearFreeMemory()
@ -8975,8 +8979,16 @@ void ImDrawList::ClearFreeMemory()
vtx_current_idx = 0; vtx_current_idx = 0;
idx_buffer.clear(); idx_buffer.clear();
idx_write = NULL; idx_write = NULL;
channel_current = 0;
clip_rect_stack.clear(); clip_rect_stack.clear();
texture_id_stack.clear(); texture_id_stack.clear();
for (int i = 0; i < channels.Size; i++)
{
if (i == 0) memset(&channels[0], 0, sizeof(channels[0])); // channel 0 is a copy of cmd_buffer/idx_buffer, don't destruct again
channels[i].cmd_buffer.clear();
channels[i].idx_buffer.clear();
}
channels.clear();
} }
void ImDrawList::AddDrawCmd() void ImDrawList::AddDrawCmd()
@ -9008,6 +9020,64 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data)
AddDrawCmd(); AddDrawCmd();
} }
void ImDrawList::ChannelsSplit(int channel_count)
{
IM_ASSERT(channel_current == 0);
int old_channels_count = channels.Size;
if (old_channels_count < channel_count)
channels.resize(channel_count);
for (int i = 0; i < channel_count; i++)
if (i >= old_channels_count)
new(&channels[i]) ImDrawChannel();
else
channels[i].cmd_buffer.resize(0), channels[i].idx_buffer.resize(0);
}
void ImDrawList::ChannelsMerge(int channel_count)
{
// Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use.
// This is why this function takes 'channel_count' as a parameter of how many channels to merge (the user knows)
if (channel_count < 2)
return;
ChannelsSetCurrent(0);
if (cmd_buffer.Size && cmd_buffer.back().elem_count == 0)
cmd_buffer.pop_back();
int new_cmd_buffer_count = 0, new_idx_buffer_count = 0;
for (int i = 1; i < channel_count; i++)
{
ImDrawChannel& ch = channels[i];
if (ch.cmd_buffer.Size && ch.cmd_buffer.back().elem_count == 0)
ch.cmd_buffer.pop_back();
new_cmd_buffer_count += ch.cmd_buffer.Size;
new_idx_buffer_count += ch.idx_buffer.Size;
}
cmd_buffer.resize(cmd_buffer.Size + new_cmd_buffer_count);
idx_buffer.resize(idx_buffer.Size + new_idx_buffer_count);
ImDrawCmd* cmd_write = cmd_buffer.Data + cmd_buffer.Size - new_cmd_buffer_count;
idx_write = idx_buffer.Data + idx_buffer.Size - new_idx_buffer_count;
for (int i = 1; i < channel_count; i++)
{
ImDrawChannel& ch = channels[i];
if (int sz = ch.cmd_buffer.Size) { memcpy(cmd_write, ch.cmd_buffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; }
if (int sz = ch.idx_buffer.Size) { memcpy(idx_write, ch.idx_buffer.Data, sz * sizeof(ImDrawIdx)); idx_write += sz; }
}
AddDrawCmd();
}
void ImDrawList::ChannelsSetCurrent(int idx)
{
if (channel_current == idx) return;
memcpy(&channels.Data[channel_current].cmd_buffer, &cmd_buffer, sizeof(cmd_buffer));
memcpy(&channels.Data[channel_current].idx_buffer, &idx_buffer, sizeof(idx_buffer));
channel_current = idx;
memcpy(&cmd_buffer, &channels.Data[channel_current].cmd_buffer, sizeof(cmd_buffer));
memcpy(&idx_buffer, &channels.Data[channel_current].idx_buffer, sizeof(idx_buffer));
idx_write = idx_buffer.Data + idx_buffer.Size;
}
void ImDrawList::UpdateClipRect() void ImDrawList::UpdateClipRect()
{ {
ImDrawCmd* current_cmd = cmd_buffer.Size ? &cmd_buffer.back() : NULL; ImDrawCmd* current_cmd = cmd_buffer.Size ? &cmd_buffer.back() : NULL;

16
imgui.h
View File

@ -1017,6 +1017,13 @@ struct ImDrawVert
IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT;
#endif #endif
// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items of each column can be batched together.
struct ImDrawChannel
{
ImVector<ImDrawCmd> cmd_buffer;
ImVector<ImDrawIdx> idx_buffer;
};
// Draw command list // Draw command list
// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. // This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering.
// At the moment, each ImGui window contains its own ImDrawList but they could potentially be merged in the future. // At the moment, each ImGui window contains its own ImDrawList but they could potentially be merged in the future.
@ -1028,10 +1035,10 @@ struct ImDrawList
{ {
// This is what you have to render // This is what you have to render
ImVector<ImDrawCmd> cmd_buffer; // Commands. Typically 1 command = 1 gpu draw call. ImVector<ImDrawCmd> cmd_buffer; // Commands. Typically 1 command = 1 gpu draw call.
ImVector<ImDrawVert> vtx_buffer; // Vertex buffer.
ImVector<ImDrawIdx> idx_buffer; // Index buffer. Each command consume ImDrawCmd::idx_count of those ImVector<ImDrawIdx> idx_buffer; // Index buffer. Each command consume ImDrawCmd::idx_count of those
ImVector<ImDrawVert> vtx_buffer; // Vertex buffer.
// [Internal to ImGui] // [Internal, used while building lists]
const char* owner_name; // Pointer to owner window's name (if any) for debugging const char* owner_name; // Pointer to owner window's name (if any) for debugging
ImDrawVert* vtx_write; // [Internal] point within vtx_buffer after each add command (to avoid using the ImVector<> operators too much) ImDrawVert* vtx_write; // [Internal] point within vtx_buffer after each add command (to avoid using the ImVector<> operators too much)
unsigned int vtx_current_idx; // [Internal] == vtx_buffer.Size unsigned int vtx_current_idx; // [Internal] == vtx_buffer.Size
@ -1039,6 +1046,8 @@ struct ImDrawList
ImVector<ImVec4> clip_rect_stack; // [Internal] ImVector<ImVec4> clip_rect_stack; // [Internal]
ImVector<ImTextureID> texture_id_stack; // [Internal] ImVector<ImTextureID> texture_id_stack; // [Internal]
ImVector<ImVec2> path; // [Internal] current path building ImVector<ImVec2> path; // [Internal] current path building
int channel_current; //
ImVector<ImDrawChannel> channels; // [Internal] draw channels for layering or columns API
ImDrawList() { owner_name = NULL; Clear(); } ImDrawList() { owner_name = NULL; Clear(); }
~ImDrawList() { ClearFreeMemory(); } ~ImDrawList() { ClearFreeMemory(); }
@ -1074,6 +1083,9 @@ struct ImDrawList
// Advanced // Advanced
IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'user_callback' in ImDrawCmd and call the function instead of rendering triangles. IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'user_callback' in ImDrawCmd and call the function instead of rendering triangles.
IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible
IMGUI_API void ChannelsSplit(int channel_count);
IMGUI_API void ChannelsMerge(int channel_count);
IMGUI_API void ChannelsSetCurrent(int idx);
// Internal helpers // Internal helpers
IMGUI_API void PrimReserve(int idx_count, int vtx_count); IMGUI_API void PrimReserve(int idx_count, int vtx_count);