/* COMPATIBILITY 
   - HLSL compilers
   - Cg   compilers
   - FX11 compilers
*/

/*
    svo's PAL single pass shader, ported to libretro
    ------------------------------------------------
    "Software composite video modulation/demodulation experiments
    The idea is to reproduce in GLSL shaders realistic composite-like
    artifacting by applying PAL modulation and demodulation.
    
    Digital texture, passed through the model of an analog channel,
    should suffer same effects as its analog counterpart and exhibit properties,
    such as dot crawl and colour bleeding, that may be desirable for faithful
    reproduction of look and feel of old computer games."
    
    https://github.com/svofski/CRT
    Copyright (c) 2016, Viacheslav Slavinsky
    All rights reserved.
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
    1. Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED.
    IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Parameter lines go here:
#pragma parameter FIR_GAIN    "FIR lowpass gain"               1.5 0.0 5.0 0.1
#pragma parameter FIR_INVGAIN "Inverse gain for luma recovery" 1.1 0.0 5.0 0.1
#pragma parameter PHASE_NOISE "Phase noise"                    1.0 0.0 5.0 0.1
#ifdef PARAMETER_UNIFORM
uniform float FIR_GAIN;
uniform float FIR_INVGAIN;
uniform float PHASE_NOISE;
#else
#define FIR_GAIN 1.5
#define FIR_INVGAIN 1.1
#define PHASE_NOISE 1.0
#endif

#include "../../compat_includes.inc"
uniform COMPAT_Texture2D(decal) : TEXUNIT0;
uniform float4x4 modelViewProj;

struct out_vertex
{
	float4 position : COMPAT_POS;
	float2 texCoord : TEXCOORD0;
#ifndef HLSL_4
	float4 Color    : COLOR;
#endif
};

out_vertex main_vertex(COMPAT_IN_VERTEX)
{
	out_vertex OUT;
#ifdef HLSL_4
	float4 position = VIN.position;
	float2 texCoord = VIN.texCoord;
#else
	OUT.Color = color;
#endif
	OUT.position = mul(modelViewProj, position);
	OUT.texCoord = texCoord;
	
	return OUT;
}

/* Subcarrier frequency */
#define FSC          4433618.75

/* Line frequency */
#define FLINE        15625.

#define VISIBLELINES 312.

#define PI           3.14159265358

#define RGB_to_YIQ  float3x3( 0.299,    0.595716,  0.211456,\
                            0.587,   -0.274453, -0.522591,\
                            0.114,   -0.321263,  0.311135)

#define YIQ_to_RGB  float3x3( 1.0   ,   1.0,       1.0,\
                            0.9563,  -0.2721,   -1.1070,\
                            0.6210,  -0.6474,    1.7046)

#define RGB_to_YUV  float3x3( 0.299,   -0.14713,   0.615,\
                            0.587,   -0.28886,  -0.514991,\
                            0.114,    0.436,    -0.10001)

#define YUV_to_RGB  float3x3( 1.0,      1.0,       1.0,\
                            0.0,     -0.39465,   2.03211,\
                            1.13983, -0.58060,   0.0)
                            
#define fetch(ofs,center,invx) COMPAT_Sample(decal, float2((ofs) * (invx) + center.x, center.y))

#define FIRTAPS 20.
float FIR1 = -0.008030271;
float FIR2 = 0.003107906;
float FIR3 = 0.016841352;
float FIR4 = 0.032545161;
float FIR5 = 0.049360136;
float FIR6 = 0.066256720;
float FIR7 = 0.082120150;
float FIR8 = 0.095848433;
float FIR9 = 0.106453014;
float FIR10 = 0.113151423;
float FIR11 = 0.115441842;
float FIR12 = 0.113151423;
float FIR13 = 0.106453014;
float FIR14 = 0.095848433;
float FIR15 = 0.082120150;
float FIR16 = 0.066256720;
float FIR17 = 0.049360136;
float FIR18 = 0.032545161;
float FIR19 = 0.016841352;
float FIR20 = 0.003107906;

/* subcarrier counts per scan line = FSC/FLINE = 283.7516 */
/* We save the reciprocal of this only to optimize it */
float counts_per_scanline_reciprocal = 1.0 / (FSC/FLINE);

float invx;

/* http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ */
float rand(float2 co)
{
    float a  = 12.9898;
    float b  = 78.233;
    float c  = 43758.5453;
    float dt = dot(co.xy, float2(a, b));
    float sn = mod(dt,3.14);

    return fract(sin(sn) * c);
}

float modulated(float2 xy, float sinwt, float coswt, COMPAT_Texture2D(decal), float invx) 
{
    float3 rgb = fetch(0., xy, invx).xyz;
    float3 yuv = mul(rgb, RGB_to_YUV);

    return clamp(yuv.x + yuv.y * sinwt + yuv.z * coswt, 0.0, 1.0);    
}

float2 modem_uv(float2 xy, float ofs, float2 texsize, float framecount, COMPAT_Texture2D(decal), float width_ratio, float altv, float invx)
{
    float t  = (xy.x + ofs * invx) * texsize.x;
    float wt = t * 2. * PI / width_ratio;

    float sinwt = sin(wt);
    float coswt = cos(wt + altv);

    float3 rgb = fetch(ofs, xy, invx).xyz;
    float3 yuv = mul(rgb, RGB_to_YUV);
    float signal = clamp(yuv.x + yuv.y * sinwt + yuv.z * coswt, 0.0, 1.0);
    
    if (PHASE_NOISE != 0.)
    {
        /* .yy is horizontal noise, .xx looks bad, .xy is classic noise */
        float2 seed = xy.yy * float(framecount);
        wt        = wt + PHASE_NOISE * (rand(seed) - 0.5);
        sinwt     = sin(wt);
        coswt     = cos(wt + altv);
    }

    return float2(signal * sinwt, signal * coswt);
}

float4 svo_PAL(float2 texCoord, float2 texture_size, float frame_count, COMPAT_Texture2D(decal))
{
    float2 xy    = texCoord;
    float width_ratio  = texture_size.x * (counts_per_scanline_reciprocal);
    float height_ratio = texture_size.y / VISIBLELINES;
    float altv         = fmod(floor(xy.y * VISIBLELINES + 0.5), 2.0) * PI;
    float invx         = 0.25 * (counts_per_scanline_reciprocal); // equals 4 samples per Fsc period

    // lowpass U/V at baseband
    float2 filtered = float2(0.0, 0.0);

	float2 uv;
	
	#define macro_loopz(c)	uv = modem_uv(xy, float(c) - FIRTAPS*0.5, texture_size, frame_count, decal, width_ratio, altv, invx); \
        filtered += FIR_GAIN * uv * FIR##c;
		
	macro_loopz(1)
	macro_loopz(2)
	macro_loopz(3)
	macro_loopz(4)
	macro_loopz(5)
	macro_loopz(6)
	macro_loopz(7)
	macro_loopz(8)
	macro_loopz(9)
	macro_loopz(10)
	macro_loopz(11)
	macro_loopz(12)
	macro_loopz(13)
	macro_loopz(14)
	macro_loopz(15)
	macro_loopz(16)
	macro_loopz(17)
	macro_loopz(18)
	macro_loopz(19)
	macro_loopz(20)

    float t  = xy.x * texture_size.x;
    float wt = t * 2. * PI / width_ratio;

    float sinwt = sin(wt);
    float coswt = cos(wt + altv);

    float luma = modulated(xy, sinwt, coswt, decal, invx) - FIR_INVGAIN * (filtered.x * sinwt + filtered.y * coswt);
    float3 yuv_result = float3(luma, filtered.x, filtered.y);

	return float4(mul(yuv_result, YUV_to_RGB), 1.0);
}

float4 main_fragment(COMPAT_IN_FRAGMENT) : COMPAT_Output
{
	return svo_PAL(VOUT.texCoord, COMPAT_texture_size, COMPAT_frame_count, decal);
}
COMPAT_END