#version 330 core
// One jump-flooding step for the cutout stroke's signed distance field.
//
// Run repeatedly with u_step halving each time (…, 8, 4, 2, 1), ping-ponging
// between the two RG32F scratch targets. Each invocation keeps whichever of
// the 9 candidate seeds (self + 8 neighbours at ±u_step) is closest to this
// texel's own centre.
//
// Reads use texelFetch, NOT texture(): the stored values are COORDINATES, and
// bilinear interpolation of two different seed positions produces a point that
// is on neither silhouette. texelFetch bypasses filtering entirely, which is
// also why the scratch textures can stay GL_LINEAR for
// cutout_sdf_resolve.fs's output to be sampled smoothly later.

layout (location = 0) out vec2 FragOut;

uniform sampler2D u_field;
uniform vec2      u_sdfSize;   // logical raster size (may be < allocated FBO)
uniform int       u_step;      // this pass's jump distance, in texels

const float kNoSeed = -1.0e6;

void main() {
    ivec2 p      = ivec2(floor(gl_FragCoord.xy));
    vec2  centre = vec2(p) + 0.5;
    ivec2 limit  = ivec2(u_sdfSize) - ivec2(1);

    vec2  best     = texelFetch(u_field, p, 0).rg;
    float bestDist = (best.x < -1.0e5) ? 1.0e30 : distance(centre, best);

    for (int dy = -1; dy <= 1; ++dy) {
        for (int dx = -1; dx <= 1; ++dx) {
            if (dx == 0 && dy == 0) continue;
            ivec2 q = clamp(p + ivec2(dx, dy) * u_step, ivec2(0), limit);
            vec2  candidate = texelFetch(u_field, q, 0).rg;
            if (candidate.x < -1.0e5) continue;
            float d = distance(centre, candidate);
            if (d < bestDist) {
                bestDist = d;
                best     = candidate;
            }
        }
    }

    FragOut = best;
}
