// CRT Hyllian
// Based on Hyllian's CRT Shader (MIT License)
// Copyright (C) 2011-2016 Hyllian - sergiogdb@gmail.com
// Ported from https://github.com/libretro/common-shaders/blob/master/crt/shaders/crt-hyllian.cg
// Note: Best results with integer scaling

//!BGFX EFFECT
//!VERSION 1
//!NAME CRT Hyllian
//!CATEGORY CRT
//!DESCRIPTION Hyllian's CRT shader featuring cubic filtering, phosphor mask simulation, and configurable scanlines. Supports both horizontal and vertical scanline modes. Best with integer scaling.

//!PARAMETER
//!LABEL Phosphor
//!DESC Enable phosphor mask pattern simulation
//!DEFAULT 1
//!MIN 0
//!MAX 1
//!STEP 1
int phosphor;

//!PARAMETER
//!LABEL Vertical Scanlines
//!DESC Use vertical scanlines instead of horizontal
//!DEFAULT 0
//!MIN 0
//!MAX 1
//!STEP 1
int vScanlines;

//!PARAMETER
//!LABEL Input Gamma
//!DESC Gamma correction for input image decoding
//!DEFAULT 2.5
//!MIN 0
//!MAX 5
//!STEP 0.01
float inputGamma;

//!PARAMETER
//!LABEL Output Gamma
//!DESC Gamma correction for output image encoding
//!DEFAULT 2.2
//!MIN 0
//!MAX 5
//!STEP 0.01
float outputGamma;

//!PARAMETER
//!LABEL Sharpness
//!DESC Texture sampling sharpness (higher = sharper pixels)
//!DEFAULT 1
//!MIN 1
//!MAX 5
//!STEP 1
int sharpness;

//!PARAMETER
//!LABEL Color Boost
//!DESC Overall color saturation multiplier
//!DEFAULT 1.5
//!MIN 1
//!MAX 2
//!STEP 0.01
float colorBoost;

//!PARAMETER
//!LABEL Red Boost
//!DESC Red channel intensity multiplier
//!DEFAULT 1
//!MIN 1
//!MAX 2
//!STEP 0.01
float redBoost;

//!PARAMETER
//!LABEL Green Boost
//!DESC Green channel intensity multiplier
//!DEFAULT 1
//!MIN 1
//!MAX 2
//!STEP 0.01
float greenBoost;

//!PARAMETER
//!LABEL Blue Boost
//!DESC Blue channel intensity multiplier
//!DEFAULT 1
//!MIN 1
//!MAX 2
//!STEP 0.01
float blueBoost;

//!PARAMETER
//!LABEL Scanline Strength
//!DESC Intensity of the scanline darkening effect
//!DEFAULT 0.5
//!MIN 0
//!MAX 1
//!STEP 0.01
float scanlinesStrength;

//!PARAMETER
//!LABEL Min Beam Width
//!DESC Minimum scanline beam width (affects dark areas)
//!DEFAULT 0.86
//!MIN 0
//!MAX 1
//!STEP 0.01
float beamMinWidth;

//!PARAMETER
//!LABEL Max Beam Width
//!DESC Maximum scanline beam width (affects bright areas)
//!DEFAULT 1
//!MIN 0
//!MAX 1
//!STEP 0.01
float beamMaxWidth;

//!PARAMETER
//!LABEL Anti-Ringing
//!DESC Reduces ringing artifacts from cubic filtering
//!DEFAULT 0.8
//!MIN 0
//!MAX 1
//!STEP 0.01
float crtAntiRinging;

//!TEXTURE
Texture2D INPUT;

//!TEXTURE
Texture2D OUTPUT;

//!SAMPLER
//!FILTER POINT
SamplerState sam;


//!PASS 1
//!STYLE PS
//!IN INPUT
//!OUT OUTPUT

#pragma warning(disable: 3571)

// Gamma conversion macros
#define GAMMA_IN(color) pow(color, float3(inputGamma, inputGamma, inputGamma))
#define GAMMA_OUT(color) pow(color, float3(1.0 / outputGamma, 1.0 / outputGamma, 1.0 / outputGamma))

// Cubic filter configuration
// Common filter presets:
//   B = 0.0, C = 0.0  =>  Hermite cubic filter
//   B = 1.0, C = 0.0  =>  Cubic B-Spline filter
//   B = 0.0, C = 0.5  =>  Catmull-Rom Spline filter (default)
//   B = C = 1.0/3.0   =>  Mitchell-Netravali cubic filter
//   B = 0.3782, C = 0.3109  =>  Robidoux filter
//   B = 0.2620, C = 0.3690  =>  Robidoux Sharp filter
const static float B = 0.0;
const static float C = 0.5;

// Precomputed cubic filter matrix
const static float4x4 invX = float4x4(
    (-B - 6.0 * C) / 6.0, (3.0 * B + 12.0 * C) / 6.0, (-3.0 * B - 6.0 * C) / 6.0, B / 6.0,
    (12.0 - 9.0 * B - 6.0 * C) / 6.0, (-18.0 + 12.0 * B + 6.0 * C) / 6.0, 0.0, (6.0 - 2.0 * B) / 6.0,
    -(12.0 - 9.0 * B - 6.0 * C) / 6.0, (18.0 - 15.0 * B - 12.0 * C) / 6.0, (3.0 * B + 6.0 * C) / 6.0, B / 6.0,
    (B + 6.0 * C) / 6.0, -C, 0.0, 0.0
);

float4 Pass1(float2 pos) {
    uint2 inputSize = GetInputSize();
    uint2 outputSize = GetOutputSize();
    float3 color;

    float2 TextureSize = float2(sharpness * inputSize.x, inputSize.y);

    // Direction vectors for scanline orientation
    float2 dx = lerp(float2(1.0 / TextureSize.x, 0.0), float2(0.0, 1.0 / TextureSize.y), vScanlines);
    float2 dy = lerp(float2(0.0, 1.0 / TextureSize.y), float2(1.0 / TextureSize.x, 0.0), vScanlines);

    float2 pix_coord = pos * TextureSize + float2(-0.5, 0.5);
    float2 tc = lerp(
        (floor(pix_coord) + float2(0.5, 0.5)) / TextureSize,
        (floor(pix_coord) + float2(1.0, -0.5)) / TextureSize,
        vScanlines
    );
    float2 fp = lerp(frac(pix_coord), frac(pix_coord.yx), vScanlines);

    // Sample 8 neighboring texels for cubic interpolation
    float3 c00 = GAMMA_IN(INPUT.SampleLevel(sam, tc - dx - dy, 0).xyz);
    float3 c01 = GAMMA_IN(INPUT.SampleLevel(sam, tc - dy, 0).xyz);
    float3 c02 = GAMMA_IN(INPUT.SampleLevel(sam, tc + dx - dy, 0).xyz);
    float3 c03 = GAMMA_IN(INPUT.SampleLevel(sam, tc + 2.0 * dx - dy, 0).xyz);
    float3 c10 = GAMMA_IN(INPUT.SampleLevel(sam, tc - dx, 0).xyz);
    float3 c11 = GAMMA_IN(INPUT.SampleLevel(sam, tc, 0).xyz);
    float3 c12 = GAMMA_IN(INPUT.SampleLevel(sam, tc + dx, 0).xyz);
    float3 c13 = GAMMA_IN(INPUT.SampleLevel(sam, tc + 2.0 * dx, 0).xyz);

    // Compute min/max for anti-ringing
    float3 min_sample = min(min(c01, c11), min(c02, c12));
    float3 max_sample = max(max(c01, c11), max(c02, c12));

    // Build color matrices for cubic filter
    float4x3 color_matrix0 = float4x3(c00, c01, c02, c03);
    float4x3 color_matrix1 = float4x3(c10, c11, c12, c13);

    // Apply cubic interpolation
    float4 invX_Px = mul(invX, float4(fp.x * fp.x * fp.x, fp.x * fp.x, fp.x, 1.0));
    float3 color0 = mul(invX_Px, color_matrix0);
    float3 color1 = mul(invX_Px, color_matrix1);

    // Anti-ringing: clamp to prevent overshoot
    float3 aux = color0;
    color0 = clamp(color0, min_sample, max_sample);
    color0 = lerp(aux, color0, crtAntiRinging);
    aux = color1;
    color1 = clamp(color1, min_sample, max_sample);
    color1 = lerp(aux, color1, crtAntiRinging);

    // Scanline beam calculation
    float pos0 = fp.y;
    float pos1 = 1 - fp.y;

    float3 lum0 = lerp(beamMinWidth, beamMaxWidth, color0);
    float3 lum1 = lerp(beamMinWidth, beamMaxWidth, color1);

    float3 d0 = clamp(pos0 / (lum0 + 0.0000001), 0.0, 1.0);
    float3 d1 = clamp(pos1 / (lum1 + 0.0000001), 0.0, 1.0);

    d0 = exp(-10.0 * scanlinesStrength * d0 * d0);
    d1 = exp(-10.0 * scanlinesStrength * d1 * d1);

    color = clamp(color0 * d0 + color1 * d1, 0.0, 1.0);

    // Apply color boost
    color *= colorBoost * float3(redBoost, greenBoost, blueBoost);

    // Phosphor mask pattern
    float mod_factor = lerp(pos.x * outputSize.x, pos.y * outputSize.y, vScanlines);
    float3 dotMaskWeights = lerp(
        float3(1.0, 0.7, 1.0),
        float3(0.7, 1.0, 0.7),
        floor(fmod(mod_factor, 2.0))
    );
    color.rgb *= lerp(1.0, dotMaskWeights, phosphor);

    // Output gamma correction
    color = GAMMA_OUT(color);

    return float4(color, 1.0);
}
