From 913a0429bd29acab7f897763160b864c7fc7739f Mon Sep 17 00:00:00 2001 From: Tanishq Dubey Date: Wed, 12 Jun 2024 10:03:06 -0400 Subject: [PATCH] able to comple with tex inspect --- lib/backends/tex_inspect_opengl.cpp | 708 ++++++++++++++++ lib/backends/tex_inspect_opengl.h | 9 + lib/imgui_tex_inspect.cpp | 1182 +++++++++++++++++++++++++++ lib/imgui_tex_inspect.h | 308 +++++++ lib/imgui_tex_inspect_internal.h | 182 +++++ main.cpp | 52 +- 6 files changed, 2438 insertions(+), 3 deletions(-) create mode 100644 lib/backends/tex_inspect_opengl.cpp create mode 100644 lib/backends/tex_inspect_opengl.h create mode 100644 lib/imgui_tex_inspect.cpp create mode 100644 lib/imgui_tex_inspect.h create mode 100644 lib/imgui_tex_inspect_internal.h diff --git a/lib/backends/tex_inspect_opengl.cpp b/lib/backends/tex_inspect_opengl.cpp new file mode 100644 index 0000000..29570fe --- /dev/null +++ b/lib/backends/tex_inspect_opengl.cpp @@ -0,0 +1,708 @@ +// ImGuiTexInspect, a texture inspector widget for dear imgui + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif +#if defined EMSCRIPTEN && defined IMGUI_TEX_INSPECT_FLOAT_READ_ENABLED +#warning "Float texture read back is disabled on Emscripten" +#undef IMGUI_TEX_INSPECT_FLOAT_READ_ENABLED +#endif +#include "../imgui_tex_inspect_internal.h" + +// ========================================================================== +// This file is largely based on: +// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp +// +// In the following section the ImGui_ImplOpenGL3_Init function has been +// changed to not rewrite global ImGui state. It has also been wrapped in a +// namespace to not clash with the main ImGui version. Aside from that this +// section is identical to the imgui original. +// +// It's reproduced here because none of this code is exposed in the ImGui API +// in a way that be reused (nor should it be). +// +// Search for "END COPIED" to find the end of the copied segment. +// =========================================================================== + +// COPIED FROM imgui_impl_opengl3.cp //////////////////////////////////////// +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "../imgui.h" +#include "imgui_impl_opengl3.h" +#include +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// GL includes +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#elif defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 3 +#else +#include // Use GL ES 3 +#endif +#else +// 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. +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +#include // Needs to be initialized with gl3wInit() in user's code +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) +#include // Needs to be initialized with glewInit() in user's code. +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) +#include // Needs to be initialized with gladLoadGL() in user's code. +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2) +#include // Needs to be initialized with gladLoadGL(...) or gladLoaderLoadGL() in user's code. +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) +#ifndef GLFW_INCLUDE_NONE +#define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. +#endif +#include // Needs to be initialized with glbinding::Binding::initialize() in user's code. +#include +using namespace gl; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) +#ifndef GLFW_INCLUDE_NONE +#define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. +#endif +#include // Needs to be initialized with glbinding::initialize() in user's code. +#include +using namespace gl; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_EPOXY) +#include +#else +#include +#include +#include +#endif +#endif + +// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET +#endif + +// Desktop GL 3.3+ has glBindSampler() +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_3) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER +#endif + +// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART +#endif + +namespace imgui_impl_opengl +{ +// OpenGL Data +static GLuint g_GlVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) +static char g_GlslVersionString[32] = ""; // Specified by user or detected based on compile time GL settings. +static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; +static GLint g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location +static GLuint g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0; // Vertex attributes location +static GLint g_UniformLocationForceNearestSampling = 0; +static GLint g_UniformLocationGridWidth = 0; + +// Functions +static bool ImGui_ImplOpenGL3_Init(const char* glsl_version) +{ + // Query for GL version (e.g. 320 for GL 3.2) +#if !defined(IMGUI_IMPL_OPENGL_ES2) + GLint major = 0; + GLint minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + if (major == 0 && minor == 0) + { + // Query GL_VERSION in desktop GL 2.x, the string will start with "." + const char* gl_version = (const char*)glGetString(GL_VERSION); + sscanf(gl_version, "%d.%d", &major, &minor); + } + g_GlVersion = (GLuint)(major * 100 + minor * 10); +#else + g_GlVersion = 200; // GLES 2 +#endif + + + // Store GLSL version string so we can refer to it later in case we recreate shaders. + // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. +#if defined(IMGUI_IMPL_OPENGL_ES2) + if (glsl_version == NULL) + glsl_version = "#version 100"; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + if (glsl_version == NULL) + glsl_version = "#version 300 es"; +#elif defined(__APPLE__) + if (glsl_version == NULL) + glsl_version = "#version 150"; +#else + if (glsl_version == NULL) + glsl_version = "#version 130"; +#endif + IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); + strcpy(g_GlslVersionString, glsl_version); + strcat(g_GlslVersionString, "\n"); + + // Debugging construct to make it easily visible in the IDE and debugger which GL loader has been selected. + // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! + // If auto-detection fails or doesn't select the same GL loader file as used by your application, + // you are likely to get a crash below. + // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. + const char* gl_loader = "Unknown"; + IM_UNUSED(gl_loader); +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) + gl_loader = "GL3W"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) + gl_loader = "GLEW"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) + gl_loader = "GLAD"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2) + gl_loader = "GLAD2"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) + gl_loader = "glbinding2"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) + gl_loader = "glbinding3"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) + gl_loader = "custom"; +#else + gl_loader = "none"; +#endif + + // Make an arbitrary GL call (we don't actually need the result) + // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code. + // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above. + GLint current_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + + return true; +} + +// =========================================================================== +// COPIED FROM A DIFFERENT PART OF imgui_impl_opengl3.cpp +// =========================================================================== + +// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. +static bool CheckShader(GLuint handle, const char* desc) +{ + GLint status = 0, log_length = 0; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +// If you get an error please report on GitHub. You may try different GL context version or GLSL version. +static bool CheckProgram(GLuint handle, const char* desc) +{ + GLint status = 0, log_length = 0; + glGetProgramiv(handle, GL_LINK_STATUS, &status); + glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height) +{ + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glEnable(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + if (g_GlVersion >= 310) + glDisable(GL_PRIMITIVE_RESTART); +#endif +#ifdef GL_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif + + // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) +#if defined(GL_CLIP_ORIGIN) + bool clip_origin_lower_left = true; + if (g_GlVersion >= 450) + { + GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin); + if (current_clip_origin == GL_UPPER_LEFT) + clip_origin_lower_left = false; + } +#endif + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; +#if defined(GL_CLIP_ORIGIN) + if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left +#endif + const float ortho_projection[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + + + + glEnableVertexAttribArray(g_AttribLocationVtxPos); + glEnableVertexAttribArray(g_AttribLocationVtxUV); + //glEnableVertexAttribArray(g_AttribLocationVtxColor); //Our shader doesn't use vertex color + glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + //glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); +} + +// =========================================================================== +// END COPIED FROM imgui_impl_opengl3.cpp +// --------------------------------------------------------------------------- +// Note that a lot of the following code still orginated in +// imgui_impl_opengl3.cpp but there are more changes from here on. +// =========================================================================== + +// New uniforms for ImGuiTexInspect fragment shader +static GLint g_UniformLocationTextureSize; +static GLint g_UniformLocationColorTransform; +static GLint g_UniformLocationColorOffset; +static GLint g_UniformLocationBackgroundColor; +static GLint g_UniformLocationPremultiplyAlpha; +static GLint g_UniformLocationDisableFinalAlpha; +static GLint g_UniformLocationGrid; + + +// Vertex shaders are directly from imgui_impl_opengl3.cpp +const GLchar* vertex_shader_glsl_120 = + "uniform mat4 ProjMtx;\n" + "attribute vec2 Position;\n" + "attribute vec2 UV;\n" + "attribute vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + +const GLchar* vertex_shader_glsl_130 = + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + +const GLchar* vertex_shader_glsl_300_es = + "precision mediump float;\n" + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + +const GLchar* vertex_shader_glsl_410_core = + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + +//------------------------------------------------------------------------- +// [SECTION] IMGUI_TEX_INSPECT FRAGMENT SHADERS +//------------------------------------------------------------------------- +const GLchar *fragment_shader_glsl_120 = "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D Texture;\n" + "uniform vec2 TextureSize;\n" + "uniform mat4 ColorTransform;\n" + "uniform vec4 ColorOffset;\n" + "uniform vec3 BackgroundColor;\n" + "uniform float PremultiplyAlpha;\n" + "uniform float DisableFinalAlpha;\n" + "uniform bool ForceNearestSampling;\n" + "uniform vec4 Grid;\n" + "uniform vec2 GridWidth;\n" + "varying vec2 Frag_UV;\n" + "void main()\n" + "{\n" + " vec2 uv;\n" + " vec2 texel = Frag_UV * TextureSize;\n" + " if (ForceNearestSampling)\n" + " uv = (floor(texel) + vec2(0.5,0.5)) / TextureSize;\n" + " else\n" + " uv = Frag_UV;\n" + " vec2 texelEdge = step(mod(texel,vec2(1.0,1.0)),GridWidth);\n" + " float isGrid = max(texelEdge.x, texelEdge.y);\n" + " vec4 ct = ColorTransform * texture2D(Texture, uv) + ColorOffset;\n" + " ct.rgb = ct.rgb * mix(1.0, ct.a, PremultiplyAlpha);\n" + " ct.rgb += BackgroundColor * (1.0-ct.a);\n" + " ct.a = mix(ct.a, 1.0, DisableFinalAlpha);\n" + " ct = mix(ct, vec4(Grid.rgb,1), Grid.a * isGrid);\n" + " gl_FragColor = ct;\n" + "}\n"; + +const GLchar *fragment_shader_glsl_130 = "uniform sampler2D Texture;\n" + "uniform vec2 TextureSize;\n" + "uniform mat4 ColorTransform;\n" + "uniform vec4 ColorOffset;\n" + "uniform vec3 BackgroundColor;\n" + "uniform float PremultiplyAlpha;\n" + "uniform float DisableFinalAlpha;\n" + "uniform bool ForceNearestSampling;\n" + "uniform vec4 Grid;\n" + "uniform vec2 GridWidth;\n" + "in vec2 Frag_UV;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " vec2 uv;\n" + " vec2 texel = Frag_UV * TextureSize;\n" + " if (ForceNearestSampling)\n" + " uv = (floor(texel) + vec2(0.5,0.5)) / TextureSize;\n" + " else\n" + " uv = Frag_UV;\n" + " vec2 texelEdge = step(mod(texel,vec2(1.0,1.0)),GridWidth);\n" + " float isGrid = max(texelEdge.x, texelEdge.y);\n" + " vec4 ct = ColorTransform * texture(Texture, uv) + ColorOffset;\n" + " ct.rgb = ct.rgb * mix(1.0, ct.a, PremultiplyAlpha);\n" + " ct.rgb += BackgroundColor * (1.0-ct.a);\n" + " ct.a = mix(ct.a, 1.0, DisableFinalAlpha);\n" + " ct = mix(ct, vec4(Grid.rgb,1), Grid.a * isGrid);\n" + " Out_Color = ct;\n" + "}\n"; + +const GLchar *fragment_shader_glsl_300_es = "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "uniform vec2 TextureSize;\n" + "uniform mat4 ColorTransform;\n" + "uniform vec4 ColorOffset;\n" + "uniform vec3 BackgroundColor;\n" + "uniform float PremultiplyAlpha;\n" + "uniform float DisableFinalAlpha;\n" + "uniform bool ForceNearestSampling;\n" + "uniform vec4 Grid;\n" + "uniform vec2 GridWidth;\n" + "in vec2 Frag_UV;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " vec2 uv;\n" + " vec2 texel = Frag_UV * TextureSize;\n" + " if (ForceNearestSampling)\n" + " uv = (floor(texel) + vec2(0.5,0.5)) / TextureSize;\n" + " else\n" + " uv = Frag_UV;\n" + " vec2 texelEdge = step(mod(texel,vec2(1.0,1.0)),GridWidth);\n" + " float isGrid = max(texelEdge.x, texelEdge.y);\n" + " vec4 ct = ColorTransform * texture(Texture, uv) + ColorOffset;\n" + " ct.rgb = ct.rgb * mix(1.0, ct.a, PremultiplyAlpha);\n" + " ct.rgb += BackgroundColor * (1.0-ct.a);\n" + " ct.a = mix(ct.a, 1.0, DisableFinalAlpha);\n" + " ct = mix(ct, vec4(Grid.rgb,1), Grid.a * isGrid);\n" + " Out_Color = ct;\n" + "}\n"; + +const GLchar *fragment_shader_glsl_410_core = "uniform sampler2D Texture;\n" + "uniform vec2 TextureSize;\n" + "uniform mat4 ColorTransform;\n" + "uniform vec4 ColorOffset;\n" + "uniform vec3 BackgroundColor;\n" + "uniform float PremultiplyAlpha;\n" + "uniform float DisableFinalAlpha;\n" + "uniform bool ForceNearestSampling;\n" + "uniform vec4 Grid;\n" + "uniform vec2 GridWidth;\n" + "in vec2 Frag_UV;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " vec2 uv;\n" + " vec2 texel = Frag_UV * TextureSize;\n" + " if (ForceNearestSampling)\n" + " uv = (floor(texel) + vec2(0.5,0.5)) / TextureSize;\n" + " else\n" + " uv = Frag_UV;\n" + " vec2 texelEdge = step(mod(texel,vec2(1.0,1.0)),GridWidth);\n" + " float isGrid = max(texelEdge.x, texelEdge.y);\n" + " vec4 ct = ColorTransform * texture(Texture, uv) + ColorOffset;\n" + " ct.rgb = ct.rgb * mix(1.0, ct.a, PremultiplyAlpha);\n" + " ct.rgb += BackgroundColor * (1.0-ct.a);\n" + " ct.a = mix(ct.a, 1.0, DisableFinalAlpha);\n" + " ct = mix(ct, vec4(Grid.rgb,1), Grid.a * isGrid);\n" + " Out_Color = ct;\n" + "}\n"; + +/* BuildShader is from imgui_impl_opengl3.cpp. Only change is to query the + * additional uniform locations for the new fragment shader*/ +void BuildShader() +{ + // Shader selection code based on imgui_impl_opengl3.cpp + + // Parse GLSL version string + int glsl_version = 130; + sscanf(g_GlslVersionString, "#version %d", &glsl_version); + + // Select shaders matching our GLSL versions + const GLchar *vertex_shader = NULL; + const GLchar *fragment_shader = NULL; + if (glsl_version < 130) + { + vertex_shader = vertex_shader_glsl_120; + fragment_shader = fragment_shader_glsl_120; + } + else if (glsl_version >= 410) + { + vertex_shader = vertex_shader_glsl_410_core; + fragment_shader = fragment_shader_glsl_410_core; + } + else if (glsl_version == 300) + { + vertex_shader = vertex_shader_glsl_300_es; + fragment_shader = fragment_shader_glsl_300_es; + } + else + { + vertex_shader = vertex_shader_glsl_130; + fragment_shader = fragment_shader_glsl_130; + } + + if (fragment_shader == NULL) + { + fprintf(stderr, "ERROR: imgui_tex_inspect fragment shader for %s not implemented yet", g_GlslVersionString); + } + else + { + // Create shaders + const GLchar *vertex_shader_with_version[2] = {g_GlslVersionString, vertex_shader}; + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); + glCompileShader(g_VertHandle); + CheckShader(g_VertHandle, "vertex shader"); + + const GLchar *fragment_shader_with_version[2] = {g_GlslVersionString, fragment_shader}; + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); + glCompileShader(g_FragHandle); + CheckShader(g_FragHandle, "fragment shader"); + + g_ShaderHandle = glCreateProgram(); + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + CheckProgram(g_ShaderHandle, "shader program"); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationVtxPos = (GLuint)glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationVtxUV = (GLuint)glGetAttribLocation(g_ShaderHandle, "UV"); + + // Change from imgui_impl_opengl3.cpp (Our shader doesn't use vertex color) + //g_AttribLocationVtxColor = (GLuint)glGetAttribLocation(g_ShaderHandle, "Color"); + + // New uniforms used by imgui_tex_inspect + g_UniformLocationTextureSize = glGetUniformLocation(g_ShaderHandle, "TextureSize"); + g_UniformLocationColorTransform = glGetUniformLocation(g_ShaderHandle, "ColorTransform"); + g_UniformLocationColorOffset = glGetUniformLocation(g_ShaderHandle, "ColorOffset"); + g_UniformLocationBackgroundColor = glGetUniformLocation(g_ShaderHandle, "BackgroundColor"); + g_UniformLocationPremultiplyAlpha = glGetUniformLocation(g_ShaderHandle, "PremultiplyAlpha"); + g_UniformLocationDisableFinalAlpha = glGetUniformLocation(g_ShaderHandle, "DisableFinalAlpha"); + g_UniformLocationGrid = glGetUniformLocation(g_ShaderHandle, "Grid"); + g_UniformLocationForceNearestSampling = glGetUniformLocation(g_ShaderHandle, "ForceNearestSampling"); + g_UniformLocationGridWidth = glGetUniformLocation(g_ShaderHandle, "GridWidth"); + } +} + +} // namespace imgui_impl_opengl + + +namespace ImGuiTexInspect +{ +using namespace imgui_impl_opengl; + +static GLuint readbackFramebuffer = 0; + +//------------------------------------------------------------------------- +// [SECTION] Init and Shutdown +//------------------------------------------------------------------------- + +bool ImplOpenGL3_Init(const char *glsl_version) +{ + imgui_impl_opengl::ImGui_ImplOpenGL3_Init(glsl_version); + BuildShader(); + glGenFramebuffers(1, &readbackFramebuffer); + return true; +} +void ImplOpenGl3_Shutdown() +{ + // No need to call ImGui_ImplOpenGL3_Shutdown, it doesn't even + // exist in the imgui_impl_opengl namespace. Our version of + // ImGui_ImplOpenGL3_Init doesn't affect OpenGL state. + + glDeleteShader(g_VertHandle); + glDeleteShader(g_FragHandle); + glDeleteProgram(g_ShaderHandle); + + g_VertHandle = 0; + g_FragHandle = 0; + g_ShaderHandle = 0; + + glDeleteFramebuffers(1, &readbackFramebuffer); + readbackFramebuffer = 0; +} + +void GiveNotInitializedWarning() +{ + static bool warningGiven = false; + if (!warningGiven) + { + fprintf(stderr, "ERROR: ImGuiTexInspect backend not initialized\n"); + warningGiven = true; + } +} + +//------------------------------------------------------------------------- +// [SECTION] BackEnd functions declared in imgui_tex_inspect_internal.h +//------------------------------------------------------------------------- + +void BackEnd_SetShader(const ImDrawList *, const ImDrawCmd *, const Inspector *inspector) +{ + const ShaderOptions *texConversion = &inspector->CachedShaderOptions; + if (g_ShaderHandle) + { + ImDrawData *draw_data = ImGui::GetDrawData(); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + + if (fb_width <= 0 || fb_height <= 0) + return; + + // Setup normal ImGui GL state + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height); + + // Setup imgui_tex_inspect specific shader uniforms + glUniformMatrix4fv(g_UniformLocationColorTransform, 1, GL_FALSE, texConversion->ColorTransform); + glUniform2fv(g_UniformLocationTextureSize, 1, &inspector->TextureSize.x); + glUniform4fv(g_UniformLocationColorOffset, 1, texConversion->ColorOffset); + glUniform3fv(g_UniformLocationBackgroundColor, 1, &texConversion->BackgroundColor.x); + glUniform1f(g_UniformLocationPremultiplyAlpha, texConversion->PremultiplyAlpha); + glUniform1f(g_UniformLocationDisableFinalAlpha, texConversion->DisableFinalAlpha); + glUniform1i(g_UniformLocationForceNearestSampling, texConversion->ForceNearestSampling); + glUniform2fv(g_UniformLocationGridWidth, 1, &texConversion->GridWidth.x); + glUniform4fv(g_UniformLocationGrid, 1, &texConversion->GridColor.x); + } + else + { + GiveNotInitializedWarning(); + } +} +bool BackEnd_GetData(Inspector *inspector, ImTextureID texture, int /*x*/, int /*y*/, int /*width*/, int /*height*/, BufferDesc *bufferDesc) +{ + // Current simple implementation just gets data for whole texture + + if (readbackFramebuffer == 0) + { + GiveNotInitializedWarning(); + return false; + } + const int numChannels = 4; + glGetError(); // Discard any current error so that check at end of function is useful + void * data; + int texWidth = (int)inspector->TextureSize.x; + int texHeight = (int)inspector->TextureSize.y; + GLuint glTexture = (GLuint)(uintptr_t)texture; //Double cast to avoid warning + +#ifdef IMGUI_TEX_INSPECT_FLOAT_READ_ENABLED + size_t bufferSize = sizeof(float) * texWidth * texHeight * numChannels; + bufferDesc->Data_float = (float *)GetBuffer(inspector, bufferSize); + GLuint type = GL_FLOAT; + data = (void *)bufferDesc->Data_float; +#else + size_t bufferSize = sizeof(uint8_t) * texWidth * texHeight * numChannels; + bufferDesc->Data_uint8_t = (uint8_t *)GetBuffer(inspector, bufferSize); + GLuint type = GL_UNSIGNED_BYTE; + data = (void *)bufferDesc->Data_uint8_t; +#endif + + if (data == NULL) + return false; + + bufferDesc->BufferByteSize = bufferSize; + bufferDesc->Red = 0; // Data is packed such that red channel is first + bufferDesc->Green = 1; // then green, then blue, the alpha. Hence, 0,1,2,3 + bufferDesc->Blue = 2; // for these channel order values. + bufferDesc->Alpha = 3; + bufferDesc->ChannelCount = 4; // RGBA + bufferDesc->LineStride = (int)inspector->TextureSize.x * numChannels; + bufferDesc->Stride = 4; // No padding between each RGBA texel + bufferDesc->StartX = 0; // We queried the whole texture + bufferDesc->StartY = 0; + bufferDesc->Width = texWidth; + bufferDesc->Height = texHeight; + + // Save current frame buffer so we can restore it + GLuint currentFramebuffer; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)¤tFramebuffer); + + // Read texture data + glBindFramebuffer(GL_FRAMEBUFFER, readbackFramebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTexture, 0); + glReadPixels(0, 0, texWidth, texHeight, GL_RGBA, type, data); + + // Restore previous framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, currentFramebuffer); + + return glGetError() == GL_NO_ERROR; +} +} // namespace ImGuiTexInspect diff --git a/lib/backends/tex_inspect_opengl.h b/lib/backends/tex_inspect_opengl.h new file mode 100644 index 0000000..4f8e7c6 --- /dev/null +++ b/lib/backends/tex_inspect_opengl.h @@ -0,0 +1,9 @@ +// ImGuiTexInspect, a texture inspector widget for dear imgui +#pragma once + +#include +namespace ImGuiTexInspect +{ +bool ImplOpenGL3_Init(const char *glsl_version = NULL); +void ImplOpenGl3_Shutdown(); +} // namespace ImGuiTexInspect diff --git a/lib/imgui_tex_inspect.cpp b/lib/imgui_tex_inspect.cpp new file mode 100644 index 0000000..7f96710 --- /dev/null +++ b/lib/imgui_tex_inspect.cpp @@ -0,0 +1,1182 @@ +// ImGuiTexInspect, a texture inspector widget for dear imgui + +//------------------------------------------------------------------------- +// [SECTION] INCLUDES +//------------------------------------------------------------------------- +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_tex_inspect.h" +#include "imgui_tex_inspect_internal.h" + +#include "imgui.h" +#include "imgui_internal.h" + +#if defined(_MSC_VER) +#pragma warning(disable : 4996) // 'sprintf' considered unsafe +#endif + +namespace ImGuiTexInspect +{ + +//------------------------------------------------------------------------- +// [SECTION] FORWARD DECLARATIONS +//------------------------------------------------------------------------- +void UpdateShaderOptions(Inspector *inspector); +void InspectorDrawCallback(const ImDrawList *parent_list, const ImDrawCmd *cmd); +bool GetVisibleTexelRegionAndGetData(Inspector *inspector, ImVec2 &texelTL, ImVec2 &texelBR); + +//------------------------------------------------------------------------- +// [SECTION] GLOBAL STATE +//------------------------------------------------------------------------- + +// Input mapping structure, default values listed in the comments. +struct InputMap +{ + ImGuiMouseButton PanButton; // LMB enables panning when held + InputMap(); +}; + +InputMap::InputMap() +{ + PanButton = ImGuiMouseButton_Left; +} + +// Settings configured via SetNextPanelOptions etc. +struct NextPanelSettings +{ + InspectorFlags ToSet = 0; + InspectorFlags ToClear = 0; +}; + +// Main context / configuration structure for imgui_tex_inspect +struct Context +{ + InputMap Input; // Input mapping config + ImGuiStorage Inspectors; // All the inspectors we've seen + Inspector * CurrentInspector; // Inspector currently being processed + NextPanelSettings NextPanelOptions; // Options configured for next inspector panel + float ZoomRate = 1.3f; // How fast mouse wheel affects zoom + float DefaultPanelHeight = 600; // Height of panel in pixels + float DefaultInitialPanelWidth = 600; // Only applies when window first appears + int MaxAnnotations = 1000; // Limit number of texel annotations for performance +}; + +Context *GContext = nullptr; + +//------------------------------------------------------------------------- +// [SECTION] USER FUNCTIONS +//------------------------------------------------------------------------- + +void Init() +{ + // Nothing to do here. But there might be in a later version. So client code should still call it! +} + +void Shutdown() +{ + // Nothing to do here. But there might be in a later version. So client code should still call it! +} + +Context *CreateContext() +{ + GContext = IM_NEW(Context); + SetCurrentContext(GContext); + return GContext; +} + +void DestroyContext(Context *ctx) +{ + if (ctx == NULL) + { + ctx = GContext; + } + + if (ctx == GContext) + { + GContext = NULL; + } + + for (ImGuiStorage::ImGuiStoragePair &pair : ctx->Inspectors.Data) + { + Inspector *inspector = (Inspector *)pair.val_p; + if (inspector) + { + IM_DELETE(inspector); + } + } + + IM_DELETE(ctx); +} + + +void SetCurrentContext(Context *context) +{ + ImGuiTexInspect::GContext = context; +} + +void SetNextPanelFlags(InspectorFlags setFlags, InspectorFlags clearFlags) +{ + SetFlag(GContext->NextPanelOptions.ToSet, setFlags); + SetFlag(GContext->NextPanelOptions.ToClear, clearFlags); +} + +bool BeginInspectorPanel(const char *title, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags, + SizeIncludingBorder sizeIncludingBorder) +{ + const int borderWidth = 1; + // Unpack size param. It's in the SizeIncludingBorder structure just to make sure users know what they're requesting + ImVec2 size = sizeIncludingBorder.Size; + + ImGuiWindow *window = ImGui::GetCurrentWindow(); + + Context *ctx = GContext; + + const ImGuiID ID = window->GetID(title); + const ImGuiIO &IO = ImGui::GetIO(); + + // Create or find inspector + bool justCreated = GetByKey(ctx, ID) == NULL; + ctx->CurrentInspector = GetOrAddByKey(ctx, ID); + Inspector *inspector = ctx->CurrentInspector; + justCreated |= !inspector->Initialized; + + // Cache the basics + inspector->ID = ID; + inspector->Texture = texture; + inspector->TextureSize = textureSize; + inspector->Initialized = true; + + // Handle incoming flags. We keep special track of the + // newly set flags because somethings only take effect + // the first time the flag is set. + InspectorFlags newlySetFlags = ctx->NextPanelOptions.ToSet; + if (justCreated) + { + SetFlag(newlySetFlags, flags); + inspector->MaxAnnotatedTexels = ctx->MaxAnnotations; + } + SetFlag(inspector->Flags, newlySetFlags); + ClearFlag(inspector->Flags, ctx->NextPanelOptions.ToClear); + ClearFlag(newlySetFlags, ctx->NextPanelOptions.ToClear); + ctx->NextPanelOptions = NextPanelSettings(); + + // Calculate panel size + ImVec2 contentRegionAvail = ImGui::GetContentRegionAvail(); + + ImVec2 panelSize; + // A size value of zero indicates we should use defaults + if (justCreated) + { + panelSize = {size.x == 0 ? ImMax(ctx->DefaultInitialPanelWidth, contentRegionAvail.x) : size.x, + size.y == 0 ? ctx->DefaultPanelHeight : size.y}; + } + else + { + panelSize = {size.x == 0 ? contentRegionAvail.x : size.x, size.y == 0 ? ctx->DefaultPanelHeight : size.y}; + } + + inspector->PanelSize = panelSize; + ImVec2 availablePanelSize = panelSize - ImVec2(borderWidth, borderWidth) * 2; + + { + // Possibly update scale + float newScale = -1; + + if (HasFlag(newlySetFlags, InspectorFlags_FillVertical)) + { + newScale = availablePanelSize.y / textureSize.y; + } + else if (HasFlag(newlySetFlags, InspectorFlags_FillHorizontal)) + { + newScale = availablePanelSize.x / textureSize.x; + } + else if (justCreated) + { + newScale = 1; + } + + if (newScale != -1) + { + inspector->Scale = ImVec2(newScale, newScale); + SetPanPos(inspector, ImVec2(0.5f, 0.5f)); + } + } + + RoundPanPos(inspector); + + ImVec2 textureSizePixels = inspector->Scale * textureSize; // Size whole texture would appear on screen + ImVec2 viewSizeUV = availablePanelSize / textureSizePixels; // Cropped size in terms of UV + ImVec2 uv0 = inspector->PanPos - viewSizeUV * 0.5; + ImVec2 uv1 = inspector->PanPos + viewSizeUV * 0.5; + + ImVec2 drawImageOffset{borderWidth, borderWidth}; + ImVec2 viewSize = availablePanelSize; + + if ((inspector->Flags & InspectorFlags_ShowWrap) == 0) + { + /* Don't crop the texture to UV [0,1] range. What you see outside this + * range will depend on API and texture properties */ + if (textureSizePixels.x < availablePanelSize.x) + { + // Not big enough to horizontally fill view + viewSize.x = ImFloor(textureSizePixels.x); + drawImageOffset.x += ImFloor((availablePanelSize.x - textureSizePixels.x) / 2); + uv0.x = 0; + uv1.x = 1; + viewSizeUV.x = 1; + inspector->PanPos.x = 0.5f; + } + if (textureSizePixels.y < availablePanelSize.y) + { + // Not big enough to vertically fill view + viewSize.y = ImFloor(textureSizePixels.y); + drawImageOffset.y += ImFloor((availablePanelSize.y - textureSizePixels.y) / 2); + uv0.y = 0; + uv1.y = 1; + viewSizeUV.y = 1; + inspector->PanPos.y = 0.5; + } + } + + if (HasFlag(flags,InspectorFlags_FlipX)) + { + ImSwap(uv0.x, uv1.x); + viewSizeUV.x *= -1; + } + + if (HasFlag(flags,InspectorFlags_FlipY)) + { + ImSwap(uv0.y, uv1.y); + viewSizeUV.y *= -1; + } + + inspector->ViewSize = viewSize; + inspector->ViewSizeUV = viewSizeUV; + + /* We use mouse scroll to zoom so we don't want scroll to propagate to + * parent window. For this to happen we must NOT set + * ImGuiWindowFlags_NoScrollWithMouse. This seems strange but it's the way + * ImGui works. Also we must ensure the ScrollMax.y is not zero for the + * child window. */ + if (ImGui::BeginChild(title, panelSize, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove)) + { + // See comment above + ImGui::GetCurrentWindow()->ScrollMax.y = 1.0f; + + // Callback for using our own image shader + ImGui::GetWindowDrawList()->AddCallback(InspectorDrawCallback, inspector); + + // Keep track of size of area that we draw for borders later + inspector->PanelTopLeftPixel = ImGui::GetCursorScreenPos(); + ImGui::SetCursorPos(ImGui::GetCursorPos() + drawImageOffset); + inspector->ViewTopLeftPixel = ImGui::GetCursorScreenPos(); + + UpdateShaderOptions(inspector); + inspector->CachedShaderOptions = inspector->ActiveShaderOptions; + ImGui::Image(texture, viewSize, uv0, uv1); + ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ResetRenderState, nullptr); + + /* Matrices for going back and forth between texel coordinates in the + * texture and screen coordinates based on where texture is drawn. + * Useful for annotations and mouse hover etc. */ + inspector->TexelsToPixels = GetTexelsToPixels(inspector->ViewTopLeftPixel, viewSize, uv0, viewSizeUV, inspector->TextureSize); + inspector->PixelsToTexels = inspector->TexelsToPixels.Inverse(); + + ImVec2 mousePos = ImGui::GetMousePos(); + ImVec2 mousePosTexel = inspector->PixelsToTexels * mousePos; + ImVec2 mouseUV = mousePosTexel / textureSize; + mousePosTexel.x = Modulus(mousePosTexel.x, textureSize.x); + mousePosTexel.y = Modulus(mousePosTexel.y, textureSize.y); + + if (ImGui::IsItemHovered() && (inspector->Flags & ImGuiTexInspect::InspectorFlags_NoTooltip) == 0) + { + // Show a tooltip for currently hovered texel + ImVec2 texelTL; + ImVec2 texelBR; + if (GetVisibleTexelRegionAndGetData(inspector, texelTL, texelBR)) + { + ImVec4 color = GetTexel(&inspector->Buffer, (int)mousePosTexel.x, (int)mousePosTexel.y); + + char buffer[128]; + sprintf(buffer, "UV: (%.5f, %.5f)\nTexel: (%d, %d)", mouseUV.x, mouseUV.y, (int)mousePosTexel.x, (int)mousePosTexel.y); + + ImGui::ColorTooltip(buffer, &color.x, 0); + } + } + + bool hovered = ImGui::IsWindowHovered(); + + { //DRAGGING + + // start drag + if (!inspector->IsDragging && hovered && IO.MouseClicked[ctx->Input.PanButton]) + { + inspector->IsDragging = true; + } + // carry on dragging + else if (inspector->IsDragging) + { + ImVec2 uvDelta = IO.MouseDelta * viewSizeUV / viewSize; + inspector->PanPos -= uvDelta; + RoundPanPos(inspector); + } + + // end drag + if (inspector->IsDragging && (IO.MouseReleased[ctx->Input.PanButton] || !IO.MouseDown[ctx->Input.PanButton])) + { + inspector->IsDragging = false; + } + } + + // ZOOM + if (hovered && IO.MouseWheel != 0) + { + float zoomRate = ctx->ZoomRate; + float scale = inspector->Scale.y; + float prevScale = scale; + + bool keepTexelSizeRegular = scale > inspector->MinimumGridSize && !HasFlag(inspector->Flags, InspectorFlags_NoGrid); + if (IO.MouseWheel > 0) + { + scale *= zoomRate; + if (keepTexelSizeRegular) + { + // It looks nicer when all the grid cells are the same size + // so keep scale integer when zoomed in + scale = ImCeil(scale); + } + } + else + { + scale /= zoomRate; + if (keepTexelSizeRegular) + { + // See comment above. We're doing a floor this time to make + // sure the scale always changes when scrolling + scale = ImFloorSigned(scale); + } + } + /* To make it easy to get back to 1:1 size we ensure that we stop + * here without going straight past it*/ + if ((prevScale < 1 && scale > 1) || (prevScale > 1 && scale < 1)) + { + scale = 1; + } + SetScale(inspector, ImVec2(inspector->PixelAspectRatio * scale, scale)); + SetPanPos(inspector, inspector->PanPos + (mouseUV - inspector->PanPos) * (1 - prevScale / scale)); + } + + return true; + } + else + { + return false; + } +} + +bool BeginInspectorPanel(const char *name, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags) +{ + return BeginInspectorPanel(name, texture, textureSize, flags, SizeIncludingBorder{{0, 0}}); +} + +bool BeginInspectorPanel(const char *name, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags, SizeExcludingBorder size) +{ + // Correct the size to include the border, but preserve 0 which has a special meaning + return BeginInspectorPanel(name, texture, textureSize, flags, + SizeIncludingBorder{ImVec2{size.size.x == 0 ? 0 : size.size.x + 2, + size.size.y == 0 ? 0 : size.size.y + 2}}); +} + +void EndInspectorPanel() +{ + const ImU32 innerBorderColour = 0xFFFFFFFF; + const ImU32 outerBorderColour = 0xFF888888; + Inspector *inspector = GContext->CurrentInspector; + + // Draw out border around whole inspector panel + ImGui::GetWindowDrawList()->AddRect(inspector->PanelTopLeftPixel, inspector->PanelTopLeftPixel + inspector->PanelSize, + outerBorderColour); + + // Draw innder border around texture. If zoomed in this will completely cover the outer border + ImGui::GetWindowDrawList()->AddRect(inspector->ViewTopLeftPixel - ImVec2(1, 1), + inspector->ViewTopLeftPixel + inspector->ViewSize + ImVec2(1, 1), innerBorderColour); + + ImGui::EndChild(); + + // We set this back to false every frame in case the texture is dynamic + if (!HasFlag(inspector->Flags, InspectorFlags_NoAutoReadTexture)) + { + inspector->HaveCurrentTexelData = false; + } +} + +void ReleaseInspectorData(ImGuiID ID) +{ + Inspector *inspector = GetByKey(GContext, ID); + + if (inspector == NULL) + return; + + if (inspector->DataBuffer) + { + IM_FREE(inspector->DataBuffer); + inspector->DataBuffer = NULL; + inspector->DataBufferSize = 0; + } + + /* In a later version we will remove inspector from the inspector table + * altogether. For now we reset the whole inspector structure to prevent + * clients relying on persisted data. + */ + *inspector = Inspector(); +} + + +ImGuiID CurrentInspector_GetID() +{ + return GContext->CurrentInspector->ID; +} + +void CurrentInspector_SetColorMatrix(const float (&matrix)[16], const float (&colorOffset)[4]) +{ + Inspector *inspector = GContext->CurrentInspector; + ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions; + memcpy(shaderOptions->ColorTransform, matrix, sizeof(matrix)); + memcpy(shaderOptions->ColorOffset, colorOffset, sizeof(colorOffset)); +} + +void CurrentInspector_ResetColorMatrix() +{ + Inspector *inspector = GContext->CurrentInspector; + ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions; + shaderOptions->ResetColorTransform(); +} + +void CurrentInspector_SetAlphaMode(InspectorAlphaMode mode) +{ + Inspector *inspector = GContext->CurrentInspector; + ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions; + + inspector->AlphaMode = mode; + + switch (mode) + { + case InspectorAlphaMode_Black: + shaderOptions->BackgroundColor = ImVec4(0, 0, 0, 1); + shaderOptions->DisableFinalAlpha = 1; + shaderOptions->PremultiplyAlpha = 1; + break; + case InspectorAlphaMode_White: + shaderOptions->BackgroundColor = ImVec4(1, 1, 1, 1); + shaderOptions->DisableFinalAlpha = 1; + shaderOptions->PremultiplyAlpha = 1; + break; + case InspectorAlphaMode_ImGui: + shaderOptions->BackgroundColor = ImVec4(0, 0, 0, 0); + shaderOptions->DisableFinalAlpha = 0; + shaderOptions->PremultiplyAlpha = 0; + break; + case InspectorAlphaMode_CustomColor: + shaderOptions->BackgroundColor = inspector->CustomBackgroundColor; + shaderOptions->DisableFinalAlpha = 1; + shaderOptions->PremultiplyAlpha = 1; + break; + } +} + +void CurrentInspector_SetFlags(InspectorFlags toSet, InspectorFlags toClear) +{ + Inspector *inspector = GContext->CurrentInspector; + SetFlag(inspector->Flags, toSet); + ClearFlag(inspector->Flags, toClear); +} + +void CurrentInspector_SetGridColor(ImU32 color) +{ + Inspector *inspector = GContext->CurrentInspector; + float alpha = inspector->ActiveShaderOptions.GridColor.w; + inspector->ActiveShaderOptions.GridColor = ImColor(color); + inspector->ActiveShaderOptions.GridColor.w = alpha; +} + +void CurrentInspector_SetMaxAnnotations(int maxAnnotations) +{ + Inspector *inspector = GContext->CurrentInspector; + inspector->MaxAnnotatedTexels = maxAnnotations; +} + +void CurrentInspector_InvalidateTextureCache() +{ + Inspector *inspector = GContext->CurrentInspector; + inspector->HaveCurrentTexelData = false; +} + +void CurrentInspector_SetCustomBackgroundColor(ImVec4 color) +{ + Inspector *inspector = GContext->CurrentInspector; + inspector->CustomBackgroundColor = color; + if (inspector->AlphaMode == InspectorAlphaMode_CustomColor) + { + inspector->ActiveShaderOptions.BackgroundColor = color; + } +} + +void CurrentInspector_SetCustomBackgroundColor(ImU32 color) +{ + CurrentInspector_SetCustomBackgroundColor(ImGui::ColorConvertU32ToFloat4(color)); +} + +void DrawColorMatrixEditor() +{ + const char *colorVectorNames[] = {"R", "G", "B", "A", "1"}; + const char *finalColorVectorNames[] = {"R'", "G'", "B'", "A'"}; + const float dragSpeed = 0.02f; + Inspector *inspector = GContext->CurrentInspector; + ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions; + + // Left hand side of equation. The final color vector which is the actual drawn color + TextVector("FinalColorVector", finalColorVectorNames, IM_ARRAYSIZE(finalColorVectorNames)); + + ImGui::SameLine(); + ImGui::TextUnformatted("="); + ImGui::SameLine(); + + // Right hand side of the equation: the Matrix. This is the editable part + ImGui::BeginGroup(); + for (int i = 0; i < 4; ++i) + { + ImGui::PushID(i); + for (int j = 0; j < 4; ++j) + { + ImGui::PushID(j); + ImGui::SetNextItemWidth(50); + ImGui::DragFloat("##f", &shaderOptions->ColorTransform[j * 4 + i], dragSpeed); + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::SetNextItemWidth(50); + ImGui::DragFloat("##offset", &shaderOptions->ColorOffset[i], dragSpeed); + ImGui::PopID(); + } + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::TextUnformatted("*"); + ImGui::SameLine(); + + // Right hand side of equation. The input vector, the source color of the texel. + TextVector("ColorVector", colorVectorNames, IM_ARRAYSIZE(colorVectorNames)); +} + +void DrawGridEditor() +{ + Inspector *inspector = GContext->CurrentInspector; + + ImGui::BeginGroup(); + bool gridEnabled = !HasFlag(inspector->Flags, InspectorFlags_NoGrid); + if (ImGui::Checkbox("Grid", &gridEnabled)) + { + if (gridEnabled) + { + CurrentInspector_ClearFlags(InspectorFlags_NoGrid); + } + else + { + CurrentInspector_SetFlags(InspectorFlags_NoGrid); + } + } + if (gridEnabled) + { + ImGui::SameLine(); + ImGui::ColorEdit3("Grid Color", (float *)&inspector->ActiveShaderOptions.GridColor, ImGuiColorEditFlags_NoInputs); + } + + ImGui::EndGroup(); +} + +void DrawColorChannelSelector() +{ + Inspector *inspector = GContext->CurrentInspector; + ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions; + + ImGuiStorage *storage = ImGui::GetStateStorage(); + const ImGuiID greyScaleID = ImGui::GetID("greyScale"); + + bool greyScale = storage->GetBool(greyScaleID, false); + + bool red = shaderOptions->ColorTransform[0] > 0; + bool green = shaderOptions->ColorTransform[5] > 0; + bool blue = shaderOptions->ColorTransform[10] > 0; + + bool changed = false; + + // In greyScale made we draw the red, green, blue checkboxes as disabled + if (greyScale) + { + PushDisabled(); + } + ImGui::BeginGroup(); + changed |= ImGui::Checkbox("Red", &red); + changed |= ImGui::Checkbox("Green", &green); + changed |= ImGui::Checkbox("Blue", &blue); + ImGui::EndGroup(); + + ImGui::SameLine(); + + if (greyScale) + { + PopDisabled(); + } + + if (changed) + { + // Overwrite the color transform matrix with one based on the settings + shaderOptions->ResetColorTransform(); + shaderOptions->ColorTransform[0] = red ? 1.0f : 0.0f; + shaderOptions->ColorTransform[5] = green ? 1.0f : 0.0f; + shaderOptions->ColorTransform[10] = blue ? 1.0f : 0.0f; + } + + ImGui::BeginGroup(); + if (ImGui::Checkbox("Grey", &greyScale)) + { + shaderOptions->ResetColorTransform(); + storage->SetBool(greyScaleID, greyScale); + if (greyScale) + { + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + shaderOptions->ColorTransform[i * 4 + j] = 0.333f; + } + } + } + } + + ImGui::EndGroup(); +} + +void DrawAlphaModeSelector() +{ + Inspector *inspector = GContext->CurrentInspector; + + const char *alphaModes[] = {"ImGui Background", "Black", "White", "Custom Color"}; + + ImGui::SetNextItemWidth(200); + + InspectorAlphaMode currentAlphaMode = inspector->AlphaMode; + + ImGui::Combo("Alpha Modes", (int *)¤tAlphaMode, alphaModes, IM_ARRAYSIZE(alphaModes)); + + CurrentInspector_SetAlphaMode(currentAlphaMode); + + if (inspector->AlphaMode == InspectorAlphaMode_CustomColor) + { + ImVec4 backgroundColor = inspector->CustomBackgroundColor; + if (ImGui::ColorEdit3("Background Color", (float *)&backgroundColor, 0)) + { + CurrentInspector_SetCustomBackgroundColor(backgroundColor); + } + } +} + +void SetZoomRate(float rate) +{ + GContext->ZoomRate = rate; +} + +//------------------------------------------------------------------------- +// [SECTION] Life Cycle +//------------------------------------------------------------------------- + +Inspector::~Inspector() +{ + if (DataBuffer) + { + IM_FREE(DataBuffer); + } +} +//------------------------------------------------------------------------- +// [SECTION] Scaling and Panning +//------------------------------------------------------------------------- +void RoundPanPos(Inspector *inspector) +{ + if ((inspector->Flags & InspectorFlags_ShowWrap) > 0) + { + /* PanPos is the point in the center of the current view. Allow the + * user to pan anywhere as long as the view center is inside the + * texture.*/ + inspector->PanPos = ImClamp(inspector->PanPos, ImVec2(0, 0), ImVec2(1, 1)); + } + else + { + /* When ShowWrap mode is disabled the limits are a bit more strict. We + * try to keep it so that the user cannot pan past the edge of the + * texture at all.*/ + ImVec2 absViewSizeUV = Abs(inspector->ViewSizeUV); + inspector->PanPos = ImMax(inspector->PanPos - absViewSizeUV / 2, ImVec2(0, 0)) + absViewSizeUV / 2; + inspector->PanPos = ImMin(inspector->PanPos + absViewSizeUV / 2, ImVec2(1, 1)) - absViewSizeUV / 2; + } + + /* If inspector->scale is 1 then we should ensure that pixels are aligned + * with texel centers to get pixel-perfect texture rendering*/ + ImVec2 topLeftSubTexel = inspector->PanPos * inspector->Scale * inspector->TextureSize - inspector->ViewSize * 0.5f; + + if (inspector->Scale.x >= 1) + { + topLeftSubTexel.x = Round(topLeftSubTexel.x); + } + if (inspector->Scale.y >= 1) + { + topLeftSubTexel.y = Round(topLeftSubTexel.y); + } + inspector->PanPos = (topLeftSubTexel + inspector->ViewSize * 0.5f) / (inspector->Scale * inspector->TextureSize); +} + +void SetPanPos(Inspector *inspector, ImVec2 pos) +{ + inspector->PanPos = pos; + RoundPanPos(inspector); +} + +void SetScale(Inspector *inspector, ImVec2 scale) +{ + scale = ImClamp(scale, inspector->ScaleMin, inspector->ScaleMax); + + inspector->ViewSizeUV *= inspector->Scale / scale; + + inspector->Scale = scale; + + // Only force nearest sampling if zoomed in + inspector->ActiveShaderOptions.ForceNearestSampling = + (inspector->Scale.x > 1.0f || inspector->Scale.y > 1.0f) && !HasFlag(inspector->Flags, InspectorFlags_NoForceFilterNearest); + inspector->ActiveShaderOptions.GridWidth = ImVec2(1.0f / inspector->Scale.x, 1.0f / inspector->Scale.y); +} + +void SetScale(Inspector *inspector, float scaleY) +{ + SetScale(inspector, ImVec2(scaleY * inspector->PixelAspectRatio, scaleY)); +} +//------------------------------------------------------------------------- +// [SECTION] INSPECTOR MAP +//------------------------------------------------------------------------- + +Inspector *GetByKey(const Context *ctx, ImGuiID key) +{ + return (Inspector *)ctx->Inspectors.GetVoidPtr(key); +} + +Inspector *GetOrAddByKey(Context *ctx, ImGuiID key) +{ + Inspector *inspector = GetByKey(ctx, key); + if (inspector) + { + return inspector; + } + else + { + inspector = IM_NEW(Inspector); + ctx->Inspectors.SetVoidPtr(key, inspector); + return inspector; + } +} + +//------------------------------------------------------------------------- +// [SECTION] TextureConversion class +//------------------------------------------------------------------------- + +void ShaderOptions::ResetColorTransform() +{ + memset(ColorTransform, 0, sizeof(ColorTransform)); + for (int i = 0; i < 4; ++i) + { + ColorTransform[i * 4 + i] = 1; + } +} + +ShaderOptions::ShaderOptions() +{ + ResetColorTransform(); + memset(ColorOffset, 0, sizeof(ColorOffset)); +} + +//------------------------------------------------------------------------- +// [SECTION] UI and CONFIG +//------------------------------------------------------------------------- + +void UpdateShaderOptions(Inspector *inspector) +{ + if (HasFlag(inspector->Flags, InspectorFlags_NoGrid) == false && inspector->Scale.y > inspector->MinimumGridSize) + { + // Enable grid in shader + inspector->ActiveShaderOptions.GridColor.w = 1; + SetScale(inspector, Round(inspector->Scale.y)); + } + else + { + // Disable grid in shader + inspector->ActiveShaderOptions.GridColor.w = 0; + } + + inspector->ActiveShaderOptions.ForceNearestSampling = + (inspector->Scale.x > 1.0f || inspector->Scale.y > 1.0f) && !HasFlag(inspector->Flags, InspectorFlags_NoForceFilterNearest); +} + +// Draws a single column ImGui table with one row for each provided string +void TextVector(const char *title, const char *const *strings, int stringCount) +{ + ImGui::BeginGroup(); + ImGui::SetNextItemWidth(50); + if (ImGui::BeginTable(title, 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX)) + { + for (int i = 0; i < stringCount; ++i) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::SetNextItemWidth(50); + ImGui::Text("%s", strings[i]); + } + ImGui::EndTable(); + } + ImGui::EndGroup(); +} + +const ImGuiCol disabledUIColorIds[] = {ImGuiCol_FrameBg, + ImGuiCol_FrameBgActive, + ImGuiCol_FrameBgHovered, + ImGuiCol_Text, + ImGuiCol_CheckMark}; + +// Push disabled style for ImGui elements +void PushDisabled() +{ + for (ImGuiCol colorId : disabledUIColorIds) + { + ImVec4 color = ImGui::GetStyleColorVec4(colorId); + color = color * ImVec4(0.5f, 0.5f, 0.5f, 0.5f); + ImGui::PushStyleColor(colorId, color); + } + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); +} + +// Pop disabled style for ImGui elements +void PopDisabled() +{ + for (ImGuiCol colorId : disabledUIColorIds) + { + (void)colorId; + ImGui::PopStyleColor(); + } + ImGui::PopItemFlag(); +} + +//------------------------------------------------------------------------- +// [SECTION] Rendering & Buffer Management +//------------------------------------------------------------------------- + +void InspectorDrawCallback(const ImDrawList *parent_list, const ImDrawCmd *cmd) +{ + // Forward call to API-specific backend + Inspector *inspector = (Inspector *)cmd->UserCallbackData; + BackEnd_SetShader(parent_list, cmd, inspector); +} + +// Calculate a transform to convert from texel coordinates to screen pixel coordinates +Transform2D GetTexelsToPixels(ImVec2 screenTopLeft, ImVec2 screenViewSize, ImVec2 uvTopLeft, ImVec2 uvViewSize, ImVec2 textureSize) +{ + ImVec2 uvToPixel = screenViewSize / uvViewSize; + + Transform2D transform; + transform.Scale = uvToPixel / textureSize; + transform.Translate.x = screenTopLeft.x - uvTopLeft.x * uvToPixel.x; + transform.Translate.y = screenTopLeft.y - uvTopLeft.y * uvToPixel.y; + return transform; +} + +/* Fills in the AnnotationsDesc structure which provides all necessary + * information for code which draw annoations. Returns false if no annoations + * should be drawn. The maxAnnotatedTexels argument provides a way to override + * the default maxAnnotatedTexels. + */ +bool GetAnnotationDesc(AnnotationsDesc *ad, ImU64 maxAnnotatedTexels) +{ + Inspector *inspector = GContext->CurrentInspector; + + if (maxAnnotatedTexels == 0) + { + maxAnnotatedTexels = inspector->MaxAnnotatedTexels; + } + if (maxAnnotatedTexels != 0) + { + /* Check if we would draw too many annotations. This is to avoid poor + * frame rate when too zoomed out. Increase MaxAnnotatedTexels if you + * want to draw more annotations. Note that we don't use texelTL & + * texelBR to get total visible texels as this would cause flickering + * while panning as the exact number of visible texels changes. + */ + + ImVec2 screenViewSizeTexels = Abs(inspector->PixelsToTexels.Scale) * inspector->ViewSize; + ImU64 approxVisibleTexelCount = (ImU64)screenViewSizeTexels.x * (ImU64)screenViewSizeTexels.y; + if (approxVisibleTexelCount > maxAnnotatedTexels) + { + return false; + } + } + + // texelTL & texelBL will describe the currently visible texel region + ImVec2 texelTL; + ImVec2 texelBR; + + if (GetVisibleTexelRegionAndGetData(inspector, texelTL, texelBR)) + { + ad->Buffer= inspector->Buffer; + ad->DrawList = ImGui::GetWindowDrawList(); + ad->TexelsToPixels = inspector->TexelsToPixels; + ad->TexelTopLeft = texelTL; + ad->TexelViewSize = texelBR - texelTL; + return true; + } + + return false; +} + +/* Calculates currently visible region of texture (which is returned in texelTL + * and texelBR) then also actually ensure that that data is in memory. Returns + * false if fetching data failed. + */ +bool GetVisibleTexelRegionAndGetData(Inspector *inspector, ImVec2 &texelTL, ImVec2 &texelBR) +{ + /* Figure out which texels correspond to the top left and bottom right + * corners of the texture view. The plus + ImVec2(1,1) is because we + * want to draw partially visible texels on the bottom and right edges. + */ + texelTL = ImFloor(inspector->PixelsToTexels * inspector->ViewTopLeftPixel); + texelBR = ImFloor(inspector->PixelsToTexels * (inspector->ViewTopLeftPixel + inspector->ViewSize)); + + if (texelTL.x > texelBR.x) + { + ImSwap(texelTL.x, texelBR.x); + } + if (texelTL.y > texelBR.y) + { + ImSwap(texelTL.y, texelBR.y); + } + + /* Add ImVec2(1,1) because we want to draw partially visible texels on the + * bottom and right edges.*/ + texelBR += ImVec2(1,1); + + texelTL = ImClamp(texelTL, ImVec2(0, 0), inspector->TextureSize); + texelBR = ImClamp(texelBR, ImVec2(0, 0), inspector->TextureSize); + + if (inspector->HaveCurrentTexelData) + { + return true; + } + + // Now request pixel data for this region from backend + + ImVec2 texelViewSize = texelBR - texelTL; + + if (ImMin(texelViewSize.x, texelViewSize.y) > 0) + { + if (BackEnd_GetData(inspector, inspector->Texture, (int)texelTL.x, (int)texelTL.y, (int)texelViewSize.x, (int)texelViewSize.y, + &inspector->Buffer)) + { + inspector->HaveCurrentTexelData = true; + return true; + } + } + return false; +} + +/* This is a function the backends can use to allocate a buffer for storing + * texture texel data. The buffer is owned by the inpsector so the backend + * code doesn't need to worry about freeing it. + */ +ImU8 *GetBuffer(Inspector *inspector, size_t bytes) +{ + if (inspector->DataBufferSize < bytes || inspector->DataBuffer == nullptr) + { + // We need to allocate a buffer + + if (inspector->DataBuffer) + { + IM_FREE(inspector->DataBuffer); + } + + // Allocate slightly more than we need to avoid reallocating + // very frequently in the case that size is increasing. + size_t size = bytes * 5 / 4; + inspector->DataBuffer = (ImU8 *)IM_ALLOC(size); + inspector->DataBufferSize = size; + } + + return inspector->DataBuffer; +} + +ImVec4 GetTexel(const BufferDesc *bd, int x, int y) +{ + if (x < bd->StartX || x >= bd->StartX + bd->Width || y < bd->StartY || y >= bd->StartY + bd->Height) + { + // Outside the range of data in the buffer. + return ImVec4(); + } + // Calculate position in array + size_t offset = ((size_t)bd->LineStride * (y - bd->StartY) + bd->Stride * (x - bd->StartX)); + if (bd->Data_float) + { + const float *texel = bd->Data_float + offset; + // It's possible our buffer doesn't have all 4 channels so fill gaps in with zeros + return ImVec4( texel[bd->Red], + bd->ChannelCount >= 2 ? texel[bd->Green] : 0, + bd->ChannelCount >= 3 ? texel[bd->Blue] : 0, + bd->ChannelCount >= 4 ? texel[bd->Alpha] : 0); + } + else if (bd->Data_uint8_t) + { + const ImU8 *texel = bd->Data_uint8_t + offset; + // It's possible our buffer doesn't have all 4 channels so fill gaps in with zeros. + // Also map from [0,255] to [0,1] + return ImVec4( (float)texel[bd->Red] / 255.0f, + bd->ChannelCount >= 2 ? (float)texel[bd->Green] / 255.0f : 0, + bd->ChannelCount >= 3 ? (float)texel[bd->Blue] / 255.0f : 0, + bd->ChannelCount >= 4 ? (float)texel[bd->Alpha] / 255.0f : 0); + } + else + { + return ImVec4(); + } +} +//------------------------------------------------------------------------- +// [SECTION] Annotations +//------------------------------------------------------------------------- + +ValueText::ValueText(Format format) +{ + /* The ValueText annotation draws a string inside each texel displaying the + * values of each channel. We now select a format string based on the enum + * parameter*/ + switch (format) + { + case Format::HexString: + TextFormatString = "#%02X%02X%02X%02X"; + TextColumnCount = 9; + TextRowCount = 1; + FormatAsFloats = false; + break; + case Format::BytesHex: + TextFormatString = "R:#%02X\nG:#%02X\nB:#%02X\nA:#%02X"; + TextColumnCount = 5; + TextRowCount = 4; + FormatAsFloats = false; + break; + case Format::BytesDec: + TextFormatString = "R:%3d\nG:%3d\nB:%3d\nA:%3d"; + TextColumnCount = 5; + TextRowCount = 4; + FormatAsFloats = false; + break; + case Format::Floats: + TextFormatString = "%5.3f\n%5.3f\n%5.3f\n%5.3f"; + TextColumnCount = 5; + TextRowCount = 4; + FormatAsFloats = true; + break; + } +} + +void ValueText::DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value) +{ + char buffer[64]; + + float fontHeight = ImGui::GetFontSize(); + float fontWidth = fontHeight / 2; /* WARNING this is a hack that gets a constant + * character width from half the height. This work for the default font but + * won't work on other fonts which may even not be monospace.*/ + + // Calculate size of text and check if it fits + ImVec2 textSize = ImVec2((float)TextColumnCount * fontWidth, (float)TextRowCount * fontHeight); + + if (textSize.x > ImAbs(texelsToPixels.Scale.x) || textSize.y > ImAbs(texelsToPixels.Scale.y)) + { + // Not enough room in texel to fit the text. Don't draw it. + return; + } + + /* Choose black or white text based on how bright the texel. I.e. don't + * draw black text on a dark background or vice versa. */ + float brightness = (value.x + value.y + value.z) * value.w / 3; + ImU32 lineColor = brightness > 0.5 ? 0xFF000000 : 0xFFFFFFFF; + + if (FormatAsFloats) + { + sprintf(buffer, TextFormatString, value.x, value.y, value.z, value.w); + } + else + { + /* Map [0,1] to [0,255]. Also clamp it since input data wasn't + * necessarily in [0,1] range. */ + ImU8 r = (ImU8)Round((ImClamp(value.x, 0.0f, 1.0f)) * 255); + ImU8 g = (ImU8)Round((ImClamp(value.y, 0.0f, 1.0f)) * 255); + ImU8 b = (ImU8)Round((ImClamp(value.z, 0.0f, 1.0f)) * 255); + ImU8 a = (ImU8)Round((ImClamp(value.w, 0.0f, 1.0f)) * 255); + sprintf(buffer, TextFormatString, r, g, b, a); + } + + // Add text to drawlist! + ImVec2 pixelCenter = texelsToPixels * texel; + drawList->AddText(pixelCenter - textSize * 0.5f, lineColor, buffer); +} + +Arrow::Arrow(int xVectorIndex, int yVectorIndex, ImVec2 lineScale) + : VectorIndex_x(xVectorIndex), VectorIndex_y(yVectorIndex), LineScale(lineScale) +{ +} + +Arrow &Arrow::UsePreset(Preset preset) +{ + switch (preset) + { + default: + case Preset::NormalMap: + VectorIndex_x = 0; + VectorIndex_y = 1; + LineScale = ImVec2(1, -1); + ZeroPoint = ImVec2(128.0f / 255, 128.0f / 255); + break; + case Preset::NormalizedFloat: + VectorIndex_x = 0; + VectorIndex_y = 1; + LineScale = ImVec2(0.5f, -0.5f); + ZeroPoint = ImVec2(0, 0); + break; + } + return *this; +} + +void Arrow::DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value) +{ + const float arrowHeadScale = 0.35f; + const ImU32 lineColor = 0xFFFFFFFF; + float *vecPtr = &value.x; + + // Draw an arrow! + ImVec2 lineDir = (ImVec2(vecPtr[VectorIndex_x], vecPtr[VectorIndex_y]) - ZeroPoint) * LineScale; + ImVec2 lineStart = texel; + ImVec2 lineEnd = lineStart + lineDir; + + ImVec2 arrowHead1 = ImVec2(lineDir.x - lineDir.y, lineDir.x + lineDir.y) * -arrowHeadScale; + ImVec2 arrowHead2 = ImVec2(lineDir.x + lineDir.y, -lineDir.x + lineDir.y) * -arrowHeadScale; + + DrawAnnotationLine(drawList, lineStart, lineEnd, texelsToPixels, lineColor); + DrawAnnotationLine(drawList, lineEnd, lineEnd + arrowHead1, texelsToPixels, lineColor); + DrawAnnotationLine(drawList, lineEnd, lineEnd + arrowHead2, texelsToPixels, lineColor); +} + +void DrawAnnotationLine(ImDrawList *drawList, ImVec2 fromTexel, ImVec2 toTexel, Transform2D texelsToPixels, ImU32 color) +{ + ImVec2 lineFrom = texelsToPixels * fromTexel; + ImVec2 lineTo = texelsToPixels * toTexel; + drawList->AddLine(lineFrom, lineTo, color, 1.0f); +} +} // namespace ImGuiTexInspect diff --git a/lib/imgui_tex_inspect.h b/lib/imgui_tex_inspect.h new file mode 100644 index 0000000..a7c3d6c --- /dev/null +++ b/lib/imgui_tex_inspect.h @@ -0,0 +1,308 @@ +// ImGuiTexInspect, a texture inspector widget for dear imgui + +#pragma once +#include "imgui.h" + +namespace ImGuiTexInspect +{ +struct Context; +struct Transform2D; +//------------------------------------------------------------------------- +// [SECTION] INIT & SHUTDOWN +//------------------------------------------------------------------------- +void Init(); +void Shutdown(); + +Context *CreateContext(); +void DestroyContext(Context *); +void SetCurrentContext(Context *); + +//------------------------------------------------------------------------- +// [SECTION] BASIC USAGE +//------------------------------------------------------------------------- + +enum InspectorAlphaMode +{ + InspectorAlphaMode_ImGui, // Alpha is transparency so you see the ImGui panel background behind image + InspectorAlphaMode_Black, // Alpha is used to blend over a black background + InspectorAlphaMode_White, // Alpha is used to blend over a white background + InspectorAlphaMode_CustomColor // Alpha is used to blend over a custom colour. +}; + +typedef ImU64 InspectorFlags; +enum InspectorFlags_ +{ + InspectorFlags_ShowWrap = 1 << 0, // Draw beyong the [0,1] uv range. What you see will depend on API + InspectorFlags_NoForceFilterNearest = 1 << 1, // Normally we force nearest neighbour sampling when zoomed in. Set to disable this. + InspectorFlags_NoGrid = 1 << 2, // By default a grid is shown at high zoom levels + InspectorFlags_NoTooltip = 1 << 3, // Disable tooltip on hover + InspectorFlags_FillHorizontal = 1 << 4, // Scale to fill available space horizontally + InspectorFlags_FillVertical = 1 << 5, // Scale to fill available space vertically + InspectorFlags_NoAutoReadTexture = 1 << 6, // By default texture data is read to CPU every frame for tooltip and annotations + InspectorFlags_FlipX = 1 << 7, // Horizontally flip the way the texture is displayed + InspectorFlags_FlipY = 1 << 8, // Vertically flip the way the texture is displayed +}; + +/* Use one of these Size structs if you want to specify an exact size for the inspector panel. + * E.g. + * BeginInspectorPanel("MyPanel", texture_1K, ImVec2(1024,1024), 0, SizeExcludingBorder(ImVec2(1024,1024))); + * + * However, most of the time the default size will be fine. E.g. + * + * BeginInspectorPanel("MyPanel", texture_1K, ImVec2(1024,1024)); + */ +struct SizeIncludingBorder {ImVec2 Size; SizeIncludingBorder(ImVec2 size):Size(size){}}; +struct SizeExcludingBorder {ImVec2 size; SizeExcludingBorder(ImVec2 size):size(size){}}; +/* BeginInspectorPanel + * Returns true if panel is drawn. Note that flags will only be considered on the first call */ +bool BeginInspectorPanel(const char *name, ImTextureID, ImVec2 textureSize, InspectorFlags flags = 0); +bool BeginInspectorPanel(const char *name, ImTextureID, ImVec2 textureSize, InspectorFlags flags, SizeIncludingBorder size); +bool BeginInspectorPanel(const char *name, ImTextureID, ImVec2 textureSize, InspectorFlags flags, SizeExcludingBorder size); + +/* EndInspectorPanel + * Always call after BeginInspectorPanel and after you have drawn any required annotations*/ +void EndInspectorPanel(); + +/* ReleaseInspectorData + * ImGuiTexInspect keeps texture data cached in memory. If you know you won't + * be displaying a particular panel for a while you can call this to release + * the memory. It won't be allocated again until next time you call + * BeginInspectorPanel. If id is NULL then the current (most recent) inspector + * will be affected. Unless you have a lot of different Inspector instances + * you can probably not worry about this. Call CurrentInspector_GetID to get + * the ID of an inspector. + */ +void ReleaseInspectorData(ImGuiID id); + +//------------------------------------------------------------------------- +// [SECTION] CURRENT INSPECTOR MANIPULATORS +//------------------------------------------------------------------------- +/* All the functions starting with CurrentInspector_ can be used after calling + * BeginInspector until the end of the frame. It is not necessary to call them + * before the matching EndInspectorPanel + */ + +/* CurrentInspector_SetColorMatrix + * colorMatrix and colorOffset describe the transform which happens to the + * color of each texel. + * The calculation is finalColor = colorMatrix * originalColor + colorOffset. + * Where finalColor, originalColor and colorOffset are column vectors with + * components (r,g,b,a) and colorMatrix is a column-major matrix. + */ +void CurrentInspector_SetColorMatrix(const float (&colorMatrix)[16], const float (&colorOffset)[4]); +void CurrentInspector_ResetColorMatrix(); + +/* CurrentInspector_SetAlphaMode - see enum comments for details*/ +void CurrentInspector_SetAlphaMode(InspectorAlphaMode); +void CurrentInspector_SetFlags(InspectorFlags toSet, InspectorFlags toClear = 0); +inline void CurrentInspector_ClearFlags(InspectorFlags toClear) {CurrentInspector_SetFlags(0, toClear);} +void CurrentInspector_SetGridColor(ImU32 color); +void CurrentInspector_SetMaxAnnotations(int maxAnnotations); + +/* CurrentInspector_InvalidateTextureCache + * If using the InspectorFlags_NoAutoReadTexture flag then call this to + * indicate your texture has changed context. + */ +void CurrentInspector_InvalidateTextureCache(); + +/* CurrentInspector_SetCustomBackgroundColor + * If using InspectorAlphaMode_CustomColor then this is the color that will be + * blended as the background where alpha is less than one. + */ +void CurrentInspector_SetCustomBackgroundColor(ImVec4 color); +void CurrentInspector_SetCustomBackgroundColor(ImU32 color); + +/* CurrentInspector_GetID + * Get the ID of the current inspector. Currently only used for calling + * ReleaseInspectorData. + */ +ImGuiID CurrentInspector_GetID(); + +/* Some convenience functions for drawing ImGui controls for the current Inspector */ +void DrawColorMatrixEditor(); // ColorMatrix editor. See comments on ColorMatrix below. +void DrawGridEditor(); // Grid editor. Enable/Disable grid. Set Grid Color. +void DrawColorChannelSelector(); // For toggling R,G,B channels +void DrawAlphaModeSelector(); // A combo box for selecting the alpha mode + +//------------------------------------------------------------------------- +// [SECTION] CONTEXT-WIDE SETTINGS +//------------------------------------------------------------------------- +/* SetZoomRate + * factor should be greater than 1. A value of 1.5 means one mouse wheel + * scroll will increase zoom level by 50%. The factor used for zooming out is + * 1/factor. */ +void SetZoomRate(float factor); + +//------------------------------------------------------------------------- +// [SECTION] ANNOTATION TOOLS +//------------------------------------------------------------------------- + +/* DrawAnnotationLine + * Convenience function to add a line to draw list using texel coordinates. + */ +void DrawAnnotationLine(ImDrawList *drawList, ImVec2 fromTexel, ImVec2 toTexel, Transform2D texelsToPixels, ImU32 color); + +//------------------------------------------------------------------------- +// [SECTION] Annotation Classes +//------------------------------------------------------------------------- + +/* To draw annotations call DrawAnnotions in between BeginInspectorPanel and + * EndInspectorPanel. Example usage: + * DrawAnnotations(ValueText(ValueText::HexString)); + * + * To provide your own Annotation drawing class just define a class that + * implements the DrawAnnotation method. See imgui_tex_inspect_demo.cpp + * for an example. + */ +template +void DrawAnnotations(T drawer, ImU64 maxAnnotatedTexels = 0); + +/* ValueText + * An annoation class that draws text inside each texel when zoom level is high enough for it to fit. + * The text shows the value of the texel. E.g. "R:255, G: 128, B:0, A:255" + */ +class ValueText +{ + protected: + int TextRowCount; + int TextColumnCount; + const char *TextFormatString; + bool FormatAsFloats; + + public: + enum Format + { + HexString, // E.g. #EF97B9FF + BytesHex, // E.g. R:#EF G:#97 B:#B9 A:#FF (split over 4 lines) + BytesDec, // E.g. R:239 G: 151 B:185 A:255 (split over 4 lines) + Floats // E.g. 0.937 0.592 0.725 1.000 (split over 4 lines) + }; + ValueText(Format format = HexString); + void DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value); +}; + +/* Arrow + * An annotation class that draws an arrow inside each texel when zoom level is + * high enough. The direction and length of the arrow are determined by texel + * values. + * The X and Y components of the arrow is determined by the VectorIndex_x, and + * VectorIndex_y channels of the texel value. Examples: + + * VectorIndex_x = 0, VectorIndex_y = 1 means X component is red and Y component is green + * VectorIndex_x = 1, VectorIndex_y = 2 means X component is green and Y component is blue + * VectorIndex_x = 0, VectorIndex_y = 3 means X component is red and Y component is alpha + * + * ZeroPoint is the texel value which corresponds to a zero length vector. E.g. + * ZeroPoint = (0.5, 0.5) means (0.5, 0.5) will be drawn as a zero length arrow + * + * All public properties can be directly manipulated. There are also presets that can be set + * by calling UsePreset. + + */ +class Arrow +{ + public: + int VectorIndex_x; + int VectorIndex_y; + ImVec2 LineScale; + ImVec2 ZeroPoint = {0, 0}; + + enum Preset + { + NormalMap, // For normal maps. I.e. Arrow is in (R,G) channels. 128, 128 is zero point + NormalizedFloat // Arrow in (R,G) channels. 0,0 is zero point, (1,0) will draw an arrow exactly to + // right edge of texture. (0,-1) will draw exactly to the bottom etc. + }; + Arrow(int xVectorIndex = 0, int yVectorIndex = 1, ImVec2 lineScale = ImVec2(1, 1)); + Arrow &UsePreset(Preset); + void DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value); +}; + +//------------------------------------------------------------------------- +// [SECTION] INTERNAL +//------------------------------------------------------------------------- + +struct Transform2D +{ + ImVec2 Scale; + ImVec2 Translate; + + /* Transform a vector by this transform. Scale is applied first */ + ImVec2 operator*(const ImVec2 &rhs) const + { + return ImVec2(Scale.x * rhs.x + Translate.x, Scale.y * rhs.y + Translate.y); + } + + /* Return an inverse transform such that transform.Inverse() * transform * vector == vector*/ + Transform2D Inverse() const + { + ImVec2 inverseScale(1 / Scale.x, 1 / Scale.y); + return {inverseScale, ImVec2(-inverseScale.x * Translate.x, -inverseScale.y * Translate.y)}; + } +}; + +struct BufferDesc +{ + float *Data_float = nullptr; // Only one of these + ImU8 *Data_uint8_t = nullptr; // two pointers should be non NULL + size_t BufferByteSize = 0; // Size of buffer pointed to by one of above pointers + int Stride = 0; // Measured in size of data type, not bytes! + int LineStride = 0; // Measured in size of data type, not bytes! + int StartX = 0; // Texel coordinates of data start + int StartY = 0; + int Width = 0; // Size of block of texels which are in data + int Height = 0; + + unsigned char ChannelCount = 0; // Number of color channels in data. E.g. 2 means just red and green + + /* These 4 values describe where each color is stored relative to the beginning of the texel in memory + * E.g. the float containing the red value would be at: + * Data_float[texelIndex + bufferDesc.Red] + */ + unsigned char Red = 0; + unsigned char Green = 0; + unsigned char Blue = 0; + unsigned char Alpha = 0; +}; + +/* We use this struct for annotations rather than the Inspector struct so that + * the whole Inspector struct doesn't have to be exposed in this header. + */ +struct AnnotationsDesc +{ + ImDrawList *DrawList; + ImVec2 TexelViewSize; // How many texels are visible for annotating + ImVec2 TexelTopLeft; // Coordinated in texture space of top left visible texel + BufferDesc Buffer; // Description of cache texel data + Transform2D TexelsToPixels; // Transform to go from texel space to screen pixel space +}; + +//------------------------------------------------------------------------- +// [SECTION] FORWARD DECLARATIONS FOR TEMPLATE IMPLEMENTATION - Do not call directly +//------------------------------------------------------------------------- + +ImVec4 GetTexel(const BufferDesc *bd, int x, int y); +bool GetAnnotationDesc(AnnotationsDesc *, ImU64 maxAnnotatedTexels); + +//------------------------------------------------------------------------- +// [SECTION] TEMPLATE IMPLEMENTATION +//------------------------------------------------------------------------- +template +void DrawAnnotations(T drawer, ImU64 maxAnnotatedTexels) +{ + AnnotationsDesc ad; + if (GetAnnotationDesc(&ad, maxAnnotatedTexels)) + { + ImVec2 texelBottomRight = ImVec2(ad.TexelTopLeft.x + ad.TexelViewSize.x, ad.TexelTopLeft.y + ad.TexelViewSize.y); + for (int ty = (int)ad.TexelTopLeft.y; ty < (int)texelBottomRight.y; ++ty) + { + for (int tx = (int)ad.TexelTopLeft.x; tx < (int)texelBottomRight.x; ++tx) + { + ImVec4 color = GetTexel(&ad.Buffer, tx, ty); + ImVec2 center = {(float)tx + 0.5f, (float)ty + 0.5f}; + drawer.DrawAnnotation(ad.DrawList, center, ad.TexelsToPixels, color); + } + } + } +} +} // namespace ImGuiTexInspect diff --git a/lib/imgui_tex_inspect_internal.h b/lib/imgui_tex_inspect_internal.h new file mode 100644 index 0000000..8385cd1 --- /dev/null +++ b/lib/imgui_tex_inspect_internal.h @@ -0,0 +1,182 @@ +// ImGuiTexInspect, a texture inspector widget for dear imgui + +#pragma once +#include "imgui.h" +#include "imgui_internal.h" +#include "imgui_tex_inspect.h" + +namespace ImGuiTexInspect +{ +//------------------------------------------------------------------------- +// [SECTION] UTILITIES +//------------------------------------------------------------------------- +// Returns true if a flag is set +template +static inline bool HasFlag(TSet set, TFlag flag) +{ + return (set & flag) == flag; +} + +// Set flag or flags in set +template +static inline void SetFlag(TSet &set, TFlag flags) +{ + set = static_cast(set | flags); +} + +// Clear flag or flags in set +template +static inline void ClearFlag(TSet &set, TFlag flag) +{ + set = static_cast(set & ~flag); +} + +static inline float ImFloorSigned(float f) +{ + return (float)((f >= 0 || (int)f == f) ? (int)f : (int)f - 1); +} + +// Proper modulus operator, as opposed to remainder as calculated by % +template +static inline T Modulus(T a, T b) +{ + return a - b * ImFloorSigned(a / b); +} + +// Defined in recent versions of imgui_internal.h. Included here in case user is on older +// imgui version. + +static inline float Round(float f) +{ + return ImFloorSigned(f + 0.5f); +} + +static inline ImVec2 Abs(ImVec2 v) +{ + return ImVec2(ImAbs(v.x), ImAbs(v.y)); +} + +//------------------------------------------------------------------------- +// [SECTION] STRUCTURES +//------------------------------------------------------------------------- +struct ShaderOptions +{ + float ColorTransform[16] = {}; // See CurrentInspector_SetColorMatrix for details + float ColorOffset[4] = {}; + + ImVec4 BackgroundColor = {0,0,0,0}; // Color used for alpha blending + float PremultiplyAlpha = 0; // If 1 then color will be multiplied by alpha in shader, before blend stage + float DisableFinalAlpha = 0; // If 1 then fragment shader will always output alpha = 1 + + bool ForceNearestSampling = false; // If true fragment shader will always sample from texel centers + + ImVec2 GridWidth = {0,0}; // Width in UV coords of grid line + ImVec4 GridColor = {0,0,0,0}; + + void ResetColorTransform(); + ShaderOptions(); +}; + +struct Inspector +{ + ImGuiID ID; + bool Initialized = false; + + // Texture + ImTextureID Texture = ImTextureID{}; + ImVec2 TextureSize = {0, 0}; // Size in texels of texture + float PixelAspectRatio = 1; // Values other than 1 not supported yet + + // View State + bool IsDragging = false; // Is user currently dragging to pan view + ImVec2 PanPos = {0.5f, 0.5f}; // The UV value at the center of the current view + ImVec2 Scale = {1, 1}; // 1 pixel is 1 texel + + ImVec2 PanelTopLeftPixel = {0, 0}; // Top left of view in ImGui pixel coordinates + ImVec2 PanelSize = {0, 0}; // Size of area allocated to drawing the image in pixels. + + ImVec2 ViewTopLeftPixel = {0, 0}; // Position in ImGui pixel coordinates + ImVec2 ViewSize = {0, 0}; // Rendered size of current image. This could be smaller than panel size if user has zoomed out. + ImVec2 ViewSizeUV = {0, 0}; // Visible region of the texture in UV coordinates + + /* Conversion transforms to go back and forth between screen pixels (what ImGui considers screen pixels) and texels*/ + Transform2D TexelsToPixels; + Transform2D PixelsToTexels; + + // Cached pixel data + bool HaveCurrentTexelData = false; + BufferDesc Buffer; + + /* We don't actually access texel data through this pointer. We just + * manage its lifetime. The backend might have asked us to allocated a + * buffer, or it might not. The pointer we actually use to access texel + * data is in the Buffer object above (which depending on what the backend + * did might point to the same memory as this pointer) + */ + ImU8 *DataBuffer = nullptr; + size_t DataBufferSize = 0; + + // Configuration + InspectorFlags Flags = 0; + + // Background mode + InspectorAlphaMode AlphaMode = InspectorAlphaMode_ImGui; + ImVec4 CustomBackgroundColor = {0, 0, 0, 1}; + + // Scaling limits + ImVec2 ScaleMin = {0.02f, 0.02f}; + ImVec2 ScaleMax = {500, 500}; + + // Grid + float MinimumGridSize = 4; // Don't draw the grid if lines would be closer than MinimumGridSize pixels + + // Annotations + ImU32 MaxAnnotatedTexels = 0; + + // Color transformation + ShaderOptions ActiveShaderOptions; + ShaderOptions CachedShaderOptions; + + ~Inspector(); +}; + +//------------------------------------------------------------------------- +// [SECTION] INTERNAL FUNCTIONS +//------------------------------------------------------------------------- + +Inspector *GetByKey(const Context *ctx, ImGuiID key); +Inspector *GetOrAddByKey(Context *ctx, ImGuiID key); + +void SetPanPos(Inspector *inspector, ImVec2 pos); +void SetScale(Inspector *inspector, ImVec2 scale); +void SetScale(Inspector *inspector, float scaleY); +void RoundPanPos(Inspector *inspector); + +ImU8 *GetBuffer(Inspector *inspector, size_t bytes); + +/* GetTexelsToPixels + * Calculate a transform to convert from texel coordinates to screen pixel coordinates + * */ +Transform2D GetTexelsToPixels(ImVec2 screenTopLeft, ImVec2 screenViewSize, ImVec2 uvTopLeft, ImVec2 uvViewSize, ImVec2 textureSize); + +//------------------------------------------------------------------------- +// [SECTION] IMGUI UTILS +//------------------------------------------------------------------------- +/* TextVector + * Draws a single-column ImGui table with one row for each provided string + */ +void TextVector(const char *title, const char *const *strings, int n); + +/* PushDisabled & PopDisabled + * Push and Pop and ImGui styles that disable and "grey out" ImGui elements + * by making them non interactive and transparent*/ +void PushDisabled(); +void PopDisabled(); + +//------------------------------------------------------------------------- +// [SECTION] BACKEND FUNCTIONS +//------------------------------------------------------------------------- +void BackEnd_SetShader(const ImDrawList *drawList, const ImDrawCmd *cmd, const Inspector *inspector); +bool BackEnd_GetData(Inspector *inspector, ImTextureID texture, int x, int y, int width, int height, BufferDesc *buffer); + +} // namespace ImGuiTexInspect diff --git a/main.cpp b/main.cpp index 290bab3..c76c88c 100644 --- a/main.cpp +++ b/main.cpp @@ -28,14 +28,53 @@ #include #endif #else -#include +#include #if defined(IMGUI_IMPL_OPENGL_ES2) -#include +#include #else -#include +#include #endif #endif +#include "lib/imgui_tex_inspect.h" +#include "lib/backends/tex_inspect_opengl.h" + +struct Texture +{ + ImTextureID texture; + ImVec2 size; +}; + +Texture LoadTexture(const char * path) +{ + const int channelCount = 4; + int imageFileChannelCount; + int width, height; + uint8_t *image = (uint8_t *)stbi_load(path, &width, &height, &imageFileChannelCount, channelCount); + if (image == NULL) + { + fprintf(stderr, "%s\nFailed to open %s\n", stbi_failure_reason(), path); + + return {nullptr,{0,0}}; + } + + GLenum dataFormat = GL_RGBA; + GLuint textureHandle; + glGenTextures(1, &textureHandle); + glBindTexture(GL_TEXTURE_2D, textureHandle); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, dataFormat, GL_UNSIGNED_BYTE, image); + + Texture t; + t.texture = (void*)(uintptr_t)(textureHandle); + t.size = ImVec2((float)width,(float)height); + + stbi_image_free(image); + return t; +} + + static bool init = true; // Main code @@ -110,6 +149,10 @@ int main(int, char **) { // Platform Windows // io.ConfigViewportsNoAutoMerge = true; // io.ConfigViewportsNoTaskBarIcon = true; + // + ImGuiTexInspect::ImplOpenGL3_Init(); // Or DirectX 11 equivalent (check your chosen backend header file) + ImGuiTexInspect::Init(); + ImGuiTexInspect::CreateContext(); // Setup Dear ImGui style ImGui::StyleColorsDark(); @@ -129,6 +172,8 @@ int main(int, char **) { ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + Texture t; + // Main loop bool done = false; while (!done) @@ -185,6 +230,7 @@ int main(int, char **) { ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); ImGui::DockBuilderDockWindow("Editor", dockspace_id); ImGui::DockBuilderFinish(dockspace_id); + t = LoadTexture("/home/dubey/Pictures/DSC03199.JPG"); } if (ImGui::BeginMainMenuBar()) {