Skip to content

Instantly share code, notes, and snippets.

@Seneral
Last active June 12, 2024 15:57
Show Gist options
  • Save Seneral/b4b34a283539938869cd10b2d065a88c to your computer and use it in GitHub Desktop.
Save Seneral/b4b34a283539938869cd10b2d065a88c to your computer and use it in GitHub Desktop.
Dear ImGUI OpenGL integrated rendering - integrate GL views, update views and labels with partial rendering when UI and rest of screen is only updated on events
diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp
index 2a4b1d7..aff8dbc
--- a/backends/imgui_impl_opengl3.cpp
+++ b/backends/imgui_impl_opengl3.cpp
@@ -114,6 +114,7 @@
#endif
#include "imgui.h"
+#include "imgui_internal.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_opengl3.h"
#include <stdio.h>
@@ -492,7 +493,7 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid
// OpenGL3 Render function.
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
// This is in order to be able to run within an OpenGL engine that doesn't do so.
-void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
+void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data, ImVec2 clipMin, ImVec2 clipMax)
{
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
@@ -557,7 +558,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
- const ImDrawList* cmd_list = draw_data->CmdLists[n];
+ ImDrawList* cmd_list = draw_data->CmdLists[n];
// Upload vertex/index buffers
// - OpenGL drivers are in a very sorry state nowadays....
@@ -592,15 +593,27 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
{
- const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+ ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback, registered via ImDrawList::AddCallback()
+ ImRect clipRect(pcmd->ClipRect);
+ ImRect clipOverride(clipMin, clipMax);
+ if (!clipOverride.Overlaps(clipRect))
+ continue;
+ clipOverride.ClipWith(clipRect);
+
+ // Overwrite with overlapping clip rect
+ pcmd->ClipRect = clipOverride.ToVec4();
+
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
else
pcmd->UserCallback(cmd_list, pcmd);
+
+ // Restore to original clip rect
+ pcmd->ClipRect = clipRect.ToVec4();
}
else
{
@@ -609,9 +622,17 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
+ ImVec2 clip_override_min((clipMin.x - clip_off.x) * clip_scale.x, (clipMin.y - clip_off.y) * clip_scale.y);
+ ImVec2 clip_override_max((clipMax.x - clip_off.x) * clip_scale.x, (clipMax.y - clip_off.y) * clip_scale.y);
+
+ ImRect clip(clip_min, clip_max);
+ ImRect clipOverride(clip_override_min, clip_override_max);
+ if (!clip.Overlaps(clipOverride))
+ continue;
+ clip.ClipWith(clipOverride);
// Apply scissor/clipping rectangle (Y is inverted in OpenGL)
- GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));
+ GL_CALL(glScissor((int)clip.Min.x, (int)((float)fb_height - clip.Max.y), (int)(clip.Max.x - clip.Min.x), (int)(clip.Max.y - clip.Min.y)));
// Bind texture, Draw
GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
@@ -670,6 +691,81 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
(void)bd; // Not all compilation paths use this
}
+
+// OpenGL3 Render function.
+void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
+{
+ ImGui_ImplOpenGL3_RenderDrawData(draw_data, draw_data->DisplayPos,
+ ImVec2(draw_data->DisplayPos.x+draw_data->DisplaySize.x, draw_data->DisplayPos.y+draw_data->DisplaySize.y));
+}
+
+void ImGui_ImplOpenGL3_RenderDrawList(ImGuiViewport *viewport, ImDrawList *cmd_list)
+{ // Assumes to be nested within RenderDrawData
+
+ // Setup own buffers since main buffers are already used (current main draw list data is uploaded to it)
+ static unsigned int VboHandle = 0, ElementsHandle = 0;
+ if (VboHandle == 0 && ElementsHandle == 0)
+ {
+ glGenBuffers(1, &VboHandle);
+ glGenBuffers(1, &ElementsHandle);
+ }
+
+ if ((viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0)
+ return;
+
+ ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+
+ GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer);
+ GLuint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint*)&last_element_array_buffer);
+
+ const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);
+ const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);
+
+ GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, VboHandle));
+ GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ElementsHandle));
+ GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW));
+ GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW));
+ GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));
+ GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));
+ GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));
+ GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos)));
+ GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv)));
+ GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col)));
+
+ ImVec2 clip_off = viewport->Pos; // (0,0) unless using multi-viewports
+ ImVec2 clip_scale = ImGui::GetIO().DisplayFramebufferScale; // (1,1) unless using retina display which are often (2,2)
+ int fb_height = (int)(viewport->Size.y * ImGui::GetIO().DisplayFramebufferScale.y);
+
+ for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+ {
+ ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+ ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
+ ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
+
+ GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));
+
+ // Bind texture, Draw
+ GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
+ if (bd->GlVersion >= 320)
+ GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset));
+ else
+#endif
+ GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))));
+ }
+
+ GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer));
+ GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer));
+ // Data still intact
+ GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));
+ GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));
+ GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));
+ GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos)));
+ GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv)));
+ GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col)));
+
+}
+
bool ImGui_ImplOpenGL3_CreateFontsTexture()
{
ImGuiIO& io = ImGui::GetIO();
diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h
index ae718d8..c11c9c6
--- a/backends/imgui_impl_opengl3.h
+++ b/backends/imgui_impl_opengl3.h
@@ -35,6 +35,8 @@ IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullpt
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
+IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data, ImVec2 clipMin, ImVec2 clipMax);
+IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawList(ImGuiViewport *viewport, ImDrawList *cmd_list);
// (Optional) Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture();
/**
Copyright (c) 2024 Seneral
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h"
#endif
#include "imgui_onDemand.hpp"
#include "backends/imgui_impl_opengl3.h"
#include "GL/glew.h"
std::list<OnDemandItem> onDemandStack;
void OnDemandNewFrame()
{
onDemandStack.clear();
}
OnDemandItem &MarkOnDemandArea(const ImRect &bb)
{
OnDemandItem state = {};
auto viewWin = ImGui::GetCurrentWindowRead();
state.renderOwn = true; // cause a partial render to render this area
state.bb = bb;
state.clip = bb;
state.viewport = ImGui::GetWindowViewport();
state.renderScale = ImGui::GetIO().DisplayFramebufferScale;
state.renderSize = state.viewport->Size * state.renderScale;
onDemandStack.push_back(state);
return onDemandStack.back();
}
ImVec2 SetOnDemandRenderArea(const OnDemandItem &state, const ImVec4 &ClipRect)
{
const auto scale = [](ImVec4 rect, const ImVec2 &scale)
{
rect.x *= scale.x;
rect.y *= scale.y;
rect.z *= scale.x;
rect.w *= scale.y;
return rect;
};
auto view = scale(state.bb.ToVec4(), state.renderScale);
auto clip = scale(ClipRect, state.renderScale);
glViewport(view.x, state.renderSize.y-view.w, view.z-view.x, view.w-view.y);
glScissor(clip.x, state.renderSize.y-clip.w, clip.z-clip.x, clip.w-clip.y);
return ImVec2(view.z-view.x, view.w-view.y);
}
OnDemandItem *AddOnDemandRender(ImRect bb, ImDrawCallback RenderCallback, void *userData, bool resetRenderState)
{
if (ImGui::GetCurrentWindowRead()->SkipItems) return nullptr;
ImGuiWindow* window = ImGui::GetCurrentWindow();
window->DrawList->PushClipRect(bb.Min, bb.Max, true);
ImRect clipped = ImRect(window->DrawList->GetClipRectMin(), window->DrawList->GetClipRectMax());
if (clipped.GetArea() == 0)
{
window->DrawList->PopClipRect();
return nullptr;
}
OnDemandItem &state = MarkOnDemandArea(bb);
state.clip = clipped;
state.userData = userData;
window->DrawList->AddCallback(RenderCallback, &state);
if (resetRenderState)
window->DrawList->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
window->DrawList->PopClipRect();
return &state;
}
OnDemandItem *AddOnDemandIcon(const char *idLabel, const ImVec2 &size, const ImVec2 &icon, ImDrawCallback RenderCallback, void *userData)
{
if (ImGui::GetCurrentWindowRead()->SkipItems) return nullptr;
ImVec2 padding = ImGui::GetStyle().FramePadding;
auto c = ImGui::GetCurrentWindowRead()->DC.CursorPos + padding;
const ImRect bb(c.x, c.y, c.x+size.x+padding.x, c.y+size.y+padding.y);
ImGui::ItemSize(size + padding * 2);
ImGui::ItemAdd(bb, ImGui::GetID(idLabel));
ImRect bbR(bb.GetCenter()-icon/2, bb.GetCenter()+icon/2);
return AddOnDemandRender(bbR, RenderCallback, userData);
}
OnDemandItem *AddOnDemandText(const char* maxText, ImDrawCallback RenderCallback)
{
if (ImGui::GetCurrentWindowRead()->SkipItems) return nullptr;
ImGuiWindow* window = ImGui::GetCurrentWindow();
const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
const ImVec2 text_size = ImGui::CalcTextSize(maxText, NULL, true, 0.0f);
ImRect bb(text_pos, text_pos + text_size);
ImGui::ItemSize(text_size);
ImGui::ItemAdd(bb, ImGui::GetID(maxText));
OnDemandItem *state = AddOnDemandRender(bb, RenderCallback, nullptr, false);
if (!state) return nullptr;
state->font = ImGui::GetFont();
state->fontSize = ImGui::GetFontSize();
return state;
}
void RenderOnDemandText(const OnDemandItem &state, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
RenderOnDemandTextV(state, fmt, args);
va_end(args);
}
void RenderOnDemandTextV(const OnDemandItem &state, const char* fmt, va_list args)
{
const char* text, *text_end;
ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
static ImDrawList onDemandDrawList(ImGui::GetDrawListSharedData());
onDemandDrawList._ResetForNewFrame();
onDemandDrawList.PushClipRect(state.bb.Min, state.bb.Max, false);
onDemandDrawList.PushTextureID(state.font->ContainerAtlas->TexID);
onDemandDrawList.AddText(state.font, state.fontSize, state.bb.Min, ImGui::GetColorU32(ImGuiCol_Text), text, text_end);
onDemandDrawList.PopTextureID();
onDemandDrawList.PopClipRect();
ImGui_ImplOpenGL3_RenderDrawList(state.viewport, &onDemandDrawList);
}
/**
Copyright (c) 2024 Seneral
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "imgui.h"
#include "imgui_internal.h"
#include <list>
#ifndef IMGUI_ONDEMAND_H
#define IMGUI_ONDEMAND_H
/**
* Allows for integrating OpenGL elements into the UI with ImDrawCallback
* Allows for rendering parts of the screen faster than the UI is updated
*
* Usage for just integrating OpenGL elements:
* - Call OnDemandNewFrame(); after ImGui::NewFrame();
* - Use AddOnDemand**** functions to register callbacks for custom rendering
* - Call ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); normally
*
* Usage to render OnDemandItems partially and independent from UI update:
* - Modify backend (e.g. OpenGL3) to allow specifying render rect
* - Loop over onDemandStack and render OnDemandItem::clip only, e.g.:
* for (OnDemandState &onDemandState : onDemandStack)
* {
* if (onDemandState.renderOwn)
* ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData(), onDemandState.clip.Min, onDemandState.clip.Max);
* }
*/
struct OnDemandItem
{
bool renderOwn;
ImGuiViewport *viewport;
ImRect bb, clip;
ImVec2 renderSize, renderScale;
ImFont *font = NULL;
float fontSize;
void *userData = NULL;
};
extern std::list<OnDemandItem> onDemandStack;
void OnDemandNewFrame();
/**
* Add a bounding box to the OnDemand stack
*/
OnDemandItem &MarkOnDemandArea(const ImRect &bb);
/**
* Setup OpenGL render area for rendering specified OnDemandItem
* Overwrites glViewport and glScissors
*/
ImVec2 SetOnDemandRenderArea(const OnDemandItem &state, const ImVec4 &ClipRect);
/**
* Register a bounding box for custom rendering in callback
* Use SetOnDemandRenderArea in callback to set up for OpenGL rendering
*/
OnDemandItem *AddOnDemandRender(ImRect bb, ImDrawCallback RenderCallback, void *userData = nullptr, bool resetRenderState = true);
/**
* Register an icon of size 'icon' for custom rendering in callback
* Icon is centered in area of size 'size' plus FramePadding
* Use SetOnDemandRenderArea in callback to set up for OpenGL rendering
*/
OnDemandItem *AddOnDemandIcon(const char *idLabel, const ImVec2 &size, const ImVec2 &icon, ImDrawCallback RenderCallback, void *userData = nullptr);
/**
* Register text to be rendered on-demand with maxText specifying the size to reserve
* Use RenderOnDemandText in callback to render the text
*/
OnDemandItem *AddOnDemandText(const char* maxText, ImDrawCallback RenderCallback);
/**
* Uses OpenGL3 to immediately render the specifying text.
* Assumes OpenGL state to be already set up by the OpenGL backend, and it is expected to be called from a ImDrawCallback only.
*/
void RenderOnDemandText(const OnDemandItem &state, const char* fmt, ...);
/**
* Uses OpenGL3 to immediately render the specifying text.
* Assumes OpenGL state to be already set up by the OpenGL backend, and it is expected to be called from a ImDrawCallback only.
*/
void RenderOnDemandTextV(const OnDemandItem &state, const char* fmt, va_list args);
#endif // IMGUI_ONDEMAND_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment