#ifndef __TAA__
#define __TAA__

#pragma only_renderers ps4 xboxone d3d11 d3d9 xbox360 opengl glcore
#pragma exclude_renderers gles

#include "UnityCG.cginc"
#include "Common.cginc"

// -----------------------------------------------------------------------------
// Solver

#define TAA_USE_STABLE_BUT_GHOSTY_VARIANT 0

#if !defined(TAA_DILATE_MOTION_VECTOR_SAMPLE)
    #define TAA_DILATE_MOTION_VECTOR_SAMPLE 1
#endif

#define TAA_FRAGMENT_MOTION_HISTORY_DECAY 0.85

#define TAA_FINAL_BLEND_STATIC_FACTOR _FinalBlendParameters.x
#define TAA_FINAL_BLEND_DYNAMIC_FACTOR _FinalBlendParameters.y
#define TAA_MOTION_AMPLIFICATION _FinalBlendParameters.z

struct VaryingsSolver
{
    float4 vertex : SV_POSITION;
    float4 uv : TEXCOORD0; // [xy: _MainTex.uv, zw: _HistoryTex.uv]
};

struct OutputSolver
{
    float4 destination : SV_Target0;
    float4 history : SV_Target1;
};

sampler2D _HistoryTex;

sampler2D _CameraMotionVectorsTexture;
sampler2D _CameraDepthTexture;

float4 _HistoryTex_TexelSize;
float4 _CameraDepthTexture_TexelSize;

float2 _Jitter;
float4 _SharpenParameters;
float4 _FinalBlendParameters;

VaryingsSolver VertSolver(AttributesDefault input)
{
    VaryingsSolver output;

    float4 vertex = UnityObjectToClipPos(input.vertex);

    output.vertex = vertex;
    output.uv = input.texcoord.xyxy;

#if UNITY_UV_STARTS_AT_TOP
    if (_MainTex_TexelSize.y < 0)
        output.uv.y = 1.0 - input.texcoord.y;
#endif

    return output;
}

float2 GetClosestFragment(float2 uv)
{
    const float2 k = _CameraDepthTexture_TexelSize.xy;
    const float4 neighborhood = float4(
        SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv - k),
        SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv + float2(k.x, -k.y)),
        SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv + float2(-k.x, k.y)),
        SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv + k)
        );

#if defined(UNITY_REVERSED_Z)
    #define COMPARE_DEPTH(a, b) step(b, a)
#else
    #define COMPARE_DEPTH(a, b) step(a, b)
#endif

    float3 result = float3(0.0, 0.0, SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv));
    result = lerp(result, float3(-1.0, -1.0, neighborhood.x), COMPARE_DEPTH(neighborhood.x, result.z));
    result = lerp(result, float3( 1.0, -1.0, neighborhood.y), COMPARE_DEPTH(neighborhood.y, result.z));
    result = lerp(result, float3(-1.0,  1.0, neighborhood.z), COMPARE_DEPTH(neighborhood.z, result.z));
    result = lerp(result, float3( 1.0,  1.0, neighborhood.w), COMPARE_DEPTH(neighborhood.w, result.z));

    return (uv + result.xy * k);
}

// Adapted from Playdead's TAA implementation
// https://github.com/playdeadgames/temporal
float4 ClipToAABB(float4 color, float p, float3 minimum, float3 maximum)
{
    // note: only clips towards aabb center (but fast!)
    float3 center  = 0.5 * (maximum + minimum);
    float3 extents = 0.5 * (maximum - minimum);

    // This is actually `distance`, however the keyword is reserved
    float4 offset = color - float4(center, p);
    float3 repeat = abs(offset.xyz / extents);

    repeat.x = max(repeat.x, max(repeat.y, repeat.z));

    if (repeat.x > 1.0)
    {
        // `color` is not intersecting (nor inside) the AABB; it's clipped to the closest extent
        return float4(center, p) + offset / repeat.x;
    }
    else
    {
        // `color` is intersecting (or inside) the AABB.

        // Note: for whatever reason moving this return statement from this else into a higher
        // scope makes the NVIDIA drivers go beyond bonkers
        return color;
    }
}

OutputSolver FragSolver(VaryingsSolver input)
{
#if TAA_DILATE_MOTION_VECTOR_SAMPLE
    float2 motion = tex2D(_CameraMotionVectorsTexture, GetClosestFragment(input.uv.zw)).xy;
#else
    // Don't dilate in ortho !
    float2 motion = tex2D(_CameraMotionVectorsTexture, input.uv.zw).xy;
#endif

    const float2 k = _MainTex_TexelSize.xy;
    float2 uv = input.uv.xy;

#if UNITY_UV_STARTS_AT_TOP
    uv -= _MainTex_TexelSize.y < 0 ? _Jitter * float2(1.0, -1.0) : _Jitter;
#else
    uv -= _Jitter;
#endif

    float4 color = tex2D(_MainTex, uv);

    float4 topLeft = tex2D(_MainTex, uv - k * 0.5);
    float4 bottomRight = tex2D(_MainTex, uv + k * 0.5);

    float4 corners = 4.0 * (topLeft + bottomRight) - 2.0 * color;

    // Sharpen output
    color += (color - (corners * 0.166667)) * 2.718282 * _SharpenParameters.x;
    color = max(0.0, color);

    // Tonemap color and history samples
    float4 average = FastToneMap((corners + color) * 0.142857);

    topLeft = FastToneMap(topLeft);
    bottomRight = FastToneMap(bottomRight);

    color = FastToneMap(color);

    float4 history = tex2D(_HistoryTex, input.uv.zw - motion);

// Only use this variant for arch viz or scenes that don't have any animated objects (camera animation is fine)
#if TAA_USE_STABLE_BUT_GHOSTY_VARIANT
    float4 luma = float4(Luminance(topLeft.rgb), Luminance(bottomRight.rgb), Luminance(average.rgb), Luminance(color.rgb));
    float nudge = lerp(6.28318530718, 0.5, saturate(2.0 * history.a)) * max(abs(luma.z - luma.w), abs(luma.x - luma.y));

    float4 minimum = lerp(bottomRight, topLeft, step(luma.x, luma.y)) - nudge;
    float4 maximum = lerp(topLeft, bottomRight, step(luma.x, luma.y)) + nudge;
#else
    float2 luma = float2(Luminance(average.rgb), Luminance(color.rgb));
    float nudge = 4.0 * abs(luma.x - luma.y);

    float4 minimum = min(bottomRight, topLeft) - nudge;
    float4 maximum = max(topLeft, bottomRight) + nudge;
#endif

    history = FastToneMap(history);

    // Clip history samples
    history = ClipToAABB(history, history.a, minimum.xyz, maximum.xyz);

    // Store fragment motion history
    color.a = saturate(smoothstep(0.002 * _MainTex_TexelSize.z, 0.0035 * _MainTex_TexelSize.z, length(motion)));

    // Blend method
    float weight = clamp(lerp(TAA_FINAL_BLEND_STATIC_FACTOR, TAA_FINAL_BLEND_DYNAMIC_FACTOR,
        length(motion) * TAA_MOTION_AMPLIFICATION), TAA_FINAL_BLEND_DYNAMIC_FACTOR, TAA_FINAL_BLEND_STATIC_FACTOR);

    color = FastToneUnmap(lerp(color, history, weight));

    OutputSolver output;

    output.destination = color;
    color.a *= TAA_FRAGMENT_MOTION_HISTORY_DECAY;

    output.history = color;

    return output;
}

// -----------------------------------------------------------------------------
// Alpha clearance

float4 FragAlphaClear(VaryingsDefault input) : SV_Target
{
    return float4(tex2D(_MainTex, input.uv).rgb, 0.0);
}

#endif // __TAA__
