refactor(sdl3gpu): extreure shaders MSL i helpers a headers compartits (CONV-03)
Els dos pipelines GPU (PostFX i CrtPi) tenien: - Raw strings MSL incrustades al .cpp amb NOLINTBEGIN(readability- identifier-naming) envoltant ~150 línies cadascun. - Funcions createShaderMSL / createShaderSPIRV duplicades byte a byte en cada .cpp (DUP-04), amb un comentari admetent-ho. - El vertex shader postfx_vs escrit dues voltes, idèntic (DUP-05). S'introdueixen: - source/core/rendering/sdl3gpu/shader_factory.hpp amb els dos helpers inline, per a reutilitzar des de qualsevol pipeline. - source/core/rendering/sdl3gpu/msl/ amb un header per shader (postfx_vert compartit, postfx_frag, crtpi_frag); cada constant es diu kCamelCase i cau per defecte sota el project naming, així que els blocs NOLINT desapareixen (PATCH-07). - Suppresió a cppcheck per a sdl3gpu/msl/* (és MSL, no C++). Els .cpp es redueixen ~190 línies cada un i queden només amb la lògica del pipeline. Build + smoke test de 5s sota xvfb OK (exit 124). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d479475dcf
commit
5f46fb22a4
8 files changed
+377
-395
No files matched your search
@@ -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
|
||||
|
||||
@@ -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`
|
||||
- `<pendent>` — DUP-02: `Surface::render` delega a `renderWithColorReplace`
|
||||
- `d479475` — DUP-02: `Surface::render` delega a `renderWithColorReplace`
|
||||
- `<pendent>` — CONV-03 + DUP-04 + DUP-05 + PATCH-07: shaders MSL i helpers extrets a `sdl3gpu/msl/` i `sdl3gpu/shader_factory.hpp`
|
||||
@@ -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 <metal_stdlib>
|
||||
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<float> 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__
|
||||
@@ -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 <metal_stdlib>
|
||||
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<float> 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__
|
||||
@@ -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 <metal_stdlib>
|
||||
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__
|
||||
@@ -3,206 +3,17 @@
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#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 <metal_stdlib>
|
||||
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 <metal_stdlib>
|
||||
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<float> 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<const Uint8*>(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);
|
||||
|
||||
@@ -4,203 +4,17 @@
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#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 <metal_stdlib>
|
||||
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 <metal_stdlib>
|
||||
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<float> 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<const Uint8*>(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);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// 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<const Uint8*>(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
|
||||
Reference in new issue
Block a user