#version 400 compatibility

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

    Copyright (C) 2022 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: 5,9,10,11 */
layout(location = 0) out vec3 fogScattering;
layout(location = 1) out vec4 transmittanceHistory;
layout(location = 2) out vec4 scatterHistory;
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 uv;

uniform sampler2D colortex5;
uniform sampler2D colortex9;
uniform sampler2D colortex10;
uniform sampler2D colortex11;

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 = transMAD(gbufferPreviousModelView, prevScreenPos);
    prevScreenPos = transMAD(gbufferPreviousProjection, prevScreenPos) * (0.5 / -prevScreenPos.z) + 0.5;

    return prevScreenPos;
}

vec4 sampleCheckerboardSmooth(sampler2D tex, vec2 uv) {
    vec2 pos        = uv * 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 uv, int coeff) {
	vec2 res = textureSize(sampler, 0) * coeff;

	uv = uv * res - 0.5;

	vec2 f = fract(uv);
	uv -= 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 = uv.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 spatialFogUpscale(vec2 uv, const float LOD) {
    ivec2 pixelCoordUnscaled = ivec2(uv * viewSize);

    vec2 newCoord       = uv / LOD;
    ivec2 pixelCoord    = ivec2(newCoord * viewSize);

    ivec2 pos           = ivec2(uv * viewSize);

    vec3 centerScatter  = texelFetch(colortex5, pixelCoord, 0).rgb;
    vec3 centerTransmittance = texelFetch(colortex11, 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;
        ivec2 samplePosScaled = ivec2(vec2(samplePos) / LOD);

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

        if (!valid) continue;

        vec4 currentScatter = texelFetch(colortex5, clampTexelPos(samplePosScaled), 0);
        vec3 currentTrans   = texelFetch(colortex11, clampTexelPos(samplePosScaled), 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(colortex9);

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

    vec3 position   = screenToViewSpace(vec3(uv / ResolutionScale, 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(uv * viewSize);

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

    vec2 historyPos     = saturate(reprojection.xy) * ResolutionScale;

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

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

    vec4 checkerboard   = sampleCheckerboardSmooth(colortex9, historyPos);

    vec2 currentPos     = saturate(uv) * rcp(fogLOD) - offset * rcp(fogLOD) * pixelSize;

    vec3 currentScatter = texelFetch(colortex5, ivec2(currentPos * viewSize), 0).rgb;
    vec3 currentScatterSmooth = textureBicubicCustom(colortex5, currentPos, 1).rgb;

    vec3 currentTransmittance = texelFetch(colortex11, ivec2(currentPos * viewSize), 0).rgb;
    vec3 currentTransmittanceSmooth = textureBicubicCustom(colortex11, currentPos, 1).rgb;

    mat2x3 historyColor = mat2x3(historyScatter, historyTransmittance);
    mat2x3 currentColor = mat2x3(currentScatter, currentTransmittance);
    mat2x3 currentColorSmooth = mat2x3(currentScatterSmooth, currentTransmittanceSmooth);
    mat2x3 currentColorSpatial = spatialFogUpscale(uv, fogLOD);

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

    float reprojectionDistance = distance(reprojection.xy, uv.xy / ResolutionScale);

    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.5;
    float accumulationWeight2 = 1.0 - depthRejection;

    if (currentPixel){
        checkerboard.z  = 1.0;
        accumulated     = mixMat2x3(historyColor, currentColor, accumulationWeight);
    } else if (checkerboard.z < 0.9 || offscreen) {
        accumulated = currentColorSpatial;
    } else {
        accumulated     = mixMat2x3(historyColor, currentColorSpatial, accumulationWeight2);
    }

    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));
}