From a8155a2d85a5a5f5b9a6a005cbdf6031a1eaaae4 Mon Sep 17 00:00:00 2001 From: Tanishq Dubey Date: Tue, 8 Apr 2025 18:00:07 -0400 Subject: [PATCH] test new histogram --- imgui.ini | 20 +++ main.cpp | 341 ++++++++++++++++++++++---------------- shaders/histogram.comp | 20 ++- shaders/histogram_tf.geom | 47 ++++++ shaders/histogram_tf.vert | 25 +++ 5 files changed, 306 insertions(+), 147 deletions(-) create mode 100644 shaders/histogram_tf.geom create mode 100644 shaders/histogram_tf.vert diff --git a/imgui.ini b/imgui.ini index c226ff0..20bdaf9 100644 --- a/imgui.ini +++ b/imgui.ini @@ -261,6 +261,26 @@ Pos=290,135 Size=700,450 Collapsed=0 +[Window][Open Image File##filebrowser_107578099843392] +Pos=290,135 +Size=700,450 +Collapsed=0 + +[Window][Open Image File##filebrowser_107014633521472] +Pos=290,135 +Size=700,450 +Collapsed=0 + +[Window][Open Image File##filebrowser_94338082222400] +Pos=290,135 +Size=700,450 +Collapsed=0 + +[Window][Open Image File##filebrowser_101441870037440] +Pos=290,135 +Size=700,450 +Collapsed=0 + [Docking][Data] DockSpace ID=0xE098E157 Window=0x9E772337 Pos=0,19 Size=3840,2072 Split=X DockNode ID=0x00000001 Parent=0xE098E157 SizeRef=1641,720 Split=X diff --git a/main.cpp b/main.cpp index 42d9de1..733c055 100644 --- a/main.cpp +++ b/main.cpp @@ -640,13 +640,18 @@ static ImVec4 g_cropRectNormInitial = g_cropRectNorm; // Store initial static float g_cropAspectRatio = 0.0f; // 0.0f = Freeform, > 0.0f = constrained (Width / Height) static int g_selectedAspectRatioIndex = 0; // Index for the dropdown -static GLuint g_histogramComputeShader = 0; -static GLuint g_histogramSSBO = 0; -const int NUM_HISTOGRAM_BINS = 256; -const int HISTOGRAM_BUFFER_SIZE = NUM_HISTOGRAM_BINS * 3; // R, G, B -static std::vector g_histogramDataCPU(HISTOGRAM_BUFFER_SIZE, 0); -static unsigned int g_histogramMaxCount = 255; // Max count found, for scaling (init to 1 to avoid div by zero) -static bool g_histogramResourcesInitialized = false; +static GLuint g_histogramTFShader = 0; // Program with VS+GS +static GLuint g_histogramTFBuffer = 0; // Buffer to store bin indices from TF +static GLuint g_histogramTFQuery = 0; // Query object to count primitives written +static GLuint g_histogramTFVAO = 0; // Dummy VAO for drawing points +static size_t g_histogramTFBufferSize = 0; // Size in bytes +static std::vector g_histogramTFDataCPU; // CPU buffer for readback (float) +// Keep the final histogram count data +const int NUM_HISTOGRAM_BINS_TF = 256; // Use separate const if needed +const int HISTOGRAM_BUFFER_SIZE_TF = NUM_HISTOGRAM_BINS_TF * 3; +static std::vector g_histogramCountsCPU(HISTOGRAM_BUFFER_SIZE_TF, 0); +static unsigned int g_histogramMaxCountTF = 1; +static bool g_histogramTFResourcesInitialized = false; // Interaction state enum class CropHandle @@ -667,75 +672,91 @@ static bool g_isDraggingCrop = false; static ImVec2 g_dragStartMousePos = ImVec2(0, 0); // Screen coords -bool InitHistogramResources(const std::string& shaderBasePath) { - printf("Initializing Histogram Resources...\n"); - // Load Compute Shader - // We need a way to load compute shaders, modify shader_utils or add here - std::string compSource = ReadFile(shaderBasePath + "histogram.comp"); // Assuming ReadFile exists - if (compSource.empty()) { - fprintf(stderr, "ERROR: Failed to read histogram.comp\n"); - return false; - } - // Simple Compute Shader Compilation/Linking (add error checking!) - GLuint computeShaderObj = glCreateShader(GL_COMPUTE_SHADER); - const char* src = compSource.c_str(); - glShaderSource(computeShaderObj, 1, &src, nullptr); - glCompileShader(computeShaderObj); - // --- Add GLint success; glGetShaderiv; glGetShaderInfoLog checks --- - GLint success; - glGetShaderiv(computeShaderObj, GL_COMPILE_STATUS, &success); - if (!success) { - GLint logLength; - glGetShaderiv(computeShaderObj, GL_INFO_LOG_LENGTH, &logLength); - std::vector log(logLength); - glGetShaderInfoLog(computeShaderObj, logLength, nullptr, log.data()); - fprintf(stderr, "ERROR::SHADER::HISTOGRAM::COMPILATION_FAILED\n%s\n", log.data()); - glDeleteShader(computeShaderObj); +bool InitHistogramTFResources(const std::string& shaderBasePath) { + printf("Initializing Histogram Transform Feedback Resources...\n"); + g_histogramTFResourcesInitialized = false; // Assume failure until success + + // 1. Load Vertex and Geometry Shaders + std::string vsSource = ReadFile(shaderBasePath + "histogram_tf.vert"); + std::string gsSource = ReadFile(shaderBasePath + "histogram_tf.geom"); + if (vsSource.empty() || gsSource.empty()) { + fprintf(stderr, "ERROR: Failed to read histogram_tf shaders.\n"); return false; } - - g_histogramComputeShader = glCreateProgram(); - glAttachShader(g_histogramComputeShader, computeShaderObj); - glLinkProgram(g_histogramComputeShader); - // --- Add GLint success; glGetProgramiv; glGetProgramInfoLog checks --- - glGetProgramiv(g_histogramComputeShader, GL_LINK_STATUS, &success); - if (!success) { - GLint logLength; - glGetProgramiv(g_histogramComputeShader, GL_INFO_LOG_LENGTH, &logLength); - std::vector log(logLength); - glGetProgramInfoLog(g_histogramComputeShader, logLength, nullptr, log.data()); - fprintf(stderr, "ERROR::PROGRAM::HISTOGRAM::LINKING_FAILED\n%s\n", log.data()); - glDeleteProgram(g_histogramComputeShader); - g_histogramComputeShader = 0; - glDeleteShader(computeShaderObj); // Delete shader obj even on link failure + GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource); // Assuming CompileShader exists + GLuint gs = CompileShader(GL_GEOMETRY_SHADER, gsSource); + if (!vs || !gs) { + if(vs) glDeleteShader(vs); + if(gs) glDeleteShader(gs); return false; } + // 2. Create and Link Shader Program + g_histogramTFShader = glCreateProgram(); + glAttachShader(g_histogramTFShader, vs); + glAttachShader(g_histogramTFShader, gs); - glDeleteShader(computeShaderObj); // Delete shader object after linking - printf("Histogram compute shader loaded and linked successfully (Program ID: %u).\n", g_histogramComputeShader); + // 3. Specify Transform Feedback Varying *** BEFORE LINKING *** + const char* varyings[] = { "tf_BinIndex" }; // Must match 'out' variable in GS + glTransformFeedbackVaryings(g_histogramTFShader, 1, varyings, GL_INTERLEAVED_ATTRIBS); // Or GL_SEPARATE_ATTRIBS if needed + glLinkProgram(g_histogramTFShader); - // Create Shader Storage Buffer Object (SSBO) - glGenBuffers(1, &g_histogramSSBO); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, g_histogramSSBO); - // Allocate buffer size: 3 channels * 256 bins * size of uint - glBufferData(GL_SHADER_STORAGE_BUFFER, HISTOGRAM_BUFFER_SIZE * sizeof(unsigned int), NULL, GL_DYNAMIC_READ); // Data will be written by GPU, read by CPU - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Unbind - - GLenum err = glGetError(); - if (err != GL_NO_ERROR || g_histogramSSBO == 0) { - fprintf(stderr, "ERROR: Failed to create histogram SSBO. OpenGL Error: %u\n", err); - if (g_histogramComputeShader) glDeleteProgram(g_histogramComputeShader); - g_histogramComputeShader = 0; + // --- Add linking error checks (glGetProgramiv, glGetProgramInfoLog) --- + GLint linkSuccess; + glGetProgramiv(g_histogramTFShader, GL_LINK_STATUS, &linkSuccess); + if (!linkSuccess) { + // ... get and print link error log ... + fprintf(stderr, "ERROR: Failed to link histogram_tf shader program.\n"); + glDeleteProgram(g_histogramTFShader); g_histogramTFShader = 0; + glDeleteShader(vs); glDeleteShader(gs); return false; - } else { - printf("Histogram SSBO created successfully (Buffer ID: %u, Size: %d bytes).\n", g_histogramSSBO, HISTOGRAM_BUFFER_SIZE * sizeof(unsigned int)); } + printf("Histogram TF shader linked successfully (Program ID: %u).\n", g_histogramTFShader); + + // Detach and delete shaders after linking + glDetachShader(g_histogramTFShader, vs); + glDetachShader(g_histogramTFShader, gs); + glDeleteShader(vs); + glDeleteShader(gs); - g_histogramResourcesInitialized = true; + // 4. Create Transform Feedback Buffer + // Size needs to accommodate width * height * 3 floats (one index per channel per pixel) + // We allocate dynamically later when image size is known, or create large enough buffer here. + // Let's just create the ID now. Buffer allocation will happen in Compute function. + glGenBuffers(1, &g_histogramTFBuffer); + if(g_histogramTFBuffer == 0) { + fprintf(stderr, "ERROR: Failed to generate histogram TF buffer.\n"); + glDeleteProgram(g_histogramTFShader); g_histogramTFShader = 0; + return false; + } + printf("Histogram TF buffer generated (ID: %u).\n", g_histogramTFBuffer); + + + // 5. Create Query Object for primitives written (optional but good) + glGenQueries(1, &g_histogramTFQuery); + if(g_histogramTFQuery == 0) { + fprintf(stderr, "ERROR: Failed to generate histogram TF query.\n"); + glDeleteBuffers(1, &g_histogramTFBuffer); g_histogramTFBuffer = 0; + glDeleteProgram(g_histogramTFShader); g_histogramTFShader = 0; + return false; + } + printf("Histogram TF query generated (ID: %u).\n", g_histogramTFQuery); + + // 6. Create Dummy VAO (needed to initiate drawing for TF) + glGenVertexArrays(1, &g_histogramTFVAO); + if(g_histogramTFVAO == 0) { + fprintf(stderr, "ERROR: Failed to generate histogram TF VAO.\n"); + glDeleteQueries(1, &g_histogramTFQuery); g_histogramTFQuery = 0; + glDeleteBuffers(1, &g_histogramTFBuffer); g_histogramTFBuffer = 0; + glDeleteProgram(g_histogramTFShader); g_histogramTFShader = 0; + return false; + } + printf("Histogram TF VAO generated (ID: %u).\n", g_histogramTFVAO); + + g_histogramTFResourcesInitialized = true; return true; } @@ -1074,96 +1095,127 @@ void InitShaderOperations(const std::string &shaderBasePath) // Add this function somewhere accessible, e.g., before main() -void ComputeHistogramGPU(GLuint inputTextureID, int width, int height) { - if (!g_histogramResourcesInitialized || inputTextureID == 0 || width <= 0 || height <= 0) { - // Clear CPU data if not computed - std::fill(g_histogramDataCPU.begin(), g_histogramDataCPU.end(), 0); - g_histogramMaxCount = 1; - printf("Histogram resources not initialized or invalid input. Skipping computation.\n"); +void ComputeHistogramTF(GLuint linearTextureID, int width, int height) { + if (!g_histogramTFResourcesInitialized || linearTextureID == 0 || width <= 0 || height <= 0) { + std::fill(g_histogramCountsCPU.begin(), g_histogramCountsCPU.end(), 0); + g_histogramMaxCountTF = 1; return; } - // 1. Clear the SSBO buffer data to zeros - glBindBuffer(GL_SHADER_STORAGE_BUFFER, g_histogramSSBO); - // Using glBufferSubData might be marginally faster than glClearBufferData if driver optimizes zeroing - // static std::vector zeros(HISTOGRAM_BUFFER_SIZE, 0); // Create once - // glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, HISTOGRAM_BUFFER_SIZE * sizeof(unsigned int), zeros.data()); - // Or use glClearBufferData (often recommended) - GLuint zero = 0; - glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, &zero); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Unbind + size_t requiredFloats = static_cast(width) * height * 3; + size_t requiredBytes = requiredFloats * sizeof(float); + + // 1. Ensure TF Buffer is large enough + if (requiredBytes > g_histogramTFBufferSize) { + printf("Resizing Histogram TF buffer to %zu bytes\n", requiredBytes); + glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, g_histogramTFBuffer); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, requiredBytes, NULL, GL_DYNAMIC_READ); // Resize/allocate + glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0); + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + fprintf(stderr, "ERROR: Failed to resize histogram TF buffer. GL Error: %u\n", err); + return; // Cannot proceed + } + g_histogramTFBufferSize = requiredBytes; + g_histogramTFDataCPU.resize(requiredFloats); // Resize CPU buffer too + } else { + // Ensure CPU buffer is correct size even if GPU buffer wasn't resized + if (g_histogramTFDataCPU.size() != requiredFloats) { + g_histogramTFDataCPU.resize(requiredFloats); + } + } - // 2. Bind resources and dispatch compute shader - glUseProgram(g_histogramComputeShader); + // 2. Setup Transform Feedback State + glUseProgram(g_histogramTFShader); - // Bind input texture as image unit 0 (read-only) - // IMPORTANT: Ensure the format matches the compute shader layout qualifier (e.g., rgba8) - // If textureToDisplay is RGBA16F, you'd use layout(rgba16f) in shader - glBindImageTexture(0, inputTextureID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); // Assuming display texture is RGBA8 + // Bind the input texture (Linear Float format) + glActiveTexture(GL_TEXTURE0); // Use texture unit 0 + glBindTexture(GL_TEXTURE_2D, linearTextureID); + glUniform1i(glGetUniformLocation(g_histogramTFShader, "InputTexture"), 0); // Tell shader sampler is on unit 0 - // Bind SSBO to binding point 1 - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, g_histogramSSBO); + // Disable rasterization - we only care about the TF output + glEnable(GL_RASTERIZER_DISCARD); - // Calculate number of work groups - GLuint workGroupSizeX = 16; // Must match layout in shader - GLuint workGroupSizeY = 16; - GLuint numGroupsX = (width + workGroupSizeX - 1) / workGroupSizeX; - GLuint numGroupsY = (height + workGroupSizeY - 1) / workGroupSizeY; + // Bind the buffer for transform feedback output + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, g_histogramTFBuffer); // Binding index 0 - // Dispatch the compute shader - glDispatchCompute(numGroupsX, numGroupsY, 1); + // 3. Begin Transform Feedback and Query + glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, g_histogramTFQuery); + glBeginTransformFeedback(GL_POINTS); // Outputting points from GS - // 3. Synchronization: Ensure compute shader writes finish before CPU reads buffer - // Use a memory barrier on the SSBO writes - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + // 4. Draw Points (one per pixel) - using dummy VAO + glBindVertexArray(g_histogramTFVAO); + glDrawArrays(GL_POINTS, 0, width * height); // Draw one point for each pixel + glBindVertexArray(0); - // Unbind resources (optional here, but good practice) - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); - glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); + // 5. End Transform Feedback and Query + glEndTransformFeedback(); + glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); + + // Unbind TF buffer + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); + + // Re-enable rasterization + glDisable(GL_RASTERIZER_DISCARD); + + // Unbind texture and program + glBindTexture(GL_TEXTURE_2D, 0); glUseProgram(0); - // 4. Read histogram data back from SSBO to CPU vector - glBindBuffer(GL_SHADER_STORAGE_BUFFER, g_histogramSSBO); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, HISTOGRAM_BUFFER_SIZE * sizeof(unsigned int), g_histogramDataCPU.data()); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Unbind + // Optional: Get number of primitives written from query + // GLuint primitivesWritten = 0; + // glGetQueryObjectuiv(g_histogramTFQuery, GL_QUERY_RESULT, &primitivesWritten); + // printf("TF Primitives Written: %u (Expected: %d)\n", primitivesWritten, width * height * 3); - // 5. Find the maximum count for scaling the plot (optional, can be capped) - g_histogramMaxCount = 255; // Reset to 255 (prevents div by zero) - for (unsigned int count : g_histogramDataCPU) { - if (count > g_histogramMaxCount) { - g_histogramMaxCount = count; + // 6. Read back the Transform Feedback buffer + // Make sure TF operations are finished. glFlush/glFinish might be needed + // depending on driver, but often implicitly synced by readback. Add if needed. + // glFlush(); + glBindBuffer(GL_ARRAY_BUFFER, g_histogramTFBuffer); // Bind to generic target for readback + glGetBufferSubData(GL_ARRAY_BUFFER, 0, requiredBytes, g_histogramTFDataCPU.data()); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + GLenum readErr = glGetError(); + if (readErr != GL_NO_ERROR) { + fprintf(stderr, "OpenGL Error during histogram TF readback: %u\n", readErr); + std::fill(g_histogramCountsCPU.begin(), g_histogramCountsCPU.end(), 0); + g_histogramMaxCountTF = 1; + return; + } + + // 7. Process the readback data CPU-side to build histogram counts + std::fill(g_histogramCountsCPU.begin(), g_histogramCountsCPU.end(), 0); // Clear counts + g_histogramMaxCountTF = 1; + + for (float binIndexF : g_histogramTFDataCPU) { + // Convert float index back to uint, handle potential slight inaccuracies + int binIndex = static_cast(round(binIndexF)); + + // Check bounds (0 to 767) + if (binIndex >= 0 && binIndex < HISTOGRAM_BUFFER_SIZE_TF) { + g_histogramCountsCPU[binIndex]++; + if (g_histogramCountsCPU[binIndex] > g_histogramMaxCountTF) { + g_histogramMaxCountTF = g_histogramCountsCPU[binIndex]; + } } + // else { printf("Warning: Out of bounds TF bin index: %d\n", binIndex); } // Debugging } - // Optional: Cap max count to prevent extreme peaks from flattening the rest - // unsigned int capThreshold = (width * height) / 50; // e.g., cap at 2% of pixels - // g_histogramMaxCount = std::min(g_histogramMaxCount, capThreshold); - // if (g_histogramMaxCount == 0) g_histogramMaxCount = 1; // Ensure not zero after capping + // if (g_histogramMaxCountTF == 1) { + // printf("Warning: Max histogram count is still 1 after TF processing.\n"); + // } - - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - fprintf(stderr, "OpenGL Error during histogram computation/readback: %u\n", err); - // Optionally clear CPU data on error - std::fill(g_histogramDataCPU.begin(), g_histogramDataCPU.end(), 0); - g_histogramMaxCount = 1; - printf("Histogram computation failed. Data cleared.\n"); - } - else { - printf("Histogram computed. Max count: %u\n", g_histogramMaxCount); - } } - // Add this function somewhere accessible, e.g., before main() void DrawHistogramWidget(const char* widgetId, ImVec2 graphSize) { - if (g_histogramDataCPU.empty() || g_histogramMaxCount <= 1) { // Check if data is valid - if (g_histogramDataCPU.empty()) { + if (g_histogramCountsCPU.empty() || g_histogramMaxCountTF <= 1) { // Check if data is valid + if (g_histogramCountsCPU.empty()) { ImGui::Text("Histogram data not initialized."); } else { ImGui::Text("Histogram data is empty or invalid."); } - if (g_histogramMaxCount <= 1) { + if (g_histogramMaxCountTF <= 1) { ImGui::Text("Histogram max count is invalid."); } ImGui::Text("Histogram data not available."); @@ -1183,8 +1235,8 @@ void DrawHistogramWidget(const char* widgetId, ImVec2 graphSize) { drawList->AddRectFilled(widgetPos, widgetPos + graphSize, IM_COL32(30, 30, 30, 200)); // Calculate scaling factors - float barWidth = graphSize.x / float(NUM_HISTOGRAM_BINS); - float scaleY = graphSize.y / float(g_histogramMaxCount); // Scale based on max count + float barWidth = graphSize.x / float(NUM_HISTOGRAM_BINS_TF); + float scaleY = graphSize.y / float(g_histogramMaxCountTF); // Define colors (with some transparency for overlap visibility) const ImU32 colR = IM_COL32(255, 0, 0, 180); @@ -1192,11 +1244,11 @@ void DrawHistogramWidget(const char* widgetId, ImVec2 graphSize) { const ImU32 colB = IM_COL32(0, 0, 255, 180); // Draw the histogram bars (R, G, B) - for (int i = 0; i < NUM_HISTOGRAM_BINS; ++i) { - // Get heights (clamped to graph size) - float hR = ImMin(float(g_histogramDataCPU[i]) * scaleY, graphSize.y); - float hG = ImMin(float(g_histogramDataCPU[i + NUM_HISTOGRAM_BINS]) * scaleY, graphSize.y); - float hB = ImMin(float(g_histogramDataCPU[i + NUM_HISTOGRAM_BINS * 2]) * scaleY, graphSize.y); + for (int i = 0; i < NUM_HISTOGRAM_BINS_TF; ++i) { + // Get heights from g_histogramCountsCPU + float hR = ImMin(float(g_histogramCountsCPU[i]) * scaleY, graphSize.y); + float hG = ImMin(float(g_histogramCountsCPU[i + NUM_HISTOGRAM_BINS_TF]) * scaleY, graphSize.y); + float hB = ImMin(float(g_histogramCountsCPU[i + NUM_HISTOGRAM_BINS_TF * 2]) * scaleY, graphSize.y); // Calculate bar positions float x0 = widgetPos.x + float(i) * barWidth; @@ -1358,7 +1410,7 @@ int main(int, char **) InitShaderOperations("shaders/"); // Initialize shader operations - if (!InitHistogramResources("shaders/")) { + if (!InitHistogramTFResources("shaders/")) { // Handle error - maybe disable histogram feature fprintf(stderr, "Histogram initialization failed, feature disabled.\n"); } @@ -1428,6 +1480,13 @@ int main(int, char **) textureToSave = 0; } + if (g_imageIsLoaded && textureToSave != 0) { + ComputeHistogramTF(textureToSave, g_loadedImage.getWidth(), g_loadedImage.getHeight()); // <<< Call TF version + } else { + std::fill(g_histogramCountsCPU.begin(), g_histogramCountsCPU.end(), 0); + g_histogramMaxCountTF = 1; + } + // --- Menu Bar --- if (ImGui::BeginMainMenuBar()) { @@ -1740,7 +1799,6 @@ int main(int, char **) GLuint displayTexId = textureToDisplay; // Use the display texture ID if (displayTexId != 0) { - ComputeHistogramGPU(displayTexId, g_loadedImage.getWidth(), g_loadedImage.getHeight()); // Assume ImGuiTexInspect fills available space. This might need adjustment. ImVec2 displaySize = availableContentSize; float displayAspect = displaySize.x / displaySize.y; @@ -1932,8 +1990,6 @@ int main(int, char **) ImVec2 textSize = ImGui::CalcTextSize("No Image Loaded"); ImGui::SetCursorPos(ImVec2((winSize.x - textSize.x) * 0.5f, (winSize.y - textSize.y) * 0.5f)); ImGui::Text("No Image Loaded. File -> Open... to load an image"); - std::fill(g_histogramDataCPU.begin(), g_histogramDataCPU.end(), 0); - g_histogramMaxCount = 1; // Or maybe: "File -> Open... to load an image" } ImGui::End(); // End Image View @@ -1942,7 +1998,6 @@ int main(int, char **) ImGui::Begin("Image Exif"); if (g_imageIsLoaded) { - ComputeHistogramGPU(textureToDisplay, g_loadedImage.getWidth(), g_loadedImage.getHeight()); ImGui::Text("Image Width: %d", g_loadedImage.m_width); ImGui::Text("Image Height: %d", g_loadedImage.m_height); ImGui::Text("Image Loaded: %s", g_imageIsLoaded ? "Yes" : "No"); @@ -2260,9 +2315,9 @@ int main(int, char **) g_loadedImage.m_textureId = 0; } - if (g_histogramResourcesInitialized) { - if (g_histogramSSBO) glDeleteBuffers(1, &g_histogramSSBO); - if (g_histogramComputeShader) glDeleteProgram(g_histogramComputeShader); + if (g_histogramTFResourcesInitialized) { + if (g_histogramTFVAO) glDeleteBuffers(1, &g_histogramTFVAO); + if (g_histogramTFShader) glDeleteProgram(g_histogramTFShader); printf("Cleaned up histogram resources.\n"); } diff --git a/shaders/histogram.comp b/shaders/histogram.comp index 01eeb6c..631e396 100644 --- a/shaders/histogram.comp +++ b/shaders/histogram.comp @@ -15,6 +15,19 @@ layout(std430, binding = 1) buffer HistogramBuffer { // Workgroup size (adjust based on GPU architecture for performance, 16x16 is often reasonable) layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +// Helper to convert linear float (potentially HDR 0.0+) to 0-255 bin index +// Applies an approximate gamma curve for perceptual binning. +uint linearToVisualBin(float linearVal) { + // Clamp linear value to 0.0-1.0 range before gamma/binning + // This histograms the displayable range after processing. + float clampedVal = clamp(linearVal, 0.0, 1.0); + // Apply approximate sRGB gamma for perceptual brightness mapping + float displayApprox = pow(clampedVal, 1.0/2.2); + // Convert 0.0-1.0 display value to 0-255 bin index + return uint(displayApprox * 255.999); // Use 255.999 to ensure 1.0 maps to 255 +} + void main() { // Get the global invocation ID (like pixel coordinates) ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy); @@ -30,10 +43,9 @@ void main() { // Calculate bin indices (0-255) // We clamp just in case, although imageLoad from rgba8 should be in range. - uint rBin = uint(clamp(pixelColor.r, 0.0, 1.0) * 255.0); - uint gBin = uint(clamp(pixelColor.g, 0.0, 1.0) * 255.0); - uint bBin = uint(clamp(pixelColor.b, 0.0, 1.0) * 255.0); - + uint rBin = linearToVisualBin(pixelColor.r); + uint gBin = linearToVisualBin(pixelColor.g); + uint bBin = linearToVisualBin(pixelColor.b); // Atomically increment the counters in the SSBO // Offset Green bins by 256, Blue bins by 512 atomicAdd(histogram.bins[rBin], 1u); diff --git a/shaders/histogram_tf.geom b/shaders/histogram_tf.geom new file mode 100644 index 0000000..43a2ef9 --- /dev/null +++ b/shaders/histogram_tf.geom @@ -0,0 +1,47 @@ +#version 330 core +layout (points) in; // Input vertices are points +layout (points, max_vertices = 3) out; // Output 3 points (R, G, B bin index) + +in VS_OUT { + vec3 linearColor; +} gs_in[]; // Input from Vertex Shader (array size 1 for points) + +// Output variable captured by Transform Feedback +// MUST match the name used in glTransformFeedbackVaryings +out float tf_BinIndex; // Using float for simplicity, will cast later + +// Helper to convert linear float to 0-255 bin index (gamma corrected) +uint linearToVisualBin(float linearVal) { + float clampedVal = clamp(linearVal, 0.0, 1.0); + float displayApprox = pow(clampedVal, 1.0/2.2); + return uint(displayApprox * 255.999); +} + +void main() { + // Check if the input vertex is valid (from VS boundary check) + if (gs_in[0].linearColor.r < 0.0) { + return; // Skip invalid pixels + } + + vec3 color = gs_in[0].linearColor; + + // Calculate bins + uint rBin = linearToVisualBin(color.r); + uint gBin = linearToVisualBin(color.g); + uint bBin = linearToVisualBin(color.b); + + // Emit vertex for Red channel bin + tf_BinIndex = float(rBin); // Output bin index (0-255) + EmitVertex(); + EndPrimitive(); // End point primitive + + // Emit vertex for Green channel bin + tf_BinIndex = float(gBin + 256u); // Output bin index (256-511) + EmitVertex(); + EndPrimitive(); + + // Emit vertex for Blue channel bin + tf_BinIndex = float(bBin + 512u); // Output bin index (512-767) + EmitVertex(); + EndPrimitive(); +} \ No newline at end of file diff --git a/shaders/histogram_tf.vert b/shaders/histogram_tf.vert new file mode 100644 index 0000000..161c9aa --- /dev/null +++ b/shaders/histogram_tf.vert @@ -0,0 +1,25 @@ +#version 330 core // Transform Feedback available in 330+ + +layout (location = 0) in vec2 aPos; // Dummy input vertex position (we don't use it) + +uniform sampler2D InputTexture; // Linear Float texture (e.g., RGBA16F) + +// Output to Geometry Shader +out VS_OUT { + vec3 linearColor; +} vs_out; + +void main() { + // We use gl_VertexID to figure out which texel to read + ivec2 textureSize = textureSize(InputTexture, 0); + ivec2 texelCoord = ivec2(gl_VertexID % textureSize.x, gl_VertexID / textureSize.x); + + // Boundary check (important if drawing more vertices than pixels) + if (texelCoord.x >= textureSize.x || texelCoord.y >= textureSize.y) { + vs_out.linearColor = vec3(-1.0); // Indicate invalid pixel + } else { + vs_out.linearColor = texture(InputTexture, vec2(texelCoord) / vec2(textureSize)).rgb; + } + + // We don't output gl_Position as rasterization is disabled +} \ No newline at end of file