diff --git a/CMakeLists.txt b/CMakeLists.txt index 234e381..51cee2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,6 +207,7 @@ if(CPPCHECK_EXE) --suppress=toomanyconfigs --suppress=*:*source/external/* --suppress=*:*source/core/rendering/sdl3gpu/spv/* + --suppress=*:*source/core/rendering/sdl3gpu/msl/* --suppress=normalCheckLevelMaxBranches -D_DEBUG -DLINUX_BUILD diff --git a/docs/audit-core-rendering.md b/docs/audit-core-rendering.md index da23023..bea2f77 100644 --- a/docs/audit-core-rendering.md +++ b/docs/audit-core-rendering.md @@ -20,7 +20,7 @@ Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fic |----|--------|--------| | COUP-01 | Inversión de dependencia `core/rendering → game/*` | DONE | | DUP-02 | `Surface::render` / `renderWithColorReplace` duplicados | DONE | -| CONV-03 | Shaders MSL como raw strings en `.cpp` + helpers duplicados | PENDING | +| CONV-03 | Shaders MSL como raw strings en `.cpp` + helpers duplicados | DONE | | DUP-03 | Familia `Text::write*` con bucle UTF-8 repetido 5 veces | PENDING | | COUP-02 | `Screen` instancia `SDL3GPUShader` directamente + `Surface` → `Screen::get()` | PENDING | @@ -33,8 +33,8 @@ Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fic | DUP-01 | `gif.cpp:238-276` | 3 ramas del switch extension saltan bloque+subloops idénticos | Importante | PENDING | helper `skipExtensionBlocks` | | DUP-02 | `surface.cpp:80-137` | `render` vs `renderWithColorReplace` ~90% idénticos | Crítico | DONE | `render()` delega amb source==target==transparent | | DUP-03 | `text.cpp:145-289` | bucle UTF-8 repetido en write/writeColored/writeMonoDigits/length/lengthMonoDigits | Importante | PENDING | iterador de glifos | -| DUP-04 | `sdl3gpu_shader.cpp:165-201` + `crtpi_pipeline.cpp:165-202` | `createShaderMSL/SPIRV` duplicados | Importante | PENDING | extraer a `shader_factory` | -| DUP-05 | `sdl3gpu_shader.cpp:18-35` + `crtpi_pipeline.cpp:17-34` | vertex shader MSL duplicado | Menor | PENDING | compartir string | +| DUP-04 | `sdl3gpu_shader.cpp:165-201` + `crtpi_pipeline.cpp:165-202` | `createShaderMSL/SPIRV` duplicados | Importante | DONE | `sdl3gpu/shader_factory.hpp` | +| DUP-05 | `sdl3gpu_shader.cpp:18-35` + `crtpi_pipeline.cpp:17-34` | vertex shader MSL duplicado | Menor | DONE | `sdl3gpu/msl/postfx_vert.msl.h` compartit | | DUP-06 | `surface.cpp:14-43` | constructor Surface abre y parsea GIF 2 veces | Importante | PENDING | `loadGif` devuelve pixels+palette | | DUP-07 | `crtpi_pipeline.hpp:70-87` + `shader_backend.hpp:30-45` + `crtpi_pipeline.cpp:45-62` | defaults CRT-Pi triples | Importante | PENDING | single source of truth | @@ -63,7 +63,7 @@ Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fic | PATCH-04 | `sprite.cpp:47-49` | `Sprite::update` vacío hook no invocado | Menor | PENDING | borrar (ver DEAD-10) | | PATCH-05 | `gif.cpp:12-15` | comentario "reemplaçar macro READ" | Menor | PENDING | limpiar comentario | | PATCH-06 | `gif.cpp:83-86` | guard self-reference defensivo | Menor | WONTFIX | dejar, cambiar `cerr` → logger futuro | -| PATCH-07 | `sdl3gpu_shader.cpp:17,162` + `crtpi_pipeline.cpp:16,162` | `NOLINTBEGIN` gigantes sobre MSL | Importante | PENDING | se resuelve con CONV-03 | +| PATCH-07 | `sdl3gpu_shader.cpp:17,162` + `crtpi_pipeline.cpp:16,162` | `NOLINTBEGIN` gigantes sobre MSL | Importante | DONE | eliminats amb CONV-03 | | PATCH-08 | `render_info.cpp:64,138-139` | `mono_digits=true` asignado 2 veces | Menor | PENDING | init única | ## 4. Acoplamiento innecesario @@ -111,7 +111,7 @@ Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fic |----|----------|-----------|--------| | CONV-01 | Magic numbers vs "sin magic numbers" | HC-* | cubierto por HC | | CONV-02 | HUD hardcoded vs "configurabilidad desde ficheros" | HC-04 | cubierto por HC-04 | -| CONV-03 | Shaders MSL como raw strings en `.cpp` | DUP-04/05, PATCH-07 | PENDING | +| CONV-03 | Shaders MSL como raw strings en `.cpp` | DUP-04/05, PATCH-07 | DONE | | CONV-04 | Singletons vs "interfaces + callbacks" | COUP-06 | DEFERRED | | CONV-05 | `core → game` deps | COUP-01 | cubierto por COUP-01 | @@ -122,4 +122,5 @@ Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fic - `9243db1` — docs: full de treball inicial (aquest fitxer) - `9434e4e` — COUP-01a: `Notifier` desacoblat de `game/ui/notification_presets` - `9395520` — COUP-01b: `RenderInfo` desacoblat de `game/options` + `game/ui/ui_palette` -- `` — DUP-02: `Surface::render` delega a `renderWithColorReplace` +- `d479475` — DUP-02: `Surface::render` delega a `renderWithColorReplace` +- `` — CONV-03 + DUP-04 + DUP-05 + PATCH-07: shaders MSL i helpers extrets a `sdl3gpu/msl/` i `sdl3gpu/shader_factory.hpp` diff --git a/source/core/rendering/sdl3gpu/msl/crtpi_frag.msl.h b/source/core/rendering/sdl3gpu/msl/crtpi_frag.msl.h new file mode 100644 index 0000000..e770feb --- /dev/null +++ b/source/core/rendering/sdl3gpu/msl/crtpi_frag.msl.h @@ -0,0 +1,138 @@ +#pragma once + +#ifdef __APPLE__ + +// Fragment shader del shader "crtpi" (algoritme CRT-Pi): scanlines amb +// pesos gaussians, multisample opcional, gamma i màscara de subpíxels. +namespace Rendering::Msl { + + inline constexpr const char* kCrtpiFrag = R"( +#include +using namespace metal; + +struct PostVOut { + float4 pos [[position]]; + float2 uv; +}; + +struct CrtPiUniforms { + float scanline_weight; + float scanline_gap_brightness; + float bloom_factor; + float input_gamma; + float output_gamma; + float mask_brightness; + float curvature_x; + float curvature_y; + int mask_type; + int enable_scanlines; + int enable_multisample; + int enable_gamma; + int enable_curvature; + int enable_sharper; + float texture_width; + float texture_height; +}; + +static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) { + float2 curvature = float2(cx, cy); + float2 barrel_scale = 1.0f - (0.23f * curvature); + coord *= screen_scale; + coord -= 0.5f; + float rsq = coord.x * coord.x + coord.y * coord.y; + coord += coord * (curvature * rsq); + coord *= barrel_scale; + if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); } + coord += 0.5f; + coord /= screen_scale; + return coord; +} + +static float crtpi_scan_weight(float dist, float sw, float gap) { + return max(1.0f - dist * dist * sw, gap); +} + +static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) { + float w = crtpi_scan_weight(dy, sw, gap); + if (ms) { + w += crtpi_scan_weight(dy - filter_w, sw, gap); + w += crtpi_scan_weight(dy + filter_w, sw, gap); + w *= 0.3333333f; + } + return w; +} + +fragment float4 crtpi_fs(PostVOut in [[stage_in]], + texture2d tex [[texture(0)]], + sampler samp [[sampler(0)]], + constant CrtPiUniforms& u [[buffer(0)]]) { + float2 tex_size = float2(u.texture_width, u.texture_height); + float filter_width = (768.0f / u.texture_height) / 3.0f; + float2 texcoord = in.uv; + + if (u.enable_curvature != 0) { + texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y); + if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); } + } + + float2 coord_in_pixels = texcoord * tex_size; + float2 tc; + float scan_weight; + + if (u.enable_sharper != 0) { + float2 temp = floor(coord_in_pixels) + 0.5f; + tc = temp / tex_size; + float2 deltas = coord_in_pixels - temp; + scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0); + float2 signs = sign(deltas); + deltas.x *= 2.0f; + deltas = deltas * deltas; + deltas.y = deltas.y * deltas.y; + deltas.x *= 0.5f; + deltas.y *= 8.0f; + deltas /= tex_size; + deltas *= signs; + tc = tc + deltas; + } else { + float temp_y = floor(coord_in_pixels.y) + 0.5f; + float y_coord = temp_y / tex_size.y; + float dy = coord_in_pixels.y - temp_y; + scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0); + float sign_y = sign(dy); + dy = dy * dy; + dy = dy * dy; + dy *= 8.0f; + dy /= tex_size.y; + dy *= sign_y; + tc = float2(texcoord.x, y_coord + dy); + } + + float3 colour = tex.sample(samp, tc).rgb; + + if (u.enable_scanlines != 0) { + if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); } + colour *= scan_weight * u.bloom_factor; + if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); } + } + + if (u.mask_type == 1) { + float wm = fract(in.pos.x * 0.5f); + float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness) + : float3(1.0f, u.mask_brightness, 1.0f); + colour *= mask; + } else if (u.mask_type == 2) { + float wm = fract(in.pos.x * 0.3333333f); + float3 mask = float3(u.mask_brightness); + if (wm < 0.3333333f) mask.x = 1.0f; + else if (wm < 0.6666666f) mask.y = 1.0f; + else mask.z = 1.0f; + colour *= mask; + } + + return float4(colour, 1.0f); +} +)"; + +} // namespace Rendering::Msl + +#endif // __APPLE__ diff --git a/source/core/rendering/sdl3gpu/msl/postfx_frag.msl.h b/source/core/rendering/sdl3gpu/msl/postfx_frag.msl.h new file mode 100644 index 0000000..0a9fce8 --- /dev/null +++ b/source/core/rendering/sdl3gpu/msl/postfx_frag.msl.h @@ -0,0 +1,137 @@ +#pragma once + +#ifdef __APPLE__ + +// Fragment shader del shader "postfx": vignette, chroma, scanlines, mask, +// gamma, curvature, bleeding i flicker. Els paràmetres venen via uniforms. +namespace Rendering::Msl { + + inline constexpr const char* kPostfxFrag = R"( +#include +using namespace metal; + +struct PostVOut { + float4 pos [[position]]; + float2 uv; +}; + +struct PostFXUniforms { + float vignette_strength; + float chroma_strength; + float scanline_strength; + float screen_height; + float mask_strength; + float gamma_strength; + float curvature; + float bleeding; + float pixel_scale; + float time; + float oversample; + float flicker; +}; + +static float3 rgb_to_ycc(float3 rgb) { + return float3( + 0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b, + -0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f, + 0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f + ); +} +static float3 ycc_to_rgb(float3 ycc) { + float y = ycc.x; + float cb = ycc.y - 0.5f; + float cr = ycc.z - 0.5f; + return clamp(float3( + y + 1.402f*cr, + y - 0.344f*cb - 0.714f*cr, + y + 1.772f*cb + ), 0.0f, 1.0f); +} + +fragment float4 postfx_fs(PostVOut in [[stage_in]], + texture2d scene [[texture(0)]], + sampler samp [[sampler(0)]], + constant PostFXUniforms& u [[buffer(0)]]) { + float2 uv = in.uv; + + if (u.curvature > 0.0f) { + float2 c = uv - 0.5f; + float rsq = dot(c, c); + float2 dist = float2(0.05f, 0.1f) * u.curvature; + float2 barrelScale = 1.0f - 0.23f * dist; + c += c * (dist * rsq); + c *= barrelScale; + if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) { + return float4(0.0f, 0.0f, 0.0f, 1.0f); + } + uv = c + 0.5f; + } + + float3 base = scene.sample(samp, uv).rgb; + + float3 colour; + if (u.bleeding > 0.0f) { + float tw = float(scene.get_width()); + float step = u.oversample / tw; + float3 ycc = rgb_to_ycc(base); + float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb); + float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb); + float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb); + float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb); + ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f; + colour = mix(base, ycc_to_rgb(ycc), u.bleeding); + } else { + colour = base; + } + + float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f)); + colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r; + colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b; + + if (u.gamma_strength > 0.0f) { + float3 lin = pow(colour, float3(2.4f)); + colour = mix(colour, lin, u.gamma_strength); + } + + const float SCAN_DARK_RATIO = 0.333f; + const float SCAN_DARK_FLOOR = 0.42f; + if (u.scanline_strength > 0.0f) { + float ps = max(1.0f, round(u.pixel_scale)); + float frac_in_row = fract(uv.y * u.screen_height); + float row_pos = floor(frac_in_row * ps); + float bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO))); + float is_dark = step(bright_rows, row_pos); + float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark); + colour *= mix(1.0f, scan, u.scanline_strength); + } + + if (u.gamma_strength > 0.0f) { + float3 enc = pow(colour, float3(1.0f/2.2f)); + colour = mix(colour, enc, u.gamma_strength); + } + + float2 d = uv - 0.5f; + float vignette = 1.0f - dot(d, d) * u.vignette_strength; + colour *= clamp(vignette, 0.0f, 1.0f); + + if (u.mask_strength > 0.0f) { + float whichMask = fract(in.pos.x * 0.3333333f); + float3 mask = float3(0.80f); + if (whichMask < 0.3333333f) mask.x = 1.0f; + else if (whichMask < 0.6666667f) mask.y = 1.0f; + else mask.z = 1.0f; + colour = mix(colour, colour * mask, u.mask_strength); + } + + if (u.flicker > 0.0f) { + float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f; + colour *= 1.0f - u.flicker * 0.04f * flicker_wave; + } + + return float4(colour, 1.0f); +} +)"; + +} // namespace Rendering::Msl + +#endif // __APPLE__ diff --git a/source/core/rendering/sdl3gpu/msl/postfx_vert.msl.h b/source/core/rendering/sdl3gpu/msl/postfx_vert.msl.h new file mode 100644 index 0000000..dc50b38 --- /dev/null +++ b/source/core/rendering/sdl3gpu/msl/postfx_vert.msl.h @@ -0,0 +1,30 @@ +#pragma once + +#ifdef __APPLE__ + +// Vertex shader compartit per tots els pipelines de post-procés: +// fullscreen-triangle que cobreix tota l'àrea del swapchain amb UVs a [0,1]. +namespace Rendering::Msl { + + inline constexpr const char* kPostfxVert = R"( +#include +using namespace metal; + +struct PostVOut { + float4 pos [[position]]; + float2 uv; +}; + +vertex PostVOut postfx_vs(uint vid [[vertex_id]]) { + const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} }; + const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} }; + PostVOut out; + out.pos = float4(positions[vid], 0.0, 1.0); + out.uv = uvs[vid]; + return out; +} +)"; + +} // namespace Rendering::Msl + +#endif // __APPLE__ diff --git a/source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp b/source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp index 63e1114..9aed88b 100644 --- a/source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp +++ b/source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp @@ -3,206 +3,17 @@ #include #include +#include "core/rendering/sdl3gpu/shader_factory.hpp" #include "core/rendering/sdl3gpu/viewport.hpp" -#ifndef __APPLE__ +#ifdef __APPLE__ +#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h" +#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h" +#else #include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h" #include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h" #endif -namespace { - -#ifdef __APPLE__ - // NOLINTBEGIN(readability-identifier-naming) - const char* CRTPI_VERT_MSL_ = R"( -#include -using namespace metal; - -struct PostVOut { - float4 pos [[position]]; - float2 uv; -}; - -vertex PostVOut postfx_vs(uint vid [[vertex_id]]) { - const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} }; - const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} }; - PostVOut out; - out.pos = float4(positions[vid], 0.0, 1.0); - out.uv = uvs[vid]; - return out; -} -)"; - - const char* CRTPI_FRAG_MSL_ = R"( -#include -using namespace metal; - -struct PostVOut { - float4 pos [[position]]; - float2 uv; -}; - -struct CrtPiUniforms { - float scanline_weight; - float scanline_gap_brightness; - float bloom_factor; - float input_gamma; - float output_gamma; - float mask_brightness; - float curvature_x; - float curvature_y; - int mask_type; - int enable_scanlines; - int enable_multisample; - int enable_gamma; - int enable_curvature; - int enable_sharper; - float texture_width; - float texture_height; -}; - -static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) { - float2 curvature = float2(cx, cy); - float2 barrel_scale = 1.0f - (0.23f * curvature); - coord *= screen_scale; - coord -= 0.5f; - float rsq = coord.x * coord.x + coord.y * coord.y; - coord += coord * (curvature * rsq); - coord *= barrel_scale; - if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); } - coord += 0.5f; - coord /= screen_scale; - return coord; -} - -static float crtpi_scan_weight(float dist, float sw, float gap) { - return max(1.0f - dist * dist * sw, gap); -} - -static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) { - float w = crtpi_scan_weight(dy, sw, gap); - if (ms) { - w += crtpi_scan_weight(dy - filter_w, sw, gap); - w += crtpi_scan_weight(dy + filter_w, sw, gap); - w *= 0.3333333f; - } - return w; -} - -fragment float4 crtpi_fs(PostVOut in [[stage_in]], - texture2d tex [[texture(0)]], - sampler samp [[sampler(0)]], - constant CrtPiUniforms& u [[buffer(0)]]) { - float2 tex_size = float2(u.texture_width, u.texture_height); - float filter_width = (768.0f / u.texture_height) / 3.0f; - float2 texcoord = in.uv; - - if (u.enable_curvature != 0) { - texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y); - if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); } - } - - float2 coord_in_pixels = texcoord * tex_size; - float2 tc; - float scan_weight; - - if (u.enable_sharper != 0) { - float2 temp = floor(coord_in_pixels) + 0.5f; - tc = temp / tex_size; - float2 deltas = coord_in_pixels - temp; - scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0); - float2 signs = sign(deltas); - deltas.x *= 2.0f; - deltas = deltas * deltas; - deltas.y = deltas.y * deltas.y; - deltas.x *= 0.5f; - deltas.y *= 8.0f; - deltas /= tex_size; - deltas *= signs; - tc = tc + deltas; - } else { - float temp_y = floor(coord_in_pixels.y) + 0.5f; - float y_coord = temp_y / tex_size.y; - float dy = coord_in_pixels.y - temp_y; - scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0); - float sign_y = sign(dy); - dy = dy * dy; - dy = dy * dy; - dy *= 8.0f; - dy /= tex_size.y; - dy *= sign_y; - tc = float2(texcoord.x, y_coord + dy); - } - - float3 colour = tex.sample(samp, tc).rgb; - - if (u.enable_scanlines != 0) { - if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); } - colour *= scan_weight * u.bloom_factor; - if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); } - } - - if (u.mask_type == 1) { - float wm = fract(in.pos.x * 0.5f); - float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness) - : float3(1.0f, u.mask_brightness, 1.0f); - colour *= mask; - } else if (u.mask_type == 2) { - float wm = fract(in.pos.x * 0.3333333f); - float3 mask = float3(u.mask_brightness); - if (wm < 0.3333333f) mask.x = 1.0f; - else if (wm < 0.6666666f) mask.y = 1.0f; - else mask.z = 1.0f; - colour *= mask; - } - - return float4(colour, 1.0f); -} -)"; - // NOLINTEND(readability-identifier-naming) -#endif - - // Helpers per a crear shaders. Duplicats en cada pipeline per mantindre els - // fitxers autònoms — són funcions trivials i no val la pena un header compartit. -#ifdef __APPLE__ - auto createShaderMSL(SDL_GPUDevice* device, - const char* msl_source, - const char* entrypoint, - SDL_GPUShaderStage stage, - Uint32 num_samplers, - Uint32 num_uniform_buffers) -> SDL_GPUShader* { - SDL_GPUShaderCreateInfo info{}; - info.code = reinterpret_cast(msl_source); - info.code_size = std::strlen(msl_source) + 1; - info.entrypoint = entrypoint; - info.format = SDL_GPU_SHADERFORMAT_MSL; - info.stage = stage; - info.num_samplers = num_samplers; - info.num_uniform_buffers = num_uniform_buffers; - return SDL_CreateGPUShader(device, &info); - } -#else - auto createShaderSPIRV(SDL_GPUDevice* device, - const std::uint8_t* spv_code, - std::size_t spv_size, - const char* entrypoint, - SDL_GPUShaderStage stage, - Uint32 num_samplers, - Uint32 num_uniform_buffers) -> SDL_GPUShader* { - SDL_GPUShaderCreateInfo info{}; - info.code = spv_code; - info.code_size = spv_size; - info.entrypoint = entrypoint; - info.format = SDL_GPU_SHADERFORMAT_SPIRV; - info.stage = stage; - info.num_samplers = num_samplers; - info.num_uniform_buffers = num_uniform_buffers; - return SDL_CreateGPUShader(device, &info); - } -#endif - -} // namespace - namespace Rendering { CrtPiPipeline::~CrtPiPipeline() { cleanup(); } @@ -211,8 +22,8 @@ namespace Rendering { device_ = device; #ifdef __APPLE__ - SDL_GPUShader* vert = createShaderMSL(device_, CRTPI_VERT_MSL_, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* frag = createShaderMSL(device_, CRTPI_FRAG_MSL_, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* frag = createShaderMSL(device_, Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #else SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp index 56ee556..be40cb6 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp @@ -4,203 +4,17 @@ #include #include +#include "core/rendering/sdl3gpu/shader_factory.hpp" #include "core/rendering/sdl3gpu/viewport.hpp" -#ifndef __APPLE__ +#ifdef __APPLE__ +#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h" +#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h" +#else #include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h" #include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h" #endif -namespace { - -#ifdef __APPLE__ - // NOLINTBEGIN(readability-identifier-naming) - const char* POSTFX_VERT_MSL_ = R"( -#include -using namespace metal; - -struct PostVOut { - float4 pos [[position]]; - float2 uv; -}; - -vertex PostVOut postfx_vs(uint vid [[vertex_id]]) { - const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} }; - const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} }; - PostVOut out; - out.pos = float4(positions[vid], 0.0, 1.0); - out.uv = uvs[vid]; - return out; -} -)"; - - const char* POSTFX_FRAG_MSL_ = R"( -#include -using namespace metal; - -struct PostVOut { - float4 pos [[position]]; - float2 uv; -}; - -struct PostFXUniforms { - float vignette_strength; - float chroma_strength; - float scanline_strength; - float screen_height; - float mask_strength; - float gamma_strength; - float curvature; - float bleeding; - float pixel_scale; - float time; - float oversample; - float flicker; -}; - -static float3 rgb_to_ycc(float3 rgb) { - return float3( - 0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b, - -0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f, - 0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f - ); -} -static float3 ycc_to_rgb(float3 ycc) { - float y = ycc.x; - float cb = ycc.y - 0.5f; - float cr = ycc.z - 0.5f; - return clamp(float3( - y + 1.402f*cr, - y - 0.344f*cb - 0.714f*cr, - y + 1.772f*cb - ), 0.0f, 1.0f); -} - -fragment float4 postfx_fs(PostVOut in [[stage_in]], - texture2d scene [[texture(0)]], - sampler samp [[sampler(0)]], - constant PostFXUniforms& u [[buffer(0)]]) { - float2 uv = in.uv; - - if (u.curvature > 0.0f) { - float2 c = uv - 0.5f; - float rsq = dot(c, c); - float2 dist = float2(0.05f, 0.1f) * u.curvature; - float2 barrelScale = 1.0f - 0.23f * dist; - c += c * (dist * rsq); - c *= barrelScale; - if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) { - return float4(0.0f, 0.0f, 0.0f, 1.0f); - } - uv = c + 0.5f; - } - - float3 base = scene.sample(samp, uv).rgb; - - float3 colour; - if (u.bleeding > 0.0f) { - float tw = float(scene.get_width()); - float step = u.oversample / tw; - float3 ycc = rgb_to_ycc(base); - float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb); - float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb); - float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb); - float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb); - ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f; - colour = mix(base, ycc_to_rgb(ycc), u.bleeding); - } else { - colour = base; - } - - float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f)); - colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r; - colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b; - - if (u.gamma_strength > 0.0f) { - float3 lin = pow(colour, float3(2.4f)); - colour = mix(colour, lin, u.gamma_strength); - } - - const float SCAN_DARK_RATIO = 0.333f; - const float SCAN_DARK_FLOOR = 0.42f; - if (u.scanline_strength > 0.0f) { - float ps = max(1.0f, round(u.pixel_scale)); - float frac_in_row = fract(uv.y * u.screen_height); - float row_pos = floor(frac_in_row * ps); - float bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO))); - float is_dark = step(bright_rows, row_pos); - float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark); - colour *= mix(1.0f, scan, u.scanline_strength); - } - - if (u.gamma_strength > 0.0f) { - float3 enc = pow(colour, float3(1.0f/2.2f)); - colour = mix(colour, enc, u.gamma_strength); - } - - float2 d = uv - 0.5f; - float vignette = 1.0f - dot(d, d) * u.vignette_strength; - colour *= clamp(vignette, 0.0f, 1.0f); - - if (u.mask_strength > 0.0f) { - float whichMask = fract(in.pos.x * 0.3333333f); - float3 mask = float3(0.80f); - if (whichMask < 0.3333333f) mask.x = 1.0f; - else if (whichMask < 0.6666667f) mask.y = 1.0f; - else mask.z = 1.0f; - colour = mix(colour, colour * mask, u.mask_strength); - } - - if (u.flicker > 0.0f) { - float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f; - colour *= 1.0f - u.flicker * 0.04f * flicker_wave; - } - - return float4(colour, 1.0f); -} -)"; - // NOLINTEND(readability-identifier-naming) -#endif - -#ifdef __APPLE__ - auto createShaderMSL(SDL_GPUDevice* device, - const char* msl_source, - const char* entrypoint, - SDL_GPUShaderStage stage, - std::uint32_t num_samplers, - std::uint32_t num_uniform_buffers) -> SDL_GPUShader* { - SDL_GPUShaderCreateInfo info{}; - info.code = reinterpret_cast(msl_source); - info.code_size = std::strlen(msl_source) + 1; - info.entrypoint = entrypoint; - info.format = SDL_GPU_SHADERFORMAT_MSL; - info.stage = stage; - info.num_samplers = num_samplers; - info.num_uniform_buffers = num_uniform_buffers; - return SDL_CreateGPUShader(device, &info); - } -#else - auto createShaderSPIRV(SDL_GPUDevice* device, - const std::uint8_t* spv_code, - std::size_t spv_size, - const char* entrypoint, - SDL_GPUShaderStage stage, - std::uint32_t num_samplers, - std::uint32_t num_uniform_buffers) -> SDL_GPUShader* { - SDL_GPUShaderCreateInfo info{}; - info.code = spv_code; - info.code_size = spv_size; - info.entrypoint = entrypoint; - info.format = SDL_GPU_SHADERFORMAT_SPIRV; - info.stage = stage; - info.num_samplers = num_samplers; - info.num_uniform_buffers = num_uniform_buffers; - return SDL_CreateGPUShader(device, &info); - } -#endif - -} // namespace - namespace Rendering { SDL3GPUShader::~SDL3GPUShader() { cleanup(); } @@ -261,8 +75,8 @@ namespace Rendering { auto SDL3GPUShader::createShaders() -> bool { #ifdef __APPLE__ - vertex_shader_ = createShaderMSL(device_, POSTFX_VERT_MSL_, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - fragment_shader_ = createShaderMSL(device_, POSTFX_FRAG_MSL_, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + vertex_shader_ = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + fragment_shader_ = createShaderMSL(device_, Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #else vertex_shader_ = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); fragment_shader_ = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); diff --git a/source/core/rendering/sdl3gpu/shader_factory.hpp b/source/core/rendering/sdl3gpu/shader_factory.hpp new file mode 100644 index 0000000..4154c04 --- /dev/null +++ b/source/core/rendering/sdl3gpu/shader_factory.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include +#include +#include + +// Helpers de creació de shaders SDL3 GPU. Inline per evitar un .cpp +// addicional per a funcions trivials. +namespace Rendering { + +#ifdef __APPLE__ + inline auto createShaderMSL(SDL_GPUDevice* device, + const char* msl_source, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader* { + SDL_GPUShaderCreateInfo info{}; + info.code = reinterpret_cast(msl_source); + info.code_size = std::strlen(msl_source) + 1; + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_MSL; + info.stage = stage; + info.num_samplers = num_samplers; + info.num_uniform_buffers = num_uniform_buffers; + return SDL_CreateGPUShader(device, &info); + } +#else + inline auto createShaderSPIRV(SDL_GPUDevice* device, + const std::uint8_t* spv_code, + std::size_t spv_size, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader* { + SDL_GPUShaderCreateInfo info{}; + info.code = spv_code; + info.code_size = spv_size; + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.stage = stage; + info.num_samplers = num_samplers; + info.num_uniform_buffers = num_uniform_buffers; + return SDL_CreateGPUShader(device, &info); + } +#endif + +} // namespace Rendering