#ifndef __MOTION_BLUR__
#define __MOTION_BLUR__

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
//#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"

TEXTURE2D_X(_MainTex);
TEXTURE2D_X(_MotionVectorTexture);
TEXTURE2D_X(_CameraDepthTexture);

#if UNITY_VERSION <= 202310
SAMPLER(sampler_LinearClamp);
SAMPLER(sampler_PointClamp);
#endif


float4 _MainTex_TexelSize;
float4 _MotionVectorTexture_TexelSize;

// Packed velocity texture (2/10/10/10)
TEXTURE2D_X(_VelocityTex);
float2 _VelocityTex_TexelSize;

// NeighborMax texture
TEXTURE2D_X(_NeighborMaxTex);
float2 _NeighborMaxTex_TexelSize;

// Velocity scale factor
float _VelocityScale;

// TileMax filter parameters
int _TileMaxLoop;
float2 _TileMaxOffs;

// Maximum blur radius (in pixels)
half _MaxBlurRadius;
float _RcpMaxBlurRadius;

// Filter parameters/coefficients
half _LoopCount;

struct AttributesDefault
{
    float4 vertex : POSITION;
    float2 texcoord : TEXCOORD0;
};
struct VaryingsDefault
{
    float4 positionHCS : SV_POSITION;
    float2 uv : TEXCOORD0;
};
VaryingsDefault VertDefault(AttributesDefault input)
{
    VaryingsDefault output;
    output.positionHCS = TransformObjectToHClip(input.vertex.xyz);
    output.uv = input.texcoord;
    return output;
}
struct VaryingsMultitex
{
    float4 pos : SV_POSITION;
    float2 uv0 : TEXCOORD0;
    float2 uv1 : TEXCOORD1;
};

VaryingsMultitex VertMultitex(AttributesDefault v)
{
    VaryingsMultitex o;
    o.pos = TransformObjectToHClip(v.vertex.xyz);
    o.uv0 = v.texcoord.xy;
    o.uv1 = v.texcoord.xy;

#if UNITY_UV_STARTS_AT_TOP
    if (_MainTex_TexelSize.y < 0.0)
        o.uv1.y = 1.0 - v.texcoord.y;
#endif

    return o;
}

// -----------------------------------------------------------------------------
// helper functions
#define UNITY_PI_2      (UNITY_PI * 2.0)

inline half2 MaxV(half2 v1, half2 v2) { return dot(v1, v1) < dot(v2, v2) ? v2 : v1; }

// Interleaved gradient function from Jimenez 2014 http://goo.gl/eomGso
float GradientNoise(float2 uv)
{
    uv = floor(uv * _ScreenParams.xy);
    float f = dot(float2(0.06711056, 0.00583715), uv);
    return frac(52.9829189 * frac(f));
}

// Z buffer depth to linear 0-1 depth
// Handles orthographic projection correctly
float LinearizeDepth(float z)
{
    float isOrtho = unity_OrthoParams.w;
    float isPers = 1.0 - unity_OrthoParams.w;
    z *= _ZBufferParams.x;
    return (1.0 - isOrtho * z) / (isPers * z + _ZBufferParams.y);
}
// -----------------------------------------------------------------------------
// Prefilter

// Velocity texture setup
half4 FragVelocitySetup(VaryingsDefault i) : SV_Target
{
    // Sample the motion vector.
    float2 v = SAMPLE_TEXTURE2D_X(_MotionVectorTexture, sampler_LinearClamp, i.uv).xy;

    // Apply the exposure time and convert to the pixel space.
    v *= (_VelocityScale * 0.5) * _MotionVectorTexture_TexelSize.zw;

    // Clamp the vector with the maximum blur radius.
    v /= max(1.0, length(v) * _RcpMaxBlurRadius);

    // Sample the depth of the pixel.
    half d = LinearizeDepth(SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_PointClamp, i.uv).r);

    // Pack into 10/10/10/2 format.
    return half4((v * _RcpMaxBlurRadius + 1.0) * 0.5, d, 0.0);
}

// TileMax filter (2 pixel width with normalization)
half4 FragTileMax1(VaryingsDefault i) : SV_Target
{
    float4 d = _MainTex_TexelSize.xyxy * float4(-0.5, -0.5, 0.5, 0.5);

    half2 v1 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.xy).rg;
    half2 v2 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.zy).rg;
    half2 v3 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.xw).rg;
    half2 v4 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.zw).rg;

    v1 = (v1 * 2.0 - 1.0) * _MaxBlurRadius;
    v2 = (v2 * 2.0 - 1.0) * _MaxBlurRadius;
    v3 = (v3 * 2.0 - 1.0) * _MaxBlurRadius;
    v4 = (v4 * 2.0 - 1.0) * _MaxBlurRadius;

    return half4(MaxV(MaxV(MaxV(v1, v2), v3), v4), 0.0, 0.0);
}

// TileMax filter (2 pixel width)
half4 FragTileMax2(VaryingsDefault i) : SV_Target
{
    float4 d = _MainTex_TexelSize.xyxy * float4(-0.5, -0.5, 0.5, 0.5);

    half2 v1 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.xy).rg;
    half2 v2 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.zy).rg;
    half2 v3 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.xw).rg;
    half2 v4 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.zw).rg;

    return half4(MaxV(MaxV(MaxV(v1, v2), v3), v4), 0.0, 0.0);
}

// TileMax filter (variable width)
half4 FragTileMaxV(VaryingsDefault i) : SV_Target
{
    float2 uv0 = i.uv + _MainTex_TexelSize.xy * _TileMaxOffs.xy;

    float2 du = float2(_MainTex_TexelSize.x, 0.0);
    float2 dv = float2(0, _MainTex_TexelSize.y);

    half2 vo = 0;

    UNITY_LOOP
    for (int ix = 0; ix < _TileMaxLoop; ix++)
    {
        UNITY_LOOP
        for (int iy = 0; iy < _TileMaxLoop; iy++)
        {
            float2 uv = uv0 + du * ix + dv * iy;
            vo = MaxV(vo, SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, uv).rg);
        }
    }

    return half4(vo, 0.0, 0.0);
}

// NeighborMax filter
half4 FragNeighborMax(VaryingsDefault i) : SV_Target
{
    const half cw = 1.01; // Center weight tweak

    float4 d = _MainTex_TexelSize.xyxy * float4(1.0, 1.0, -1.0, 0.0);

    half2 v1 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv - d.xy).rg;
    half2 v2 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv - d.wy).rg;
    half2 v3 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv - d.zy).rg;

    half2 v4 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv - d.xw).rg;
    half2 v5 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv).rg * cw;
    half2 v6 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.xw).rg;

    half2 v7 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.zy).rg;
    half2 v8 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.wy).rg;
    half2 v9 = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv + d.xy).rg;

    half2 va = MaxV(v1, MaxV(v2, v3));
    half2 vb = MaxV(v4, MaxV(v5, v6));
    half2 vc = MaxV(v7, MaxV(v8, v9));

    return half4(MaxV(va, MaxV(vb, vc)) * (1.0 / cw), 0.0, 0.0);
}

// -----------------------------------------------------------------------------
// Reconstruction

// Returns true or false with a given interval.
bool Interval(half phase, half interval)
{
    return frac(phase / interval) > 0.499;
}

// Jitter function for tile lookup
float2 JitterTile(float2 uv)
{
    float rx, ry;
    sincos(GradientNoise(uv + float2(2.0, 0.0)) * PI * 2.0, ry, rx);
    return float2(rx, ry) * _NeighborMaxTex_TexelSize.xy * 0.25;
}

// Velocity sampling function
half3 SampleVelocity(float2 uv)
{
    half3 v = SAMPLE_TEXTURE2D_X_LOD(_VelocityTex, sampler_LinearClamp, uv, 0).xyz;
    return half3((v.xy * 2.0 - 1.0) * _MaxBlurRadius, v.z);
}

// Reconstruction filter
half4 FragReconstruction(VaryingsMultitex i) : SV_Target
{
    // Color sample at the center point
    const half4 c_p = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, i.uv0);

    // Velocity/Depth sample at the center point
    const half3 vd_p = SampleVelocity(i.uv1);
    const half l_v_p = max(length(vd_p.xy), 0.5);
    const half rcp_d_p = 1.0 / vd_p.z;

    // NeighborMax vector sample at the center point
    const half2 v_max = SAMPLE_TEXTURE2D_X(_NeighborMaxTex, sampler_LinearClamp, i.uv1 + JitterTile(i.uv1)).xy;
    const half l_v_max = length(v_max);
    const half rcp_l_v_max = 1.0 / l_v_max;

    // Escape early if the NeighborMax vector is small enough.
    if (l_v_max < 2.0) return c_p;

    // Use V_p as a secondary sampling direction except when it's too small
    // compared to V_max. This vector is rescaled to be the length of V_max.
    const half2 v_alt = (l_v_p * 2.0 > l_v_max) ? vd_p.xy * (l_v_max / l_v_p) : v_max;

    // Determine the sample count.
    const half sc = floor(min(_LoopCount, l_v_max * 0.5));

    // Loop variables (starts from the outermost sample)
    const half dt = 1.0 / sc;
    const half t_offs = (GradientNoise(i.uv0) - 0.5) * dt;
    half t = 1.0 - dt * 0.5;
    half count = 0.0;

    // Background velocity
    // This is used for tracking the maximum velocity in the background layer.
    half l_v_bg = max(l_v_p, 1.0);

    // Color accumlation
    half4 acc = 0.0;

    UNITY_LOOP while (t > dt * 0.25)
    {
        // Sampling direction (switched per every two samples)
        const half2 v_s = Interval(count, 4.0) ? v_alt : v_max;

        // Sample position (inverted per every sample)
        const half t_s = (Interval(count, 2.0) ? -t : t) + t_offs;

        // Distance to the sample position
        const half l_t = l_v_max * abs(t_s);

        // UVs for the sample position
        const float2 uv0 = i.uv0 + v_s * t_s * _MainTex_TexelSize.xy;
        const float2 uv1 = i.uv1 + v_s * t_s * _VelocityTex_TexelSize.xy;

        // Color sample
        const half3 c = SAMPLE_TEXTURE2D_X_LOD(_MainTex, sampler_LinearClamp, uv0, 0).rgb;

        // Velocity/Depth sample
        const half3 vd = SampleVelocity(uv1);

        // Background/Foreground separation
        const half fg = saturate((vd_p.z - vd.z) * 20.0 * rcp_d_p);

        // Length of the velocity vector
        const half l_v = lerp(l_v_bg, length(vd.xy), fg);

        // Sample weight
        // (Distance test) * (Spreading out by motion) * (Triangular window)
        const half w = saturate(l_v - l_t) / l_v * (1.2 - t);

        // Color accumulation
        acc += half4(c, 1.0) * w;

        // Update the background velocity.
        l_v_bg = max(l_v_bg, l_v);

        // Advance to the next sample.
        t = Interval(count, 2.0) ? t - dt : t;
        count += 1.0;
    }

    // Add the center sample.
    acc += half4(c_p.rgb, 1.0) * (1.2 / (l_v_bg * sc * 2.0));

    return half4(acc.rgb / acc.a, c_p.a);
}

#endif // __MOTION_BLUR__
