// =============================================================================
//  crt.hlsl  —  M88 用 CRT フィルタ（Pass 2）
//
//  移植元: E:\_projects\crt_viewer\shaders\crt_filter.gdshader
//          （Godot 4.6.3 / shader_type canvas_item）
//
//  移植方針:
//    ・元シェーダのロジックを 1:1 で移植している。式の形も変えていない
//    ・元の uniform 5個は、そのまま定数バッファのフィールドに対応する
//    ・PC-88 対応として ScanlineNativeHeight のみ外部化した
//      （元は TEXTURE_PIXEL_SIZE.y から算出していた固定値）
//
//  ★色空間について（最重要）
//    元シェーダは「テクスチャに sRGB エンコード済みの値が入っていて、
//    ハードウェアの sRGB デコードは掛かっていない」前提で、
//    シェーダ内で手動の EOTF/OETF (pow 2.2) を行っている。
//    M88 の Pass1 出力も PC-88 のパレット値をそのまま格納した
//    R8G8B8A8_UNORM（_SRGB ではない）なので、前提が完全に一致する。
//    したがって色空間の変換は一切追加していない。
//
//    【M88 側の必須条件】
//      ・Pass1 の出力 RT は DXGI_FORMAT_R8G8B8A8_UNORM（_SRGB 禁止）
//      ・スワップチェーンのバックバッファも _UNORM（_SRGB 禁止）
//    これを _SRGB にすると二重ガンマになり、絵が白飛びする。
// =============================================================================

// -----------------------------------------------------------------------------
//  頂点シェーダ（フルスクリーン三角形。頂点バッファ不要）
//  DrawD3D は Draw(3, 0) で呼び出すこと
// -----------------------------------------------------------------------------
struct VSOut
{
	float4 pos : SV_POSITION;
	float2 uv  : TEXCOORD0;
};

VSOut VSMain(uint id : SV_VertexID)
{
	VSOut o;
	o.uv  = float2((id << 1) & 2, id & 2);
	o.pos = float4(o.uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
	return o;
}

// -----------------------------------------------------------------------------
//  リソース
// -----------------------------------------------------------------------------
Texture2D<float4> SrcTex     : register(t0);   // Pass1 の出力 (640x400 RGBA)
SamplerState      PointSamp  : register(s0);   // Point  / Clamp
SamplerState      LinearSamp : register(s1);   // Linear / Clamp （このシェーダでは未使用）

// -----------------------------------------------------------------------------
//  定数バッファ（16バイト境界に厳密に揃えてある。変更時は必ず数え直すこと）
//
//    offset  size  member
//     0      8     SourceSize            \ 16B
//     8      8     SourceSizeInv         /
//    16      8     OutputSize            \ 16B
//    24      8     OutputSizeInv         /
//    32      4     ScanlineStrength      \
//    36      4     VignetteStrength       | 16B
//    40      4     AberrationPx           |
//    44      4     CurvatureAmount       /
//    48      4     ScanlineNativeHeight  \
//    52      4     SmoothstepEnabled      | 16B
//    56      4     MasterEnable           |
//    60      4     _pad0                 /
//                                        合計 64 バイト
// -----------------------------------------------------------------------------
cbuffer CRTParams : register(b0)
{
	float2 SourceSize;             // (640, 400)
	float2 SourceSizeInv;          // (1/640, 1/400)  ← 元の TEXTURE_PIXEL_SIZE

	float2 OutputSize;             // 描画先のピクセルサイズ（本シェーダでは未使用）
	float2 OutputSizeInv;

	float  ScanlineStrength;       // 元: scanline_strength   0.0〜1.0
	float  VignetteStrength;       // 元: vignette_strength   0.0〜1.0
	float  AberrationPx;           // 元: aberration_px       0.0〜8.0
	float  CurvatureAmount;        // 元: curvature_amount    0.0〜0.3

	float  ScanlineNativeHeight;   // 走査線の位相計算に使う「ネイティブ高さ」
	                               //   元は 1.0/texel_size.y（＝テクスチャ高さ）固定
	                               //   400.0 → 200本の暗帯（PC-88 200ライン相当。既定）
	                               //   200.0 → 100本（粗い）
	                               //   800.0 → 400本（400ラインで1ラインごと。低倍率でモアレ）
	float  SmoothstepEnabled;      // 元: smoothstep_enabled  0.0=OFF / 1.0=ON
	float  MasterEnable;           // マスタON/OFF  0.0=素のNearest表示 / 1.0=フィルタ適用
	float  _pad0;
};

static const float PI = 3.14159265358979323846;

// -----------------------------------------------------------------------------
//  ガンマ(EOTF/OETF)変換
//  元: eotf_srgb_to_linear / oetf_linear_to_srgb
// -----------------------------------------------------------------------------
float3 EotfSrgbToLinear(float3 c)
{
	return pow(c, 2.2);
}

float3 OetfLinearToSrgb(float3 c)
{
	return pow(max(c, 0.0), 1.0 / 2.2);
}

// -----------------------------------------------------------------------------
//  ガンマ考慮バイリニア補間
//  元: sample_gamma_bilinear()
//
//  テクセル中心を明示的に4点サンプルし、リニア空間で補間して sRGB へ戻す。
//  SmoothstepEnabled 時は補間率を三次曲線化して「ドット感を残した滲み」にする。
//
//  ★4点は常にテクセル中心を指すため、サンプラは Point で正しい。
//    Linear にすると二重補間になり結果が変わる。必ず PointSamp を使うこと。
// -----------------------------------------------------------------------------
float4 SampleGammaBilinear(float2 uv, float2 texelSize)
{
	float2 texSize = 1.0 / texelSize;
	float2 pixel   = uv * texSize - 0.5;
	float2 base    = floor(pixel);
	float2 f       = pixel - base;

	if (SmoothstepEnabled > 0.5)
	{
		f = smoothstep(float2(0.0, 0.0), float2(1.0, 1.0), f);
	}

	float2 uv00 = (base + float2(0.5, 0.5)) * texelSize;
	float2 uv10 = (base + float2(1.5, 0.5)) * texelSize;
	float2 uv01 = (base + float2(0.5, 1.5)) * texelSize;
	float2 uv11 = (base + float2(1.5, 1.5)) * texelSize;

	float4 c00 = SrcTex.Sample(PointSamp, uv00);
	float4 c10 = SrcTex.Sample(PointSamp, uv10);
	float4 c01 = SrcTex.Sample(PointSamp, uv01);
	float4 c11 = SrcTex.Sample(PointSamp, uv11);

	c00.rgb = EotfSrgbToLinear(c00.rgb);
	c10.rgb = EotfSrgbToLinear(c10.rgb);
	c01.rgb = EotfSrgbToLinear(c01.rgb);
	c11.rgb = EotfSrgbToLinear(c11.rgb);

	// 元の mix() と同じく、アルファも一緒に補間される（アルファはガンマ変換しない）
	float4 top    = lerp(c00, c10, f.x);
	float4 bottom = lerp(c01, c11, f.x);
	float4 result = lerp(top, bottom, f.y);

	result.rgb = OetfLinearToSrgb(result.rgb);
	return result;
}

// -----------------------------------------------------------------------------
//  メイン
//  元: fragment()
//
//  ※ 元は Godot の制約で fragment() 内に早期 return が書けず if/else で
//     包んでいたが、HLSL にはその制約がないため早期 return に直している。
//     ロジックは等価。
// -----------------------------------------------------------------------------
float4 PSMain(VSOut i) : SV_Target
{
	float2 uv = i.uv;

	// --- マスタ OFF: 素の Nearest 表示（比較の基準） ---
	if (MasterEnable < 0.5)
	{
		return float4(SrcTex.Sample(PointSamp, uv).rgb, 1.0);
	}

	// --- 画面湾曲（バレル歪み） ---
	if (CurvatureAmount > 0.0)
	{
		float2 cc = uv * 2.0 - 1.0;
		float  r2 = dot(cc, cc);
		cc *= 1.0 + CurvatureAmount * r2;
		uv = cc * 0.5 + 0.5;
	}

	// 湾曲により画面外へはみ出た領域は黒
	if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)
	{
		return float4(0.0, 0.0, 0.0, 1.0);
	}

	float2 texelSize = SourceSizeInv;

	// --- 色収差（RGBずらし。単位はネイティブ解像度の px） ---
	// 注: 元シェーダは shift を x/y 両方に適用しており、ずれは「斜め」になる。
	//     忠実性を優先してそのまま移植している。
	float4 col;
	if (AberrationPx > 0.0)
	{
		float2 shift = texelSize * AberrationPx;
		float4 c_r = SampleGammaBilinear(uv - shift, texelSize);
		float4 c_g = SampleGammaBilinear(uv,         texelSize);
		float4 c_b = SampleGammaBilinear(uv + shift, texelSize);
		col = float4(c_r.r, c_g.g, c_b.b, c_g.a);
	}
	else
	{
		col = SampleGammaBilinear(uv, texelSize);
	}

	// --- スキャンライン ---
	// 元: native_y = uv.y / texel_size.y （＝ uv.y * テクスチャ高さ）
	//     M88 では 200/400 ライン切替のため ScanlineNativeHeight で外部化した
	if (ScanlineStrength > 0.0)
	{
		float native_y = uv.y * ScanlineNativeHeight;
		float wave     = sin(native_y * PI) * 0.5 + 0.5;
		float scan     = lerp(1.0 - ScanlineStrength, 1.0, wave);
		col.rgb *= scan;
	}

	// --- ビネット ---
	if (VignetteStrength > 0.0)
	{
		float2 vc  = uv * 2.0 - 1.0;
		float  vig = 1.0 - dot(vc, vc) * VignetteStrength;
		col.rgb *= saturate(vig);
	}

	return float4(col.rgb, 1.0);
}
