#version 330 core

// fx-basic / move_scene fragment shader (NV12 variant). Same math as
// move_scene.fs but samples the hardware NV12 layout (R8 luma + RG8
// interleaved chroma) used by every HW-decoded 4K H.264 frame on
// Windows. Mirrors nv12.fs in the same way move_scene.fs mirrors
// yuv420p.fs — BT.709 limited, byte-comparable with the rest of the
// canvas.

in  vec2 textureCoordinate;
out vec4 FragColor;

uniform sampler2D tex_y;
uniform sampler2D tex_uv;
uniform float     u_opacity;

// Picture-transform applied to the fx source so the effect runs on the
// displayed (Mirror/Flip/Crop) picture, mirroring nv12.fs / yuv420p.fs.
// Defaults are no-op: u_flipX/u_flipY = 0, u_srcRect = (0,0,1,1).
uniform int  u_flipX;
uniform int  u_flipY;
uniform vec4 u_srcRect;
uniform float u_cropAngleRad;

// Rotate + place: the clip-transform rotation (mirrors yuv420p.fs applyOrient
// pixel-space branch) AND the clip's own on-canvas placement, folded into the
// fx source sampling. u_dstSize is the fx render frame (the full canvas, so
// oversize/mirror-wrap can spread past the clip's own footprint); u_unrotSize
// is the clip's own content footprint size. rotationDeg == 0 is a valid
// identity rotation (cos=1, sin=0) that still needs this remap to place the
// small content within the bigger frame -- so the gate below is keyed on
// u_unrotSize alone, not on u_rotRad. No-op (byte-equal) when u_unrotSize == 0.
uniform float u_rotRad;
uniform vec2  u_dstSize;
uniform vec2  u_unrotSize;
uniform vec2  u_rotPivot01;
uniform vec2  u_anchorPoint01;

// See move_scene.fs for the rationale; this mirrors UVs outside [0,1]
// back into [0,1] so off-frame-focus effects (e.g. Drop 01 cx=1.089)
// show a mirrored copy of the source rather than opaque black.
float mirrorTo01(float v) {
    float r = abs(v);
    r = r - 2.0 * floor(r * 0.5);   // r mod 2 in [0, 2)
    return r > 1.0 ? 2.0 - r : r;
}

void main() {
    vec2 uv = vec2(mirrorTo01(textureCoordinate.x),
                   mirrorTo01(textureCoordinate.y));

    // Rotate the mirror-wrapped (display-frame) uv back into the unrotated
    // source frame so the fx samples the rotated picture -- pixel-space
    // inverse rotation identical to yuv420p.fs applyOrient. Then Mirror/Flip,
    // then Crop (same order as applyOrient -> applySrcCrop).
    if (u_unrotSize.x > 0.0 && u_unrotSize.y > 0.0) {
        vec2 dPx = (uv - u_rotPivot01) * u_dstSize;
        float s  = sin(-u_rotRad);
        float co = cos(-u_rotRad);
        vec2 sPx = vec2(co * dPx.x - s * dPx.y, s * dPx.x + co * dPx.y);
        uv = sPx / u_unrotSize + u_anchorPoint01;
        // Outside the rotated content rect (the AABB corners at non-90 angles):
        // discard so those corners show the black background -- the fx footprint
        // is the rotated rectangle, matching macOS and the non-fx yuv420p path
        // (rather than clamped edge streaks). The 0.001 tolerance keeps exact
        // 90/180/270 from discarding a 1px edge on float rounding.
        if (uv.x < -0.001 || uv.x > 1.001 ||
            uv.y < -0.001 || uv.y > 1.001) discard;
    }
    if (u_flipX != 0) uv.x = 1.0 - uv.x;
    if (u_flipY != 0) uv.y = 1.0 - uv.y;
    if (u_cropAngleRad != 0.0) {
        // Rotates in SOURCE PIXELS about the MATERIAL's centre. Full
        // derivation (why not normalised units, why not the box's centre) is
        // in yuv420p.fs — keep all copies in step.
        vec2 texDims  = vec2(textureSize(tex_y, 0));
        vec2 cropSize = max(u_srcRect.zw - u_srcRect.xy, vec2(1e-6));
        vec2 pPx = (u_srcRect.xy + cropSize * uv) * texDims;
        vec2 cPx = 0.5 * texDims;
        vec2 d = pPx - cPx;
        // NOTE the minus: sampling rotates by -angle so the CONTENT is
        // clockwise-positive. See yuv420p.fs before "fixing" this sign.
        float s = -sin(u_cropAngleRad);
        float co = cos(u_cropAngleRad);
        vec2 rPx = cPx + vec2(co * d.x - s * d.y, s * d.x + co * d.y);
        uv = ((rPx / texDims) - u_srcRect.xy) / cropSize;
    }
    uv = mix(u_srcRect.xy, u_srcRect.zw, uv);

    float y  = (texture(tex_y,  uv).r  - 16.0/255.0)  * (255.0/219.0);
    vec2  uv2= (texture(tex_uv, uv).rg - vec2(128.0/255.0))
               * (255.0/224.0);
    float u  = uv2.x;
    float v  = uv2.y;

    // BT.709 YUV -> RGB (Kr=0.2126, Kb=0.0722).
    float r = y + 1.5748  * v;
    float g = y - 0.1873  * u - 0.4681 * v;
    float b = y + 1.8556  * u;

    vec3 rgb = clamp(vec3(r, g, b), 0.0, 1.0) * u_opacity;
    FragColor = vec4(rgb, u_opacity);
}
