/* COMPATIBILITY 
   - HLSL compilers
   - Cg   compilers
*/

/*
    CRT shader

    Copyright (C) 2010, 2011 cgwg, Themaister and DOLLS

    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the Free
    Software Foundation; either version 2 of the License, or (at your option)
    any later version.

    (cgwg gave their consent to have the original version of this shader
    distributed under the GPL in this message:

        http://board.byuu.org/viewtopic.php?p=26075#p26075

        "Feel free to distribute my shaders under the GPL. After all, the
        barrel distortion code was taken from the Curvature shader, which is
        under the GPL."
    )
*/


struct tex_coords
{
   float2 texCoord  : TEXCOORD0; 
   float mod_factor : TEXCOORD1;
   float2 one       : TEXCOORD2;
};

struct input
{
  float2 video_size;
  float2 texture_size;
  float2 output_size;
  float  frame_count;
  float  frame_direction;
  float frame_rotation;
};

/* Default Vertex shader */
void main_vertex
(
   float4 position : POSITION,
   out float4 oPosition : POSITION,
   uniform float4x4 modelViewProj,

   float4 color : COLOR,
   out float4 oColor : COLOR,

   float2 tex : TEXCOORD,

   uniform input IN,
   out tex_coords coords
 )
{
   oPosition = mul(modelViewProj, position);
   oColor = color;

   coords.texCoord = tex + float2(0.0, 0.0);
   coords.mod_factor = tex.x * IN.output_size.x * IN.texture_size.x / IN.video_size.x;
   coords.one = 1.0 / IN.texture_size;
}


// Comment the next line to disable interpolation in linear gamma (and gain speed).
#define LINEAR_PROCESSING

// Compensate for 16-235 level range as per Rec. 601.
// #define REF_LEVELS
		
// Enable screen curvature.
#define CURVATURE
		
// Controls the intensity of the barrel distortion used to emulate the
// curvature of a CRT. 0.0 is perfectly flat, 1.0 is annoyingly
// distorted, higher values are increasingly ridiculous.
#define distortion 0.0

// Simulate a CRT gamma of 2.4.
#define inputGamma  2.4

// Compensate for the standard sRGB gamma of 2.2.
#define outputGamma 2.2

// Macros.
#define FIX(c) max(abs(c), 1e-5);
#define PI 3.141592653589

#ifdef REF_LEVELS
#       define LEVELS(c) max((c - 16.0 / 255.0) * 255.0 / (235.0 - 16.0), 0.0)
#else
#       define LEVELS(c) c
#endif

#ifdef LINEAR_PROCESSING
#       define TEX2D(c) pow(LEVELS(tex2D(s_p, (c))), float4(inputGamma, inputGamma, inputGamma, inputGamma))
#else
#       define TEX2D(c) LEVELS(tex2D(s_p, (c)))
#endif

// Apply radial distortion to the given coordinate.
float2 radialDistortion(float2 coord, input ruby) {
	coord *= ruby.texture_size.x / ruby.video_size.x;
	float2 cc = coord - 0.5;
	float dist = dot(cc, cc) * distortion;				
	return (coord + cc * (1.0 + dist) * dist) * ruby.video_size.x / ruby.texture_size.x;
}

// Calculate the influence of a scanline on the current pixel.
//
// 'distance' is the distance in texture coordinates from the current
// pixel to the scanline in question.
// 'color' is the colour of the scanline at the horizontal location of
// the current pixel.
float4 scanlineWeights(float distance, float4 color)
{
	// The "width" of the scanline beam is set as 2*(1 + x^4) for
	// each RGB channel.
	float4 wid = 2.0 + 2.0 * pow(color, float4(4.0, 4.0, 4.0, 4.0));

	// The "weights" lines basically specify the formula that gives
	// you the profile of the beam, i.e. the intensity as
	// a function of distance from the vertical center of the
	// scanline. In this case, it is gaussian if width=2, and
	// becomes nongaussian for larger widths. Ideally this should
	// be normalized so that the integral across the beam is
	// independent of its width. That is, for a narrower beam
	// "weights" should have a higher peak at the center of the
	// scanline than for a wider beam.
	float4 weights = float4(distance / 0.3, distance / 0.3, distance / 0.3, distance / 0.3);
	return 1.7 * exp(-pow(weights * rsqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
}


float4 main_fragment(in tex_coords co, uniform input IN, uniform sampler2D s_p : TEXUNIT0) : COLOR
{
	// Here's a helpful diagram to keep in mind while trying to
	// understand the code:
	//
	//  |      |      |      |      |
	// -------------------------------
	//  |      |      |      |      |
	//  |  01  |  11  |  21  |  31  | <-- current scanline
	//  |      | @    |      |      |
	// -------------------------------
	//  |      |      |      |      |
	//  |  02  |  12  |  22  |  32  | <-- next scanline
	//  |      |      |      |      |
	// -------------------------------
	//  |      |      |      |      |
	//
	// Each character-cell represents a pixel on the output
	// surface, "@" represents the current pixel (always somewhere
	// in the bottom half of the current scan-line, or the top-half
	// of the next scanline). The grid of lines represents the
	// edges of the texels of the underlying texture.

	// Texture coordinates of the texel containing the active pixel.
#ifdef CURVATURE
	float2 xy = radialDistortion(co.texCoord, IN);
#else
	float2 xy = co.texCoord;
#endif

	// Of all the pixels that are mapped onto the texel we are
	// currently rendering, which pixel are we currently rendering?
	float2 ratio_scale = xy * IN.texture_size - float2(0.5, 0.5);
	float2 uv_ratio = frac(ratio_scale);

	// Snap to the center of the underlying texel.
	xy = (floor(ratio_scale) + float2(0.5, 0.5)) / IN.texture_size;

	// Calculate Lanczos scaling coefficients describing the effect
	// of various neighbour texels in a scanline on the current
	// pixel.
	float4 coeffs = PI * float4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);

	// Prevent division by zero.
	coeffs = FIX(coeffs);

	// Lanczos2 kernel.
	coeffs = 2.0 * sin(coeffs) * sin(coeffs / 2.0) / (coeffs * coeffs);

	// Normalize.
	coeffs /= dot(coeffs, float4(1.0, 1.0, 1.0, 1.0));

	// Calculate the effective colour of the current and next
	// scanlines at the horizontal location of the current pixel,
	// using the Lanczos coefficients above.
	float4 col  = clamp(mul(coeffs, float4x4(
					TEX2D(xy + float2(-co.one.x, 0.0)),
					TEX2D(xy),
					TEX2D(xy + float2(co.one.x, 0.0)),
					TEX2D(xy + float2(2.0 * co.one.x, 0.0)))),
			0.0, 1.0);
	float4 col2 = clamp(mul(coeffs, float4x4(
					TEX2D(xy + float2(-co.one.x, co.one.y)),
					TEX2D(xy + float2(0.0, co.one.y)),
					TEX2D(xy + co.one),
					TEX2D(xy + float2(2.0 * co.one.x, co.one.y)))),
			0.0, 1.0);

#ifndef LINEAR_PROCESSING
	col  = pow(col , float4(inputGamma, inputGamma, inputGamma, inputGamma));
	col2 = pow(col2, float4(inputGamma, inputGamma, inputGamma, inputGamma));
#endif

	// Calculate the influence of the current and next scanlines on
	// the current pixel.
	float4 weights  = scanlineWeights(uv_ratio.y, col);
	float4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
	float3 mul_res  = (col * weights + col2 * weights2).rgb;

	// dot-mask emulation:
	// Output pixels are alternately tinted green and magenta.
	float3 dotMaskWeights = lerp(
			float3(1.0, 0.7, 1.0),
			float3(0.7, 1.0, 0.7),
			floor(fmod(co.mod_factor, 2.0))
			);

	mul_res *= dotMaskWeights;

	// Convert the image gamma for display on our output device.
	mul_res = pow(mul_res, float3(1.0 / outputGamma, 1.0 / outputGamma, 1.0 / outputGamma));

	return float4(mul_res, 1.0);
}
