From 87ebe95fd67d66694e78b100d5053e96ced17a65 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2015 18:19:01 -0600 Subject: [PATCH] Columns/ImDrawList: dispatch render of each column in a sub-draw list and merge on closure, saving draw calls (#125) --- imgui.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- imgui.h | 20 ++++++++++++--- 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 4ce8a4e8..8c71f3bb 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6,7 +6,6 @@ // ANTI-ALIASED PRIMITIVES BRANCH // TODO -// - Redesign parameters passed to RenderFn ImDrawList etc. // - 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) { window->DC.ColumnsOffsetX = ImGui::GetColumnOffset(window->DC.ColumnsCurrent) - window->DC.ColumnsStartX + g.Style.ItemSpacing.x; + window->DrawList->ChannelsSetCurrent(window->DC.ColumnsCurrent); } else { window->DC.ColumnsCurrent = 0; window->DC.ColumnsOffsetX = 0.0f; 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.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 ImGui::PopItemWidth(); PopClipRect(); + window->DrawList->ChannelsMerge(window->DC.ColumnsCount); window->DC.ColumnsCellMaxY = ImMax(window->DC.ColumnsCellMaxY, window->DC.CursorPos.y); 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?) window->DC.ColumnsOffsetsT[column_index] = t; } - + window->DrawList->ChannelsSplit(window->DC.ColumnsCount); PushColumnClipRect(); ImGui::PushItemWidth(ImGui::GetColumnWidth() * 0.65f); } @@ -8963,8 +8965,10 @@ void ImDrawList::Clear() vtx_current_idx = 0; idx_buffer.resize(0); idx_write = NULL; + channel_current = 0; clip_rect_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() @@ -8975,8 +8979,16 @@ void ImDrawList::ClearFreeMemory() vtx_current_idx = 0; idx_buffer.clear(); idx_write = NULL; + channel_current = 0; clip_rect_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() @@ -9008,6 +9020,64 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) 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() { ImDrawCmd* current_cmd = cmd_buffer.Size ? &cmd_buffer.back() : NULL; diff --git a/imgui.h b/imgui.h index 4dee2c01..c03c8112 100644 --- a/imgui.h +++ b/imgui.h @@ -1017,6 +1017,13 @@ struct ImDrawVert IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; #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 cmd_buffer; + ImVector idx_buffer; +}; + // 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. // 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 ImVector cmd_buffer; // Commands. Typically 1 command = 1 gpu draw call. - ImVector vtx_buffer; // Vertex buffer. ImVector idx_buffer; // Index buffer. Each command consume ImDrawCmd::idx_count of those + ImVector 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 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 @@ -1039,6 +1046,8 @@ struct ImDrawList ImVector clip_rect_stack; // [Internal] ImVector texture_id_stack; // [Internal] ImVector path; // [Internal] current path building + int channel_current; // + ImVector channels; // [Internal] draw channels for layering or columns API ImDrawList() { owner_name = NULL; Clear(); } ~ImDrawList() { ClearFreeMemory(); } @@ -1072,8 +1081,11 @@ struct ImDrawList inline void PathStroke(ImU32 col, float thickness, bool closed) { AddPolyline(path.Data, path.Size, col, thickness, closed, true); PathClear(); } // 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 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 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 ChannelsSplit(int channel_count); + IMGUI_API void ChannelsMerge(int channel_count); + IMGUI_API void ChannelsSetCurrent(int idx); // Internal helpers IMGUI_API void PrimReserve(int idx_count, int vtx_count);