// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#include "./Flax/GUICommon.hlsl"

#ifndef BLUR_V
#define BLUR_V 0
#endif

META_CB_BEGIN(0, Data)
float4x4 ViewProjection;
META_CB_END

#define MAX_SAMPLES 64

META_CB_BEGIN(1, BlurData)
float2 InvBufferSize;
uint SampleCount;
float Dummy0;
float4 Bounds;
float4 WeightAndOffsets[MAX_SAMPLES / 2];
META_CB_END

Texture2D Image : register(t0);

META_VS(true, FEATURE_LEVEL_ES2)
META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT,       0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT,       0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(COLOR,    0, R32G32B32A32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(TEXCOORD, 1, R32G32B32A32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(TEXCOORD, 2, R32G32B32A32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
VS2PS VS(Render2DVertex input)
{
	VS2PS output;

	// Render2D::RenderingFeatures::VertexSnapping
	if ((int)input.CustomDataAndClipOrigin.y & 1)
		input.Position = (float2)(int2)input.Position;

	output.Position = mul(float4(input.Position, 0, 1), ViewProjection);
	output.Color = input.Color;
	output.TexCoord = input.TexCoord;
	output.ClipOriginAndPos = float4(input.CustomDataAndClipOrigin.zw, input.Position);
	output.ClipExtents = input.ClipExtents;
	output.CustomData = input.CustomDataAndClipOrigin.xy;

	return output;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Image(VS2PS input) : SV_Target0
{
	PerformClipping(input);

	return Image.Sample(SamplerLinearClamp, input.TexCoord) * input.Color;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_ImagePoint(VS2PS input) : SV_Target0
{
	PerformClipping(input);

	return Image.Sample(SamplerPointClamp, input.TexCoord) * input.Color;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Color(VS2PS input) : SV_Target0
{
	PerformClipping(input);

	return input.Color;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_LineAA(VS2PS input) : SV_Target0
{
	PerformClipping(input);

	float lineWidth = input.CustomData.x; // Set per-vertex
	float filterWidthScale = 1; // Must match C++ code
	float gradient = input.TexCoord.x;
	float2 gradientDerivative = float2(abs(ddx(gradient)), abs(ddy(gradient)));
	float pixelSizeInUV = sqrt(dot(gradientDerivative, gradientDerivative));
	float halfLineWidthUV = 0.5f * pixelSizeInUV * lineWidth;
	float halfFilterWidthUV = filterWidthScale * pixelSizeInUV;
	float distanceToLineCenter = abs(0.5f - gradient);
	float lineCoverage = smoothstep(halfLineWidthUV + halfFilterWidthUV, halfLineWidthUV - halfFilterWidthUV, distanceToLineCenter);
	if (lineCoverage <= 0.0f)
		discard;
	input.Color.a *= lineCoverage;

	return input.Color;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Font(VS2PS input) : SV_Target0
{
	PerformClipping(input);

	float4 color = input.Color;
	color.a *= Image.Sample(SamplerLinearClamp, input.TexCoord).r;
	return color;
}

float4 GetSample(float weight, float offset, float2 uv)
{
#if BLUR_V
	const float2 uvOffset = float2(offset * InvBufferSize.x, 0);
#else
	const float2 uvOffset = float2(0, offset * InvBufferSize.y);
#endif

	return Image.Sample(SamplerLinearClamp, uv + uvOffset) * weight
	     + Image.Sample(SamplerLinearClamp, uv - uvOffset) * weight;
}

// 1:-1 to 0:1
float2 ClipToUv(float2 clipPos)
{
	return clipPos * float2(0.5, -0.5) + float2(0.5, 0.5);
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Downscale(Quad_VS2PS input) : SV_Target0
{
	float2 boundsPos = input.TexCoord * Bounds.zw + Bounds.xy;

	float4 clipPos = mul(float4(boundsPos, 0, 1), ViewProjection);
	clipPos.xy /= clipPos.w;

	float2 uvPos = ClipToUv(clipPos.xy);

	// TODO: use 4-tap box blur to reduce aliasing when downscaling

	return Image.Sample(SamplerLinearClamp, uvPos);
}

META_PS(true, FEATURE_LEVEL_ES2)
META_PERMUTATION_1(BLUR_V=0)
META_PERMUTATION_1(BLUR_V=1)
float4 PS_Blur(Quad_VS2PS input) : SV_Target0
{
	float2 uv = input.TexCoord;
	float4 result = Image.Sample(SamplerLinearClamp, uv) * WeightAndOffsets[0].x;

	{
		float weight = WeightAndOffsets[0].z;
		float offset = WeightAndOffsets[0].w;
		result += GetSample(weight, offset, uv);
	}

	for (uint i = 2; i < SampleCount; i += 2)
	{
		uint index = i / 2;
		{
			float weight = WeightAndOffsets[index].x;
			float offset = WeightAndOffsets[index].y;
			result += GetSample(weight, offset, uv);
		}

		{
			float weight = WeightAndOffsets[index].z;
			float offset = WeightAndOffsets[index].w;
			result += GetSample(weight, offset, uv);
		}
	}

	return float4(result.rgb, 1);
}
