#version 400 compatibility

/*
====================================================================================================

    Copyright (C) 2021 RRe36

    All Rights Reserved unless otherwise explicitly stated.


    By downloading this you have agreed to the license and terms of use.
    These can be found inside the included license-file
    or here: https://rre36.com/copyright-license

    Violating these terms may be penalized with actions according to the Digital Millennium
    Copyright Act (DMCA), the Information Society Directive and/or similar laws
    depending on your country.

====================================================================================================
*/

/* RENDERTARGETS: 3,9,10,14 */
layout(location = 0) out vec3 fogScattering;
layout(location = 1) out vec4 scatterHistory;
layout(location = 2) out vec4 transmittanceHistory;
layout(location = 3) out vec3 fogTransmittance;

#include "/lib/head.glsl"
#include "/lib/util/encoders.glsl"

const bool colortex9Clear   = false;
const bool colortex10Clear   = false;

in vec2 coord;

uniform sampler2D colortex3;
uniform sampler2D colortex9;
uniform sampler2D colortex10;
uniform sampler2D colortex14;

uniform sampler2D depthtex0;

uniform int frameCounter;

uniform float aspectRatio;
uniform float eyeAltitude;
uniform float frameTimeCounter;
uniform float wetness;
uniform float far, near;

uniform vec2 viewSize, pixelSize;
uniform vec2 taaOffset;

uniform vec4 daytime;

uniform mat4 gbufferProjection, gbufferModelView;
uniform mat4 gbufferProjectionInverse, gbufferModelViewInverse;
uniform mat4 gbufferPreviousProjection, gbufferPreviousModelView;

uniform vec3 cameraPosition, previousCameraPosition;


/* ------ includes ------*/
#define FUTIL_LINDEPTH
#include "/lib/fUtil.glsl"
#include "/lib/util/bicubic.glsl"
#include "/lib/util/transforms.glsl"


/* --- TEMPORAL CHECKERBOARD --- */
#include "/lib/frag/checkerboard.glsl"

vec3 reproject(vec3 sceneSpace, bool hand) {
    vec3 prevScreenPos = hand ? vec3(0.0) : cameraPosition - previousCameraPosition;
    prevScreenPos = sceneSpace + prevScreenPos;
    prevScreenPos = viewMAD(gbufferPreviousModelView, prevScreenPos);
    prevScreenPos = viewMAD(gbufferPreviousProjection, prevScreenPos) * (0.5 / -prevScreenPos.z) + 0.5;

    return prevScreenPos;
}
float encode2x4(vec2 x){
	return dot(floor(15.0 * x + 0.5), vec2(1.0 / 255.0, 16.0 / 255.0));
}
vec2 decode2x4(float pack){
	vec2 xy; xy.x = modf(pack * 255.0 / 16.0, xy.y);
	return vec2(16.0 / 15.0, 1.0 / 15.0) * xy;
}

vec4 sampleCheckerboardSmooth(sampler2D tex, vec2 uv) {
    vec2 pos        = coord * viewSize - 0.5;
    ivec2 pixelPos  = ivec2(pos);

    vec2 weights    = fract(pos);

    vec4 resultA    = mix(unpack4x4(texelFetch(tex, pixelPos, 0).a)              , unpack4x4(texelFetch(tex, pixelPos + ivec2(1, 0), 0).a), weights.x);
    vec4 resultB    = mix(unpack4x4(texelFetch(tex, pixelPos + ivec2(0, 1), 0).a), unpack4x4(texelFetch(tex, pixelPos + ivec2(1, 1), 0).a), weights.x);

    return mix(resultA, resultB, weights.y);
}

vec4 textureBicubicCustom(sampler2D sampler, vec2 coord, int coeff) {
	vec2 res = textureSize(sampler, 0) * coeff;

	coord = coord * res - 0.5;

	vec2 f = fract(coord);
	coord -= f;

	vec2 ff = f * f;
	vec4 w0;
	vec4 w1;
	w0.xz = 1 - f; w0.xz *= w0.xz * w0.xz;
	w1.yw = ff * f;
	w1.xz = 3 * w1.yw + 4 - 6 * ff;
	w0.yw = 6 - w1.xz - w1.yw - w0.xz;

	vec4 s = w0 + w1;
	vec4 c = coord.xxyy + vec2(-0.5, 1.5).xyxy + w1 / s;
	c /= res.xxyy;

	vec2 m = s.xz / (s.xz + s.yw);
	return mix(
		mix(textureLod(sampler, c.yw, 0), textureLod(sampler, c.xw, 0), m.x),
		mix(textureLod(sampler, c.yz, 0), textureLod(sampler, c.xz, 0), m.x),
		m.y);
}

mat2x3 mixMat2x3(mat2x3 a, mat2x3 b, float alpha) {
    mat2x3 x    = mat2x3(0.0);
        x[0]    = mix(a[0], b[0], alpha);
        x[1]    = mix(a[1], b[1], alpha);

    return x;
}

#include "/lib/offset/gauss.glsl"

ivec2 clampTexelPos(ivec2 pos) {
    return clamp(pos, ivec2(0.0), ivec2(viewSize));
}

mat2x3 spatialFog(vec2 coord) {
    ivec2 pixelCoordUnscaled = ivec2(coord * viewSize);

    vec2 newCoord       = coord;
    ivec2 pixelCoord    = ivec2(newCoord * viewSize);

    ivec2 pos           = ivec2(coord * viewSize);

    vec3 centerScatter  = texelFetch(colortex3, pixelCoord, 0).rgb;
    vec3 centerTransmittance = texelFetch(colortex14, pixelCoord, 0).rgb;

    float centerDepth   = depthLinear(texelFetch(depthtex0, pixelCoordUnscaled, 0).x) * far;

    float totalWeight   = 0.1 / pi;
    vec3 totalScatter   = centerScatter * totalWeight;
    vec3 totalTrans     = centerTransmittance * totalWeight;

    for (int i = 0; i<25; i++) {
        ivec2 deltaPos      = kernelO_5x5[i];

        ivec2 samplePos     = pixelCoordUnscaled + deltaPos;

        bool valid          = all(greaterThanEqual(samplePos, ivec2(0))) && all(lessThan(samplePos, ivec2(viewSize)));

        if (!valid) continue;

        vec4 currentScatter = texelFetch(colortex3, clampTexelPos(samplePos), 0);
        vec3 currentTrans   = texelFetch(colortex14, clampTexelPos(samplePos), 0).rgb;
        float currentDepth  = currentScatter.a * far;

        float depthDelta    = abs(currentDepth - centerDepth) * 48.0;

        float weight        = exp(-depthDelta);

        //accumulate stuff
        totalScatter   += currentScatter.rgb * weight;
        totalTrans     += currentTrans * weight;

        totalWeight    += weight;
    }

    totalScatter   /= max(totalWeight, 1e-16);
    totalTrans     /= max(totalWeight, 1e-16);

    return mat2x3(totalScatter, totalTrans);
}

const float fogLOD      = 2.0;

void main() {
    scatterHistory   = vec4(0.0);
    transmittanceHistory = stex(colortex10);

    float sceneDepth    = texelFetch(depthtex0, ivec2(coord * viewSize), 0).x;

    vec3 position   = screenToViewSpace(vec3(coord, sceneDepth));
        position    = viewToSceneSpace(position);

    float currentDistance = length(position);

    vec3 reprojection   = reproject(position, false);

    bool offscreen  = saturate(reprojection.xy) != reprojection.xy;

    int frame       = (frameCounter) % 4;
    ivec2 offset    = temporalOffset4[frame];

    ivec2 pixels    = ivec2(coord * viewSize);

    bool currentPixel  = (mod(pixels, 3) - offset) == ivec2(0);

    vec2 historyPos     = saturate(reprojection.xy);

    float lastDistance  = clamp16F(texture(colortex9, historyPos).a);

    vec3 historyScatter = clamp16F(texture(colortex9, historyPos).rgb);
    vec3 historyTransmittance = clamp16F(texture(colortex10, historyPos).rgb);

    vec4 checkerboard   = sampleCheckerboardSmooth(colortex10, historyPos);

    vec2 currentPos     = coord;

    mat2x3 historyColor = mat2x3(historyScatter, historyTransmittance);
    mat2x3 currentColorSpatial = spatialFog(coord);

    mat2x3 accumulated  = mat2x3(vec3(0.0), vec3(1.0));

    float reprojectionDistance = distance(reprojection.xy, coord.xy);

    vec3 cameraMovement = mat3(gbufferModelView) * (cameraPosition - previousCameraPosition);
    float distanceDelta = (distance(lastDistance, currentDistance) * far) - abs(cameraMovement.z);
    float depthRejection = (offscreen) ? 0.0 : exp(-max(distanceDelta - 0.2, 0.0) * pi);

    float accumulationWeight = 1.0 - depthRejection * 0.8;

    if (offscreen) {
        accumulated = currentColorSpatial;
    } else {
        accumulated     = mixMat2x3(historyColor, currentColorSpatial, accumulationWeight);
    }

    fogScattering   = clamp16F(accumulated[0]);
    fogTransmittance = clamp16F(accumulated[1]);

    scatterHistory.rgb  = clamp16F(accumulated[0]);
    transmittanceHistory.rgb = clamp16F(accumulated[1]);

    scatterHistory.a    = clamp16F(currentDistance);

    transmittanceHistory.a = pack4x4(vec4(unpack4x4(transmittanceHistory.a).xy, checkerboard.zw));
}