Browse Source

ImDrawList::AddCallback() allows for custom rendering (e.g. 3D scene inside a imgui widget))

ocornut 10 years ago
parent
commit
2ecc285919
2 changed files with 71 additions and 49 deletions
  1. 35 31
      imgui.cpp
  2. 36 18
      imgui.h

+ 35 - 31
imgui.cpp

@@ -6039,81 +6039,84 @@ void ImDrawList::Clear()
     texture_id_stack.resize(0);
 }
 
-// This is useful if you need to force-create a new draw call (to allow for depending rendering / blending).
-// Otherwise primitives are merged into the same draw-call as much as possible.
-void ImDrawList::SplitDrawCmd()
+void ImDrawList::AddDrawCmd()
 {
     ImDrawCmd draw_cmd;
     draw_cmd.vtx_count = 0;
-    draw_cmd.clip_rect = clip_rect_stack.back();
-    draw_cmd.texture_id = texture_id_stack.back();
+    draw_cmd.clip_rect = clip_rect_stack.empty() ? GNullClipRect : clip_rect_stack.back();
+    draw_cmd.texture_id = texture_id_stack.empty() ? NULL : texture_id_stack.back();
+    draw_cmd.user_callback = NULL;
+    draw_cmd.user_callback_data = NULL;
     commands.push_back(draw_cmd);
 }
 
-void ImDrawList::SetClipRect(const ImVec4& clip_rect)
+void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data)
 {
     ImDrawCmd* current_cmd = commands.empty() ? NULL : &commands.back();
-    if (current_cmd && current_cmd->vtx_count == 0)
+    if (!current_cmd || current_cmd->vtx_count != 0 || current_cmd->user_callback != NULL)
     {
-        // Reuse existing command (high-level clipping may have discarded vertices submitted earlier)
-        // FIXME-OPT: Possibly even reuse previous command.
-        current_cmd->clip_rect = clip_rect;
+        AddDrawCmd();
+        current_cmd = &commands.back();
+    }
+    current_cmd->user_callback = callback;
+    current_cmd->user_callback_data = callback_data;
+
+    // Force a new command after us
+    // We function this way so that the most common calls (AddLine, AddRect..) always have a command to add to without doing any check.
+    AddDrawCmd();
+}
+
+void ImDrawList::UpdateClipRect()
+{
+    ImDrawCmd* current_cmd = commands.empty() ? NULL : &commands.back();
+    if (!current_cmd || (current_cmd->vtx_count != 0) || current_cmd->user_callback != NULL)
+    {
+        AddDrawCmd();
     }
     else
     {
-        ImDrawCmd draw_cmd;
-        draw_cmd.vtx_count = 0;
-        draw_cmd.clip_rect = clip_rect;
-        draw_cmd.texture_id = texture_id_stack.back();
-        commands.push_back(draw_cmd);
+        current_cmd->clip_rect = clip_rect_stack.empty() ? GNullClipRect : clip_rect_stack.back();
     }
 }
 
 void ImDrawList::PushClipRect(const ImVec4& clip_rect)
 {
     clip_rect_stack.push_back(clip_rect);
-    SetClipRect(clip_rect);
+    UpdateClipRect();
 }
 
 void ImDrawList::PopClipRect()
 {
     IM_ASSERT(clip_rect_stack.size() > 0);
     clip_rect_stack.pop_back();
-    const ImVec4 clip_rect = clip_rect_stack.empty() ? GNullClipRect : clip_rect_stack.back();
-    SetClipRect(clip_rect);
+    UpdateClipRect();
 }
 
-void ImDrawList::SetTextureID(const ImTextureID& texture_id)
+void ImDrawList::UpdateTextureID()
 {
     ImDrawCmd* current_cmd = commands.empty() ? NULL : &commands.back();
-    if (current_cmd && (current_cmd->vtx_count == 0 || current_cmd->texture_id == texture_id))
+    const ImTextureID texture_id = texture_id_stack.empty() ? NULL : texture_id_stack.back();
+    if (!current_cmd || (current_cmd->vtx_count != 0 && current_cmd->texture_id != texture_id) || current_cmd->user_callback != NULL)
     {
-        // Reuse existing command
-        // FIXME-OPT: Possibly even reuse previous command.
-        current_cmd->texture_id = texture_id;
+        AddDrawCmd();
     }
     else
     {
-        ImDrawCmd draw_cmd;
-        draw_cmd.vtx_count = 0;
-        draw_cmd.clip_rect = clip_rect_stack.empty() ? GNullClipRect: clip_rect_stack.back();
-        draw_cmd.texture_id = texture_id;
-        commands.push_back(draw_cmd);
+        current_cmd->texture_id = texture_id;
     }
 }
 
 void ImDrawList::PushTextureID(const ImTextureID& texture_id)
 {
     texture_id_stack.push_back(texture_id);
-    SetTextureID(texture_id);
+    UpdateTextureID();
 }
 
 void ImDrawList::PopTextureID()
 {
     IM_ASSERT(texture_id_stack.size() > 0);
     texture_id_stack.pop_back();
-    const ImTextureID texture_id = texture_id_stack.empty() ? NULL : texture_id_stack.back();
-    SetTextureID(texture_id);
+    UpdateTextureID();
 }
 
 void ImDrawList::ReserveVertices(unsigned int vtx_count)
@@ -6143,6 +6146,7 @@ void ImDrawList::AddVtxUV(const ImVec2& pos, ImU32 col, const ImVec2& uv)
     vtx_write++;
 }
 
+// NB: memory should be reserved for 6 vertices by the caller.
 void ImDrawList::AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col)
 {
     const float length = sqrtf(ImLengthSqr(b - a));

+ 36 - 18
imgui.h

@@ -6,6 +6,7 @@
 
 #pragma once
 
+struct ImDrawCmd;
 struct ImDrawList;
 struct ImFont;
 struct ImFontAtlas;
@@ -693,19 +694,32 @@ struct ImGuiTextEditCallbackData
 
 //-----------------------------------------------------------------------------
 // Draw List
-// Hold a series of drawing commands. The user provides a renderer for ImDrawList
+// Hold a series of drawing commands. The user provides a renderer for ImDrawList.
 //-----------------------------------------------------------------------------
 
-// Typically, One ImDrawCmd = One GPU draw call
+// Draw callbacks for advanced uses.
+// NB- You most likely DO NOT need to care about draw callbacks just to create your own widget or customized UI rendering (you can poke into the draw list for that)
+// Draw callback are useful for example if you want to render a complex 3D scene inside a UI element.
+// The expected behavior from your rendering loop is:
+//   if (cmd.user_callback != NULL)
+//       cmd.user_callback(parent_list, cmd);
+//   else
+//       RenderTriangles()
+// It is up to you to decide if your rendering loop or the callback should be responsible for backup/restoring rendering state.
+typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd);
+
+// Typically, 1 command = 1 gpu draw call
 struct ImDrawCmd
 {
-    unsigned int    vtx_count;      // Number of vertices (multiple of 3) to be drawn. The vertices are stored in the callee ImDrawList's vtx_buffer[] array
-    ImVec4          clip_rect;      // Clipping rectangle (x1, y1, x2, y2)
-    ImTextureID     texture_id;     // Copy of user-provided 'TexID' from ImFont or passed to Image*() functions. Ignore if not using images or multiple fonts
+    unsigned int    vtx_count;                  // Number of vertices (multiple of 3) to be drawn as triangles. The vertices are stored in the callee ImDrawList's vtx_buffer[] array.
+    ImVec4          clip_rect;                  // Clipping rectangle (x1, y1, x2, y2)
+    ImTextureID     texture_id;                 // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas.
+    ImDrawCallback  user_callback;              // If != NULL, call the function instead of rendering the vertices. vtx_count will be 0. clip_rect and texture_id will be set normally.
+    void*           user_callback_data;         // The draw callback code can access this.
 };
 
+// Vertex layout
 #ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT
-// Default vertex layout
 struct ImDrawVert
 {
     ImVec2  pos;
@@ -728,28 +742,20 @@ IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT;
 struct ImDrawList
 {
     // This is what you have to render
-    ImVector<ImDrawCmd>     commands;           // Commands. Typically 1 command = 1 draw call.
+    ImVector<ImDrawCmd>     commands;           // Commands. Typically 1 command = 1 gpu draw call.
     ImVector<ImDrawVert>    vtx_buffer;         // Vertex buffer. Each command consume ImDrawCmd::vtx_count of those
 
     // [Internal to ImGui]
-    ImVector<ImVec4>        clip_rect_stack;    // [internal]
-    ImVector<ImTextureID>   texture_id_stack;   // [internal] 
-    ImDrawVert*             vtx_write;          // [internal] point within vtx_buffer after each add command (to avoid using the ImVector<> operators too much)
+    ImVector<ImVec4>        clip_rect_stack;    // [Internal]
+    ImVector<ImTextureID>   texture_id_stack;   // [Internal] 
+    ImDrawVert*             vtx_write;          // [Internal] point within vtx_buffer after each add command (to avoid using the ImVector<> operators too much)
 
     ImDrawList() { Clear(); }
-
     IMGUI_API void  Clear();
-    IMGUI_API void  SetClipRect(const ImVec4& clip_rect);
     IMGUI_API void  PushClipRect(const ImVec4& clip_rect);
     IMGUI_API void  PopClipRect();
-    IMGUI_API void  SetTextureID(const ImTextureID& texture_id);
     IMGUI_API void  PushTextureID(const ImTextureID& texture_id);
     IMGUI_API void  PopTextureID();
-    IMGUI_API void  ReserveVertices(unsigned int vtx_count);
-    IMGUI_API void  AddVtx(const ImVec2& pos, ImU32 col);
-    IMGUI_API void  AddVtxUV(const ImVec2& pos, ImU32 col, const ImVec2& uv);
-    IMGUI_API void  AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col);
-    IMGUI_API void  SplitDrawCmd();             // This is useful if you need to force-create a new draw call (to allow for depending rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible
 
     // Primitives   
     IMGUI_API void  AddLine(const ImVec2& a, const ImVec2& b, ImU32 col);
@@ -761,6 +767,18 @@ struct ImDrawList
     IMGUI_API void  AddArc(const ImVec2& center, float rad, ImU32 col, int a_min, int a_max, bool tris = false, const ImVec2& third_point_offset = ImVec2(0,0));
     IMGUI_API void  AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f);
     IMGUI_API void  AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col = 0xFFFFFFFF);
+
+    // 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
+
+    // Internal helpers
+    IMGUI_API void  ReserveVertices(unsigned int vtx_count);
+    IMGUI_API void  AddVtx(const ImVec2& pos, ImU32 col);
+    IMGUI_API void  AddVtxUV(const ImVec2& pos, ImU32 col, const ImVec2& uv);
+    IMGUI_API void  AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col);
+    IMGUI_API void  UpdateClipRect();
+    IMGUI_API void  UpdateTextureID();
 };
 
 // Load and rasterize multiple TTF fonts into a same texture.