layout(local_size_x=LOCALSIZE) in;

const uint groupSize=LOCALSIZE*BLOCKSIZE;

uniform uint final;

layout(binding=0, std430) buffer offsetBuffer
{
  uint offset[];
};

layout(binding=2, std430) buffer countBuffer
{
  // Max per-pixel fragment count for this frame; atomically reduced here and
  // reset to zero at the end of every frame (see atomicExchange below)
  uint maxDepth;
  uint count[];
};

layout(binding=3, std430) buffer globalSumBuffer
{
  uint globalSum[];
};

layout(binding=8, std430) buffer feedbackBuffer
{
  uint size;
  uint fragments;
};

shared uint shuffle[groupSize+LOCALSIZE-1u];
shared uint groupSum[LOCALSIZE+1u];
shared uint maxSum[LOCALSIZE+1u]; // prefix max piggybacked on the sum reduction

void main()
{
  uint id=gl_LocalInvocationID.x;

// avoid bank conflicts and coalesce global memory accesses
  uint dataOffset=gl_WorkGroupID.x*groupSize+id;
  uint shuffleOffset=id/BLOCKSIZE+id;
  const uint stride=LOCALSIZE/BLOCKSIZE+LOCALSIZE;
  uint localMax=0u;
  for(uint i=0u; i < BLOCKSIZE; i++) {
    uint c=count[dataOffset+i*LOCALSIZE];
    shuffle[shuffleOffset+i*stride]=c;
    if(c > localMax) localMax=c;
  }

  barrier();

  uint Offset=id*BLOCKSIZE+id;
  uint stop=Offset+BLOCKSIZE;

  uint sum=0u;
  for(uint i=Offset; i < stop; ++i)
    shuffle[i]=sum += shuffle[i];

  // groupSum carries the per-thread block sums; maxSum carries the matching
  // per-thread block maxima through the same reduction (prefix max), so the
  // workgroup max of the per-pixel fragment counts adds no extra barriers.
  // It feeds the CPU's small/big blend-pipeline switch (switchBlendPipeline
  // in renderBase).
  if(id == 0u)
    {
      groupSum[0u]=0u;
      maxSum[0u]=0u;
    }
  groupSum[id+1u]=sum;
  maxSum[id+1u]=localMax;
  barrier();

  // Apply Hillis-Steele algorithm over all sums in workgroup
  for(uint shift=1u; shift < LOCALSIZE; shift *= 2u) {
    uint read, readMax;
    if(shift <= id)
      {
        read=groupSum[id]+groupSum[id-shift];
        readMax=max(maxSum[id],maxSum[id-shift]);
      }
    barrier();
    if(shift <= id)
      {
        groupSum[id]=read;
        maxSum[id]=readMax;
      }
    barrier();
  }
  // maxSum[LOCALSIZE-1] holds the workgroup max (prefix max over all threads)
  if(id == 0u)
    atomicMax(maxDepth,maxSum[LOCALSIZE-1u]);

  uint groupOffset=globalSum[gl_WorkGroupID.x];
  for(uint i=0u; i < BLOCKSIZE; ++i)
    offset[dataOffset+i*LOCALSIZE]=shuffle[shuffleOffset+i*stride]+
      groupSum[(i*LOCALSIZE+id)/BLOCKSIZE]+groupOffset;

  uint diff=final-dataOffset;
  if(diff < groupSize && diff % LOCALSIZE == 0u) {
    // Atomic read+reset: other workgroups' atomicMax() calls may still be in
    // flight; any that land after this exchange carry over into the next
    // frame's snapshot (the switch logic tolerates one stale frame).
    size=atomicExchange(maxDepth,0u);
    fragments=offset[final+1u]=offset[final];
  }
}
