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

layout(binding=2, std430) buffer countBuffer
{
  uint maxDepth;
  uint count[];
};

layout(binding=4, std430) buffer fragmentBuffer
{
  vec4 fragment[];
};

layout(binding=5, std430) buffer depthBuffer
{
  float depth[];
};

layout(binding=6, std430) buffer opaqueBuffer {
  vec4 opaqueColor[];
};

layout(binding=7, std430) buffer opaqueDepthBuffer {
  float opaqueDepth[];
};

#ifdef GPUCOMPRESS
layout(binding=1, std430) buffer indexBuffer
{
  uint index[];
};
#define INDEX(pixel) index[pixel]
#define COUNT(pixel) index[pixel]
#else
#define INDEX(pixel) pixel
#define COUNT(pixel) count[pixel]
#endif

out vec4 outColor;

uniform uint width;
uniform vec4 background;

// sRGB (perceptual) output; set at runtime, so no recompilation is needed
// when the setting changes (true = convert, false = output linear)
uniform bool srgb;

const float gamma=2.2;
const float invGamma=1.0/gamma;

/**
 * @brief Converts linear color (measuring photon count) to srgb (what our brain thinks
 * is the brightness
 * example linearToPerceptual(vec3(0.5)) is approximately vec3(0.729)
 */
vec3 linearToPerceptual(vec3 inColor)
{
  // an actual 0.5 brightness (half amount of photons) would
  // look brighter than what our eyes think is "half" light
  return pow(inColor, vec3(invGamma));
}

vec4 blend(vec4 outColor, vec4 color)
{
  return mix(outColor,color,color.a);
}

void main()
{
  uint pixel=uint(gl_FragCoord.y)*width+uint(gl_FragCoord.x);
  float OpaqueDepth=opaqueDepth[pixel];
  uint element=INDEX(pixel);

#ifdef GPUCOMPRESS
  if(element == 0u) {
   if(OpaqueDepth != 0.0)
      opaqueDepth[pixel]=0.0;
    discard;
  }
#endif

#ifdef GPUINDEXING
  uint listIndex=offset[element];
  uint size=offset[element+1u]-listIndex;
#else
  uint size=count[element];
#endif

#ifndef GPUCOMPRESS
  if(size == 0u) {
    if(OpaqueDepth != 0.0)
      opaqueDepth[pixel]=0.0;
    discard;
  }
#endif

  outColor=OpaqueDepth != 0.0 ? opaqueColor[pixel] : background;

#ifndef GPUINDEXING
  uint listIndex=offset[element]-size;
#endif

  uint k=0u;
  if(OpaqueDepth != 0.0)
    while(k < size && depth[listIndex+k] >= OpaqueDepth)
      ++k;

  uint n=size-k;

  // Sort the fragments with respect to descending depth
  if(n <= ARRAYSIZE) {
    if(n == 1u)
      outColor=blend(outColor,fragment[listIndex+k]);
    else if(n > 0u) {
      struct element {
        uint index;
        float depth;
      };

      element E[ARRAYSIZE];
      E[0u]=element(k,depth[listIndex+k]);
      uint i=1u;
      while(++k < size) {
        float d=depth[listIndex+k];
        if(OpaqueDepth != 0.0) {
          while(k < size && d >= OpaqueDepth) {
            ++k;
            d=depth[listIndex+k];
          }
          if(k == size) break;
        }
        uint j=i;
        while(j > 0u && d > E[j-1u].depth) {
          E[j]=E[j-1u];
          --j;
        }
        E[j]=element(k,d);
        ++i;
      }
      for(uint j=0u; j < i; ++j)
        outColor=blend(outColor,fragment[listIndex+E[j].index]);
    }

    if(OpaqueDepth != 0.0)
      opaqueDepth[pixel]=0.0;
  } else {
    for(uint i=k+1u; i < size; i++) {
      vec4 temp=fragment[listIndex+i];
      float d=depth[listIndex+i];
      uint j=i;
      while(j > 0u && d > depth[listIndex+j-1u]) {
        fragment[listIndex+j]=fragment[listIndex+j-1u];
        depth[listIndex+j]=depth[listIndex+j-1u];
        --j;
      }
      fragment[listIndex+j]=temp;
      depth[listIndex+j]=d;
    }

    uint stop=listIndex+size;
    if(OpaqueDepth == 0.0)
      for(uint i=listIndex+k; i < stop; i++)
        outColor=blend(outColor,fragment[i]);
    else {
      for(uint i=listIndex+k; i < stop; i++) {
        if(depth[i] < OpaqueDepth)
          outColor=blend(outColor,fragment[i]);
      }
      opaqueDepth[pixel]=0.0;
    }
  }

  // Match the fragment shader: the blended color is linear, convert to
  // perceptual if sRGB output is requested
  if(srgb) {
    vec3 perceptualColor=linearToPerceptual(outColor.rgb);
    outColor=vec4(perceptualColor,outColor.a);
  }

  COUNT(pixel)=0u;
}
