﻿#pragma kernel CSMain

RWTexture2D<float4> Result;

float Width;
float Height;

float Rotation;

float Hue;
float Saturation;
float Brightness;
float2 Scale;
float2 Offset;

Texture2D<float4> Inputs[16];
SamplerState sampler_Inputs[16];
float4 InputTextureIsValid[16];

struct Connection
{
	int FromTextureIndex;
	int FromChannel;
	int ToChannel;
	int RemapMode;
	float4 Remapping;
};

StructuredBuffer<Connection> Connections;
int ConnectionCount;

struct OutputChannel
{
	int BlendMode;
	bool Invert;
	float Fallback;
};

StructuredBuffer<OutputChannel> OutputChannels;

// blendmodes
// 0 = add
// 1 = multiply
// 3 = max
// 4 = min

float SampleTexture(Texture2D<float4> tex, SamplerState texSampler, int channel, float2 uv)
{
	float4 pixelColor = tex.SampleLevel(texSampler,uv,0);
	if (channel == 0) return pixelColor.r;
	else if (channel == 1) return pixelColor.g;
	else if (channel == 2) return pixelColor.b;
	else if (channel == 3) return pixelColor.a;
	else return max(pixelColor.r, max(pixelColor.g, pixelColor.b));
}

float SampleTexture(int textureIndex, int channel, float2 uv)
{
	float result = 0;
	[unroll]
	for (int i = 0; i < 16; i++)
	{
		if (textureIndex == i)
			result = SampleTexture(Inputs[i], sampler_Inputs[i], channel, uv);
	}
	return result;
}

float3 rgb2hsv(float3 rgb){
	float4 p = (rgb.g < rgb.b) ? float4(rgb.bg, -1.0, 2.0/3.0) : float4(rgb.gb, 0.0, -1.0/3.0);
	float4 q = (rgb.r < p.x) ? float4(p.xyw, rgb.r) : float4(rgb.r, p.yzx);
	float d = q.x - min(q.w, q.y);
	float e = 1.0e-10;
	return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

float3 hsv2rgb(float3 hsv){
	float4 K = float4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
	float3 p = abs(frac(hsv.xxx + K.xyz) * 6.0 - K.www);
	return hsv.z * lerp(K.xxx, saturate(p - K.xxx), hsv.y);
}

float4 ApplyHSV(float4 pixel, float hue, float saturation, float value){
	float3 hsv = rgb2hsv(pixel.rgb);
	hsv.r += hue;
	hsv.g *= saturation;
	hsv.b *= value;
	return float4(hsv2rgb(hsv.rgb), pixel.a);
}

float Remap(float value, float2 from, float2 to)
{
	return lerp(to.x, to.y, saturate((value - from.x) / (from.y - from.x)));
}

float Remap(float value, float4 fromTo)
{
	return Remap(value, fromTo.xy, fromTo.zw);
}

float BlendValue(float base, float blend, int mode)
{
	if (mode == 0) return base + blend;
	else if (mode == 1) return base * blend;
	else if (mode == 2) return max(base, blend);
	else if (mode == 3) return min(base, blend);
	else return blend;
}

float4 SetChannelOnVec4(float4 vec, int channel, float value)
{
	if (channel == 0) vec.r = value;
	else if (channel == 1) vec.g = value;
	else if (channel == 2) vec.b = value;
	else if (channel == 3) vec.a = value;
	return vec;
}

int4 SetChannelOnVec4(int4 vec, int channel, int value)
{
	if (channel == 0) vec.r = value;
	else if (channel == 1) vec.g = value;
	else if (channel == 2) vec.b = value;
	else if (channel == 3) vec.a = value;
	return vec;
}

[numthreads(8, 8, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
	float2 uv = float2((id.x+0.5) / Width, (id.y+0.5) / Height)  + Offset;

	// Rotate uv by Rotation around 0.5 0.5 and scale by Scale
	float2 center = float2(0.5, 0.5);
	float2 rotated = float2(0, 0);
	rotated.x = (uv.x - center.x) * cos(Rotation) - (uv.y - center.y) * sin(Rotation);
	rotated.y = (uv.x - center.x) * sin(Rotation) + (uv.y - center.y) * cos(Rotation);
	uv = rotated * Scale + center;

	float4 pixel = float4(1, 1, 1, 1);
	int4 pixelSet = int4(0, 0, 0, 0);
	[loop] 
	for(int i = 0; i < ConnectionCount; i++)
	{
		Connection conn = Connections[i];
		if(conn.FromTextureIndex >= 0 && InputTextureIsValid[conn.FromTextureIndex].x == 1)
		{
			float value = SampleTexture(conn.FromTextureIndex, conn.FromChannel, uv);
			if(conn.RemapMode == 1) // Range to Range
			{
				value = Remap(value, conn.Remapping);
			}
			if(pixelSet[conn.ToChannel] == 0)
			{
				pixelSet = SetChannelOnVec4(pixelSet, conn.ToChannel, 1);
			}else
			{
				value = BlendValue(pixel[conn.ToChannel], value, OutputChannels[conn.ToChannel].BlendMode);
			}
			pixel = SetChannelOnVec4(pixel, conn.ToChannel, value);
		}
	}
	for(int j = 0; j < 4; j++)
	{
		OutputChannel outCh = OutputChannels[j];
		if(outCh.Invert)
		{
			pixel = SetChannelOnVec4(pixel, j, 1 - pixel[j]);
		}
		if(pixelSet[j] == 0)
		{
			pixel = SetChannelOnVec4(pixel, j, outCh.Fallback);
		}
	}
	
	pixel = ApplyHSV(pixel, Hue, Saturation, Brightness);
	Result[id.xy] = pixel;
}


#pragma kernel CS_Kernel

Texture2D<float4> Kernel_Input;
float4 Kernel_X[25];
float4 Kernel_Y[25];
bool Kernel_Grayscale;
bool Kernel_TwoPass;
float4 Kernel_Channels;

[numthreads(8, 8, 1)]
void CS_Kernel(uint3 id : SV_DispatchThreadID)
{
	float4 pixel_x = float4(0, 0, 0, 0);
	float4 pixel_y = float4(0, 0, 0, 0);
	
	for (int x = -2; x <= 2; x++)
	{
		for (int y = -2; y <= 2; y++)
		{
			float4 p = Kernel_Input.Load(int3(id.xy + int2(x,y), 0));
			[branch]
			if(Kernel_Grayscale)
			{
				float gray = dot(p.rgb, float3(0.2126, 0.7152, 0.0722));
				pixel_x += float4(gray * Kernel_X[x + 2 + (y + 2) * 5].x, 0, 0, 0);
				[branch]
				if(Kernel_TwoPass)
				{
					pixel_y += float4(gray * Kernel_Y[x + 2 + (y + 2) * 5].x, 0, 0, 0);
				}
			}else
			{
				pixel_x += p * Kernel_X[x + 2 + (y + 2) * 5].x;
				[branch]
				if(Kernel_TwoPass)
				{
					pixel_y += p * Kernel_Y[x + 2 + (y + 2) * 5].x;
				}
			}
		}
	}

	float4 color = Kernel_Input.Load(int3(id.xy, 0));
	float4 res = color;
	[branch]
	if(Kernel_Grayscale)
	{
		color = float4(dot(color.rgb, float3(0.2126, 0.7152, 0.0722)), 0, 0, color.a);
		[branch]
		if(Kernel_TwoPass)
		{
			res = sqrt((pixel_x * pixel_x + pixel_y * pixel_y)).x;
		}
		else
		{
			res = pixel_x.x;
		}
	}else
	{
		[branch]
		if(Kernel_TwoPass)
		{
			res = sqrt((pixel_x * pixel_x + pixel_y * pixel_y));
		}
		else
		{
			res = pixel_x;
		}
	}
	color.r = lerp(color.r, res.r, Kernel_Channels.x);
	color.g = lerp(color.g, res.g, Kernel_Channels.y);
	color.b = lerp(color.b, res.b, Kernel_Channels.z);
	color.a = lerp(color.a, res.a, Kernel_Channels.w);
	Result[id.xy] = color;
	
}

#pragma kernel CS_ChannelLerper

Texture2D<float4> Unpacker_Input;
float4 Channels_Strength_R;
float4 Channels_Strength_G;
float4 Channels_Strength_B;
float4 Channels_Strength_A;
float4 Channels_Add;

[numthreads(8, 8, 1)]
void CS_ChannelLerper(uint3 id : SV_DispatchThreadID)
{
	float4 color = Unpacker_Input.Load(int3(id.xy, 0));
	float4 res = color;
	res.r = lerp(0, color.r, Channels_Strength_R.x) 
		+ lerp(0, color.g, Channels_Strength_R.y) 
		+ lerp(0, color.b, Channels_Strength_R.z) 
		+ lerp(0, color.a, Channels_Strength_R.w);
	res.g = lerp(0, color.r, Channels_Strength_G.x)
		+ lerp(0, color.g, Channels_Strength_G.y)
		+ lerp(0, color.b, Channels_Strength_G.z)
		+ lerp(0, color.a, Channels_Strength_G.w);
	res.b = lerp(0, color.r, Channels_Strength_B.x)
		+ lerp(0, color.g, Channels_Strength_B.y)
		+ lerp(0, color.b, Channels_Strength_B.z)
		+ lerp(0, color.a, Channels_Strength_B.w);
	res.a = lerp(0, color.r, Channels_Strength_A.x)
		+ lerp(0, color.g, Channels_Strength_A.y)
		+ lerp(0, color.b, Channels_Strength_A.z)
		+ lerp(0, color.a, Channels_Strength_A.w);
	res += Channels_Add;
	Result[id.xy] = res;
}