/********************************************************
    © 2020 Continuum Graphics LLC. All Rights Reserved
 ********************************************************/

//This file is here because the effect is non standard and can be spread over multiple files,
//also we dont know how many programs the shader will use, meaning we don't know the number of the program.

#include "/../ContinuumLib/Syntax.glsl"

#define Antialiasing_glsl 40
#define DynamicShaderStage Antialiasing_glsl


varying vec2 texcoord;


/***********************************************************************/
#if defined vsh

uniform vec2 viewDimensions;

uniform mat4 gbufferProjection;
uniform mat4 gbufferProjectionInverse;

uniform float fov;

#include "/../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"

void main() {
    texcoord = gl_Vertex.xy;

    gl_Position = vec4(gl_Vertex.xy * 2.0 - 1.0, 0.0, 1.0);
}

#endif
/***********************************************************************/



/***********************************************************************/
#if defined fsh

#include "/../ContinuumLib/Debug.glsl"

uniform sampler2D depthtex0;

uniform sampler2D colortex0;
uniform sampler2D colortex4;

uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferProjection;
uniform mat4 gbufferProjectionInverse;

uniform mat4 gbufferPreviousModelView;
uniform mat4 gbufferPreviousProjection;

uniform vec3 cameraPosition;
uniform vec3 previousCameraPosition;

uniform vec2 viewDimensions;
uniform vec2 pixelSize;
uniform vec2 taaJitter;

uniform int frameCounter;
uniform float frameTime;

/* DRAWBUFFERS:4 */
layout (location = 0) out vec4 Buffer4;
#define LAYOUT_0 Buffer4
#include "/../ContinuumLib/Exit.glsl"

#include "/../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"

/*******************************************************************************
- Space Conversions
******************************************************************************/

vec3 CalculateViewSpacePosition(vec3 screenPos) {
    screenPos = screenPos * 2.0 - 1.0;
    return projMAD(gbufferProjectionInverse, screenPos) / (screenPos.z * gbufferProjectionInverse[2].w + gbufferProjectionInverse[3].w);
}

vec3 ViewSpaceToScreenSpace(vec3 viewPos) {
    return projMAD(gbufferProjection, viewPos) / -viewPos.z * 0.5 + 0.5;
}

float ScreenToViewSpaceDepth(float depth) {
    depth = depth * 2.0 - 1.0;
    return -1.0 / (depth * gbufferProjectionInverse[2][3] + gbufferProjectionInverse[3][3]);
}

/*******************************************************************************
- Lookups
******************************************************************************/

vec3 SampleColor(vec2 coord) {
    return pow(texture(colortex0, coord).rgb, vec3(2.2));
}

float SampleDepth(vec2 coord) {
    return texture2D(depthtex0, coord).x;
}

vec3 SamplePreviousColor(vec2 coord) {
    return pow(texture2D(colortex4, coord).rgb, vec3(2.2));
}

vec3 FindClosestFragment3x3(vec2 p, vec2 pixelSize) {
    vec3 depth1 = vec3(pixelSize.x, 0.0, SampleDepth(p + vec2(pixelSize.x, 0.0)));
    vec3 depth2 = vec3(-pixelSize.x, 0.0, SampleDepth(p + vec2(-pixelSize.x, 0.0)));
    vec3 depth3 = vec3(pixelSize.x, pixelSize.y, SampleDepth(p + vec2(pixelSize.x, pixelSize.y)));
    vec3 depth4 = vec3(-pixelSize.x, pixelSize.y, SampleDepth(p + vec2(-pixelSize.x, pixelSize.y)));
    vec3 depth5 = vec3(pixelSize.x, -pixelSize.y, SampleDepth(p + vec2(pixelSize.x, -pixelSize.y)));
    vec3 depth6 = vec3(-pixelSize.x, -pixelSize.y, SampleDepth(p + vec2(-pixelSize.x, -pixelSize.y)));
    vec3 depth7 = vec3(0.0, pixelSize.y, SampleDepth(p + vec2(0.0, pixelSize.y)));
    vec3 depth8 = vec3(0.0, -pixelSize.y, SampleDepth(p + vec2(0.0, -pixelSize.y)));

    vec3 depthMin = depth1;
    depthMin = depthMin.z > depth2.z ? depth2 : depthMin;
    depthMin = depthMin.z > depth3.z ? depth3 : depthMin;

    depthMin = depthMin.z > depth4.z ? depth4 : depthMin;
    depthMin = depthMin.z > depth5.z ? depth5 : depthMin;
    depthMin = depthMin.z > depth6.z ? depth6 : depthMin;

    depthMin = depthMin.z > depth7.z ? depth7 : depthMin;
    depthMin = depthMin.z > depth8.z ? depth8 : depthMin;

    return vec3(depthMin.xy + p, depthMin.z);
}

/*******************************************************************************
- TAA
******************************************************************************/

vec2 CalculateCameraVelocity(vec3 screenPos) {
    vec3 projection = mat3(gbufferModelViewInverse) * CalculateViewSpacePosition(screenPos) + gbufferModelViewInverse[3].xyz;
         projection = (screenPos.z < 1.0 ? (cameraPosition - previousCameraPosition) : vec3(0.0)) + projection;
         projection = mat3(gbufferPreviousModelView) * projection + gbufferPreviousModelView[3].xyz;
         projection = (diagonal3(gbufferPreviousProjection) * projection + gbufferPreviousProjection[3].xyz) / -projection.z * 0.5 + 0.5;

    return (screenPos.xy - projection.xy);
}


vec3 clipAABB(vec3 boxMin, vec3 boxMax, vec3 q) {
    vec3 p_clip = 0.5 * (boxMax + boxMin);
    vec3 e_clip = 0.5 * (boxMax - boxMin);

    vec3 v_clip = q - p_clip;
    vec3 v_unit = v_clip.xyz / e_clip;
    vec3 a_unit = abs(v_unit);
    float ma_unit = max3(a_unit.x, a_unit.y, a_unit.z);

    if (ma_unit > 1.0)
        return v_clip / ma_unit + p_clip;
    else
        return q;
}

vec3 CalculateMotionBlur(vec2 uv, vec2 velocity) {
    const int steps = 3;
    const float rSteps = 1.0 / steps;

	float dither = bayer16(gl_FragCoord.xy);

	vec2 vtap = velocity * (rSteps * 0.5);
	vec2 pos0 = uv + vtap * (0.5 * dither);
	vec3 accu = vec3(0.0);
	float wsum = 0.0;

	for (int i = -steps; i <= steps; i++) {
		float w = 1.0;
		accu += w * SampleColor(pos0 + float(i) * vtap);
		wsum += w;
	}

	return accu / wsum;
}

//#define MOTION_BLUR

vec3 TemporalReprojection(vec2 coord, vec2 velocity, vec2 dd) {
    vec3 previousSample = SamplePreviousColor(coord - velocity);
    
    coord = coord + taaJitter * 0.5;
    vec3 currentSample = SampleColor(coord);

    vec3 col1 = SampleColor(coord + vec2(dd.x, 0.0));
    vec3 col2 = SampleColor(coord + vec2(-dd.x, 0.0));
    vec3 col3 = SampleColor(coord + vec2(dd.x, dd.y));
    vec3 col4 = SampleColor(coord + vec2(-dd.x, dd.y));
    vec3 col5 = SampleColor(coord + vec2(dd.x, -dd.y));
    vec3 col6 = SampleColor(coord + vec2(-dd.x, -dd.y));
    vec3 col7 = SampleColor(coord + vec2(0.0, dd.y));
    vec3 col8 = SampleColor(coord + vec2(0.0, -dd.y));

    vec3 colMin = min(currentSample, min(min4(col1, col2, col3, col4), min4(col5, col6, col7, col8)));
    vec3 colMax = max(currentSample, max(max4(col1, col2, col3, col4), max4(col5, col6, col7, col8)));
    vec3 colAVG = (currentSample + col1 + col2 + col3 + col4 + col5 + col6 + col7 + col8) * (1.0 / 9.0);

    #ifdef TAA_SHARPEN
        vec3 sharpen = vec3(1.0) - exp(-(currentSample - clamp(colAVG, colMin, colMax)));
        currentSample += sharpen * TAA_SHARPNESS;
    #endif

    //Clipping
    previousSample = clipAABB(colMin, colMax, previousSample);

    vec2 pixelVelocity = abs(fract(velocity * viewDimensions) - 0.5) * 2.0;

    float blendWeight = 0.97;
        //Zombye's neat way to get rid of blurring and ghosting. Thanks <3
        blendWeight *= sqrt(pixelVelocity.x * pixelVelocity.y) * 0.25 + 0.75;

    coord -= velocity;
    blendWeight = clamp01(coord) != coord ? 0.0 : blendWeight;

    return mix(currentSample, previousSample, blendWeight);
}

vec3 CalculateTAA(vec2 coord) {
    #if 0
    vec3 closest = FindClosestFragment3x3(coord, pixelSize);
    #else
    vec3 closest = vec3(coord, SampleDepth(coord));
    #endif

    vec2 velocity = CalculateCameraVelocity(closest);

    #ifdef MOTION_BLUR
        vec3 colorTemporal = TemporalReprojection(coord, velocity, pixelSize);

        vec2 v = velocity * ((1.0 / CAMERA_SHUTTER_SPEED) * (1.0 - frameTime));
        float vel_mag = length(v * viewDimensions);
        const float vel_trust_full = 2.0;
        const float vel_trust_none = 15.0;
        const float vel_trust_span = vel_trust_none - vel_trust_full;
        float trust = clamp(vel_mag - vel_trust_full, 0.0, vel_trust_span) / vel_trust_span;

        vec3 colorMotion = CalculateMotionBlur(coord, v);

        return mix(colorTemporal, colorMotion, trust);
    #else
        return TemporalReprojection(coord, velocity, pixelSize);
    #endif
}

void main() {
    #ifdef TAA
        vec3 currentFrame = pow(CalculateTAA(texcoord), vec3(1.0 / 2.2));
    #else
        vec3 currentFrame = texture(colortex0, texcoord).rgb;
    #endif
    
    float alphaPassthrough = texture(colortex4, texcoord).a;
    Buffer4 = vec4(currentFrame, alphaPassthrough);
	
	exit();
}

#endif
/***********************************************************************/
