#version 430 core // Need SSBOs and atomic counters // Input Texture (the processed image ready for display) // Binding = 0 matches glBindImageTexture unit // Use rgba8 format as we assume display texture is 8-bit sRGB (adjust if needed) layout(binding = 0, rgba8) uniform readonly image2D InputTexture; // Output Histogram Buffer (SSBO) // Binding = 1 matches glBindBufferBase index // Contains 256 bins for R, 256 for G, 256 for B sequentially (total 768 uints) layout(std430, binding = 1) buffer HistogramBuffer { uint bins[]; // Use an unsized array } histogram; // 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; void main() { // Get the global invocation ID (like pixel coordinates) ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy); ivec2 textureSize = imageSize(InputTexture); // Boundary check: Don't process outside the image bounds if (pixelCoord.x >= textureSize.x || pixelCoord.y >= textureSize.y) { return; } // Load the pixel color (values are normalized float 0.0-1.0 from rgba8 image load) vec4 pixelColor = imageLoad(InputTexture, pixelCoord); // 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); // Atomically increment the counters in the SSBO // Offset Green bins by 256, Blue bins by 512 atomicAdd(histogram.bins[rBin], 1u); atomicAdd(histogram.bins[gBin + 256u], 1u); atomicAdd(histogram.bins[bBin + 512u], 1u); // Optional: Track Max Value (more complex, requires another SSBO or different strategy) // Example: atomicMax(histogram.maxBinValue, histogram.bins[rBin]); // Needs careful sync }