// FXAA, temporal resolve, AMD FSR 1 EASU port, and RCAS helpers.
// EASU/RCAS portions are derived from AMD FidelityFX FSR 1 under the MIT
// License. Copyright (c) 2021 Advanced Micro Devices, Inc.
// See /THIRD_PARTY_NOTICES.md in the shader-pack root.
#ifndef NOISE_ANTIALIASING_GLSL
#define NOISE_ANTIALIASING_GLSL

#include "/lib/noise_common.glsl"
#include "/lib/noise_render_scale.glsl"

vec2 noiseClampSourceUv(vec2 uv, vec2 pixelSize) {
    vec2 scale = noiseRenderScale2(1.0 / max(pixelSize, vec2(1e-8)));
    return clamp(uv, pixelSize * 0.5, scale - pixelSize * 0.5);
}

vec2 noiseClampFxaaUv(vec2 uv, vec2 pixelSize, vec2 sourceScale) {
    return clamp(uv, pixelSize * 0.5,
        sourceScale - pixelSize * 0.5);
}

float noiseFxaaLuma(vec3 colorValue) {
    return dot(colorValue, vec3(0.299, 0.587, 0.114));
}

vec3 noiseFxaaClamped(sampler2D sourceTexture, vec2 uv, vec2 pixelSize,
                      vec2 sourceScale) {
    vec3 rgbM = texture(sourceTexture,
        noiseClampFxaaUv(uv, pixelSize, sourceScale)).rgb;
    float lumaM = noiseFxaaLuma(rgbM);
    float lumaN = noiseFxaaLuma(texture(sourceTexture, noiseClampFxaaUv(uv + vec2(0.0, -pixelSize.y), pixelSize, sourceScale)).rgb);
    float lumaS = noiseFxaaLuma(texture(sourceTexture, noiseClampFxaaUv(uv + vec2(0.0,  pixelSize.y), pixelSize, sourceScale)).rgb);
    float lumaW = noiseFxaaLuma(texture(sourceTexture, noiseClampFxaaUv(uv + vec2(-pixelSize.x, 0.0), pixelSize, sourceScale)).rgb);
    float lumaE = noiseFxaaLuma(texture(sourceTexture, noiseClampFxaaUv(uv + vec2( pixelSize.x, 0.0), pixelSize, sourceScale)).rgb);

    float lumaMin = min(lumaM, min(min(lumaN, lumaS), min(lumaW, lumaE)));
    float lumaMax = max(lumaM, max(max(lumaN, lumaS), max(lumaW, lumaE)));
    float contrast = lumaMax - lumaMin;
    if (contrast < max(0.0312, lumaMax * 0.125)) return rgbM;

    float edgeHorizontal = abs(lumaN + lumaS - 2.0 * lumaM);
    float edgeVertical = abs(lumaW + lumaE - 2.0 * lumaM);
    vec2 direction = edgeHorizontal >= edgeVertical ? vec2(pixelSize.x, 0.0) : vec2(0.0, pixelSize.y);
    float gradientA = edgeHorizontal >= edgeVertical ? abs(lumaN - lumaM) : abs(lumaW - lumaM);
    float gradientB = edgeHorizontal >= edgeVertical ? abs(lumaS - lumaM) : abs(lumaE - lumaM);
    direction *= gradientA < gradientB ? 1.0 : -1.0;

    vec3 nearColor = texture(sourceTexture,
        noiseClampFxaaUv(uv + direction * 0.5, pixelSize, sourceScale)).rgb;
    vec3 farColor = texture(sourceTexture,
        noiseClampFxaaUv(uv + direction * 1.5, pixelSize, sourceScale)).rgb;
    float subpixel = clamp(abs((lumaN + lumaS + lumaW + lumaE) * 0.25 - lumaM) / max(contrast, 1e-5), 0.0, 1.0);
    subpixel = subpixel * subpixel * (3.0 - 2.0 * subpixel);
    return mix(nearColor, (nearColor + farColor) * 0.5, subpixel * 0.65);
}

vec3 noiseFxaa(sampler2D sourceTexture, vec2 uv, vec2 pixelSize) {
    vec2 sourceScale = noiseRenderScale2(
        1.0 / max(pixelSize, vec2(1e-8)));
    return noiseFxaaClamped(sourceTexture, uv, pixelSize, sourceScale);
}

vec3 noiseFxaaFullResolution(sampler2D sourceTexture, vec2 uv,
                             vec2 pixelSize) {
    return noiseFxaaClamped(sourceTexture, uv, pixelSize, vec2(1.0));
}

vec3 noiseFetchViewport(sampler2D sourceTexture, ivec2 pixel, ivec2 viewportSize) {
    return texelFetch(sourceTexture, clamp(pixel, ivec2(0), viewportSize - 1), 0).rgb;
}

void noiseEasuSet(inout vec2 direction, inout float edgeLength, vec2 subpixel,
                  float weight, float lumaA, float lumaB, float lumaC, float lumaD, float lumaE) {
    float deltaDC = lumaD - lumaC;
    float deltaCB = lumaC - lumaB;
    float lengthX = max(abs(deltaDC), abs(deltaCB));
    float directionX = lumaD - lumaB;
    direction.x += directionX * weight;
    lengthX = clamp(abs(directionX) / max(lengthX, 1e-5), 0.0, 1.0);
    edgeLength += lengthX * lengthX * weight;

    float deltaEC = lumaE - lumaC;
    float deltaCA = lumaC - lumaA;
    float lengthY = max(abs(deltaEC), abs(deltaCA));
    float directionY = lumaE - lumaA;
    direction.y += directionY * weight;
    lengthY = clamp(abs(directionY) / max(lengthY, 1e-5), 0.0, 1.0);
    edgeLength += lengthY * lengthY * weight;
}

void noiseEasuTap(inout vec3 accumulatedColor, inout float accumulatedWeight,
                  vec2 offset, vec2 direction, vec2 anisotropy,
                  float negativeLobe, float clippingPoint, vec3 tapColor) {
    vec2 rotatedOffset = vec2(
        offset.x * direction.x + offset.y * direction.y,
        offset.x * -direction.y + offset.y * direction.x
    ) * anisotropy;
    float distanceSquared = min(dot(rotatedOffset, rotatedOffset), clippingPoint);
    float base = 0.4 * distanceSquared - 1.0;
    float window = negativeLobe * distanceSquared - 1.0;
    base *= base;
    window *= window;
    float weight = (1.5625 * base - 0.5625) * window;
    accumulatedColor += tapColor * weight;
    accumulatedWeight += weight;
}

vec3 noiseEasuOffset(sampler2D sourceTexture, ivec2 outputPixel, ivec2 outputSize,
                     vec2 sourceOffset) {
    ivec2 viewportSize = noiseRenderViewportSize(outputSize);
    vec2 scale = vec2(viewportSize) / vec2(outputSize);
    vec2 sourcePosition = (vec2(outputPixel) + 0.5) * scale - 0.5 + sourceOffset;
    ivec2 basePixel = ivec2(floor(sourcePosition));
    vec2 subpixel = fract(sourcePosition);

    vec3 b = noiseFetchViewport(sourceTexture, basePixel + ivec2( 0,-1), viewportSize);
    vec3 c = noiseFetchViewport(sourceTexture, basePixel + ivec2( 1,-1), viewportSize);
    vec3 e = noiseFetchViewport(sourceTexture, basePixel + ivec2(-1, 0), viewportSize);
    vec3 f = noiseFetchViewport(sourceTexture, basePixel + ivec2( 0, 0), viewportSize);
    vec3 g = noiseFetchViewport(sourceTexture, basePixel + ivec2( 1, 0), viewportSize);
    vec3 h = noiseFetchViewport(sourceTexture, basePixel + ivec2( 2, 0), viewportSize);
    vec3 i = noiseFetchViewport(sourceTexture, basePixel + ivec2(-1, 1), viewportSize);
    vec3 j = noiseFetchViewport(sourceTexture, basePixel + ivec2( 0, 1), viewportSize);
    vec3 k = noiseFetchViewport(sourceTexture, basePixel + ivec2( 1, 1), viewportSize);
    vec3 l = noiseFetchViewport(sourceTexture, basePixel + ivec2( 2, 1), viewportSize);
    vec3 n = noiseFetchViewport(sourceTexture, basePixel + ivec2( 0, 2), viewportSize);
    vec3 o = noiseFetchViewport(sourceTexture, basePixel + ivec2( 1, 2), viewportSize);

    float bL = b.b * 0.5 + b.r * 0.5 + b.g;
    float cL = c.b * 0.5 + c.r * 0.5 + c.g;
    float eL = e.b * 0.5 + e.r * 0.5 + e.g;
    float fL = f.b * 0.5 + f.r * 0.5 + f.g;
    float gL = g.b * 0.5 + g.r * 0.5 + g.g;
    float hL = h.b * 0.5 + h.r * 0.5 + h.g;
    float iL = i.b * 0.5 + i.r * 0.5 + i.g;
    float jL = j.b * 0.5 + j.r * 0.5 + j.g;
    float kL = k.b * 0.5 + k.r * 0.5 + k.g;
    float lL = l.b * 0.5 + l.r * 0.5 + l.g;
    float nL = n.b * 0.5 + n.r * 0.5 + n.g;
    float oL = o.b * 0.5 + o.r * 0.5 + o.g;

    vec2 direction = vec2(0.0);
    float edgeLength = 0.0;
    noiseEasuSet(direction, edgeLength, subpixel, (1.0-subpixel.x)*(1.0-subpixel.y), bL,eL,fL,gL,jL);
    noiseEasuSet(direction, edgeLength, subpixel, subpixel.x*(1.0-subpixel.y), cL,fL,gL,hL,kL);
    noiseEasuSet(direction, edgeLength, subpixel, (1.0-subpixel.x)*subpixel.y, fL,iL,jL,kL,nL);
    noiseEasuSet(direction, edgeLength, subpixel, subpixel.x*subpixel.y, gL,jL,kL,lL,oL);

    float directionLengthSquared = dot(direction, direction);
    direction = directionLengthSquared < 1.0/32768.0 ? vec2(1.0, 0.0) : direction * inversesqrt(directionLengthSquared);
    edgeLength = edgeLength * 0.5;
    edgeLength *= edgeLength;
    float stretch = 1.0 / max(max(abs(direction.x), abs(direction.y)), 1e-5);
    vec2 anisotropy = vec2(mix(1.0, stretch, edgeLength), mix(1.0, 0.5, edgeLength));
    float negativeLobe = mix(0.5, 0.21, edgeLength);
    float clippingPoint = 1.0 / negativeLobe;

    vec3 minimumColor = min(min(f, g), min(j, k));
    vec3 maximumColor = max(max(f, g), max(j, k));
    vec3 accumulatedColor = vec3(0.0);
    float accumulatedWeight = 0.0;
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 0,-1)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, b);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 1,-1)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, c);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2(-1, 0)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, e);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 0, 0)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, f);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 1, 0)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, g);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 2, 0)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, h);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2(-1, 1)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, i);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 0, 1)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, j);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 1, 1)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, k);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 2, 1)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, l);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 0, 2)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, n);
    noiseEasuTap(accumulatedColor, accumulatedWeight, vec2( 1, 2)-subpixel, direction, anisotropy, negativeLobe, clippingPoint, o);
    return clamp(accumulatedColor / max(accumulatedWeight, 1e-5), minimumColor, maximumColor);
}

vec3 noiseEasu(sampler2D sourceTexture, ivec2 outputPixel, ivec2 outputSize) {
    return noiseEasuOffset(sourceTexture, outputPixel, outputSize, vec2(0.0));
}

ivec2 noiseUpscaleSourcePixel(ivec2 outputPixel, ivec2 outputSize) {
    ivec2 viewportSize = noiseRenderViewportSize(outputSize);
    vec2 scale = vec2(viewportSize) / vec2(outputSize);
    vec2 sourcePosition = (vec2(outputPixel) + 0.5) * scale - 0.5;
    return clamp(ivec2(floor(sourcePosition + 0.5)),
        ivec2(0), viewportSize - 1);
}

vec3 noiseFastUpscale(sampler2D sourceTexture, ivec2 outputPixel,
                      ivec2 outputSize, vec2 sourceOffset) {
    ivec2 viewportSize = noiseRenderViewportSize(outputSize);
    if (all(equal(viewportSize, outputSize))) {
        return texelFetch(sourceTexture,
            clamp(outputPixel, ivec2(0), outputSize - 1), 0).rgb;
    }
    vec2 scale = vec2(viewportSize) / vec2(outputSize);
    vec2 sourcePosition = (vec2(outputPixel) + 0.5) * scale - 0.5 + sourceOffset;
    vec2 sourceUv = (sourcePosition + 0.5) / vec2(outputSize);
    vec2 halfPixel = 0.5 / vec2(outputSize);
    vec2 maximumUv = (vec2(viewportSize) - 0.5) / vec2(outputSize);
    return texture(sourceTexture, clamp(sourceUv, halfPixel, maximumUv)).rgb;
}

vec3 noiseRcas(sampler2D sourceTexture, ivec2 pixel, ivec2 imageSizeValue, float sharpnessStops) {
    ivec2 maximumPixel = imageSizeValue - 1;
    vec3 b = texelFetch(sourceTexture, clamp(pixel + ivec2( 0,-1), ivec2(0), maximumPixel), 0).rgb;
    vec3 d = texelFetch(sourceTexture, clamp(pixel + ivec2(-1, 0), ivec2(0), maximumPixel), 0).rgb;
    vec3 e = texelFetch(sourceTexture, clamp(pixel,                  ivec2(0), maximumPixel), 0).rgb;
    vec3 f = texelFetch(sourceTexture, clamp(pixel + ivec2( 1, 0), ivec2(0), maximumPixel), 0).rgb;
    vec3 h = texelFetch(sourceTexture, clamp(pixel + ivec2( 0, 1), ivec2(0), maximumPixel), 0).rgb;

    vec3 minimumRing = min(min(b, d), min(f, h));
    vec3 maximumRing = max(max(b, d), max(f, h));
    vec3 hitMinimum = minimumRing / max(4.0 * maximumRing, vec3(1e-5));
    vec3 hitMaximum = (vec3(1.0) - maximumRing) / min(4.0 * minimumRing - vec3(4.0), vec3(-1e-5));
    vec3 lobeChannels = max(-hitMinimum, hitMaximum);
    float lobe = max(-0.1875, min(max(max(lobeChannels.r, lobeChannels.g), lobeChannels.b), 0.0));
    lobe *= exp2(-sharpnessStops);

    float centerLuma = noiseFxaaLuma(e);
    float ringLuma = noiseFxaaLuma((b + d + f + h) * 0.25);
    float noiseFactor = 1.0 - 0.5 * clamp(abs(ringLuma - centerLuma) /
        max(max(noiseFxaaLuma(maximumRing) - noiseFxaaLuma(minimumRing), 1e-4), 1e-4), 0.0, 1.0);
    lobe *= noiseFactor;
    return (lobe * (b + d + f + h) + e) / max(4.0 * lobe + 1.0, 1e-5);
}

#endif
