#include "UnityCG.cginc"

RWStructuredBuffer<uint4> _Histogram;
Texture2D<float4> _Source;

CBUFFER_START (Params)
    uint _IsLinear;
    float4 _Res;
    uint4 _Channels;
CBUFFER_END

groupshared uint4 gs_histogram[256];

#define GROUP_SIZE 16

#pragma kernel KHistogramGather
[numthreads(GROUP_SIZE, GROUP_SIZE,1)]
void KHistogramGather(uint2 dispatchThreadId : SV_DispatchThreadID, uint2 groupThreadId : SV_GroupThreadID)
{
    const uint localThreadId = groupThreadId.y * GROUP_SIZE + groupThreadId.x;
    
    if (localThreadId < 256)
        gs_histogram[localThreadId] = uint4(0, 0, 0, 0);

    GroupMemoryBarrierWithGroupSync();

    if (dispatchThreadId.x < (uint)_Res.x && dispatchThreadId.y < (uint)_Res.y)
    {
        // We want a gamma histogram (like Photoshop & all)
        float3 color = saturate(_Source[dispatchThreadId].xyz);
        if (_IsLinear > 0)
            color = LinearToGammaSpace(color);
        
        // Convert color & luminance to histogram bin
        uint3 idx_c = (uint3)(round(color * 255.0));
        uint idx_l = (uint)(round(dot(color.rgb, float3(0.2125, 0.7154, 0.0721)) * 255.0));
    
        // Fill the group shared histogram
        if (_Channels.x > 0u) InterlockedAdd(gs_histogram[idx_c.x].x, 1); // Red
        if (_Channels.y > 0u) InterlockedAdd(gs_histogram[idx_c.y].y, 1); // Green
        if (_Channels.z > 0u) InterlockedAdd(gs_histogram[idx_c.z].z, 1); // Blue
        if (_Channels.w > 0u) InterlockedAdd(gs_histogram[idx_l].w, 1);   // Luminance
    }

    GroupMemoryBarrierWithGroupSync();

    // Merge
    if (localThreadId < 256)
    {
        uint4 h = gs_histogram[localThreadId];
        if (_Channels.x > 0u && h.x > 0) InterlockedAdd(_Histogram[localThreadId].x, h.x); // Red
        if (_Channels.y > 0u && h.y > 0) InterlockedAdd(_Histogram[localThreadId].y, h.y); // Green
        if (_Channels.z > 0u && h.z > 0) InterlockedAdd(_Histogram[localThreadId].z, h.z); // Blue
        if (_Channels.w > 0u && h.w > 0) InterlockedAdd(_Histogram[localThreadId].w, h.w); // Luminance
    }
}

// Scaling pass
groupshared uint4 gs_pyramid[256];

#pragma kernel KHistogramScale
[numthreads(16,16,1)]
void KHistogramScale(uint2 groupThreadId : SV_GroupThreadID)
{
    const uint localThreadId = groupThreadId.y * 16 + groupThreadId.x;
    gs_pyramid[localThreadId] = _Histogram[localThreadId];

    GroupMemoryBarrierWithGroupSync();

    // Parallel reduction to find the max value
    UNITY_UNROLL
    for(uint i = 256 >> 1; i > 0; i >>= 1)
    {
        if(localThreadId < i)
            gs_pyramid[localThreadId] = max(gs_pyramid[localThreadId], gs_pyramid[localThreadId + i]);

        GroupMemoryBarrierWithGroupSync();
    }

    // Actual scaling
    float4 factor = _Res.y / (float4)gs_pyramid[0];
    _Histogram[localThreadId] = (uint4)round(_Histogram[localThreadId] * factor);
}

#pragma kernel KHistogramClear
[numthreads(GROUP_SIZE, GROUP_SIZE, 1)]
void KHistogramClear(uint2 dispatchThreadId : SV_DispatchThreadID)
{
    if (dispatchThreadId.x < (uint)_Res.x && dispatchThreadId.y < (uint)_Res.y)
        _Histogram[dispatchThreadId.y * _Res.x + dispatchThreadId.x] = uint4(0u, 0u, 0u, 0u);
}
