test new histogram

This commit is contained in:
Tanishq Dubey 2025-04-08 18:00:07 -04:00
parent 7e7207a87e
commit a8155a2d85
5 changed files with 306 additions and 147 deletions

View File

@ -261,6 +261,26 @@ Pos=290,135
Size=700,450 Size=700,450
Collapsed=0 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] [Docking][Data]
DockSpace ID=0xE098E157 Window=0x9E772337 Pos=0,19 Size=3840,2072 Split=X DockSpace ID=0xE098E157 Window=0x9E772337 Pos=0,19 Size=3840,2072 Split=X
DockNode ID=0x00000001 Parent=0xE098E157 SizeRef=1641,720 Split=X DockNode ID=0x00000001 Parent=0xE098E157 SizeRef=1641,720 Split=X

341
main.cpp
View File

@ -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 float g_cropAspectRatio = 0.0f; // 0.0f = Freeform, > 0.0f = constrained (Width / Height)
static int g_selectedAspectRatioIndex = 0; // Index for the dropdown static int g_selectedAspectRatioIndex = 0; // Index for the dropdown
static GLuint g_histogramComputeShader = 0; static GLuint g_histogramTFShader = 0; // Program with VS+GS
static GLuint g_histogramSSBO = 0; static GLuint g_histogramTFBuffer = 0; // Buffer to store bin indices from TF
const int NUM_HISTOGRAM_BINS = 256; static GLuint g_histogramTFQuery = 0; // Query object to count primitives written
const int HISTOGRAM_BUFFER_SIZE = NUM_HISTOGRAM_BINS * 3; // R, G, B static GLuint g_histogramTFVAO = 0; // Dummy VAO for drawing points
static std::vector<unsigned int> g_histogramDataCPU(HISTOGRAM_BUFFER_SIZE, 0); static size_t g_histogramTFBufferSize = 0; // Size in bytes
static unsigned int g_histogramMaxCount = 255; // Max count found, for scaling (init to 1 to avoid div by zero) static std::vector<float> g_histogramTFDataCPU; // CPU buffer for readback (float)
static bool g_histogramResourcesInitialized = false; // 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;
// Interaction state // Interaction state
enum class CropHandle enum class CropHandle
@ -667,75 +672,91 @@ static bool g_isDraggingCrop = false;
static ImVec2 g_dragStartMousePos = ImVec2(0, 0); // Screen coords static ImVec2 g_dragStartMousePos = ImVec2(0, 0); // Screen coords
bool InitHistogramResources(const std::string& shaderBasePath) { bool InitHistogramTFResources(const std::string& shaderBasePath) {
printf("Initializing Histogram Resources...\n"); printf("Initializing Histogram Transform Feedback Resources...\n");
// Load Compute Shader g_histogramTFResourcesInitialized = false; // Assume failure until success
// We need a way to load compute shaders, modify shader_utils or add here
std::string compSource = ReadFile(shaderBasePath + "histogram.comp"); // Assuming ReadFile exists // 1. Load Vertex and Geometry Shaders
if (compSource.empty()) { std::string vsSource = ReadFile(shaderBasePath + "histogram_tf.vert");
fprintf(stderr, "ERROR: Failed to read histogram.comp\n"); std::string gsSource = ReadFile(shaderBasePath + "histogram_tf.geom");
return false; if (vsSource.empty() || gsSource.empty()) {
} fprintf(stderr, "ERROR: Failed to read histogram_tf shaders.\n");
// 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; return false;
} }
GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource); // Assuming CompileShader exists
g_histogramComputeShader = glCreateProgram(); GLuint gs = CompileShader(GL_GEOMETRY_SHADER, gsSource);
glAttachShader(g_histogramComputeShader, computeShaderObj); if (!vs || !gs) {
glLinkProgram(g_histogramComputeShader); if(vs) glDeleteShader(vs);
// --- Add GLint success; glGetProgramiv; glGetProgramInfoLog checks --- if(gs) glDeleteShader(gs);
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; 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 // 3. Specify Transform Feedback Varying *** BEFORE LINKING ***
printf("Histogram compute shader loaded and linked successfully (Program ID: %u).\n", g_histogramComputeShader); 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) // --- Add linking error checks (glGetProgramiv, glGetProgramInfoLog) ---
glGenBuffers(1, &g_histogramSSBO); GLint linkSuccess;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, g_histogramSSBO); glGetProgramiv(g_histogramTFShader, GL_LINK_STATUS, &linkSuccess);
// Allocate buffer size: 3 channels * 256 bins * size of uint if (!linkSuccess) {
glBufferData(GL_SHADER_STORAGE_BUFFER, HISTOGRAM_BUFFER_SIZE * sizeof(unsigned int), NULL, GL_DYNAMIC_READ); // Data will be written by GPU, read by CPU // ... get and print link error log ...
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Unbind fprintf(stderr, "ERROR: Failed to link histogram_tf shader program.\n");
glDeleteProgram(g_histogramTFShader); g_histogramTFShader = 0;
GLenum err = glGetError(); glDeleteShader(vs); glDeleteShader(gs);
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; 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; return true;
} }
@ -1074,96 +1095,127 @@ void InitShaderOperations(const std::string &shaderBasePath)
// Add this function somewhere accessible, e.g., before main() // Add this function somewhere accessible, e.g., before main()
void ComputeHistogramGPU(GLuint inputTextureID, int width, int height) { void ComputeHistogramTF(GLuint linearTextureID, int width, int height) {
if (!g_histogramResourcesInitialized || inputTextureID == 0 || width <= 0 || height <= 0) { if (!g_histogramTFResourcesInitialized || linearTextureID == 0 || width <= 0 || height <= 0) {
// Clear CPU data if not computed std::fill(g_histogramCountsCPU.begin(), g_histogramCountsCPU.end(), 0);
std::fill(g_histogramDataCPU.begin(), g_histogramDataCPU.end(), 0); g_histogramMaxCountTF = 1;
g_histogramMaxCount = 1;
printf("Histogram resources not initialized or invalid input. Skipping computation.\n");
return; return;
} }
// 1. Clear the SSBO buffer data to zeros size_t requiredFloats = static_cast<size_t>(width) * height * 3;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, g_histogramSSBO); size_t requiredBytes = requiredFloats * sizeof(float);
// Using glBufferSubData might be marginally faster than glClearBufferData if driver optimizes zeroing
// static std::vector<unsigned int> zeros(HISTOGRAM_BUFFER_SIZE, 0); // Create once // 1. Ensure TF Buffer is large enough
// glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, HISTOGRAM_BUFFER_SIZE * sizeof(unsigned int), zeros.data()); if (requiredBytes > g_histogramTFBufferSize) {
// Or use glClearBufferData (often recommended) printf("Resizing Histogram TF buffer to %zu bytes\n", requiredBytes);
GLuint zero = 0; glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, g_histogramTFBuffer);
glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, &zero); glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, requiredBytes, NULL, GL_DYNAMIC_READ); // Resize/allocate
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Unbind 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 // 2. Setup Transform Feedback State
glUseProgram(g_histogramComputeShader); glUseProgram(g_histogramTFShader);
// Bind input texture as image unit 0 (read-only) // Bind the input texture (Linear Float format)
// IMPORTANT: Ensure the format matches the compute shader layout qualifier (e.g., rgba8) glActiveTexture(GL_TEXTURE0); // Use texture unit 0
// If textureToDisplay is RGBA16F, you'd use layout(rgba16f) in shader glBindTexture(GL_TEXTURE_2D, linearTextureID);
glBindImageTexture(0, inputTextureID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); // Assuming display texture is RGBA8 glUniform1i(glGetUniformLocation(g_histogramTFShader, "InputTexture"), 0); // Tell shader sampler is on unit 0
// Bind SSBO to binding point 1 // Disable rasterization - we only care about the TF output
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, g_histogramSSBO); glEnable(GL_RASTERIZER_DISCARD);
// Calculate number of work groups // Bind the buffer for transform feedback output
GLuint workGroupSizeX = 16; // Must match layout in shader glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, g_histogramTFBuffer); // Binding index 0
GLuint workGroupSizeY = 16;
GLuint numGroupsX = (width + workGroupSizeX - 1) / workGroupSizeX;
GLuint numGroupsY = (height + workGroupSizeY - 1) / workGroupSizeY;
// Dispatch the compute shader // 3. Begin Transform Feedback and Query
glDispatchCompute(numGroupsX, numGroupsY, 1); 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 // 4. Draw Points (one per pixel) - using dummy VAO
// Use a memory barrier on the SSBO writes glBindVertexArray(g_histogramTFVAO);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glDrawArrays(GL_POINTS, 0, width * height); // Draw one point for each pixel
glBindVertexArray(0);
// Unbind resources (optional here, but good practice) // 5. End Transform Feedback and Query
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); glEndTransformFeedback();
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); 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); glUseProgram(0);
// 4. Read histogram data back from SSBO to CPU vector // Optional: Get number of primitives written from query
glBindBuffer(GL_SHADER_STORAGE_BUFFER, g_histogramSSBO); // GLuint primitivesWritten = 0;
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, HISTOGRAM_BUFFER_SIZE * sizeof(unsigned int), g_histogramDataCPU.data()); // glGetQueryObjectuiv(g_histogramTFQuery, GL_QUERY_RESULT, &primitivesWritten);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Unbind // 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) // 6. Read back the Transform Feedback buffer
g_histogramMaxCount = 255; // Reset to 255 (prevents div by zero) // Make sure TF operations are finished. glFlush/glFinish might be needed
for (unsigned int count : g_histogramDataCPU) { // depending on driver, but often implicitly synced by readback. Add if needed.
if (count > g_histogramMaxCount) { // glFlush();
g_histogramMaxCount = count; 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];
}
} }
// 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 // if (g_histogramMaxCountTF == 1) {
// unsigned int capThreshold = (width * height) / 50; // e.g., cap at 2% of pixels // printf("Warning: Max histogram count is still 1 after TF processing.\n");
// 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() // Add this function somewhere accessible, e.g., before main()
void DrawHistogramWidget(const char* widgetId, ImVec2 graphSize) { void DrawHistogramWidget(const char* widgetId, ImVec2 graphSize) {
if (g_histogramDataCPU.empty() || g_histogramMaxCount <= 1) { // Check if data is valid if (g_histogramCountsCPU.empty() || g_histogramMaxCountTF <= 1) { // Check if data is valid
if (g_histogramDataCPU.empty()) { if (g_histogramCountsCPU.empty()) {
ImGui::Text("Histogram data not initialized."); ImGui::Text("Histogram data not initialized.");
} else { } else {
ImGui::Text("Histogram data is empty or invalid."); 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 max count is invalid.");
} }
ImGui::Text("Histogram data not available."); 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)); drawList->AddRectFilled(widgetPos, widgetPos + graphSize, IM_COL32(30, 30, 30, 200));
// Calculate scaling factors // Calculate scaling factors
float barWidth = graphSize.x / float(NUM_HISTOGRAM_BINS); float barWidth = graphSize.x / float(NUM_HISTOGRAM_BINS_TF);
float scaleY = graphSize.y / float(g_histogramMaxCount); // Scale based on max count float scaleY = graphSize.y / float(g_histogramMaxCountTF);
// Define colors (with some transparency for overlap visibility) // Define colors (with some transparency for overlap visibility)
const ImU32 colR = IM_COL32(255, 0, 0, 180); 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); const ImU32 colB = IM_COL32(0, 0, 255, 180);
// Draw the histogram bars (R, G, B) // Draw the histogram bars (R, G, B)
for (int i = 0; i < NUM_HISTOGRAM_BINS; ++i) { for (int i = 0; i < NUM_HISTOGRAM_BINS_TF; ++i) {
// Get heights (clamped to graph size) // Get heights from g_histogramCountsCPU
float hR = ImMin(float(g_histogramDataCPU[i]) * scaleY, graphSize.y); float hR = ImMin(float(g_histogramCountsCPU[i]) * scaleY, graphSize.y);
float hG = ImMin(float(g_histogramDataCPU[i + NUM_HISTOGRAM_BINS]) * scaleY, graphSize.y); float hG = ImMin(float(g_histogramCountsCPU[i + NUM_HISTOGRAM_BINS_TF]) * scaleY, graphSize.y);
float hB = ImMin(float(g_histogramDataCPU[i + NUM_HISTOGRAM_BINS * 2]) * scaleY, graphSize.y); float hB = ImMin(float(g_histogramCountsCPU[i + NUM_HISTOGRAM_BINS_TF * 2]) * scaleY, graphSize.y);
// Calculate bar positions // Calculate bar positions
float x0 = widgetPos.x + float(i) * barWidth; float x0 = widgetPos.x + float(i) * barWidth;
@ -1358,7 +1410,7 @@ int main(int, char **)
InitShaderOperations("shaders/"); // Initialize shader operations InitShaderOperations("shaders/"); // Initialize shader operations
if (!InitHistogramResources("shaders/")) { if (!InitHistogramTFResources("shaders/")) {
// Handle error - maybe disable histogram feature // Handle error - maybe disable histogram feature
fprintf(stderr, "Histogram initialization failed, feature disabled.\n"); fprintf(stderr, "Histogram initialization failed, feature disabled.\n");
} }
@ -1428,6 +1480,13 @@ int main(int, char **)
textureToSave = 0; 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 --- // --- Menu Bar ---
if (ImGui::BeginMainMenuBar()) if (ImGui::BeginMainMenuBar())
{ {
@ -1740,7 +1799,6 @@ int main(int, char **)
GLuint displayTexId = textureToDisplay; // Use the display texture ID GLuint displayTexId = textureToDisplay; // Use the display texture ID
if (displayTexId != 0) if (displayTexId != 0)
{ {
ComputeHistogramGPU(displayTexId, g_loadedImage.getWidth(), g_loadedImage.getHeight());
// Assume ImGuiTexInspect fills available space. This might need adjustment. // Assume ImGuiTexInspect fills available space. This might need adjustment.
ImVec2 displaySize = availableContentSize; ImVec2 displaySize = availableContentSize;
float displayAspect = displaySize.x / displaySize.y; float displayAspect = displaySize.x / displaySize.y;
@ -1932,8 +1990,6 @@ int main(int, char **)
ImVec2 textSize = ImGui::CalcTextSize("No Image Loaded"); ImVec2 textSize = ImGui::CalcTextSize("No Image Loaded");
ImGui::SetCursorPos(ImVec2((winSize.x - textSize.x) * 0.5f, (winSize.y - textSize.y) * 0.5f)); 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"); 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" // Or maybe: "File -> Open... to load an image"
} }
ImGui::End(); // End Image View ImGui::End(); // End Image View
@ -1942,7 +1998,6 @@ int main(int, char **)
ImGui::Begin("Image Exif"); ImGui::Begin("Image Exif");
if (g_imageIsLoaded) if (g_imageIsLoaded)
{ {
ComputeHistogramGPU(textureToDisplay, g_loadedImage.getWidth(), g_loadedImage.getHeight());
ImGui::Text("Image Width: %d", g_loadedImage.m_width); ImGui::Text("Image Width: %d", g_loadedImage.m_width);
ImGui::Text("Image Height: %d", g_loadedImage.m_height); ImGui::Text("Image Height: %d", g_loadedImage.m_height);
ImGui::Text("Image Loaded: %s", g_imageIsLoaded ? "Yes" : "No"); ImGui::Text("Image Loaded: %s", g_imageIsLoaded ? "Yes" : "No");
@ -2260,9 +2315,9 @@ int main(int, char **)
g_loadedImage.m_textureId = 0; g_loadedImage.m_textureId = 0;
} }
if (g_histogramResourcesInitialized) { if (g_histogramTFResourcesInitialized) {
if (g_histogramSSBO) glDeleteBuffers(1, &g_histogramSSBO); if (g_histogramTFVAO) glDeleteBuffers(1, &g_histogramTFVAO);
if (g_histogramComputeShader) glDeleteProgram(g_histogramComputeShader); if (g_histogramTFShader) glDeleteProgram(g_histogramTFShader);
printf("Cleaned up histogram resources.\n"); printf("Cleaned up histogram resources.\n");
} }

View File

@ -15,6 +15,19 @@ layout(std430, binding = 1) buffer HistogramBuffer {
// Workgroup size (adjust based on GPU architecture for performance, 16x16 is often reasonable) // 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; 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() { void main() {
// Get the global invocation ID (like pixel coordinates) // Get the global invocation ID (like pixel coordinates)
ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy); ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy);
@ -30,10 +43,9 @@ void main() {
// Calculate bin indices (0-255) // Calculate bin indices (0-255)
// We clamp just in case, although imageLoad from rgba8 should be in range. // 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 rBin = linearToVisualBin(pixelColor.r);
uint gBin = uint(clamp(pixelColor.g, 0.0, 1.0) * 255.0); uint gBin = linearToVisualBin(pixelColor.g);
uint bBin = uint(clamp(pixelColor.b, 0.0, 1.0) * 255.0); uint bBin = linearToVisualBin(pixelColor.b);
// Atomically increment the counters in the SSBO // Atomically increment the counters in the SSBO
// Offset Green bins by 256, Blue bins by 512 // Offset Green bins by 256, Blue bins by 512
atomicAdd(histogram.bins[rBin], 1u); atomicAdd(histogram.bins[rBin], 1u);

47
shaders/histogram_tf.geom Normal file
View File

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

25
shaders/histogram_tf.vert Normal file
View File

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