feat(macos): suport Metal per GpuShaderPreset via spirv-cross

- CMakeLists: bloc if(APPLE) que transpila .spv → .spv.msl amb spirv-cross
- gpu_shader_preset: carrega MSL (main0) a macOS, SPIR-V (main) a la resta
- Afegeix null-terminator als buffers MSL (SDL3 els tracta com C-string)
- README: secció de dependències de shaders per plataforma (Vulkan SDK, spirv-cross)
- Inclou .spv.msl generats per ntsc-md-rainbows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 13:54:05 +01:00
parent f272bab296
commit 5b4a970157
7 changed files with 611 additions and 27 deletions

View File

@@ -0,0 +1,304 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct NTSCParams
{
float source_width;
float source_height;
float a_value;
float b_value;
float cc_value;
float scan_time;
float notch_width;
float y_freq;
float i_freq;
float q_freq;
float _pad0;
float _pad1;
};
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
float2 v_uv [[user(locn0)]];
};
fragment main0_out main0(main0_in in [[stage_in]], constant NTSCParams& u [[buffer(0)]], texture2d<float> Source [[texture(0)]], sampler SourceSmplr [[sampler(0)]])
{
main0_out out = {};
float2 source_dims = float2(u.source_width, u.source_height);
float4 BaseTexel = Source.sample(SourceSmplr, in.v_uv);
float CCValue = u.cc_value;
float ScanTime = u.scan_time;
float NotchHalfWidth = u.notch_width / 2.0;
float YFreqResponse = u.y_freq;
float IFreqResponse = u.i_freq;
float QFreqResponse = u.q_freq;
float AValue = u.a_value;
float BValue = u.b_value;
float TimePerSample = ScanTime / (source_dims.x * 4.0);
float Fc_y1 = (CCValue - NotchHalfWidth) * TimePerSample;
float Fc_y2 = (CCValue + NotchHalfWidth) * TimePerSample;
float Fc_y3 = YFreqResponse * TimePerSample;
float Fc_i = IFreqResponse * TimePerSample;
float Fc_q = QFreqResponse * TimePerSample;
float Fc_i_2 = Fc_i * 2.0;
float Fc_q_2 = Fc_q * 2.0;
float Fc_y1_2 = Fc_y1 * 2.0;
float Fc_y2_2 = Fc_y2 * 2.0;
float Fc_y3_2 = Fc_y3 * 2.0;
float Fc_i_pi2 = Fc_i * 6.283185482025146484375;
float Fc_q_pi2 = Fc_q * 6.283185482025146484375;
float Fc_y1_pi2 = Fc_y1 * 6.283185482025146484375;
float Fc_y2_pi2 = Fc_y2 * 6.283185482025146484375;
float Fc_y3_pi2 = Fc_y3 * 6.283185482025146484375;
float PI2Length = 0.098174773156642913818359375;
float W = (6.283185482025146484375 * CCValue) * ScanTime;
float WoPI = W / 3.1415927410125732421875;
float HOffset = BValue / WoPI;
float VScale = (AValue * source_dims.y) / WoPI;
float4 YAccum = float4(0.0);
float4 IAccum = float4(0.0);
float4 QAccum = float4(0.0);
float4 Cy = float4(in.v_uv.y);
float4 VPosition = Cy;
float4 SincY1;
float _259;
float _274;
float _290;
float _306;
float4 SincY2;
float _322;
float _337;
float _352;
float _367;
float4 SincY3;
float _383;
float _398;
float _413;
float _428;
float4 IdealI;
float _457;
float _474;
float _491;
float _508;
float4 IdealQ;
float _526;
float _543;
float _560;
float _577;
for (float i = 0.0; i < 64.0; i += 4.0)
{
float n = i - 32.0;
float4 n4 = float4(n) + float4(0.0, 1.0, 2.0, 3.0);
float4 Cx = float4(in.v_uv.x) + ((n4 * 0.25) / float4(source_dims.x));
float4 HPosition = Cx;
float4 C = Source.sample(SourceSmplr, float2(Cx.x, Cy.x));
float4 T = (HPosition + float4(HOffset)) + (VPosition * float4(VScale));
float4 WT = T * W;
float4 SincKernel = float4(0.540000021457672119140625) + (cos(n4 * PI2Length) * 0.4600000083446502685546875);
float4 SincYIn1 = n4 * Fc_y1_pi2;
float4 SincYIn2 = n4 * Fc_y2_pi2;
float4 SincYIn3 = n4 * Fc_y3_pi2;
float4 SincIIn = n4 * Fc_i_pi2;
float4 SincQIn = n4 * Fc_q_pi2;
if (SincYIn1.x != 0.0)
{
_259 = sin(SincYIn1.x) / SincYIn1.x;
}
else
{
_259 = 1.0;
}
SincY1.x = _259;
if (SincYIn1.y != 0.0)
{
_274 = sin(SincYIn1.y) / SincYIn1.y;
}
else
{
_274 = 1.0;
}
SincY1.y = _274;
if (SincYIn1.z != 0.0)
{
_290 = sin(SincYIn1.z) / SincYIn1.z;
}
else
{
_290 = 1.0;
}
SincY1.z = _290;
if (SincYIn1.w != 0.0)
{
_306 = sin(SincYIn1.w) / SincYIn1.w;
}
else
{
_306 = 1.0;
}
SincY1.w = _306;
if (SincYIn2.x != 0.0)
{
_322 = sin(SincYIn2.x) / SincYIn2.x;
}
else
{
_322 = 1.0;
}
SincY2.x = _322;
if (SincYIn2.y != 0.0)
{
_337 = sin(SincYIn2.y) / SincYIn2.y;
}
else
{
_337 = 1.0;
}
SincY2.y = _337;
if (SincYIn2.z != 0.0)
{
_352 = sin(SincYIn2.z) / SincYIn2.z;
}
else
{
_352 = 1.0;
}
SincY2.z = _352;
if (SincYIn2.w != 0.0)
{
_367 = sin(SincYIn2.w) / SincYIn2.w;
}
else
{
_367 = 1.0;
}
SincY2.w = _367;
if (SincYIn3.x != 0.0)
{
_383 = sin(SincYIn3.x) / SincYIn3.x;
}
else
{
_383 = 1.0;
}
SincY3.x = _383;
if (SincYIn3.y != 0.0)
{
_398 = sin(SincYIn3.y) / SincYIn3.y;
}
else
{
_398 = 1.0;
}
SincY3.y = _398;
if (SincYIn3.z != 0.0)
{
_413 = sin(SincYIn3.z) / SincYIn3.z;
}
else
{
_413 = 1.0;
}
SincY3.z = _413;
if (SincYIn3.w != 0.0)
{
_428 = sin(SincYIn3.w) / SincYIn3.w;
}
else
{
_428 = 1.0;
}
SincY3.w = _428;
float4 IdealY = ((SincY1 * Fc_y1_2) - (SincY2 * Fc_y2_2)) + (SincY3 * Fc_y3_2);
if (SincIIn.x != 0.0)
{
_457 = sin(SincIIn.x) / SincIIn.x;
}
else
{
_457 = 1.0;
}
IdealI.x = Fc_i_2 * _457;
if (SincIIn.y != 0.0)
{
_474 = sin(SincIIn.y) / SincIIn.y;
}
else
{
_474 = 1.0;
}
IdealI.y = Fc_i_2 * _474;
if (SincIIn.z != 0.0)
{
_491 = sin(SincIIn.z) / SincIIn.z;
}
else
{
_491 = 1.0;
}
IdealI.z = Fc_i_2 * _491;
if (SincIIn.w != 0.0)
{
_508 = sin(SincIIn.w) / SincIIn.w;
}
else
{
_508 = 1.0;
}
IdealI.w = Fc_i_2 * _508;
if (SincQIn.x != 0.0)
{
_526 = sin(SincQIn.x) / SincQIn.x;
}
else
{
_526 = 1.0;
}
IdealQ.x = Fc_q_2 * _526;
if (SincQIn.y != 0.0)
{
_543 = sin(SincQIn.y) / SincQIn.y;
}
else
{
_543 = 1.0;
}
IdealQ.y = Fc_q_2 * _543;
if (SincQIn.z != 0.0)
{
_560 = sin(SincQIn.z) / SincQIn.z;
}
else
{
_560 = 1.0;
}
IdealQ.z = Fc_q_2 * _560;
if (SincQIn.w != 0.0)
{
_577 = sin(SincQIn.w) / SincQIn.w;
}
else
{
_577 = 1.0;
}
IdealQ.w = Fc_q_2 * _577;
float4 FilterY = SincKernel * IdealY;
float4 FilterI = SincKernel * IdealI;
float4 FilterQ = SincKernel * IdealQ;
YAccum += (C * FilterY);
IAccum += ((C * cos(WT)) * FilterI);
QAccum += ((C * sin(WT)) * FilterQ);
}
float3 YIQ = float3(((YAccum.x + YAccum.y) + YAccum.z) + YAccum.w, (((IAccum.x + IAccum.y) + IAccum.z) + IAccum.w) * 2.0, (((QAccum.x + QAccum.y) + QAccum.z) + QAccum.w) * 2.0);
float3 RGB = float3(dot(YIQ, float3(1.0, 0.95599997043609619140625, 0.620999991893768310546875)), dot(YIQ, float3(1.0, -0.272000014781951904296875, -0.647000014781951904296875)), dot(YIQ, float3(1.0, -1.10599994659423828125, 1.70299994945526123046875)));
out.FragColor = float4(RGB, BaseTexel.w);
return out;
}