/**
 * FineSharp by Did�e
 * http://avisynth.nl/images/FineSharp.avsi
 *
 * Initial HLSL port by -Vit-
 * https://forum.doom9.org/showthread.php?t=171346
 *
 * Modified and optimized for ReShade by JPulowski
 *
 * Do not distribute without giving credit to the original author(s).
 *
 * 1.0  - Initial release
 */

#include "ReShadeUI.fxh"

uniform float sstr < __UNIFORM_SLIDER_FLOAT1
	ui_min = 0.00; ui_max = 8.00;
	ui_label = "Sharpening Strength";
> = 2.00;

uniform float cstr <
	ui_type = "drag";
	ui_min = 0.00; ui_max = 1.249;
	ui_label = "Equalization Strength";
	ui_tooltip = "Suggested settings for cstr based on sstr value: sstr=0->cstr=0, sstr=0.5->cstr=0.1, 1.0->0.6, 2.0->0.9, 2.5->1.00, 3.0->1.09, 3.5->1.15, 4.0->1.19, 8.0->1.249";
> = 0.90;

uniform float xstr < __UNIFORM_SLIDER_FLOAT1
	ui_min = 0.00; ui_max = 1.00;
	ui_tooltip = "Strength of XSharpen-style final sharpening.";
> = 0.19;

uniform float xrep < __UNIFORM_SLIDER_FLOAT1
	ui_min = 0.00; ui_max = 1.00;
	ui_tooltip = "Repair artefacts from final sharpening. (-Vit- addition to original script)";
> = 0.25;

uniform float lstr <
	ui_type = "input";
	ui_tooltip = "Modifier for non-linear sharpening";
> = 1.49;

uniform float pstr <
	ui_type = "input";
	ui_tooltip = "Exponent for non-linear sharpening";
> = 1.272;

// Viscera parameters

#ifndef ldmp
	#define ldmp (sstr+0.1)	//"low damp", to not overenhance very small differences (noise coming out of flat areas)
#endif

#include "ReShade.fxh"

// Helper functions

float4 Src(float a, float b, float2 tex) {
	return tex2D(ReShade::BackBuffer, mad(BUFFER_PIXEL_SIZE, float2(a, b), tex));
}

float3x3 RGBtoYUV(float Kb, float Kr) {
	return float3x3(float3(Kr, 1.0 - Kr - Kb, Kb), float3(-Kr, Kr + Kb - 1.0, 1.0 - Kb) / (2.0 * (1.0 - Kb)), float3(1.0 - Kr, Kr + Kb - 1.0, -Kb) / (2.0 * (1.0 - Kr)));
}

float3x3 YUVtoRGB(float Kb, float Kr) {
	return float3x3(float3(1.0, 0.0, 2.0 * (1.0 - Kr)), float3(Kb + Kr - 1.0, 2.0 * (1.0 - Kb) * Kb, 2 * Kr * (1.0 - Kr)) / (Kb + Kr - 1.0), float3(1.0, 2.0 * (1.0 - Kb), 0.0));
}

void sort(inout float a1, inout float a2) {
	float t = min(a1, a2);
	a2 = max(a1, a2);
	a1 = t;
}

float median3(float a1, float a2, float a3) {
	sort(a2, a3);
	sort(a1, a2);
	
	return min(a2, a3);
}

float median5(float a1, float a2, float a3, float a4, float a5) {
	sort(a1, a2);
	sort(a3, a4);
	sort(a1, a3);
	sort(a2, a4);
	
	return median3(a2, a3, a5);
}

float median9(float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8, float a9) {
	sort(a1, a2);
	sort(a3, a4);
	sort(a5, a6);
	sort(a7, a8);
	sort(a1, a3);
	sort(a5, a7);
	sort(a1, a5);
	
	sort(a3, a5);
	sort(a3, a7);
	sort(a2, a4);
	sort(a6, a8);
	sort(a4, a8);
	sort(a4, a6);
	sort(a2, a6);
	
	return median5(a2, a4, a5, a7, a9);
}

void sort_min_max7(inout float a1, inout float a2, inout float a3, inout float a4, inout float a5, inout float a6, inout float a7) {
	sort(a1, a2);
	sort(a3, a4);
	sort(a5, a6);
	
	sort(a1, a3);
	sort(a1, a5);
	sort(a2, a6);
	
	sort(a4, a5);
	sort(a1, a7);
	sort(a6, a7);
}

void sort_min_max9(inout float a1, inout float a2, inout float a3, inout float a4, inout float a5, inout float a6, inout float a7, inout float a8, inout float a9) {
	sort(a1, a2);
	sort(a3, a4);
	sort(a5, a6);
	sort(a7, a8);
	
	sort(a1, a3);
	sort(a5, a7);
	sort(a1, a5);
	sort(a2, a4);
	
	sort(a6, a7);
	sort(a4, a8);
	sort(a1, a9);
	sort(a8, a9);
}

void sort9_partial2(inout float a1, inout float a2, inout float a3, inout float a4, inout float a5, inout float a6, inout float a7, inout float a8, inout float a9) {
	sort_min_max9(a1,a2,a3,a4,a5,a6,a7,a8,a9);
	sort_min_max7(a2,a3,a4,a5,a6,a7,a8);
}


float SharpDiff(float4 c) {
	float t = c.a - c.x;
	return sign(t) * (sstr / 255.0) * pow(abs(t) / (lstr / 255.0), 1.0 / pstr) * ((t * t) / mad(t, t, ldmp / 65025.0));
}

// Main

float4 PS_FineSharp_P0(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
	float3 yuv = mul(RGBtoYUV(0.0722, 0.2126), Src(0.0, 0.0, texcoord).rgb ) + float3(0.0, 0.5, 0.5);
	return float4(yuv, yuv.x);
}

float4 PS_FineSharp_P1(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
	float4 o = Src(0.0, 0.0, texcoord);
	
	o.x += o.x;
	o.x += Src( 0.0, -1.0, texcoord).x + Src(-1.0,  0.0, texcoord).x + Src( 1.0, 0.0, texcoord).x + Src(0.0, 1.0, texcoord).x;
	o.x += o.x;
	o.x += Src(-1.0, -1.0, texcoord).x + Src( 1.0, -1.0, texcoord).x + Src(-1.0, 1.0, texcoord).x + Src(1.0, 1.0, texcoord).x;
	o.x *= 0.0625;

	return o;
}

float4 PS_FineSharp_P2(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
	float4 o = Src(0.0, 0.0, texcoord);

	float t1 = Src(-1.0, -1.0, texcoord).x;
	float t2 = Src( 0.0, -1.0, texcoord).x;
	float t3 = Src( 1.0, -1.0, texcoord).x;
	float t4 = Src(-1.0,  0.0, texcoord).x;
	float t5 = o.x;
	float t6 = Src( 1.0,  0.0, texcoord).x;
	float t7 = Src(-1.0,  1.0, texcoord).x;
	float t8 = Src( 0.0,  1.0, texcoord).x;
	float t9 = Src( 1.0,  1.0, texcoord).x;
	o.x = median9(t1,t2,t3,t4,t5,t6,t7,t8,t9);
	
	return o;
}

float4 PS_FineSharp_P3(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
	float4 o = Src(0.0, 0.0, texcoord);
	
	float sd = SharpDiff(o);
	o.x = o.a + sd;
	sd += sd;
	sd += SharpDiff(Src( 0.0, -1.0, texcoord)) + SharpDiff(Src(-1.0,  0.0, texcoord)) + SharpDiff(Src( 1.0, 0.0, texcoord)) + SharpDiff(Src( 0.0, 1.0, texcoord));
	sd += sd;
	sd += SharpDiff(Src(-1.0, -1.0, texcoord)) + SharpDiff(Src( 1.0, -1.0, texcoord)) + SharpDiff(Src(-1.0, 1.0, texcoord)) + SharpDiff(Src( 1.0, 1.0, texcoord));
	sd *= 0.0625;
	o.x -= cstr * sd;
	o.a = o.x;

	return o;
}

float4 PS_FineSharp_P4(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
	float4 o = Src(0.0, 0.0, texcoord);

	float t1 = Src(-1.0, -1.0, texcoord).a;
	float t2 = Src( 0.0, -1.0, texcoord).a;
	float t3 = Src( 1.0, -1.0, texcoord).a;
	float t4 = Src(-1.0,  0.0, texcoord).a;
	float t5 = o.a;
	float t6 = Src( 1.0,  0.0, texcoord).a;
	float t7 = Src(-1.0,  1.0, texcoord).a;
	float t8 = Src( 0.0,  1.0, texcoord).a;
	float t9 = Src( 1.0,  1.0, texcoord).a;

	o.x += t1 + t2 + t3 + t4 + t6 + t7 + t8 + t9;
	o.x /= 9.0;
	o.x = mad(9.9, (o.a - o.x), o.a);
	
	sort9_partial2(t1, t2, t3, t4, t5, t6, t7, t8, t9);
	o.x = max(o.x, min(t2, o.a));
	o.x = min(o.x, max(t8, o.a));

	return o;
}

float4 PS_FineSharp_P5(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
	float4 o = Src(0.0, 0.0, texcoord);

	float edge = abs(Src(0.0, -1.0, texcoord).x + Src(-1.0, 0.0, texcoord).x + Src(1.0, 0.0, texcoord).x + Src(0.0, 1.0, texcoord).x - 4 * o.x);
	o.x = lerp(o.a, o.x, xstr * (1.0 - saturate(edge * xrep)));

	return o;
}

float4 PS_FineSharp_P6(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
	float4 rgb = Src(0.0, 0.0, texcoord);
	rgb.xyz = mul(YUVtoRGB(0.0722,0.2126), rgb.xyz - float3(0.0, 0.5, 0.5));
	return rgb;
}

technique Mode1
{
	pass ToYUV
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P0;
	}
	
	pass RemoveGrain11
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P1;
	}
	
	pass RemoveGrain4
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P2;
	}
	
	pass FineSharpA
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P3;
	}
	
	pass FineSharpB
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P4;
	}
	
	pass FineSharpC
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P5;
	}
	
	pass ToRGB
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P6;
	}
}

technique Mode2
{
	pass ToYUV
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P0;
	}
	
	pass RemoveGrain4
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P2;
	}
	
	pass RemoveGrain11
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P1;
	}
	
	pass FineSharpA
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P3;
	}
	
	pass FineSharpB
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P4;
	}
	
	pass FineSharpC
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P5;
	}
	
	pass ToRGB
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P6;
	}
}

technique Mode3
{
	pass ToYUV
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P0;
	}
	
	pass RemoveGrain4_1
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P2;
	}
	
	pass RemoveGrain11
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P1;
	}
	
	pass RemoveGrain4_2
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P2;
	}
	
	pass FineSharpA
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P3;
	}
	
	pass FineSharpB
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P4;
	}
	
	pass FineSharpC
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P5;
	}
	
	pass ToRGB
	{
		VertexShader = PostProcessVS;
		PixelShader = PS_FineSharp_P6;
	}
}
