initial commit

This commit is contained in:
Tanishq Dubey 2025-04-07 20:08:16 -04:00
parent a8155a2d85
commit 79047a133f
3 changed files with 147 additions and 234 deletions

View File

@ -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

341
main.cpp
View File

@ -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<float> 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<unsigned int> 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<unsigned int> 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<char> 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<char> 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<size_t>(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<unsigned int> 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<int>(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");
}

View File

@ -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);