diff --git a/imgui.ini b/imgui.ini index 20bdaf9..c226ff0 100644 --- a/imgui.ini +++ b/imgui.ini @@ -261,26 +261,6 @@ 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 733c055..42d9de1 100644 --- a/main.cpp +++ b/main.cpp @@ -640,18 +640,13 @@ 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_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; +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; // Interaction state enum class CropHandle @@ -672,91 +667,75 @@ static bool g_isDraggingCrop = false; static ImVec2 g_dragStartMousePos = ImVec2(0, 0); // Screen coords -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"); +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); return false; } - 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); + + 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 return false; } - // 2. Create and Link Shader Program - g_histogramTFShader = glCreateProgram(); - glAttachShader(g_histogramTFShader, vs); - glAttachShader(g_histogramTFShader, gs); - // 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 + glDeleteShader(computeShaderObj); // Delete shader object after linking + printf("Histogram compute shader loaded and linked successfully (Program ID: %u).\n", g_histogramComputeShader); - glLinkProgram(g_histogramTFShader); - // --- 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); + // 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; 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); - // 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; + g_histogramResourcesInitialized = true; return true; } @@ -1095,127 +1074,96 @@ void InitShaderOperations(const std::string &shaderBasePath) // Add this function somewhere accessible, e.g., before main() -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; +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"); return; } - 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); - } - } + // 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 - // 2. Setup Transform Feedback State - glUseProgram(g_histogramTFShader); + // 2. Bind resources and dispatch compute shader + glUseProgram(g_histogramComputeShader); - // 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 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 - // Disable rasterization - we only care about the TF output - glEnable(GL_RASTERIZER_DISCARD); + // Bind SSBO to binding point 1 + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, g_histogramSSBO); - // Bind the buffer for transform feedback output - glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, g_histogramTFBuffer); // Binding index 0 + // 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; - // 3. Begin Transform Feedback and Query - glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, g_histogramTFQuery); - glBeginTransformFeedback(GL_POINTS); // Outputting points from GS + // Dispatch the compute shader + glDispatchCompute(numGroupsX, numGroupsY, 1); - // 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); + // 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); - // 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); + // 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); glUseProgram(0); - // 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); + // 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 - // 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]; - } + // 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; } - // else { printf("Warning: Out of bounds TF bin index: %d\n", binIndex); } // Debugging } - // if (g_histogramMaxCountTF == 1) { - // printf("Warning: Max histogram count is still 1 after TF processing.\n"); - // } + // 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 + + 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_histogramCountsCPU.empty() || g_histogramMaxCountTF <= 1) { // Check if data is valid - if (g_histogramCountsCPU.empty()) { + if (g_histogramDataCPU.empty() || g_histogramMaxCount <= 1) { // Check if data is valid + if (g_histogramDataCPU.empty()) { ImGui::Text("Histogram data not initialized."); } else { ImGui::Text("Histogram data is empty or invalid."); } - if (g_histogramMaxCountTF <= 1) { + if (g_histogramMaxCount <= 1) { ImGui::Text("Histogram max count is invalid."); } ImGui::Text("Histogram data not available."); @@ -1235,8 +1183,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_TF); - float scaleY = graphSize.y / float(g_histogramMaxCountTF); + float barWidth = graphSize.x / float(NUM_HISTOGRAM_BINS); + float scaleY = graphSize.y / float(g_histogramMaxCount); // Scale based on max count // Define colors (with some transparency for overlap visibility) const ImU32 colR = IM_COL32(255, 0, 0, 180); @@ -1244,11 +1192,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_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); + 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); // Calculate bar positions float x0 = widgetPos.x + float(i) * barWidth; @@ -1410,7 +1358,7 @@ int main(int, char **) InitShaderOperations("shaders/"); // Initialize shader operations - if (!InitHistogramTFResources("shaders/")) { + if (!InitHistogramResources("shaders/")) { // Handle error - maybe disable histogram feature fprintf(stderr, "Histogram initialization failed, feature disabled.\n"); } @@ -1480,13 +1428,6 @@ 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()) { @@ -1799,6 +1740,7 @@ 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; @@ -1990,6 +1932,8 @@ 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 @@ -1998,6 +1942,7 @@ 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"); @@ -2315,9 +2260,9 @@ int main(int, char **) g_loadedImage.m_textureId = 0; } - if (g_histogramTFResourcesInitialized) { - if (g_histogramTFVAO) glDeleteBuffers(1, &g_histogramTFVAO); - if (g_histogramTFShader) glDeleteProgram(g_histogramTFShader); + if (g_histogramResourcesInitialized) { + if (g_histogramSSBO) glDeleteBuffers(1, &g_histogramSSBO); + if (g_histogramComputeShader) glDeleteProgram(g_histogramComputeShader); printf("Cleaned up histogram resources.\n"); } diff --git a/shaders/histogram.comp b/shaders/histogram.comp index 631e396..01eeb6c 100644 --- a/shaders/histogram.comp +++ b/shaders/histogram.comp @@ -15,19 +15,6 @@ 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); @@ -43,9 +30,10 @@ void main() { // Calculate bin indices (0-255) // We clamp just in case, although imageLoad from rgba8 should be in range. - uint rBin = linearToVisualBin(pixelColor.r); - uint gBin = linearToVisualBin(pixelColor.g); - uint bBin = linearToVisualBin(pixelColor.b); + 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); + // Atomically increment the counters in the SSBO // Offset Green bins by 256, Blue bins by 512 atomicAdd(histogram.bins[rBin], 1u);