/*
    Copyright (c) 2024 Josh Britain (jbritain)
    Licensed under the MIT license

      _____   __   _
     / ___/  / /  (_)  __ _   __ _  ___   ____
    / (_ /  / /  / /  /  ' \ /  ' \/ -_) / __/
    \___/  /_/  /_/  /_/_/_//_/_/_/\__/ /_/

    By jbritain
    https://jbritain.net

*/

#ifndef BLOOM_GLSL
#define BLOOM_GLSL

// implemented following https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom
// which is in turn an implementation of Sledgehammer Games' bloom used for COD Advanced Warfare

vec3 powVec3(vec3 v, float p) {
  return vec3(pow(v.x, p), pow(v.y, p), pow(v.z, p));
}

vec3 toSRGB(vec3 v) {
  return powVec3(v, 1.0 / 2.2);
}

float RGBToLuminance(vec3 col) {
  return dot(col, vec3(0.2126f, 0.7152f, 0.0722f));
}

float karisAverage(vec3 col) {
  // Formula is 1 / (1 + luma)
  float luma = RGBToLuminance(toSRGB(col)) * 0.25f;
  return 1.0f / (1.0f + luma);
}

struct BloomTile {
  vec2 origin;
  int mipLevel;
  float scale;
};

BloomTile tileA = BloomTile(vec2(0.0), 1, 0.5); // 1/2 scale
BloomTile tileB = BloomTile(
    vec2(0.5 + 2 / (viewWidth), 0.0),
    2,
    0.25
  ); // 1/4 scale
BloomTile tileC = BloomTile(
    vec2(0.75 + 4 / (viewWidth), 0.0),
    3,
    0.125
  ); // 1/8 scale
BloomTile tileD = BloomTile(
    vec2(0.875 + 6 / (viewWidth), 0.0),
    4,
    0.0625
  ); // 1/16 scale
BloomTile tileE = BloomTile(
    vec2(0.9375 + 8 / (viewWidth), 0.0),
    5,
    0.03125
  ); // 1/32 scale

BloomTile tiles[5] = BloomTile[5](tileA, tileB, tileC, tileD, tileE);

vec3 downSample(sampler2D sourceTexture, vec2 coord, bool doKarisAverage) {
  // a - b - c
  // - j - k -
  // d - e - f
  // - l - m -
  // g - h - i

  float x = 1.0 / float(viewWidth);
  float y = 1.0 / float(viewHeight);

  vec3 a = textureLod(sourceTexture, vec2(coord.x - 2 * x, coord.y + 2 * y), 0).rgb;
  vec3 b = textureLod(sourceTexture, vec2(coord.x, coord.y + 2 * y), 0).rgb;
  vec3 c = textureLod(sourceTexture, vec2(coord.x + 2 * x, coord.y + 2 * y), 0).rgb;
  vec3 d = textureLod(sourceTexture, vec2(coord.x - 2 * x, coord.y), 0).rgb;
  vec3 e = textureLod(sourceTexture, vec2(coord.x, coord.y), 0).rgb;
  vec3 f = textureLod(sourceTexture, vec2(coord.x + 2 * x, coord.y), 0).rgb;
  vec3 g = textureLod(sourceTexture, vec2(coord.x - 2 * x, coord.y - 2 * y), 0).rgb;
  vec3 h = textureLod(sourceTexture, vec2(coord.x, coord.y - 2 * y), 0).rgb;
  vec3 i = textureLod(sourceTexture, vec2(coord.x + 2 * x, coord.y - 2 * y), 0).rgb;
  vec3 j = textureLod(sourceTexture, vec2(coord.x - x, coord.y + y), 0).rgb;
  vec3 k = textureLod(sourceTexture, vec2(coord.x + x, coord.y + y), 0).rgb;
  vec3 l = textureLod(sourceTexture, vec2(coord.x - x, coord.y - y), 0).rgb;
  vec3 m = textureLod(sourceTexture, vec2(coord.x + x, coord.y - y), 0).rgb;

  vec3 dsample;
  if (doKarisAverage) {
    vec3 group0 = (a + b + d + e) * (0.124 / 4.0);
    vec3 group1 = (b + c + e + f) * (0.124 / 4.0);
    vec3 group2 = (d + e + g + h) * (0.125 / 4.0);
    vec3 group3 = (e + f + h + i) * (0.125 / 4.0);
    vec3 group4 = (j + k + l + m) * (0.5 / 4.0);

    group0 *= karisAverage(group0);
    group1 *= karisAverage(group1);
    group2 *= karisAverage(group2);
    group3 *= karisAverage(group3);
    group4 *= karisAverage(group4);
    dsample = group0 + group1 + group2 + group3 + group4;
  } else {
    dsample = e * 0.125;
    dsample += (a + c + g + i) * 0.03125;
    dsample += (b + d + f + h) * 0.0625;
    dsample += (j + k + l + m) * 0.125;
  }

  dsample = max(dsample, 0.0001);

  return dsample;
}

vec3 upSample(sampler2D sourceTexture, vec2 coord) {
  //  1   | 1 2 1 |
  // -- * | 2 4 2 |
  // 16   | 1 2 1 |

  float x = BLOOM_RADIUS / (viewWidth);
  float y = BLOOM_RADIUS / (viewHeight);

  vec3 a = textureLod(sourceTexture, vec2(coord.x - x, coord.y + y), 0).rgb;
  vec3 b = textureLod(sourceTexture, vec2(coord.x, coord.y + y), 0).rgb;
  vec3 c = textureLod(sourceTexture, vec2(coord.x + x, coord.y + y), 0).rgb;

  vec3 d = textureLod(sourceTexture, vec2(coord.x - x, coord.y), 0).rgb;
  vec3 e = textureLod(sourceTexture, vec2(coord.x, coord.y), 0).rgb;
  vec3 f = textureLod(sourceTexture, vec2(coord.x + x, coord.y), 0).rgb;

  vec3 g = textureLod(sourceTexture, vec2(coord.x - x, coord.y - y), 0).rgb;
  vec3 h = textureLod(sourceTexture, vec2(coord.x, coord.y - y), 0).rgb;
  vec3 i = textureLod(sourceTexture, vec2(coord.x + x, coord.y - y), 0).rgb;

  vec3 usample = e * 4.0;
  usample += (b + d + f + h) * 2.0;
  usample += a + c + g + i;
  usample /= 16.0;

  usample = max(usample, 0.0001);

  return usample;
}

// takes a texcoord within a bloom tile and scales it up to spread across the whole screen
vec2 scaleToBloomTile(vec2 coord, BloomTile tile) {
  return (coord - tile.origin) / tile.scale;
}

// takes a full screen texcoord and scales it down to map to a bloom tile
vec2 scaleFromBloomTile(vec2 coord, BloomTile tile) {
  return coord * tile.scale + tile.origin;
}

#endif // BLOOM_GLSL
