// CRT Easymode
// Based on CRT Shader by EasyMode (GPL License)
// Ported from https://github.com/libretro/common-shaders/blob/master/crt/shaders/crt-easymode.cg
// A flat CRT shader ideally for 1080p or higher displays

//!BGFX EFFECT
//!VERSION 1
//!NAME CRT Easymode
//!CATEGORY CRT
//!DESCRIPTION A flat CRT simulation shader optimized for high resolution displays. Features customizable scanlines, phosphor mask patterns, and gamma correction. Ideal for 1080p+ displays with integer or non-integer scaling.

//!PARAMETER
//!LABEL Sharpness Horizontal
//!DESC Controls horizontal sharpness of the image interpolation
//!DEFAULT 0.5
//!MIN 0
//!MAX 1
//!STEP 0.01
float sharpnessH;

//!PARAMETER
//!LABEL Sharpness Vertical
//!DESC Controls vertical sharpness of the image interpolation
//!DEFAULT 1
//!MIN 0
//!MAX 1
//!STEP 0.01
float sharpnessV;

//!PARAMETER
//!LABEL Mask Strength
//!DESC Intensity of the RGB phosphor mask effect
//!DEFAULT 0.3
//!MIN 0
//!MAX 1
//!STEP 0.01
float maskStrength;

//!PARAMETER
//!LABEL Mask Dot Width
//!DESC Width of each phosphor dot in the mask pattern
//!DEFAULT 1
//!MIN 1
//!MAX 100
//!STEP 1
int maskDotWidth;

//!PARAMETER
//!LABEL Mask Dot Height
//!DESC Height of each phosphor dot in the mask pattern
//!DEFAULT 1
//!MIN 1
//!MAX 100
//!STEP 1
int maskDotHeight;

//!PARAMETER
//!LABEL Mask Stagger
//!DESC Offset pattern for alternating mask rows (0=aperture grille, 3=shadow mask)
//!DEFAULT 0
//!MIN 0
//!MAX 100
//!STEP 1
int maskStagger;

//!PARAMETER
//!LABEL Mask Size
//!DESC Scale factor for the overall mask pattern
//!DEFAULT 1
//!MIN 1
//!MAX 100
//!STEP 1
int maskSize;

//!PARAMETER
//!LABEL Scanline Strength
//!DESC Intensity of the horizontal scanline effect
//!DEFAULT 1
//!MIN 0
//!MAX 1
//!STEP 1
float scanlineStrength;

//!PARAMETER
//!LABEL Scanline Beam Width Min
//!DESC Minimum width of the scanline beam (affects dark areas)
//!DEFAULT 1.5
//!MIN 0.5
//!MAX 5
//!STEP 0.1
float scanlineBeamWidthMin;

//!PARAMETER
//!LABEL Scanline Beam Width Max
//!DESC Maximum width of the scanline beam (affects bright areas)
//!DEFAULT 1.5
//!MIN 0.5
//!MAX 5
//!STEP 0.1
float scanlineBeamWidthMax;

//!PARAMETER
//!LABEL Scanline Brightness Min
//!DESC Minimum brightness level for scanline gaps
//!DEFAULT 0.35
//!MIN 0
//!MAX 1
//!STEP 0.01
float scanlineBrightMin;

//!PARAMETER
//!LABEL Scanline Brightness Max
//!DESC Maximum brightness level for scanline peaks
//!DEFAULT 0.65
//!MIN 0
//!MAX 1
//!STEP 0.01
float scanlineBrightMax;

//!PARAMETER
//!LABEL Scanline Cutoff
//!DESC Vertical resolution threshold above which scanlines are disabled
//!DEFAULT 400
//!MIN 1
//!MAX 1000
//!STEP 1
int scanlineCutoff;

//!PARAMETER
//!LABEL Gamma Input
//!DESC Gamma correction applied to input image
//!DEFAULT 2
//!MIN 0.1
//!MAX 5
//!STEP 0.01
float gammaInput;

//!PARAMETER
//!LABEL Gamma Output
//!DESC Gamma correction applied to output image
//!DEFAULT 1.8
//!MIN 0.1
//!MAX 5
//!STEP 0.01
float gammaOutput;

//!PARAMETER
//!LABEL Brightness Boost
//!DESC Overall brightness multiplier for the final image
//!DEFAULT 1.2
//!MIN 1
//!MAX 2
//!STEP 0.01
float brightBoost;

//!PARAMETER
//!LABEL Dilation
//!DESC Enables color dilation for enhanced bloom-like effect
//!DEFAULT 1
//!MIN 0
//!MAX 1
//!STEP 1
int dilation;

//!TEXTURE
Texture2D INPUT;

//!TEXTURE
Texture2D OUTPUT;

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


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

#pragma warning(disable: 3571)

// Constants
#define PI 3.141592653589
#define FIX(c) max(abs(c), 1e-5)
#define mod(x,y) (x - y * trunc(x/y))

// Lanczos filtering toggle - set to 0 for linear filter and better performance
#define ENABLE_LANCZOS 1

#define TEX2D(c) dilate(INPUT.SampleLevel(sam, c, 0))

// Apply dilation effect to enhance bright pixels
float4 dilate(float4 col) {
	float4 x = lerp(float4(1, 1, 1, 1), col, dilation);
	return col * x;
}

// Half-circle S-curve for sharper interpolation
float curve_distance(float x, float sharp) {
	float x_step = step(0.5, x);
	float curve = 0.5 - sqrt(0.25 - (x - x_step) * (x - x_step)) * sign(0.5 - x);
	return lerp(x, curve, sharp);
}

// Build color matrix from neighboring texels
float4x4 get_color_matrix(float2 co) {
	float2 inputPt = GetInputPt();
	return float4x4(
		TEX2D(co - float2(inputPt.x, 0)),
		TEX2D(co),
		TEX2D(co + float2(inputPt.x, 0)),
		TEX2D(co + float2(2.0 * inputPt.x, 0))
	);
}

// Apply Lanczos2 filter to color matrix
float3 filter_lanczos(float4 coeffs, float4x4 color_matrix) {
	float4 col = mul(coeffs, color_matrix);
	float4 sample_min = min(color_matrix[1], color_matrix[2]);
	float4 sample_max = max(color_matrix[1], color_matrix[2]);
	col = clamp(col, sample_min, sample_max);
	return col.rgb;
}

float4 Pass1(float2 pos) {
	float2 inputPt = GetInputPt();
	int2 inputSize = GetInputSize();
	int2 outputSize = GetOutputSize();

	float2 pix_co = pos * inputSize - 0.5f;
	float2 tex_co = (floor(pix_co) + 0.5) * inputPt;
	float2 dist = frac(pix_co);

	float curve_x;
	float3 col, col2;

#if ENABLE_LANCZOS
	curve_x = curve_distance(dist.x, sharpnessH * sharpnessH);

	// Lanczos2 kernel coefficients
	float4 coeffs = PI * float4(1.0 + curve_x, curve_x, 1.0 - curve_x, 2.0 - curve_x);
	coeffs = FIX(coeffs);
	coeffs = 2.0 * sin(coeffs) * sin(coeffs / 2.0) / (coeffs * coeffs);
	coeffs /= dot(coeffs, 1.0);

	col = filter_lanczos(coeffs, get_color_matrix(tex_co));
	col2 = filter_lanczos(coeffs, get_color_matrix(tex_co + float2(0, inputPt.y)));
#else
	curve_x = curve_distance(dist.x, sharpnessH);
	col = lerp(TEX2D(tex_co).rgb, TEX2D(tex_co + float2(inputPt.x, 0)).rgb, curve_x);
	col2 = lerp(TEX2D(tex_co + float2(0, inputPt.y)).rgb, TEX2D(tex_co + float2(inputPt.x, inputPt.y)).rgb, curve_x);
#endif

	col = lerp(col, col2, curve_distance(dist.y, sharpnessV));
	col = pow(col, gammaInput / (dilation + 1.0));

	// Calculate luminance and brightness for scanline weighting
	float luma = dot(float3(0.2126, 0.7152, 0.0722), col);
	float bright = (max(col.r, max(col.g, col.b)) + luma) / 2.0;
	float scan_bright = clamp(bright, scanlineBrightMin, scanlineBrightMax);
	float scan_beam = clamp(bright * scanlineBeamWidthMax, scanlineBeamWidthMin, scanlineBeamWidthMax);

	// Scanline weight calculation
	float scan_weight = 1.0 - pow(cos(pos.y * 2.0 * PI * inputSize.y) * 0.5 + 0.5, scan_beam) * scanlineStrength;

	// Phosphor mask pattern
	float mask = 1.0 - maskStrength;
	float2 mod_fac = floor(pos * outputSize / float2(maskSize, maskDotHeight * maskSize));
	int dot_no = int(mod((mod_fac.x + mod(mod_fac.y, 2.0) * maskStagger) / maskDotWidth, 3.0));

	float3 mask_weight;
	if (dot_no == 0)
		mask_weight = float3(1.0, mask, mask);
	else if (dot_no == 1)
		mask_weight = float3(mask, 1.0, mask);
	else
		mask_weight = float3(mask, mask, 1.0);

	// Disable scanlines for high resolution content
	if (inputSize.y >= scanlineCutoff)
		scan_weight = 1.0;

	// Apply scanline and mask effects
	col2 = col.rgb;
	col *= scan_weight;
	col = lerp(col, col2, scan_bright);
	col *= mask_weight;

	// Output gamma correction
	col = pow(col, 1.0 / gammaOutput);

	return float4(col * brightBoost, 1);
}
