
uniform sampler2D g_color;

layout (location = 0) out vec4 frag_color;

float luma(vec3 rgb) {
    return dot(rgb, vec3(0.299, 0.587, 0.114));
}

#define ITERATIONS 10
#define QUALITY(i) (1.0 + min(0.0, float(i - 5) * 0.5))

const float FXAA_EDGE_THRESHOLD = 1.0/8.0;
const float FXAA_EDGE_THRESHOLD_MIN = 1.0/16.0;
const float FXAA_SUBPIX_QUALITY = 0.75;

void main() {
    vec2 screen_size = vec2(textureSize(g_color, 0));
    vec2 inv_screen_size = 1.0 / screen_size;
    vec2 input_uv = gl_FragCoord.xy * inv_screen_size;

    vec3 rgb_mid = texture(g_color, input_uv, 0).rgb;
    vec3 rgb_down = textureOffset(g_color, input_uv, ivec2(0, -1)).rgb;
    vec3 rgb_up = textureOffset(g_color, input_uv, ivec2(0, 1)).rgb;
    vec3 rgb_left = textureOffset(g_color, input_uv, ivec2(-1, 0)).rgb;
    vec3 rgb_right = textureOffset(g_color, input_uv, ivec2(1, 0)).rgb;

    float luma_mid = luma(rgb_mid);
    float luma_down = luma(rgb_down);
    float luma_up = luma(rgb_up);
    float luma_left = luma(rgb_left);
    float luma_right = luma(rgb_right);

    float range_min = min(luma_mid, min(min(luma_down, luma_left), min(luma_up, luma_right)));
    float range_max = max(luma_mid, max(max(luma_down, luma_left), max(luma_up, luma_right)));
    float range = range_max - range_min;

    if (range < max(FXAA_EDGE_THRESHOLD_MIN, range_max * FXAA_EDGE_THRESHOLD)) {
        frag_color = vec4(rgb_mid, 1.0);
        return;
    }

    vec3 rgb_down_left = textureOffset(g_color, input_uv, ivec2(-1, -1)).rgb;
    vec3 rgb_up_right = textureOffset(g_color, input_uv, ivec2(1, 1)).rgb;
    vec3 rgb_up_left = textureOffset(g_color, input_uv, ivec2(-1, 1)).rgb;
    vec3 rgb_down_right = textureOffset(g_color, input_uv, ivec2(1, -1)).rgb;

    float luma_down_left = luma(rgb_down_left);
    float luma_up_right = luma(rgb_up_right);
    float luma_up_left = luma(rgb_up_left);
    float luma_down_right = luma(rgb_down_right);

    float luma_up_down = luma_down + luma_up;
    float luma_left_right = luma_right + luma_left;

    float luma_left_c = luma_down_left + luma_up_left;
    float luma_down_c = luma_down_left + luma_down_right;
    float luma_right_c = luma_down_right + luma_up_right;
    float luma_up_c = luma_up_right + luma_up_left;

    float edge_horz = abs(-2.0 * luma_left + luma_left_c) + abs(-2.0 * luma_mid + luma_up_down) * 2.0 + abs(-2.0 * luma_right + luma_right_c);
    float edge_vert = abs(-2.0 * luma_up + luma_up_c) + abs(-2.0 * luma_mid + luma_left_right) * 2.0 + abs(-2.0 * luma_down + luma_down_c);

    bool is_horz = edge_horz >= edge_vert;

    float luma1 = is_horz ? luma_down : luma_left;
    float luma2 = is_horz ? luma_up : luma_right;
    float gradient1 = luma1 - luma_mid;
    float gradient2 = luma2 - luma_mid;

    bool is_1_steepest = abs(gradient1) >= abs(gradient2);

    float gradient_scaled = 0.25 * max(abs(gradient1), abs(gradient2));

    float step_length = is_horz ? inv_screen_size.y : inv_screen_size.x;
    float luma_local_avg = 0.0;
    if (is_1_steepest) {
        step_length = -step_length;
        luma_local_avg = 0.5 * (luma1 + luma_mid);
    } else {
        luma_local_avg = 0.5 * (luma2 + luma_mid);
    }

    vec2 uv = input_uv;
    if (is_horz) {
        uv.y += step_length * 0.5;
    } else {
        uv.x += step_length * 0.5;
    }

    vec2 offset = is_horz ? vec2(inv_screen_size.x, 0.0) : vec2(0.0, inv_screen_size.y);
    vec2 uv1 = uv - offset;
    vec2 uv2 = uv + offset;

    float luma_end1 = luma(texture(g_color, uv1).rgb) - luma_local_avg;
    float luma_end2 = luma(texture(g_color, uv2).rgb) - luma_local_avg;

    bool reached1 = abs(luma_end1) >= gradient_scaled;
    bool reached2 = abs(luma_end2) >= gradient_scaled;
    bool reached_both = reached1 && reached2;

    if (!reached1) {
        uv1 -= offset;
    }
    if (!reached2) {
        uv2 += offset;
    }

    for (int i = 2; i < ITERATIONS; i++) {
        if (!reached1) {
            luma_end1 = luma(texture(g_color, uv1).rgb) - luma_local_avg;
        }
        if (!reached2) {
            luma_end2 = luma(texture(g_color, uv2).rgb) - luma_local_avg;
        }
        bool reached1 = abs(luma_end1) >= gradient_scaled;
        bool reached2 = abs(luma_end2) >= gradient_scaled;
        bool reached_both = reached1 && reached2;

        if (!reached1) {
            uv1 -= offset * QUALITY(i);
        }
        if (!reached2) {
            uv2 += offset * QUALITY(i);
        }
        if (reached_both) {
            break;
        }
    }

    float distance1 = is_horz ? (input_uv.x - uv1.x) : (input_uv.y - uv1.y);
    float distance2 = is_horz ? (uv2.x - input_uv.x) : (uv2.y - input_uv.y);

    bool is_direction1 = distance1 < distance2;
    float distance_final = min(distance1, distance2);

    float edge_thickness = distance1 + distance2;
    float pixel_offset = -distance_final/edge_thickness + 0.5;

    bool is_luma_center_smaller = luma_mid < luma_local_avg;
    bool correct_variation = ((is_direction1 ? luma_end1 : luma_end2) < 0.0) != is_luma_center_smaller;
    float final_offset = correct_variation ? pixel_offset : 0.0;

    float luma_avg = (1.0 / 12.0) * (2.0 * (luma_up_down + luma_left_right) + luma_left_c + luma_right_c);
    float sub_pixel_offset1 = clamp(abs(luma_avg - luma_mid) / range, 0.0, 1.0);
    float sub_pixel_offset2 = (-2.0 * sub_pixel_offset1 + 3.0) * sub_pixel_offset1 * sub_pixel_offset1;
    float sub_pixel_offset_final = sub_pixel_offset2 * sub_pixel_offset2 * FXAA_SUBPIX_QUALITY;
    final_offset = max(final_offset, sub_pixel_offset_final);

    vec2 final_uv = input_uv;
    if (is_horz) {
        final_uv.y += final_offset * step_length;
    } else {
        final_uv.x += final_offset * step_length;
    }

    frag_color = vec4(texture(g_color, final_uv).rgb, 1.0);
}
