Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
595 changes: 595 additions & 0 deletions backends/implot3d_impl_opengl3.cpp

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions backends/implot3d_impl_opengl3.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2024-2026 Breno Cunha Queiroz

#pragma once
#include "implot3d.h"
#include "implot3d_internal.h"
#ifndef IMGUI_DISABLE

IMPLOT3D_IMPL_API bool ImPlot3D_ImplOpenGL3_Init();
IMPLOT3D_IMPL_API void ImPlot3D_ImplOpenGL3_Shutdown();

IMPLOT3D_IMPL_API void ImPlot3D_ImplOpenGL3_RenderDrawData(ImDrawData3D* draw_data);

#endif // #ifndef IMGUI_DISABLE
3 changes: 2 additions & 1 deletion example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ set(IMPLOT3D_SOURCE
${IMPLOT3D_SOURCE_DIR}/implot3d_demo.cpp
${IMPLOT3D_SOURCE_DIR}/implot3d_items.cpp
${IMPLOT3D_SOURCE_DIR}/implot3d_meshes.cpp
${IMPLOT3D_SOURCE_DIR}/backends/implot3d_impl_opengl3.cpp
)
add_library(implot3d STATIC ${IMPLOT3D_SOURCE})
target_include_directories(implot3d PUBLIC ${IMPLOT3D_SOURCE_DIR})
target_include_directories(implot3d PUBLIC ${IMPLOT3D_SOURCE_DIR};${IMPLOT3D_SOURCE_DIR}/backends/)
target_link_libraries(implot3d PUBLIC imgui)

# Add the executable
Expand Down
10 changes: 9 additions & 1 deletion example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "imgui_impl_opengl3.h"
#include "implot.h"
#include "implot3d.h"
#include "implot3d_internal.h"
#include "implot3d_impl_opengl3.h"
#include <GLFW/glfw3.h>
#include <iostream>

Expand Down Expand Up @@ -61,6 +63,7 @@ int main() {
// Setup backend
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
ImPlot3D_ImplOpenGL3_Init();

// Main loop
while (!glfwWindowShouldClose(window)) {
Expand All @@ -76,20 +79,25 @@ int main() {
ImPlot::ShowDemoWindow();
ImPlot3D::ShowDemoWindow();

// Render
// Prepare draw data for rendering
ImGui::Render();
ImPlot3D::Render();

// Render draw data
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImPlot3D_ImplOpenGL3_RenderDrawData(ImPlot3D::GetDrawData());
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

// Swap buffers
glfwSwapBuffers(window);
}

// Cleanup
ImPlot3D_ImplOpenGL3_Shutdown();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImPlot3D::DestroyContext();
Expand Down
239 changes: 226 additions & 13 deletions implot3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,115 @@ ImPlot3DContext* GetCurrentContext() { return GImPlot3D; }

void SetCurrentContext(ImPlot3DContext* ctx) { GImPlot3D = ctx; }

void Render() {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "Render() called between BeginPlot() and EndPlot()!");

// Mark all existing plot data for deletion
for (int i = 0; i < gp.DrawData.PlotData.Size; i++) {
gp.DrawData.PlotData[i].ShouldDelete = true;
gp.DrawData.PlotData[i].ShouldRender = false;
}

// Update plot data for all active plots
for (int i = 0; i < gp.Plots.GetBufSize(); i++) {
ImPlot3DPlot* plot = gp.Plots.GetByIndex(i);
if (!plot || !plot->Initialized)
continue;

// Find or create render data for this plot
ImDrawData3DPlot* plot_data = gp.DrawData.FindOrAddPlot(plot->ID);
plot_data->ShouldDelete = false;
plot_data->ShouldRender = true;

// Check if texture needs resizing
ImVec2 current_size = plot->PlotRect.GetSize();
if (plot_data->ColorTextureID == ImTextureID_Invalid) {
plot_data->ShouldResize = true;
plot_data->TextureSize = current_size;
} else {
if (ImFabs(plot_data->TextureSize.x - current_size.x) > 1.0f || ImFabs(plot_data->TextureSize.y - current_size.y) > 1.0f) {
plot_data->ShouldResize = true;
plot_data->TextureSize = current_size;
} else {
plot_data->ShouldResize = false;
}
}

// Copy draw list data with sorting
// Build triangle references for sorting
const int tri_count = plot->DrawList.ZBuffer.Size;

if (tri_count > 0) {
struct TriRef {
double z;
int tri_idx;
};
TriRef* tris = (TriRef*)IM_ALLOC(sizeof(TriRef) * tri_count);
for (int i = 0; i < tri_count; i++) {
tris[i].z = plot->DrawList.ZBuffer[i];
tris[i].tri_idx = i;
}

// Sort triangles by depth (back to front)
ImQsort(tris, (size_t)tri_count, sizeof(TriRef), [](const void* a, const void* b) {
double za = ((const TriRef*)a)->z;
double zb = ((const TriRef*)b)->z;
return (za < zb) ? -1 : (za > zb) ? 1 : 0;
});

// Copy indices in sorted order
plot_data->IdxBuffer.resize(tri_count * 3);
ImDrawIdx3D* idx_in = plot->DrawList.IdxBuffer.Data;
for (int i = 0; i < tri_count; i++) {
int tri_i = tris[i].tri_idx;
int base_idx = tri_i * 3;
plot_data->IdxBuffer[i * 3 + 0] = idx_in[base_idx + 0];
plot_data->IdxBuffer[i * 3 + 1] = idx_in[base_idx + 1];
plot_data->IdxBuffer[i * 3 + 2] = idx_in[base_idx + 2];
}

IM_FREE(tris);
} else {
plot_data->IdxBuffer.resize(0);
}

// Copy vertices
plot_data->VtxBuffer.resize(plot->DrawList.VtxBuffer.Size);
memcpy(plot_data->VtxBuffer.Data, plot->DrawList.VtxBuffer.Data, plot->DrawList.VtxBuffer.Size * sizeof(ImDrawVert3D));

// Copy commands
plot_data->CmdBuffer = plot->DrawList.CmdBuffer;

// Copy line and marker primitives
plot_data->LineBuffer = plot->DrawList.LineBuffer;
plot_data->MarkerBuffer = plot->DrawList.MarkerBuffer;

// Copy plot state needed for rendering
plot_data->Rotation = plot->Rotation;
plot_data->PlotRectMin = plot->PlotRect.Min;
plot_data->PlotRectMax = plot->PlotRect.Max;

// Set clipping parameters
// Note: NDC cube is [-0.5*NDCScale, 0.5*NDCScale] per axis
plot_data->ShouldClip = !ImHasFlag(plot->Flags, ImPlot3DFlags_NoClip);
if (plot_data->ShouldClip) {
// Clip to full NDC cube using per-axis NDCScale
plot_data->ClipMin =
ImPlot3DPoint(-0.5 * plot->Axes[ImAxis3D_X].NDCScale, -0.5 * plot->Axes[ImAxis3D_Y].NDCScale, -0.5 * plot->Axes[ImAxis3D_Z].NDCScale);
plot_data->ClipMax =
ImPlot3DPoint(0.5 * plot->Axes[ImAxis3D_X].NDCScale, 0.5 * plot->Axes[ImAxis3D_Y].NDCScale, 0.5 * plot->Axes[ImAxis3D_Z].NDCScale);
}

plot->DrawList.ResetBuffers(); // Clear plot's draw list for next frame
}
}

ImDrawData3D* GetDrawData() {
ImPlot3DContext& gp = *GImPlot3D;
return &gp.DrawData;
}

//-----------------------------------------------------------------------------
// [SECTION] Text Utils
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -1636,8 +1745,28 @@ void EndPlot() {
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Mismatched BeginPlot()/EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;

// Move triangles from 3D draw list to ImGui draw list
plot.DrawList.SortedMoveToImGuiDrawList();
if (gp.UseImPlot3DBackend) {
// Find the texture from the draw data
ImTextureID texture_id = ImTextureID_Invalid;
for (int i = 0; i < gp.DrawData.PlotData.Size; i++) {
if (gp.DrawData.PlotData[i].PlotID == plot.ID) {
texture_id = gp.DrawData.PlotData[i].ColorTextureID;
break;
}
}

// Only draw if texture is valid
if (texture_id != ImTextureID_Invalid) {
ImDrawList& draw_list = *ImGui::GetWindowDrawList();
ImVec2 uv0 = ImVec2(0, 0);
ImVec2 uv1 = ImVec2(1, 1);
ImU32 col = IM_COL32(255, 255, 255, 255);
draw_list.AddImage(texture_id, plot.PlotRect.Min, plot.PlotRect.Max, uv0, uv1, col);
}
} else {
// Move triangles from 3D draw list to ImGui draw list
plot.DrawList.SortedMoveToImGuiDrawList();
}

// Handle data fitting
if (plot.FitThisFrame) {
Expand Down Expand Up @@ -1972,6 +2101,8 @@ void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) {
// [SECTION] Plot Utils
//-----------------------------------------------------------------------------

ImPool<ImPlot3DPlot>* GetPlots() { return &GImPlot3D->Plots; }

ImPlot3DPlot* GetCurrentPlot() { return GImPlot3D->CurrentPlot; }

void BustPlotCache() {
Expand Down Expand Up @@ -3181,6 +3312,7 @@ bool ColormapSlider(const char* label, float* t, ImVec4* out, const char* format

void InitializeContext(ImPlot3DContext* ctx) {
ResetContext(ctx);
ctx->UseImPlot3DBackend = false; // Disabled by default

const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396};
const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409};
Expand Down Expand Up @@ -3600,8 +3732,8 @@ double ImPlot3DQuat::Dot(const ImPlot3DQuat& rhs) const { return x * rhs.x + y *
// [SECTION] ImDrawList3D
//-----------------------------------------------------------------------------

void ImDrawList3D::PrimReserve(int idx_count, int vtx_count) {
IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0);
void ImDrawList3D::PrimReserve(int idx_count, int vtx_count, int z_count) {
IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && z_count >= 0);

int vtx_buffer_old_size = VtxBuffer.Size;
VtxBuffer.resize(vtx_buffer_old_size + vtx_count);
Expand All @@ -3612,16 +3744,68 @@ void ImDrawList3D::PrimReserve(int idx_count, int vtx_count) {
_IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size;

int z_buffer_old_size = ZBuffer.Size;
ZBuffer.resize(z_buffer_old_size + idx_count / 3);
ZBuffer.resize(z_buffer_old_size + z_count);
_ZWritePtr = ZBuffer.Data + z_buffer_old_size;
}

void ImDrawList3D::PrimUnreserve(int idx_count, int vtx_count) {
IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0);
void ImDrawList3D::PrimUnreserve(int idx_count, int vtx_count, int z_count) {
IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && z_count >= 0);

VtxBuffer.shrink(VtxBuffer.Size - vtx_count);
IdxBuffer.shrink(IdxBuffer.Size - idx_count);
ZBuffer.shrink(ZBuffer.Size - idx_count / 3);
ZBuffer.shrink(ZBuffer.Size - z_count);
}

void ImDrawList3D::PrimReserveCmd(int cmd_count) {
int cmd_buffer_old_size = CmdBuffer.Size;
CmdBuffer.resize(cmd_buffer_old_size + cmd_count);
_CmdWritePtr = CmdBuffer.Data + cmd_buffer_old_size;
}

void ImDrawList3D::AddTriangleCmd(int idx_count) {
// Check if we can extend the last command
if (CmdBuffer.Size > 0) {
ImDrawCmd3D* last_cmd = &CmdBuffer[CmdBuffer.Size - 1];
// Extend existing triangle command
last_cmd->IdxCount += idx_count;
return;
}

// Create new triangle command
// IdxOffset is where the next set of indices will start
unsigned int next_offset = 0;
if (CmdBuffer.Size > 0) {
const ImDrawCmd3D& prev_cmd = CmdBuffer[CmdBuffer.Size - 1];
next_offset = prev_cmd.IdxOffset + prev_cmd.IdxCount;
}

PrimReserveCmd(1);
_CmdWritePtr->IdxOffset = next_offset;
_CmdWritePtr->IdxCount = idx_count;
}

void ImDrawList3D::AddLine(const ImPlot3DPoint& p0, const ImPlot3DPoint& p1, double z, ImU32 col0, ImU32 col1, float weight) {
ImDrawLinePrim prim;
prim.p0 = p0;
prim.p1 = p1;
prim.z = z;
prim.col0 = col0;
prim.col1 = col1;
prim.weight = weight;
LineBuffer.push_back(prim);
}

void ImDrawList3D::AddMarker(const ImPlot3DPoint& center, double z, ImU32 fill_col, ImU32 line_col, float size, float line_weight,
ImPlot3DMarker type) {
ImDrawMarkerPrim prim;
prim.center = center;
prim.z = z;
prim.fill_col = fill_col;
prim.line_col = line_col;
prim.size = size;
prim.line_weight = line_weight;
prim.type = type;
MarkerBuffer.push_back(prim);
}

void ImDrawList3D::SetTexture(ImTextureRef tex_ref) {
Expand Down Expand Up @@ -3660,6 +3844,8 @@ void ImDrawList3D::ResetTexture() { SetTexture(ImTextureID(0)); }

void ImDrawList3D::SortedMoveToImGuiDrawList() {
ImDrawList& draw_list = *ImGui::GetWindowDrawList();
ImPlot3DContext& gp = *ImPlot3D::GetCurrentContext();
const ImPlot3DPlot& plot = *gp.CurrentPlot;

const int tri_count = ZBuffer.Size;
if (tri_count == 0) {
Expand Down Expand Up @@ -3689,19 +3875,29 @@ void ImDrawList3D::SortedMoveToImGuiDrawList() {
// Reserve space in the ImGui draw list
draw_list.PrimReserve(IdxBuffer.Size, VtxBuffer.Size);

// Copy vertices (no reordering needed)
memcpy(draw_list._VtxWritePtr, VtxBuffer.Data, VtxBuffer.Size * sizeof(ImDrawVert));
// Convert vertices from NDC to pixel coordinates and copy to ImGui draw list
ImDrawVert* vtx_out = draw_list._VtxWritePtr;
for (int i = 0; i < VtxBuffer.Size; i++) {
const ImDrawVert3D& vtx_in = VtxBuffer[i];
// Convert 3D NDC position to 2D pixel position
ImVec2 pos_pix = ImPlot3D::NDCToPixels(plot, vtx_in.pos);
vtx_out[i].pos = pos_pix;
vtx_out[i].uv = vtx_in.uv;
vtx_out[i].col = vtx_in.col;
}

unsigned int idx_offset = draw_list._VtxCurrentIdx;
draw_list._VtxWritePtr += VtxBuffer.Size;
draw_list._VtxCurrentIdx += (unsigned int)VtxBuffer.Size;

// Maximum index allowed to not overflow ImDrawIdx
unsigned int max_index_allowed = MaxIdx() - idx_offset;
// Maximum index allowed in ImDrawIdx (ImGui's index type)
unsigned int max_imgui_idx = (sizeof(ImDrawIdx) == 2) ? 65535 : 4294967295u;
unsigned int max_index_allowed = (max_imgui_idx > idx_offset) ? (max_imgui_idx - idx_offset) : 0;

// Copy indices with triangle sorting based on distance from viewer
ImDrawIdx* idx_out_begin = draw_list._IdxWritePtr;
ImDrawIdx* idx_out = idx_out_begin;
ImDrawIdx* idx_in = IdxBuffer.Data;
ImDrawIdx3D* idx_in = IdxBuffer.Data;
for (int i = 0; i < tri_count; i++) {
int tri_i = tris[i].tri_idx;
int base_idx = tri_i * 3;
Expand Down Expand Up @@ -3937,11 +4133,15 @@ void ImPlot3D::ShowMetricsWindow(bool* p_popen) {
ImGui::Text("Mouse Position: [%.0f,%.0f]", io.MousePos.x, io.MousePos.y);
ImGui::Separator();
if (ImGui::TreeNode("Tools")) {
ImGui::SeparatorText("Cache");
if (ImGui::Button("Bust Plot Cache"))
BustPlotCache();
ImGui::SameLine();
if (ImGui::Button("Bust Item Cache"))
BustItemCache();
ImGui::SeparatorText("Rendering");
ImGui::Checkbox("Use ImPlot3D Backend", &gp.UseImPlot3DBackend);
ImGui::SeparatorText("Visualize");
ImGui::Checkbox("Show Frame Rects", &show_frame_rects);
ImGui::Checkbox("Show Canvas Rects", &show_canvas_rects);
ImGui::Checkbox("Show Plot Rects", &show_plot_rects);
Expand Down Expand Up @@ -4163,6 +4363,19 @@ void ImPlot3D::ShowMetricsWindow(bool* p_popen) {
plot.RotationAnimationEnd.y, plot.Rotation.z, plot.RotationAnimationEnd.w);
ImGui::BulletText("ViewScale: %.2f", plot.GetViewScale());

// Look up texture IDs from draw data
ImTextureID color_tex = ImTextureID_Invalid;
ImTextureID depth_tex = ImTextureID_Invalid;
for (int j = 0; j < gp.DrawData.PlotData.Size; j++) {
if (gp.DrawData.PlotData[j].PlotID == plot.ID) {
color_tex = gp.DrawData.PlotData[j].ColorTextureID;
depth_tex = gp.DrawData.PlotData[j].DepthTextureID;
break;
}
}
ImGui::BulletText("ColorTextureID: %d", (int)(size_t)color_tex);
ImGui::BulletText("DepthTextureID: %d", (int)(size_t)depth_tex);

ImGui::TreePop();
}
ImGui::PopID();
Expand Down
Loading