diff --git a/backends/implot_backend.h b/backends/implot_backend.h new file mode 100644 index 0000000..1ccfca4 --- /dev/null +++ b/backends/implot_backend.h @@ -0,0 +1,49 @@ +// MIT License + +// Copyright (c) 2021 Evan Pezent + +// 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. + +// ImPlot v0.10 WIP + +#pragma once + +#if defined(IMPLOT_BACKEND_ENABLE_OPENGL3) + #include "implot_impl_opengl3.h" +#elif defined(IMPLOT_BACKEND_ENABLE_METAL) + #include "implot_impl_metal.h" +#endif + +namespace ImPlot { +namespace Backend { + +#ifndef IMPLOT_BACKEND_ENABLED + inline void* CreateContext() { return nullptr; } + inline void DestroyContext() {} + + inline void BustPlotCache() {} + inline void BustItemCache() {} +#endif + +#ifndef IMPLOT_BACKEND_HAS_COLORMAP + inline void AddColormap(const ImU32*, int, bool) {} +#endif + +} +} diff --git a/backends/implot_impl_opengl3.cpp b/backends/implot_impl_opengl3.cpp new file mode 100644 index 0000000..e70bb58 --- /dev/null +++ b/backends/implot_impl_opengl3.cpp @@ -0,0 +1,392 @@ +#ifdef IMPLOT_BACKEND_ENABLE_OPENGL3 + +#include "../implot.h" +#include "../implot_internal.h" +#include "implot_backend.h" +#include "implot_impl_opengl3.h" + +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +// About Desktop OpenGL function loaders: +// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. +// Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). +// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +#include // Initialize with gl3wInit() +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) +#include // Initialize with glewInit() +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) +#include // Initialize with gladLoadGL() +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2) +#include // Initialize with gladLoadGL(...) or gladLoaderLoadGL() +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) +#define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. +#include // Initialize with glbinding::Binding::initialize() +#include +using namespace gl; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) +#define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. +#include // Initialize with glbinding::initialize() +#include +using namespace gl; +#else +#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#endif + +namespace ImPlot { +namespace Backend { + +struct HeatmapShader +{ + GLuint ID = 0; ///< Shader ID for the heatmap shader + + GLuint AttribLocationProjection = 0; ///< Attribute location for the projection matrix uniform + GLuint AttribLocationMinValue = 0; ///< Attribute location for the minimum value uniform + GLuint AttribLocationMaxValue = 0; ///< Attribute location for the maximum value uniform + GLuint AttribLocationAxisLog = 0; ///< Attribute location for the logarithmic axes uniform + GLuint AttribLocationMinBounds = 0; ///< Attribute location for the minimum bounds uniform + GLuint AttribLocationMaxBounds = 0; ///< Attribute location for the maximum bounds uniform +}; + +struct HeatmapData +{ + HeatmapShader* ShaderProgram; ///< Shader to be used by this heatmap (either ShaderInt or ShaderFloat) + GLuint HeatmapTexID; ///< Texture ID of the heatmap 2D texture + GLuint ColormapTexID; ///< Texture ID of the colormap 1D texture + ImPlotPoint MinBounds; ///< Minimum bounds of the heatmap + ImPlotPoint MaxBounds; ///< Maximum bounds of the heatmap + float MinValue; ///< Minimum value of the colormap + float MaxValue; ///< Maximum value of the colormap + bool AxisLogX; ///< Whether the X axis is logarithmic or not + bool AxisLogY; ///< Whether the Y axis is logarithmic or not +}; + +struct ContextData +{ + HeatmapShader ShaderInt; ///< Shader for integer heatmaps + HeatmapShader ShaderFloat; ///< Shader for floating-point heatmaps + + GLuint AttribLocationImGuiProjection = 0; ///< Attribute location for the projection matrix uniform (ImGui default shader) + GLuint ImGuiShader = 0; ///< Shader ID of ImGui's default shader + + ImVector HeatmapDataList; ///< Array of heatmap data + ImVector ColormapIDs; ///< Texture IDs of the colormap textures + ImGuiStorage ItemIDs; ///< PlotID <-> Heatmap array index table + + ImVector temp1; ///< Temporary data + ImVector temp2; ///< Temporary data + ImVector temp3; ///< Temporary data +}; + +void* CreateContext() +{ + return new ContextData; +} + +void DestroyContext() +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + for(const HeatmapData& data : Context.HeatmapDataList) + glDeleteTextures(1, &data.HeatmapTexID); + + for(GLuint texID : Context.ColormapIDs) + glDeleteTextures(1, &texID); + + glDeleteProgram(Context.ShaderInt.ID); + glDeleteProgram(Context.ShaderFloat.ID); + + Context.HeatmapDataList.clear(); + Context.ItemIDs.Clear(); +} + +#define HEATMAP_VERTEX_SHADER_CODE \ + "#version 330 core\n" \ + "precision mediump float;\n" \ + "layout (location = %d) in vec2 Position;\n" \ + "layout (location = %d) in vec2 UV;\n" \ + "\n" \ + "uniform mat4 ProjMtx;\n" \ + "out vec2 Frag_UV;\n" \ + "\n" \ + "void main()\n" \ + "{\n" \ + " Frag_UV = UV;\n" \ + " gl_Position = ProjMtx * vec4(Position.xy, 0.0f, 1.0f);\n" \ + "}\n" + +#define HEATMAP_FRAGMENT_SHADER_CODE \ + "#version 330 core\n" \ + "precision mediump float;\n" \ + "\n" \ + "in vec2 Frag_UV;\n" \ + "out vec4 Out_Color;\n" \ + "\n" \ + "uniform sampler1D colormap;\n" \ + "uniform %csampler2D heatmap;\n" \ + "uniform float min_val;\n" \ + "uniform float max_val;\n" \ + "\n" \ + "uniform vec2 bounds_min;\n" \ + "uniform vec2 bounds_max;\n" \ + "uniform bvec2 ax_log;\n" \ + "\n" \ + "float log_den(float x, float min_rng, float max_rng)\n" \ + "{\n" \ + " float minrl = log(min_rng);\n" \ + " float maxrl = log(max_rng);\n" \ + "\n" \ + " return (exp((maxrl - minrl) * x + minrl) - min_rng) / (max_rng - min_rng);" \ + "}\n" \ + "\n" \ + "void main()\n" \ + "{\n" \ + " float min_tex_offs = 0.5 / float(textureSize(colormap, 0));\n" \ + " float uv_x = ax_log.x ? log_den(Frag_UV.x, bounds_min.x, bounds_max.x) : Frag_UV.x;\n" \ + " float uv_y = ax_log.y ? log_den(Frag_UV.y, bounds_min.y, bounds_max.y) : Frag_UV.y;\n" \ + "\n" \ + " float value = float(texture(heatmap, vec2(uv_x, uv_y)).r);\n" \ + " float offset = (value - min_val) / (max_val - min_val);\n" \ + " offset = mix(min_tex_offs, 1.0 - min_tex_offs, clamp(offset, 0.0f, 1.0f));\n" \ + " Out_Color = texture(colormap, offset);\n" \ + "}\n" + +static void CompileShader(HeatmapShader& ShaderProgram, GLchar* VertexShaderCode, GLchar* FragmentShaderCode) +{ + GLuint VertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(VertexShader, 1, &VertexShaderCode, nullptr); + glCompileShader(VertexShader); + + GLuint FragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(FragmentShader, 1, &FragmentShaderCode, nullptr); + glCompileShader(FragmentShader); + + ShaderProgram.ID = glCreateProgram(); + glAttachShader(ShaderProgram.ID, VertexShader); + glAttachShader(ShaderProgram.ID, FragmentShader); + glLinkProgram(ShaderProgram.ID); + + glDetachShader(ShaderProgram.ID, VertexShader); + glDetachShader(ShaderProgram.ID, FragmentShader); + glDeleteShader(VertexShader); + glDeleteShader(FragmentShader); + + ShaderProgram.AttribLocationProjection = glGetUniformLocation(ShaderProgram.ID, "ProjMtx"); + ShaderProgram.AttribLocationMinValue = glGetUniformLocation(ShaderProgram.ID, "min_val"); + ShaderProgram.AttribLocationMaxValue = glGetUniformLocation(ShaderProgram.ID, "max_val"); + ShaderProgram.AttribLocationMinBounds = glGetUniformLocation(ShaderProgram.ID, "bounds_min"); + ShaderProgram.AttribLocationMaxBounds = glGetUniformLocation(ShaderProgram.ID, "bounds_max"); + ShaderProgram.AttribLocationAxisLog = glGetUniformLocation(ShaderProgram.ID, "ax_log"); + + glUseProgram(ShaderProgram.ID); + glUniform1i(glGetUniformLocation(ShaderProgram.ID, "heatmap"), 0); // Set texture slot of heatmap texture + glUniform1i(glGetUniformLocation(ShaderProgram.ID, "colormap"), 1); // Set texture slot of colormap texture +} + +static void CreateHeatmapShader(const ImDrawList*, const ImDrawCmd*) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&Context.ImGuiShader); + + Context.AttribLocationImGuiProjection = glGetUniformLocation(Context.ImGuiShader, "ProjMtx"); + GLuint AttribLocationVtxPos = (GLuint)glGetAttribLocation(Context.ImGuiShader, "Position"); + GLuint AttribLocationVtxUV = (GLuint)glGetAttribLocation(Context.ImGuiShader, "UV"); + + GLchar* VertexShaderCode = new GLchar[1000]; + GLchar* FragmentShaderCode = new GLchar[1000]; + + snprintf(VertexShaderCode, 1000, HEATMAP_VERTEX_SHADER_CODE, AttribLocationVtxPos, AttribLocationVtxUV); + snprintf(FragmentShaderCode, 1000, HEATMAP_FRAGMENT_SHADER_CODE, ' '); + + CompileShader(Context.ShaderFloat, VertexShaderCode, FragmentShaderCode); + + snprintf(VertexShaderCode, 1000, HEATMAP_VERTEX_SHADER_CODE, AttribLocationVtxPos, AttribLocationVtxUV); + snprintf(FragmentShaderCode, 1000, HEATMAP_FRAGMENT_SHADER_CODE, 'i'); + + CompileShader(Context.ShaderInt, VertexShaderCode, FragmentShaderCode); + glUseProgram(0); + + delete[] VertexShaderCode; + delete[] FragmentShaderCode; +} + +static void RenderCallback(const ImDrawList*, const ImDrawCmd* cmd) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + int itemID = (int)(intptr_t)cmd->UserCallbackData; + int plotIdx = Context.ItemIDs.GetInt(itemID, -1); + HeatmapData& data = Context.HeatmapDataList[plotIdx]; + + // Get projection matrix of current shader + float OrthoProjection[4][4]; + glGetUniformfv(Context.ImGuiShader, Context.AttribLocationImGuiProjection, &OrthoProjection[0][0]); + + // Enable our shader + glUseProgram(data.ShaderProgram->ID); + + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, data.HeatmapTexID); // Set texture ID of data + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_1D, data.ColormapTexID); // Set texture ID of colormap + + glUniformMatrix4fv(data.ShaderProgram->AttribLocationProjection, 1, GL_FALSE, &OrthoProjection[0][0]); + glUniform1f(data.ShaderProgram->AttribLocationMinValue, data.MinValue); // Set minimum range + glUniform1f(data.ShaderProgram->AttribLocationMaxValue, data.MaxValue); // Set maximum range + glUniform2i(data.ShaderProgram->AttribLocationAxisLog, data.AxisLogX, data.AxisLogY); // Logarithmic axis + glUniform2f(data.ShaderProgram->AttribLocationMinBounds, data.MinBounds.x, data.MinBounds.y); // Set minimum bounds + glUniform2f(data.ShaderProgram->AttribLocationMaxBounds, data.MaxBounds.x, data.MaxBounds.y); // Set maximum bounds +} + +static void ResetState(const ImDrawList*, const ImDrawCmd*) +{ + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + glUseProgram(Context.ImGuiShader); +} + +static void SetTextureData(int itemID, const void* data, GLsizei rows, GLsizei cols, GLint internalFormat, GLenum format, GLenum type) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + int idx = Context.ItemIDs.GetInt(itemID, -1); + GLuint texID = Context.HeatmapDataList[idx].HeatmapTexID; + Context.HeatmapDataList[idx].ShaderProgram = (type == GL_FLOAT ? &Context.ShaderFloat : &Context.ShaderInt); + + // Set heatmap data + glBindTexture(GL_TEXTURE_2D, texID); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, cols, rows, 0, format, type, data); +} + +void AddColormap(const ImU32* keys, int count, bool qual) +{ + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_1D, textureID); + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, count, 0, GL_RGBA, GL_UNSIGNED_BYTE, keys); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, qual ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, qual ? GL_NEAREST : GL_LINEAR); + glBindTexture(GL_TEXTURE_1D, 0); + + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + Context.ColormapIDs.push_back(textureID); +} + +static GLuint CreateHeatmapTexture() +{ + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + return textureID; +} + +void SetHeatmapData(int itemID, const ImS8* values, int rows, int cols) { SetTextureData(itemID, values, rows, cols, GL_R8I, GL_RED_INTEGER, GL_BYTE); } +void SetHeatmapData(int itemID, const ImU8* values, int rows, int cols) { SetTextureData(itemID, values, rows, cols, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE); } +void SetHeatmapData(int itemID, const ImS16* values, int rows, int cols) { SetTextureData(itemID, values, rows, cols, GL_R16I, GL_RED_INTEGER, GL_SHORT); } +void SetHeatmapData(int itemID, const ImU16* values, int rows, int cols) { SetTextureData(itemID, values, rows, cols, GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT); } +void SetHeatmapData(int itemID, const ImS32* values, int rows, int cols) { SetTextureData(itemID, values, rows, cols, GL_R32I, GL_RED_INTEGER, GL_INT); } +void SetHeatmapData(int itemID, const ImU32* values, int rows, int cols) { SetTextureData(itemID, values, rows, cols, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT); } +void SetHeatmapData(int itemID, const float* values, int rows, int cols) { SetTextureData(itemID, values, rows, cols, GL_R32F, GL_RED, GL_FLOAT); } + +void SetHeatmapData(int itemID, const double* values, int rows, int cols) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + if(Context.temp1.Size < rows * cols) + Context.temp1.resize(rows * cols); + + for(int i = 0; i < rows*cols; i++) + Context.temp1[i] = (float)values[i]; + + SetTextureData(itemID, Context.temp1.Data, rows, cols, GL_R32F, GL_RED, GL_FLOAT); +} + +void SetHeatmapData(int itemID, const ImS64* values, int rows, int cols) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + if(Context.temp2.Size < rows * cols) + Context.temp2.resize(rows * cols); + + for(int i = 0; i < rows*cols; i++) + Context.temp2[i] = (ImS32)values[i]; + + SetTextureData(itemID, Context.temp2.Data, rows, cols, GL_R32I, GL_RED_INTEGER, GL_INT); +} + +void SetHeatmapData(int itemID, const ImU64* values, int rows, int cols) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + if(Context.temp3.Size < rows * cols) + Context.temp3.resize(rows * cols); + + for(int i = 0; i < rows*cols; i++) + Context.temp3[i] = (ImU32)values[i]; + + SetTextureData(itemID, Context.temp3.Data, rows, cols, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT); +} + +void SetAxisLog(int itemID, bool x_is_log, bool y_is_log, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + int idx = Context.ItemIDs.GetInt(itemID, -1); + HeatmapData& data = Context.HeatmapDataList[idx]; + + data.AxisLogX = x_is_log; + data.AxisLogY = y_is_log; + data.MinBounds = bounds_min; + data.MaxBounds = bounds_max; +} + +void RenderHeatmap(int itemID, ImDrawList& DrawList, const ImVec2& bounds_min, const ImVec2& bounds_max, float scale_min, float scale_max, ImPlotColormap colormap, bool reverse_y) +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + int idx = Context.ItemIDs.GetInt(itemID, Context.HeatmapDataList.Size); + + if(idx == Context.HeatmapDataList.Size) + { + // New entry + Context.ItemIDs.SetInt(itemID, Context.HeatmapDataList.Size); + Context.HeatmapDataList.push_back(HeatmapData()); + Context.HeatmapDataList[idx].HeatmapTexID = CreateHeatmapTexture(); + } + + HeatmapData& data = Context.HeatmapDataList[idx]; + data.ColormapTexID = Context.ColormapIDs[colormap]; + data.MinValue = scale_min; + data.MaxValue = scale_max; + + if(Context.ShaderInt.ID == 0 || Context.ShaderFloat.ID == 0) + DrawList.AddCallback(CreateHeatmapShader, nullptr); + + DrawList.AddCallback(RenderCallback, (void*)(intptr_t)itemID); + DrawList.PrimReserve(6, 4); + DrawList.PrimRectUV(bounds_min, bounds_max, ImVec2(0.0f, reverse_y ? 1.0f : 0.0f), ImVec2(1.0f, reverse_y ? 0.0f : 1.0f), 0); + DrawList.AddCallback(ResetState, nullptr); +} + +void BustPlotCache() +{ + ContextData& Context = *((ContextData*)GImPlot->backendCtx); + + for(const HeatmapData& data : Context.HeatmapDataList) + glDeleteTextures(1, &data.HeatmapTexID); + + Context.HeatmapDataList.clear(); + Context.ItemIDs.Clear(); +} + +void BustItemCache() {} + +} +} + +#endif diff --git a/backends/implot_impl_opengl3.h b/backends/implot_impl_opengl3.h new file mode 100644 index 0000000..34695cc --- /dev/null +++ b/backends/implot_impl_opengl3.h @@ -0,0 +1,208 @@ +// MIT License + +// Copyright (c) 2021 Evan Pezent + +// 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. + +// ImPlot v0.10 WIP + +#pragma once + +#define IMPLOT_BACKEND_ENABLED +#define IMPLOT_BACKEND_HAS_HEATMAP +#define IMPLOT_BACKEND_HAS_COLORMAP + +#if !defined(IMGUI_IMPL_OPENGL_ES2) \ + && !defined(IMGUI_IMPL_OPENGL_ES3) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) + +// Try to detect GLES on matching platforms +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) +#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" +#elif defined(__EMSCRIPTEN__) +#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" + +// Otherwise try to detect supported Desktop OpenGL loaders.. +#elif defined(__has_include) +#if __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLEW +#elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLAD +#elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLAD2 +#elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GL3W +#elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLBINDING3 +#elif __has_include() + #define IMGUI_IMPL_OPENGL_LOADER_GLBINDING2 +#else + #error "Cannot detect OpenGL loader!" +#endif +#else + #define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W embedded in our repository +#endif + +#endif + +namespace ImPlot { +namespace Backend { + +//----------------------------------------------------------------------------- +// [SECTION] Misc backend functions +//----------------------------------------------------------------------------- + +/** + * @brief Struct to hold backend-related context data + * + * A backend may store in this struct any data it needs, with no constraints. A + * pointer to this struct will be stored inside ImPlot's context and can be + * accessed at any time. This pointer will be set to the returned value of @ref + * CreateContext(). All resources held by this struct must be freed inside @ref + * DestroyContext(). + */ +struct ContextData; + +/** + * @brief Create backend context + * + * Creates and intializes the backend context. The returned pointer will be saved + * in ImPlot's context and can be accessed later. + */ +IMPLOT_API void* CreateContext(); + +/** + * @brief Destroy backend context + * + * Destroys and frees any memory or resources needed by the backend. After this + * call returns, no more calls to any backend function can be performed. + */ +IMPLOT_API void DestroyContext(); + +/** @brief Bust plot cache. Called from @ref ImPlot::BustPlotCache() */ +IMPLOT_API void BustPlotCache(); + +/** @brief Bust item cache. Called from @ref ImPlot::BustItemCache() */ +IMPLOT_API void BustItemCache(); + +//----------------------------------------------------------------------------- +// [SECTION] Colormap functions +//----------------------------------------------------------------------------- + +/** + * @brief Add a colormap + * + * Adds a colormap to be handled by the backend. + * + * @param keys Colors for this colormap, in RGBA format + * @param count Number of colors in this colormap + * @param qual Qualitative: whether the colormap is continuous (`false`) or + * not (`true`) + */ +IMPLOT_API void AddColormap(const ImU32* keys, int count, bool qual); + +//----------------------------------------------------------------------------- +// [SECTION] Heatmap functions +//----------------------------------------------------------------------------- + +/** + * @brief Set heatmap data + * + * Sets the data of the heatmap with the given plot ID. + * + * @param itemID ID of the heatmap to update. + * @param values Data of the heatmap to be set.`values[0]` corresponds with the + * top-left corner of the data. + * @param rows Number of rows of this heatmap + * @param cols Number of columns of this heatmap + */ +IMPLOT_API void SetHeatmapData(int itemID, const ImS8* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const ImU8*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const ImU8* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const ImS16*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const ImS16* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const ImU16*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const ImU16* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const ImS32*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const ImS32* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const ImU32*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const ImU32* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const float*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const float* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const double*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const double* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const ImS64*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const ImS64* values, int rows, int cols); + +/** @copydoc SetHeatmapData(int,const ImU64*,int,int) */ +IMPLOT_API void SetHeatmapData(int itemID, const ImU64* values, int rows, int cols); + +/** + * @brief Render heatmap + * + * Renders the heatmap using OpenGL acceleration + * + * @param itemID ID of the heatmap to be rendered + * @param DrawList Draw list where to submit the render commands + * @param bounds_min Minimum bounds of the heatmap (without clipping) + * @param bounds_max Maximum bounds of the heatmap (without clipping) + * @param scale_min Minimum value of the heatmap + * @param scale_max Maximum value of the heatmap + * @param colormap Colormap to be used when rendering this heatmap + * + * @note There might be values greater than `scale_max` or lower than `scale_min`. + * The shader used for rendering will clamp this values. + */ +IMPLOT_API void RenderHeatmap( + int itemID, ImDrawList& DrawList, const ImVec2& bounds_min, const ImVec2& bounds_max, + float scale_min, float scale_max, ImPlotColormap colormap, bool reverse_y); + +/** + * @brief Set logarithmic axis + * + * Sets whether the X and Y axis are logarithmic or not, and their bounds. This + * function only has to be called if either the axis change state or if the bounds + * change. + * + * @param x_is_log Whether the X axis is logarithmic or not + * @param y_is_log Whether the Y axis is logarithmic or not + * @param bounds_min Minimum bounds (for X & Y) of the heatmap + * @param bounds_min Maximum bounds (for X & Y) of the heatmap + */ +void SetAxisLog(int itemID, bool x_is_log, bool y_is_log, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max); + +} +} diff --git a/implot.cpp b/implot.cpp index 837edee..862d945 100644 --- a/implot.cpp +++ b/implot.cpp @@ -374,13 +374,15 @@ void SetImGuiContext(ImGuiContext* ctx) { ImPlotContext* CreateContext() { ImPlotContext* ctx = IM_NEW(ImPlotContext)(); - Initialize(ctx); if (GImPlot == NULL) SetCurrentContext(ctx); + ctx->backendCtx = Backend::CreateContext(); + Initialize(ctx); return ctx; } void DestroyContext(ImPlotContext* ctx) { + Backend::DestroyContext(); if (ctx == NULL) ctx = GImPlot; if (GImPlot == ctx) @@ -435,7 +437,6 @@ void Initialize(ImPlotContext* ctx) { IMPLOT_APPEND_CMAP(PiYG, false); IMPLOT_APPEND_CMAP(Spectral, false); IMPLOT_APPEND_CMAP(Greys, false); - } void Reset(ImPlotContext* ctx) { @@ -489,6 +490,7 @@ ImPlotPlot* GetCurrentPlot() { void BustPlotCache() { GImPlot->Plots.Clear(); + Backend::BustPlotCache(); } void PushLinkedAxis(ImPlotAxis& axis) { diff --git a/implot_internal.h b/implot_internal.h index 994331c..9dea114 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -37,6 +37,7 @@ #include #include "imgui_internal.h" +#include "backends/implot_backend.h" #ifndef IMPLOT_VERSION #error Must include implot.h before implot_internal.h @@ -376,6 +377,7 @@ struct ImPlotColormapData { int idx = Count++; Map.SetInt(id,idx); _AppendTable(idx); + ImPlot::Backend::AddColormap(keys, count, qual); return idx; } @@ -916,6 +918,9 @@ struct ImPlotContext { ImPlotNextItemData NextItemData; ImPlotInputMap InputMap; ImPlotPoint MousePos[IMPLOT_Y_AXES]; + + // Backend + void* backendCtx; }; //----------------------------------------------------------------------------- diff --git a/implot_items.cpp b/implot_items.cpp index 332cf12..c6d081a 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -24,6 +24,7 @@ #include "implot.h" #include "implot_internal.h" +#include "backends/implot_backend.h" #ifdef _MSC_VER #define sprintf sprintf_s @@ -138,6 +139,7 @@ void HideNextItem(bool hidden, ImGuiCond cond) { void BustItemCache() { ImPlotContext& gp = *GImPlot; + Backend::BustItemCache(); for (int p = 0; p < gp.Plots.GetBufSize(); ++p) { ImPlotPlot& plot = *gp.Plots.GetByIndex(p); plot.ColormapIdx = 0; @@ -1890,13 +1892,22 @@ void RenderHeatmap(Transformer transformer, ImDrawList& DrawList, const T* value } const double yref = reverse_y ? bounds_max.y : bounds_min.y; const double ydir = reverse_y ? -1 : 1; +#ifdef IMPLOT_BACKEND_HAS_HEATMAP + ImVec2 bmin = transformer(bounds_min); + ImVec2 bmax = transformer(bounds_max); + ImPlotScale scale = GetCurrentScale(); + + // NOTE: Order is important! + Backend::RenderHeatmap(gp.CurrentItem->ID, DrawList, bmin, bmax, scale_min, scale_max, gp.Style.Colormap, reverse_y); + Backend::SetAxisLog(gp.CurrentItem->ID, + scale == ImPlotScale_LogLin || scale == ImPlotScale_LogLog, + scale == ImPlotScale_LinLog || scale == ImPlotScale_LogLog, + bounds_min, bounds_max); + Backend::SetHeatmapData(gp.CurrentItem->ID, values, rows, cols); +#else GetterHeatmap getter(values, rows, cols, scale_min, scale_max, (bounds_max.x - bounds_min.x) / cols, (bounds_max.y - bounds_min.y) / rows, bounds_min.x, yref, ydir); - switch (GetCurrentScale()) { - case ImPlotScale_LinLin: RenderPrimitives(RectRenderer, TransformerLinLin>(getter, TransformerLinLin()), DrawList, gp.CurrentPlot->PlotRect); break; - case ImPlotScale_LogLin: RenderPrimitives(RectRenderer, TransformerLogLin>(getter, TransformerLogLin()), DrawList, gp.CurrentPlot->PlotRect); break;; - case ImPlotScale_LinLog: RenderPrimitives(RectRenderer, TransformerLinLog>(getter, TransformerLinLog()), DrawList, gp.CurrentPlot->PlotRect); break;; - case ImPlotScale_LogLog: RenderPrimitives(RectRenderer, TransformerLogLog>(getter, TransformerLogLog()), DrawList, gp.CurrentPlot->PlotRect); break;; - } + RenderPrimitives(RectRenderer, Transformer>(getter, transformer), DrawList, gp.CurrentPlot->PlotRect); +#endif if (fmt != NULL) { const double w = (bounds_max.x - bounds_min.x) / cols; const double h = (bounds_max.y - bounds_min.y) / rows;