#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,8,10 */
layout(location = 0) out vec4 cloudImage;
layout(location = 1) out vec4 cloudHistory;
layout(location = 2) out vec4 historyData;

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

const bool colortex8Clear   = false;
const bool colortex10Clear   = false;

in vec2 coord;

uniform sampler2D colortex3;
uniform sampler2D colortex8;
uniform sampler2D colortex10;

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_D3X3
#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) {
    vec3 prevScreenPos = sceneSpace;
    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;
}
/*
float pack4x4(in vec4 toPack) {
    vec2 A  = vec2(encode2x4(toPack.xy), encode2x4(toPack.zw));
    return pack2x8(A);
}
vec4 unpack4x4(in float data) {
    vec2 A  = unpack2x8(data);

    return vec4(decode2x4(A.x), decode2x4(A.y));
}*/

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

    vec2 weights    = cubeSmooth(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);
}

float depthMin3x3(sampler2D depthtex, vec2 coord, vec2 px) {
    float tl    = texture(depthtex, coord + vec2(-px.x, -px.y)).x;
    float tc    = texture(depthtex, coord + vec2(0.0, -px.y)).x;
    float tr    = texture(depthtex, coord + vec2(px.x, -px.y)).x;
    float tmin  = min(tl, min(tc, tr));

    float ml    = texture(depthtex, coord + vec2(-px.x, 0.0)).x;
    float mc    = texture(depthtex, coord).x;
    float mr    = texture(depthtex, coord + vec2(px.x, 0.0)).x;
    float mmin  = min(ml, min(mc, mr));

    float bl    = texture(depthtex, coord + vec2(-px.x, px.y)).x;
    float bc    = texture(depthtex, coord + vec2(0.0, px.y)).x;
    float br    = texture(depthtex, coord + vec2(px.x, px.y)).x;
    float bmin  = min(bl, min(bc, br));

    return min(tmin, min(mmin, bmin));
}

void main() {
    cloudImage      = stex(colortex3);
    cloudHistory    = vec4(0.0, 0.0, 0.0, 1.0);
    historyData     = stex(colortex10);

    const float cLOD    = sqrt(CLOUD_RENDER_LOD);

    vec2 cloudCoord  = coord * cLOD;

    float d     = depthMax3x3(depthtex0, cloudCoord, pixelSize*cLOD);

    if (clamp(cloudCoord, -pixelSize, 1.0 + pixelSize) == cloudCoord) {
        cloudCoord      = saturate(cloudCoord);
        vec3 position   = screenToViewSpace(vec3(cloudCoord, 1.0), true);
            position    = viewToSceneSpace(position);
        
        float sceneDepth    = texelFetch(depthtex0, ivec2(cloudCoord * viewSize), 0).x;

        vec3 reprojection   = reproject(position);

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

        int frame       = (frameCounter) % 9;
        ivec2 offset    = temporalOffset9[frame];

        ivec2 pixels    = ivec2(coord * viewSize);

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

        vec2 historyPos     = clamp(reprojection.xy, -pixelSize, 1.0 + pixelSize) * rcp(cLOD);

        vec4 historyColor   = clamp16F(texture(colortex8, historyPos));
        vec4 checkerboard   = sampleCheckerboardSmooth(colortex10, historyPos);

        vec2 currentPos     = saturate(cloudCoord) * rcp(cLOD * 3.0) - offset * rcp(3.0) * pixelSize;
        vec4 currentColor   = texelFetch(colortex3, ivec2(currentPos * viewSize), 0);
        vec4 currentColorSmooth = textureBicubicCustom(colortex3, currentPos, 1);

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

        float accumulationWeight = 1.0 - sqr(rcp(1.0 + reprojectionDistance * 128.0)) * 0.5;
            accumulationWeight  = max(accumulationWeight, float(checkerboard.x < 0.75));

        if (clamp16F(historyColor) != historyColor) historyColor = currentColorSmooth;

        vec4 accumulated    = historyColor;

        if (currentPixel){
            checkerboard.x  = landMask(sceneDepth) ? 0.0 : 1.0;
            if (checkerboard.y < 0.75) accumulated = currentColor;
            else accumulated = mix(accumulated, currentColor, accumulationWeight);
        } else if (offscreen || checkerboard.x < 0.75 || reprojectionDistance > 0.16) {
            accumulated = currentColorSmooth;
        }

        checkerboard.y  = landMask(sceneDepth) ? 0.0 : 1.0;

        if (landMask(d)) {
            cloudImage  = vec4(0.0, 0.0, 0.0, 1.0);
            cloudHistory = vec4(0.0, 0.0, 0.0, 1.0);
            checkerboard.xy = vec2(0.0);
        }

        cloudImage  = accumulated;
        cloudHistory = accumulated;

        //cloudImage.r    = float(checkerboard.x) * pi;
        //cloudImage.gb   = vec2(0.0);

        //cloudImage.rgb  = vec3(float(checkerboard.x));
        //cloudImage.a    = 0.1;

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

    cloudImage      = clamp16F(cloudImage);
    cloudHistory    = clamp16F(cloudHistory);
}