#version 430 core
////////////////////////////////////////////////////////////////////////////////
// <copyright>
// Copyright (c) 2015 Hue AS. All rights reserved.
//
// All rights are reserved. Reproduction or transmission in whole or in part, in
// any form or by any means, electronic, mechanical or otherwise, is prohibited
// without the prior written permission of the copyright owner.
// </copyright>
////////////////////////////////////////////////////////////////////////////////

#define W $COMPUTE_WIDTH$
#define H $COMPUTE_HEIGHT$
#define F $REDUCE_FACTOR$

layout(binding=0) uniform usampler2D counts;
layout(binding=1) uniform usampler2D offsets;

uniform uint pitch;
uniform uint keepVolumeBits;
uniform uint replaceVolumeBits;
uniform mat4 inverseProjection;
uniform uvec2 frameSize;

layout(rg32ui, binding=0) restrict uniform uimage2D colorOrNormals; // Really rgba16f, but we only move the data.
layout(r32f, binding=1) restrict uniform image2D depths;
layout(r16ui, binding=2) restrict uniform uimage2D stencils;

ivec2 indexToPos(uint index)
{
  return ivec2(index % (pitch / F), index / (pitch / F)) * F + ivec2(gl_GlobalInvocationID.xy) % F;
}

void swap(inout uvec2 x, inout uvec2 y) { uvec2 temp = x; x = y; y = temp; }
void swap(inout uint x, inout uint y) { uint temp = x; x = y; y = temp; }
void swap(inout float x, inout float y) { float temp = x; x = y; y = temp; }

layout(local_size_x = W, local_size_y = H) in;

void main()
{
  if (!all(lessThan(gl_GlobalInvocationID.xy, frameSize))) return;

  vec2 position = (vec2(gl_GlobalInvocationID.xy) + 0.5) / vec2(frameSize) * 2.0 - 1.0;

  vec4 viewFarPos = inverseProjection * vec4(position, 1.0, 1.0);
  vec4 viewNearPos = inverseProjection * vec4(position, -1.0, 1.0);

  vec4 nearClipZ = inverseProjection * vec4(0.0, 0.0, -1.0, 1.0);
  vec4 farClipZ = inverseProjection * vec4(0.0, 0.0, 1.0, 1.0);
  nearClipZ /= nearClipZ.w;
  farClipZ /= farClipZ.w;

  ivec2 texPos = ivec2(gl_GlobalInvocationID.xy);

  uint count = texelFetch(counts, texPos, 0).x;
  uint offset = texelFetch(offsets, texPos, 0).x - count;
  uint currentStencil = 0u;

  ivec2 previousImgPos = ivec2(0);
  uvec2 previousColorOrNormal = uvec2(0u);
  float previousDepth = 0.0;
  uint previousStencil = 0u;

  for (uint i = 0u; i < count; ++i)
  {
    ivec2 imgPos = indexToPos(offset + i);
    float oldDepth = imageLoad(depths, imgPos).x;

    uint minDepthIndex = i;
    float depth = oldDepth;

    for (uint j = i + 1u; j < count; ++j)
    {
      ivec2 testImgPos = indexToPos(offset + j);
      float testDepth = imageLoad(depths, testImgPos).x;

      if (testDepth < depth)
      {
        minDepthIndex = j;
        depth = testDepth;
      }
    }

    ivec2 minDepthImgPos = indexToPos(offset + minDepthIndex);
    uvec2 colorOrNormal = imageLoad(colorOrNormals, minDepthImgPos).xy;
    uint stencil = imageLoad(stencils, minDepthImgPos).x;

    if (minDepthIndex != i)
    {
      uvec2 oldColorOrNormal = imageLoad(colorOrNormals, imgPos).xy;
      uint oldStencil = imageLoad(stencils, imgPos).x;

      imageStore(colorOrNormals, minDepthImgPos, uvec4(oldColorOrNormal, 0u, 0u));
      imageStore(depths, minDepthImgPos, vec4(oldDepth));
      imageStore(stencils, minDepthImgPos, uvec4(oldStencil));
    }

    if (stencil != 0) depth = -1.0e-30 - depth;

    currentStencil ^= stencil;

    uint lowestBit = currentStencil & (~currentStencil + 1);
    uint keepMaskBits = ~keepVolumeBits | lowestBit; // All volume probes with 'keep' blend mode, except the first volume probe, should be eliminated.
    uint replaceMaskBits = replaceVolumeBits & currentStencil;
    replaceMaskBits |= replaceMaskBits >> 1;
    replaceMaskBits |= replaceMaskBits >> 2;
    replaceMaskBits |= replaceMaskBits >> 4;
    replaceMaskBits |= replaceMaskBits >> 8;
    replaceMaskBits = ~(replaceMaskBits >> 1); // All volume probes before the last one with 'replace' blend mode should be eliminated.

    stencil = currentStencil & replaceMaskBits & keepMaskBits;

    if (i != 0u)
    {
      // Eliminate very short volume spans to reduce Z-fighting.
      if (previousStencil != 0 && previousDepth < 0.0 && depth < 0.0)
      {
        vec3 frontFaceNormal = vec3(unpackHalf2x16(previousColorOrNormal.x), unpackHalf2x16(previousColorOrNormal.y).x);
        vec3 backFaceNormal = vec3(unpackHalf2x16(colorOrNormal.x), unpackHalf2x16(colorOrNormal.y).x);

        vec4 frontPos = mix(viewNearPos, viewFarPos, -previousDepth);
        vec4 backPos = mix(viewNearPos, viewFarPos, -depth);
        frontPos /= frontPos.w;
        backPos /= backPos.w;

        vec3 viewDirection = backPos.xyz - frontPos.xyz;

        // If the distance is less than some epsilon, ignore the span to avoid Z-fighting.
        if (max(abs(dot(frontFaceNormal, viewDirection)), abs(dot(backFaceNormal, viewDirection))) <= 0.00004 * (nearClipZ.z - farClipZ.z))
        {
          previousStencil = 0u;
        }
      }

      imageStore(colorOrNormals, previousImgPos, uvec4(previousColorOrNormal, 0u, 0u));
      imageStore(depths, previousImgPos, vec4(previousDepth));
      imageStore(stencils, previousImgPos, uvec4(previousStencil));
    }

    previousImgPos = imgPos;
    previousColorOrNormal = colorOrNormal;
    previousDepth = depth;
    previousStencil = stencil;
  }

  if (count != 0u)
  {
    imageStore(colorOrNormals, previousImgPos, uvec4(previousColorOrNormal, 0u, 0u));
    imageStore(depths, previousImgPos, vec4(previousDepth));
    imageStore(stencils, previousImgPos, uvec4(previousStencil));
  }
}
