treballant en sdl3gpu
This commit is contained in:
@@ -24,6 +24,9 @@ set(APP_SOURCES
|
|||||||
source/core/rendering/screen.cpp
|
source/core/rendering/screen.cpp
|
||||||
source/core/rendering/text.cpp
|
source/core/rendering/text.cpp
|
||||||
|
|
||||||
|
# Core - SDL3 GPU shader backend
|
||||||
|
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
||||||
|
|
||||||
# Core - Input global (nueva)
|
# Core - Input global (nueva)
|
||||||
source/core/input/global_inputs.cpp
|
source/core/input/global_inputs.cpp
|
||||||
|
|
||||||
@@ -48,9 +51,66 @@ set(APP_SOURCES
|
|||||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
||||||
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
||||||
|
|
||||||
|
# --- COMPILACIÓ SHADERS SPIR-V (Linux/Windows — macOS usa Metal) ---
|
||||||
|
if(NOT APPLE)
|
||||||
|
find_program(GLSLC_EXE NAMES glslc)
|
||||||
|
|
||||||
|
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
||||||
|
set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu")
|
||||||
|
|
||||||
|
set(ALL_SHADER_HEADERS
|
||||||
|
"${HEADERS_DIR}/postfx_vert_spv.h"
|
||||||
|
"${HEADERS_DIR}/postfx_frag_spv.h"
|
||||||
|
"${HEADERS_DIR}/upscale_frag_spv.h"
|
||||||
|
"${HEADERS_DIR}/downscale_frag_spv.h"
|
||||||
|
"${HEADERS_DIR}/crtpi_frag_spv.h"
|
||||||
|
)
|
||||||
|
set(ALL_SHADER_SOURCES
|
||||||
|
"${SHADERS_DIR}/postfx.vert"
|
||||||
|
"${SHADERS_DIR}/postfx.frag"
|
||||||
|
"${SHADERS_DIR}/upscale.frag"
|
||||||
|
"${SHADERS_DIR}/downscale.frag"
|
||||||
|
"${SHADERS_DIR}/crtpi_frag.glsl"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(GLSLC_EXE)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${ALL_SHADER_HEADERS}
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-D GLSLC=${GLSLC_EXE}
|
||||||
|
-D SHADERS_DIR=${SHADERS_DIR}
|
||||||
|
-D HEADERS_DIR=${HEADERS_DIR}
|
||||||
|
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake
|
||||||
|
DEPENDS ${ALL_SHADER_SOURCES}
|
||||||
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
|
COMMENT "Compilant shaders SPIR-V..."
|
||||||
|
)
|
||||||
|
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
|
||||||
|
message(STATUS "glslc trobat: shaders es compilaran automàticament")
|
||||||
|
else()
|
||||||
|
foreach(HDR ${ALL_SHADER_HEADERS})
|
||||||
|
if(NOT EXISTS "${HDR}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"glslc no trobat i header SPIR-V no existeix: ${HDR}\n"
|
||||||
|
" Instal·la glslc: sudo apt install glslang-tools (Linux)\n"
|
||||||
|
" choco install vulkan-sdk (Windows)"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
message(STATUS "glslc no trobat — usant headers SPIR-V precompilats")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(STATUS "macOS: shaders SPIR-V omesos (usa Metal)")
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- EJECUTABLE ---
|
# --- EJECUTABLE ---
|
||||||
add_executable(${PROJECT_NAME} ${APP_SOURCES})
|
add_executable(${PROJECT_NAME} ${APP_SOURCES})
|
||||||
|
|
||||||
|
# Shaders han de compilar-se abans que l'executable (Linux/Windows amb glslc)
|
||||||
|
if(NOT APPLE AND GLSLC_EXE)
|
||||||
|
add_dependencies(${PROJECT_NAME} shaders)
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- DIRECTORIOS DE INCLUSIÓN ---
|
# --- DIRECTORIOS DE INCLUSIÓN ---
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||||
"${CMAKE_SOURCE_DIR}/source"
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
|
|||||||
152
data/shaders/crtpi_frag.glsl
Normal file
152
data/shaders/crtpi_frag.glsl
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — CRT-Pi PostFX
|
||||||
|
// Algoritmo de scanlines continuas con pesos gaussianos, bloom y máscara de fósforo.
|
||||||
|
// Basado en el shader CRT-Pi original (GLSL 3.3), portado a GLSL 4.50 con parámetros uniformes.
|
||||||
|
//
|
||||||
|
// Compile: glslc -fshader-stage=frag --target-env=vulkan1.0 crtpi_frag.glsl -o crtpi_frag.spv
|
||||||
|
// xxd -i crtpi_frag.spv > ../../source/core/rendering/sdl3gpu/crtpi_frag_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D Texture;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform CrtPiBlock {
|
||||||
|
// vec4 #0
|
||||||
|
float scanline_weight; // Ajuste gaussiano de scanlines (default 6.0)
|
||||||
|
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||||
|
float bloom_factor; // Factor de brillo en zonas iluminadas (default 3.5)
|
||||||
|
float input_gamma; // Gamma de entrada — linealización (default 2.4)
|
||||||
|
// vec4 #1
|
||||||
|
float output_gamma; // Gamma de salida — codificación (default 2.2)
|
||||||
|
float mask_brightness; // Brillo sub-píxeles de la máscara (default 0.80)
|
||||||
|
float curvature_x; // Distorsión barrel eje X (default 0.05)
|
||||||
|
float curvature_y; // Distorsión barrel eje Y (default 0.10)
|
||||||
|
// vec4 #2
|
||||||
|
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
int enable_scanlines; // 0 = off, 1 = on
|
||||||
|
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico de scanlines)
|
||||||
|
int enable_gamma; // 0 = off, 1 = on
|
||||||
|
// vec4 #3
|
||||||
|
int enable_curvature; // 0 = off, 1 = on
|
||||||
|
int enable_sharper; // 0 = off, 1 = on
|
||||||
|
float texture_width; // Ancho del canvas lógico en píxeles
|
||||||
|
float texture_height; // Alto del canvas lógico en píxeles
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// Distorsión barrel CRT
|
||||||
|
vec2 distort(vec2 coord, vec2 screen_scale) {
|
||||||
|
vec2 curvature = vec2(u.curvature_x, u.curvature_y);
|
||||||
|
vec2 barrel_scale = 1.0 - (0.23 * curvature);
|
||||||
|
coord *= screen_scale;
|
||||||
|
coord -= vec2(0.5);
|
||||||
|
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||||
|
coord += coord * (curvature * rsq);
|
||||||
|
coord *= barrel_scale;
|
||||||
|
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5) {
|
||||||
|
return vec2(-1.0); // fuera de pantalla
|
||||||
|
}
|
||||||
|
coord += vec2(0.5);
|
||||||
|
coord /= screen_scale;
|
||||||
|
return coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcScanLineWeight(float dist) {
|
||||||
|
return max(1.0 - dist * dist * u.scanline_weight, u.scanline_gap_brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcScanLine(float dy, float filter_width) {
|
||||||
|
float weight = calcScanLineWeight(dy);
|
||||||
|
if (u.enable_multisample != 0) {
|
||||||
|
weight += calcScanLineWeight(dy - filter_width);
|
||||||
|
weight += calcScanLineWeight(dy + filter_width);
|
||||||
|
weight *= 0.3333333;
|
||||||
|
}
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 tex_size = vec2(u.texture_width, u.texture_height);
|
||||||
|
|
||||||
|
// filterWidth: equivalente al original (768.0 / TextureSize.y) / 3.0
|
||||||
|
float filter_width = (768.0 / u.texture_height) / 3.0;
|
||||||
|
|
||||||
|
vec2 texcoord = v_uv;
|
||||||
|
|
||||||
|
// Curvatura barrel opcional
|
||||||
|
if (u.enable_curvature != 0) {
|
||||||
|
texcoord = distort(texcoord, vec2(1.0, 1.0));
|
||||||
|
if (texcoord.x < 0.0) {
|
||||||
|
out_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 texcoord_in_pixels = texcoord * tex_size;
|
||||||
|
vec2 tc;
|
||||||
|
float scan_line_weight;
|
||||||
|
|
||||||
|
if (u.enable_sharper != 0) {
|
||||||
|
// Modo SHARPER: filtrado bicúbico-like con subpixel sharpen
|
||||||
|
vec2 temp_coord = floor(texcoord_in_pixels) + 0.5;
|
||||||
|
tc = temp_coord / tex_size;
|
||||||
|
vec2 deltas = texcoord_in_pixels - temp_coord;
|
||||||
|
scan_line_weight = calcScanLine(deltas.y, filter_width);
|
||||||
|
vec2 signs = sign(deltas);
|
||||||
|
deltas.x *= 2.0;
|
||||||
|
deltas = deltas * deltas;
|
||||||
|
deltas.y = deltas.y * deltas.y;
|
||||||
|
deltas.x *= 0.5;
|
||||||
|
deltas.y *= 8.0;
|
||||||
|
deltas /= tex_size;
|
||||||
|
deltas *= signs;
|
||||||
|
tc = tc + deltas;
|
||||||
|
} else {
|
||||||
|
// Modo estándar
|
||||||
|
float temp_y = floor(texcoord_in_pixels.y) + 0.5;
|
||||||
|
float y_coord = temp_y / tex_size.y;
|
||||||
|
float dy = texcoord_in_pixels.y - temp_y;
|
||||||
|
scan_line_weight = calcScanLine(dy, filter_width);
|
||||||
|
float sign_y = sign(dy);
|
||||||
|
dy = dy * dy;
|
||||||
|
dy = dy * dy;
|
||||||
|
dy *= 8.0;
|
||||||
|
dy /= tex_size.y;
|
||||||
|
dy *= sign_y;
|
||||||
|
tc = vec2(texcoord.x, y_coord + dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 colour = texture(Texture, tc).rgb;
|
||||||
|
|
||||||
|
if (u.enable_scanlines != 0) {
|
||||||
|
if (u.enable_gamma != 0) {
|
||||||
|
colour = pow(colour, vec3(u.input_gamma));
|
||||||
|
}
|
||||||
|
colour *= scan_line_weight * u.bloom_factor;
|
||||||
|
if (u.enable_gamma != 0) {
|
||||||
|
colour = pow(colour, vec3(1.0 / u.output_gamma));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Máscara de fósforo
|
||||||
|
if (u.mask_type == 1) {
|
||||||
|
float which_mask = fract(gl_FragCoord.x * 0.5);
|
||||||
|
vec3 mask = (which_mask < 0.5)
|
||||||
|
? vec3(u.mask_brightness, 1.0, u.mask_brightness)
|
||||||
|
: vec3(1.0, u.mask_brightness, 1.0);
|
||||||
|
colour *= mask;
|
||||||
|
} else if (u.mask_type == 2) {
|
||||||
|
float which_mask = fract(gl_FragCoord.x * 0.3333333);
|
||||||
|
vec3 mask = vec3(u.mask_brightness);
|
||||||
|
if (which_mask < 0.3333333)
|
||||||
|
mask.x = 1.0;
|
||||||
|
else if (which_mask < 0.6666666)
|
||||||
|
mask.y = 1.0;
|
||||||
|
else
|
||||||
|
mask.z = 1.0;
|
||||||
|
colour *= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = vec4(colour, 1.0);
|
||||||
|
}
|
||||||
48
data/shaders/downscale.frag
Normal file
48
data/shaders/downscale.frag
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#version 450
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D source;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform DownscaleUniforms {
|
||||||
|
int algorithm; // 0 = Lanczos2 (ventana 2, ±2 taps), 1 = Lanczos3 (ventana 3, ±3 taps)
|
||||||
|
float pad0;
|
||||||
|
float pad1;
|
||||||
|
float pad2;
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// Kernel Lanczos normalizado: sinc(t) * sinc(t/a) para |t| < a, 0 fuera.
|
||||||
|
float lanczos(float t, float a) {
|
||||||
|
t = abs(t);
|
||||||
|
if (t < 0.0001) { return 1.0; }
|
||||||
|
if (t >= a) { return 0.0; }
|
||||||
|
const float PI = 3.14159265358979;
|
||||||
|
float pt = PI * t;
|
||||||
|
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 src_size = vec2(textureSize(source, 0));
|
||||||
|
// Posición en coordenadas de texel (centros de texel en N+0.5)
|
||||||
|
vec2 p = v_uv * src_size;
|
||||||
|
vec2 p_floor = floor(p);
|
||||||
|
|
||||||
|
float a = (u.algorithm == 0) ? 2.0 : 3.0;
|
||||||
|
int win = int(a);
|
||||||
|
|
||||||
|
vec4 color = vec4(0.0);
|
||||||
|
float weight_sum = 0.0;
|
||||||
|
|
||||||
|
for (int j = -win; j <= win; j++) {
|
||||||
|
for (int i = -win; i <= win; i++) {
|
||||||
|
// Centro del texel (i,j) relativo a p_floor
|
||||||
|
vec2 tap_center = p_floor + vec2(float(i), float(j)) + 0.5;
|
||||||
|
vec2 offset = tap_center - p;
|
||||||
|
float w = lanczos(offset.x, a) * lanczos(offset.y, a);
|
||||||
|
color += texture(source, tap_center / src_size) * w;
|
||||||
|
weight_sum += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = (weight_sum > 0.0) ? (color / weight_sum) : vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
149
data/shaders/postfx.frag
Normal file
149
data/shaders/postfx.frag
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — PostFX effects
|
||||||
|
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
|
||||||
|
// Compile: glslc postfx.frag -o postfx.frag.spv
|
||||||
|
// xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
|
||||||
|
//
|
||||||
|
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
|
||||||
|
// (8 floats, 32 bytes, std140/scalar layout).
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform 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; // physical pixels per logical pixel (vh / tex_height_)
|
||||||
|
float time; // seconds since SDL init
|
||||||
|
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||||
|
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// YCbCr helpers for NTSC bleeding
|
||||||
|
vec3 rgb_to_ycc(vec3 rgb) {
|
||||||
|
return vec3(
|
||||||
|
0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b,
|
||||||
|
-0.169*rgb.r - 0.331*rgb.g + 0.500*rgb.b + 0.5,
|
||||||
|
0.500*rgb.r - 0.419*rgb.g - 0.081*rgb.b + 0.5
|
||||||
|
);
|
||||||
|
}
|
||||||
|
vec3 ycc_to_rgb(vec3 ycc) {
|
||||||
|
float y = ycc.x;
|
||||||
|
float cb = ycc.y - 0.5;
|
||||||
|
float cr = ycc.z - 0.5;
|
||||||
|
return clamp(vec3(
|
||||||
|
y + 1.402*cr,
|
||||||
|
y - 0.344*cb - 0.714*cr,
|
||||||
|
y + 1.772*cb
|
||||||
|
), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = v_uv;
|
||||||
|
|
||||||
|
// Curvatura barrel CRT
|
||||||
|
if (u.curvature > 0.0) {
|
||||||
|
vec2 c = uv - 0.5;
|
||||||
|
float rsq = dot(c, c);
|
||||||
|
vec2 dist = vec2(0.05, 0.1) * u.curvature;
|
||||||
|
vec2 barrelScale = vec2(1.0) - 0.23 * dist;
|
||||||
|
c += c * (dist * rsq);
|
||||||
|
c *= barrelScale;
|
||||||
|
if (abs(c.x) >= 0.5 || abs(c.y) >= 0.5) {
|
||||||
|
out_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uv = c + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Muestra base
|
||||||
|
vec3 base = texture(scene, uv).rgb;
|
||||||
|
|
||||||
|
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||||
|
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
|
||||||
|
vec3 colour;
|
||||||
|
if (u.bleeding > 0.0) {
|
||||||
|
float tw = float(textureSize(scene, 0).x);
|
||||||
|
float step = u.oversample / tw; // 1 pixel lógico en UV
|
||||||
|
vec3 ycc = rgb_to_ycc(base);
|
||||||
|
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
|
||||||
|
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
|
||||||
|
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
|
||||||
|
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 0.0)).rgb);
|
||||||
|
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
|
||||||
|
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||||
|
} else {
|
||||||
|
colour = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aberración cromática (drift animado con time para efecto NTSC real)
|
||||||
|
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
|
||||||
|
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
|
||||||
|
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
|
||||||
|
|
||||||
|
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||||
|
if (u.gamma_strength > 0.0) {
|
||||||
|
vec3 lin = pow(colour, vec3(2.4));
|
||||||
|
colour = mix(colour, lin, u.gamma_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanlines — proporción 2/3 brillantes + 1/3 oscuras por fila lógica.
|
||||||
|
// Casos especiales: 1 subfila → sin efecto; 2 subfilas → 1+1 (50/50).
|
||||||
|
// Constantes ajustables:
|
||||||
|
const float SCAN_DARK_RATIO = 0.333; // fracción de subfilas oscuras (ps >= 3)
|
||||||
|
const float SCAN_DARK_FLOOR = 0.42; // multiplicador de brillo de subfilas oscuras
|
||||||
|
if (u.scanline_strength > 0.0) {
|
||||||
|
float ps = max(1.0, round(u.pixel_scale));
|
||||||
|
float frac_in_row = fract(uv.y * u.screen_height);
|
||||||
|
float row_pos = floor(frac_in_row * ps);
|
||||||
|
// bright_rows: cuántas subfilas son brillantes
|
||||||
|
// ps==1 → ps (todo brillante → is_dark nunca se activa)
|
||||||
|
// ps==2 → 1 brillante + 1 oscura
|
||||||
|
// ps>=3 → floor(ps * (1 - DARK_RATIO)) brillantes
|
||||||
|
float bright_rows = (ps < 2.0) ? ps : ((ps < 3.0) ? 1.0 : floor(ps * (1.0 - SCAN_DARK_RATIO)));
|
||||||
|
float is_dark = step(bright_rows, row_pos);
|
||||||
|
float scan = mix(1.0, SCAN_DARK_FLOOR, is_dark);
|
||||||
|
colour *= mix(1.0, scan, u.scanline_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u.gamma_strength > 0.0) {
|
||||||
|
vec3 enc = pow(colour, vec3(1.0 / 2.2));
|
||||||
|
colour = mix(colour, enc, u.gamma_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viñeta
|
||||||
|
vec2 d = uv - 0.5;
|
||||||
|
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
|
||||||
|
colour *= clamp(vignette, 0.0, 1.0);
|
||||||
|
|
||||||
|
// Máscara de fósforo RGB — después de scanlines (orden original):
|
||||||
|
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
|
||||||
|
if (u.mask_strength > 0.0) {
|
||||||
|
float whichMask = fract(gl_FragCoord.x * 0.3333333);
|
||||||
|
vec3 mask = vec3(0.80);
|
||||||
|
if (whichMask < 0.3333333)
|
||||||
|
mask.x = 1.0;
|
||||||
|
else if (whichMask < 0.6666666)
|
||||||
|
mask.y = 1.0;
|
||||||
|
else
|
||||||
|
mask.z = 1.0;
|
||||||
|
colour = mix(colour, colour * mask, u.mask_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parpadeo de fósforo CRT (~50 Hz)
|
||||||
|
if (u.flicker > 0.0) {
|
||||||
|
float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
|
||||||
|
colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = vec4(colour, 1.0);
|
||||||
|
}
|
||||||
24
data/shaders/postfx.vert
Normal file
24
data/shaders/postfx.vert
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL vertex shader — postfx full-screen triangle
|
||||||
|
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
|
||||||
|
// Compile: glslc postfx.vert -o postfx.vert.spv
|
||||||
|
// xxd -i postfx.vert.spv > ../../source/core/rendering/sdl3gpu/postfx_vert_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 v_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Full-screen triangle (no vertex buffer needed)
|
||||||
|
const vec2 positions[3] = vec2[3](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 3.0, -1.0),
|
||||||
|
vec2(-1.0, 3.0)
|
||||||
|
);
|
||||||
|
const vec2 uvs[3] = vec2[3](
|
||||||
|
vec2(0.0, 1.0),
|
||||||
|
vec2(2.0, 1.0),
|
||||||
|
vec2(0.0,-1.0)
|
||||||
|
);
|
||||||
|
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||||
|
v_uv = uvs[gl_VertexIndex];
|
||||||
|
}
|
||||||
15
data/shaders/upscale.frag
Normal file
15
data/shaders/upscale.frag
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — Nearest-neighbour upscale pass
|
||||||
|
// Used as the first render pass when supersampling is active.
|
||||||
|
// Compile: glslc upscale.frag -o upscale.frag.spv
|
||||||
|
// xxd -i upscale.frag.spv > ../../source/core/rendering/sdl3gpu/upscale_frag_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = texture(scene, v_uv);
|
||||||
|
}
|
||||||
@@ -10,46 +10,93 @@
|
|||||||
|
|
||||||
namespace GlobalInputs {
|
namespace GlobalInputs {
|
||||||
|
|
||||||
static bool dec_zoom_was_pressed = false;
|
static bool dec_zoom_prev = false;
|
||||||
static bool inc_zoom_was_pressed = false;
|
static bool inc_zoom_prev = false;
|
||||||
static bool fullscreen_was_pressed = false;
|
static bool fullscreen_prev = false;
|
||||||
|
static bool shader_prev = false;
|
||||||
|
static bool aspect_prev = false;
|
||||||
|
static bool ss_prev = false;
|
||||||
|
static bool next_shader_prev = false;
|
||||||
|
static bool next_preset_prev = false;
|
||||||
|
|
||||||
auto handle() -> bool {
|
auto handle() -> bool {
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
|
|
||||||
// Decrement zoom
|
// F1 — Reduir zoom
|
||||||
bool dec_zoom = JI_KeyPressed(Options::keys_gui.dec_zoom);
|
bool dec_zoom = JI_KeyPressed(Options::keys_gui.dec_zoom);
|
||||||
if (dec_zoom && !dec_zoom_was_pressed) {
|
if (dec_zoom && !dec_zoom_prev) {
|
||||||
Screen::get()->decZoom();
|
Screen::get()->decZoom();
|
||||||
char msg[32];
|
char msg[32];
|
||||||
snprintf(msg, sizeof(msg), "ZOOM %dx", Screen::get()->getZoom());
|
snprintf(msg, sizeof(msg), "ZOOM %dx", Screen::get()->getZoom());
|
||||||
Overlay::showNotification(msg);
|
Overlay::showNotification(msg);
|
||||||
consumed = true;
|
|
||||||
}
|
}
|
||||||
if (dec_zoom) consumed = true; // Mentres estiga polsada, consumir-la
|
if (dec_zoom) consumed = true;
|
||||||
dec_zoom_was_pressed = dec_zoom;
|
dec_zoom_prev = dec_zoom;
|
||||||
|
|
||||||
// Increment zoom
|
// F2 — Augmentar zoom
|
||||||
bool inc_zoom = JI_KeyPressed(Options::keys_gui.inc_zoom);
|
bool inc_zoom = JI_KeyPressed(Options::keys_gui.inc_zoom);
|
||||||
if (inc_zoom && !inc_zoom_was_pressed) {
|
if (inc_zoom && !inc_zoom_prev) {
|
||||||
Screen::get()->incZoom();
|
Screen::get()->incZoom();
|
||||||
char msg[32];
|
char msg[32];
|
||||||
snprintf(msg, sizeof(msg), "ZOOM %dx", Screen::get()->getZoom());
|
snprintf(msg, sizeof(msg), "ZOOM %dx", Screen::get()->getZoom());
|
||||||
Overlay::showNotification(msg);
|
Overlay::showNotification(msg);
|
||||||
consumed = true;
|
|
||||||
}
|
}
|
||||||
if (inc_zoom) consumed = true;
|
if (inc_zoom) consumed = true;
|
||||||
inc_zoom_was_pressed = inc_zoom;
|
inc_zoom_prev = inc_zoom;
|
||||||
|
|
||||||
// Toggle fullscreen
|
// F3 — Toggle pantalla completa
|
||||||
bool fullscreen = JI_KeyPressed(Options::keys_gui.fullscreen);
|
bool fullscreen = JI_KeyPressed(Options::keys_gui.fullscreen);
|
||||||
if (fullscreen && !fullscreen_was_pressed) {
|
if (fullscreen && !fullscreen_prev) {
|
||||||
Screen::get()->toggleFullscreen();
|
Screen::get()->toggleFullscreen();
|
||||||
Overlay::showNotification(Screen::get()->isFullscreen() ? "FULLSCREEN" : "WINDOWED");
|
Overlay::showNotification(Screen::get()->isFullscreen() ? "PANTALLA COMPLETA" : "FINESTRA");
|
||||||
consumed = true;
|
|
||||||
}
|
}
|
||||||
if (fullscreen) consumed = true;
|
if (fullscreen) consumed = true;
|
||||||
fullscreen_was_pressed = fullscreen;
|
fullscreen_prev = fullscreen;
|
||||||
|
|
||||||
|
// F4 — Toggle shaders
|
||||||
|
bool shader = JI_KeyPressed(Options::keys_gui.toggle_shader);
|
||||||
|
if (shader && !shader_prev) {
|
||||||
|
Screen::get()->toggleShaders();
|
||||||
|
Overlay::showNotification(Options::video.shader_enabled ? "SHADER ON" : "SHADER OFF");
|
||||||
|
}
|
||||||
|
if (shader) consumed = true;
|
||||||
|
shader_prev = shader;
|
||||||
|
|
||||||
|
// F5 — Toggle aspect ratio 4:3
|
||||||
|
bool aspect = JI_KeyPressed(Options::keys_gui.toggle_aspect_ratio);
|
||||||
|
if (aspect && !aspect_prev) {
|
||||||
|
Screen::get()->toggleAspectRatio();
|
||||||
|
Overlay::showNotification(Options::video.aspect_ratio_4_3 ? "4:3 CRT" : "PIXELS QUADRATS");
|
||||||
|
}
|
||||||
|
if (aspect) consumed = true;
|
||||||
|
aspect_prev = aspect;
|
||||||
|
|
||||||
|
// F6 — Toggle supersampling
|
||||||
|
bool ss = JI_KeyPressed(Options::keys_gui.toggle_supersampling);
|
||||||
|
if (ss && !ss_prev) {
|
||||||
|
Screen::get()->toggleSupersampling();
|
||||||
|
Overlay::showNotification(Options::video.supersampling ? "SUPERSAMPLING ON" : "SUPERSAMPLING OFF");
|
||||||
|
}
|
||||||
|
if (ss) consumed = true;
|
||||||
|
ss_prev = ss;
|
||||||
|
|
||||||
|
// F7 — Canviar shader (PostFX ↔ CrtPi)
|
||||||
|
bool next_shader = JI_KeyPressed(Options::keys_gui.next_shader);
|
||||||
|
if (next_shader && !next_shader_prev) {
|
||||||
|
Screen::get()->nextShaderPreset();
|
||||||
|
Overlay::showNotification(Screen::get()->isHardwareAccelerated() ? "POSTFX / CRT-PI" : "SENSE GPU");
|
||||||
|
}
|
||||||
|
if (next_shader) consumed = true;
|
||||||
|
next_shader_prev = next_shader;
|
||||||
|
|
||||||
|
// F8 — Pròxim preset del shader actiu
|
||||||
|
bool next_preset = JI_KeyPressed(Options::keys_gui.next_shader_preset);
|
||||||
|
if (next_preset && !next_preset_prev) {
|
||||||
|
// TODO: ciclar presets quan estiguen implementats (YAML)
|
||||||
|
Overlay::showNotification("PRESET: DEFAULT");
|
||||||
|
}
|
||||||
|
if (next_preset) consumed = true;
|
||||||
|
next_preset_prev = next_preset;
|
||||||
|
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ int waitTime = 0;
|
|||||||
static bool isGuiKey(SDL_Scancode sc) {
|
static bool isGuiKey(SDL_Scancode sc) {
|
||||||
return sc == Options::keys_gui.dec_zoom ||
|
return sc == Options::keys_gui.dec_zoom ||
|
||||||
sc == Options::keys_gui.inc_zoom ||
|
sc == Options::keys_gui.inc_zoom ||
|
||||||
sc == Options::keys_gui.fullscreen;
|
sc == Options::keys_gui.fullscreen ||
|
||||||
|
sc == Options::keys_gui.toggle_shader ||
|
||||||
|
sc == Options::keys_gui.toggle_aspect_ratio ||
|
||||||
|
sc == Options::keys_gui.toggle_supersampling ||
|
||||||
|
sc == Options::keys_gui.next_shader ||
|
||||||
|
sc == Options::keys_gui.next_shader_preset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JI_DisableKeyboard(Uint32 time) {
|
void JI_DisableKeyboard(Uint32 time) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "core/rendering/overlay.hpp"
|
#include "core/rendering/overlay.hpp"
|
||||||
|
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
|
||||||
#include "game/defines.hpp"
|
#include "game/defines.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ Screen::Screen() {
|
|||||||
if (zoom_ > max_zoom_) zoom_ = max_zoom_;
|
if (zoom_ > max_zoom_) zoom_ = max_zoom_;
|
||||||
|
|
||||||
int w = GAME_WIDTH * zoom_;
|
int w = GAME_WIDTH * zoom_;
|
||||||
int h = GAME_HEIGHT * zoom_;
|
int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;
|
||||||
|
|
||||||
window_ = SDL_CreateWindow(Texts::WINDOW_TITLE, w, h, fullscreen_ ? SDL_WINDOW_FULLSCREEN : 0);
|
window_ = SDL_CreateWindow(Texts::WINDOW_TITLE, w, h, fullscreen_ ? SDL_WINDOW_FULLSCREEN : 0);
|
||||||
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
||||||
@@ -41,6 +42,9 @@ Screen::Screen() {
|
|||||||
texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, GAME_WIDTH, GAME_HEIGHT);
|
texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, GAME_WIDTH, GAME_HEIGHT);
|
||||||
SDL_SetTextureScaleMode(texture_, SDL_SCALEMODE_NEAREST);
|
SDL_SetTextureScaleMode(texture_, SDL_SCALEMODE_NEAREST);
|
||||||
|
|
||||||
|
// Inicialitza backend GPU si l'acceleració està activada
|
||||||
|
initShaders();
|
||||||
|
|
||||||
std::cout << "Screen initialized: " << w << "x" << h << " (zoom " << zoom_ << ", max " << max_zoom_ << ")\n";
|
std::cout << "Screen initialized: " << w << "x" << h << " (zoom " << zoom_ << ", max " << max_zoom_ << ")\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,17 +53,74 @@ Screen::~Screen() {
|
|||||||
Options::window.zoom = zoom_;
|
Options::window.zoom = zoom_;
|
||||||
Options::window.fullscreen = fullscreen_;
|
Options::window.fullscreen = fullscreen_;
|
||||||
|
|
||||||
|
// Destrueix el backend GPU
|
||||||
|
if (shader_backend_) {
|
||||||
|
auto* gpu = dynamic_cast<Rendering::SDL3GPUShader*>(shader_backend_.get());
|
||||||
|
if (gpu) gpu->destroy();
|
||||||
|
shader_backend_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (texture_) SDL_DestroyTexture(texture_);
|
if (texture_) SDL_DestroyTexture(texture_);
|
||||||
if (renderer_) SDL_DestroyRenderer(renderer_);
|
if (renderer_) SDL_DestroyRenderer(renderer_);
|
||||||
if (window_) SDL_DestroyWindow(window_);
|
if (window_) SDL_DestroyWindow(window_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::initShaders() {
|
||||||
|
if (!Options::video.gpu_acceleration) return;
|
||||||
|
|
||||||
|
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||||
|
|
||||||
|
const std::string FALLBACK_DRIVER = "none";
|
||||||
|
shader_backend_->setPreferredDriver(
|
||||||
|
Options::video.gpu_acceleration ? "" : FALLBACK_DRIVER);
|
||||||
|
|
||||||
|
// init() rep la finestra i la textura (la textura s'usa com a referència, el GPU fa uploadPixels)
|
||||||
|
if (!shader_backend_->init(window_, texture_, "", "")) {
|
||||||
|
std::cerr << "GPU shader backend initialization failed, using SDL_Renderer fallback\n";
|
||||||
|
shader_backend_.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* gpu = dynamic_cast<Rendering::SDL3GPUShader*>(shader_backend_.get());
|
||||||
|
if (gpu) {
|
||||||
|
std::cout << "GPU driver: " << gpu->getDriverName() << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplica opcions de vídeo
|
||||||
|
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||||
|
shader_backend_->setStretch4_3(Options::video.aspect_ratio_4_3);
|
||||||
|
shader_backend_->setLinearUpscale(Options::video.linear_upscale);
|
||||||
|
shader_backend_->setDownscaleAlgo(Options::video.downscale_algo);
|
||||||
|
|
||||||
|
if (Options::video.supersampling) {
|
||||||
|
shader_backend_->setOversample(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplica presets per defecte (de moment hardcoded, futur: YAML)
|
||||||
|
applyCurrentPostFXPreset();
|
||||||
|
applyCurrentCrtPiPreset();
|
||||||
|
}
|
||||||
|
|
||||||
void Screen::present(Uint32* pixel_data) {
|
void Screen::present(Uint32* pixel_data) {
|
||||||
Overlay::render(pixel_data);
|
Overlay::render(pixel_data);
|
||||||
SDL_UpdateTexture(texture_, nullptr, pixel_data, GAME_WIDTH * sizeof(Uint32));
|
|
||||||
SDL_RenderClear(renderer_);
|
if (shader_backend_ && shader_backend_->isHardwareAccelerated() && Options::video.shader_enabled) {
|
||||||
SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);
|
// Path GPU: puja els píxels i renderitza amb shaders
|
||||||
SDL_RenderPresent(renderer_);
|
shader_backend_->uploadPixels(pixel_data, GAME_WIDTH, GAME_HEIGHT);
|
||||||
|
shader_backend_->render();
|
||||||
|
} else if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||||
|
// GPU activa però shaders desactivats: renderitza net (sense efectes)
|
||||||
|
Rendering::PostFXParams clean{};
|
||||||
|
shader_backend_->setPostFXParams(clean);
|
||||||
|
shader_backend_->uploadPixels(pixel_data, GAME_WIDTH, GAME_HEIGHT);
|
||||||
|
shader_backend_->render();
|
||||||
|
} else {
|
||||||
|
// Fallback SDL_Renderer
|
||||||
|
SDL_UpdateTexture(texture_, nullptr, pixel_data, GAME_WIDTH * sizeof(Uint32));
|
||||||
|
SDL_RenderClear(renderer_);
|
||||||
|
SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);
|
||||||
|
SDL_RenderPresent(renderer_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::toggleFullscreen() {
|
void Screen::toggleFullscreen() {
|
||||||
@@ -68,7 +129,6 @@ void Screen::toggleFullscreen() {
|
|||||||
if (!fullscreen_) {
|
if (!fullscreen_) {
|
||||||
adjustWindowSize();
|
adjustWindowSize();
|
||||||
}
|
}
|
||||||
std::cout << (fullscreen_ ? "Fullscreen ON\n" : "Fullscreen OFF\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::incZoom() {
|
void Screen::incZoom() {
|
||||||
@@ -89,9 +149,85 @@ void Screen::setZoom(int zoom) {
|
|||||||
adjustWindowSize();
|
adjustWindowSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::toggleShaders() {
|
||||||
|
Options::video.shader_enabled = !Options::video.shader_enabled;
|
||||||
|
if (Options::video.shader_enabled) {
|
||||||
|
applyCurrentPostFXPreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::toggleSupersampling() {
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;
|
||||||
|
Options::video.supersampling = !Options::video.supersampling;
|
||||||
|
shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::toggleAspectRatio() {
|
||||||
|
Options::video.aspect_ratio_4_3 = !Options::video.aspect_ratio_4_3;
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->setStretch4_3(Options::video.aspect_ratio_4_3);
|
||||||
|
}
|
||||||
|
if (!fullscreen_) {
|
||||||
|
adjustWindowSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::toggleIntegerScale() {
|
||||||
|
Options::video.integer_scale = !Options::video.integer_scale;
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::nextShaderPreset() {
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;
|
||||||
|
|
||||||
|
// Cicla entre PostFX i CrtPi
|
||||||
|
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
|
||||||
|
shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
|
||||||
|
applyCurrentCrtPiPreset();
|
||||||
|
} else {
|
||||||
|
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||||
|
applyCurrentPostFXPreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->setActiveShader(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::applyCurrentPostFXPreset() {
|
||||||
|
if (!shader_backend_) return;
|
||||||
|
// Preset per defecte "CRT" — futur: carregar des de YAML
|
||||||
|
Rendering::PostFXParams p;
|
||||||
|
p.vignette = 0.4F;
|
||||||
|
p.scanlines = 0.5F;
|
||||||
|
p.chroma = 0.1F;
|
||||||
|
p.mask = 0.0F;
|
||||||
|
p.gamma = 0.0F;
|
||||||
|
p.curvature = 0.0F;
|
||||||
|
p.bleeding = 0.0F;
|
||||||
|
p.flicker = 0.0F;
|
||||||
|
shader_backend_->setPostFXParams(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::applyCurrentCrtPiPreset() {
|
||||||
|
if (!shader_backend_) return;
|
||||||
|
// Preset per defecte — futur: carregar des de YAML
|
||||||
|
Rendering::CrtPiParams p;
|
||||||
|
shader_backend_->setCrtPiParams(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::isHardwareAccelerated() const -> bool {
|
||||||
|
return shader_backend_ && shader_backend_->isHardwareAccelerated();
|
||||||
|
}
|
||||||
|
|
||||||
void Screen::adjustWindowSize() {
|
void Screen::adjustWindowSize() {
|
||||||
int w = GAME_WIDTH * zoom_;
|
int w = GAME_WIDTH * zoom_;
|
||||||
int h = GAME_HEIGHT * zoom_;
|
// Si 4:3 actiu, l'alçada visual és 240 per zoom (200 * 1.2)
|
||||||
|
int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;
|
||||||
SDL_SetWindowSize(window_, w, h);
|
SDL_SetWindowSize(window_, w, h);
|
||||||
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "core/rendering/shader_backend.hpp"
|
||||||
|
|
||||||
class Screen {
|
class Screen {
|
||||||
public:
|
public:
|
||||||
@@ -19,9 +22,20 @@ class Screen {
|
|||||||
void decZoom();
|
void decZoom();
|
||||||
void setZoom(int zoom);
|
void setZoom(int zoom);
|
||||||
|
|
||||||
|
// Shaders i vídeo
|
||||||
|
void toggleShaders();
|
||||||
|
void toggleSupersampling();
|
||||||
|
void toggleAspectRatio();
|
||||||
|
void toggleIntegerScale();
|
||||||
|
void nextShaderPreset(); // Futur: ciclar presets
|
||||||
|
void setActiveShader(Rendering::ShaderType type);
|
||||||
|
void applyCurrentPostFXPreset();
|
||||||
|
void applyCurrentCrtPiPreset();
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
[[nodiscard]] auto isFullscreen() const -> bool { return fullscreen_; }
|
[[nodiscard]] auto isFullscreen() const -> bool { return fullscreen_; }
|
||||||
[[nodiscard]] auto getZoom() const -> int { return zoom_; }
|
[[nodiscard]] auto getZoom() const -> int { return zoom_; }
|
||||||
|
[[nodiscard]] auto isHardwareAccelerated() const -> bool;
|
||||||
[[nodiscard]] auto getWindow() -> SDL_Window* { return window_; }
|
[[nodiscard]] auto getWindow() -> SDL_Window* { return window_; }
|
||||||
[[nodiscard]] auto getRenderer() -> SDL_Renderer* { return renderer_; }
|
[[nodiscard]] auto getRenderer() -> SDL_Renderer* { return renderer_; }
|
||||||
|
|
||||||
@@ -31,12 +45,16 @@ class Screen {
|
|||||||
|
|
||||||
void adjustWindowSize();
|
void adjustWindowSize();
|
||||||
void calculateMaxZoom();
|
void calculateMaxZoom();
|
||||||
|
void initShaders();
|
||||||
|
|
||||||
static Screen* instance_;
|
static Screen* instance_;
|
||||||
|
|
||||||
SDL_Window* window_{nullptr};
|
SDL_Window* window_{nullptr};
|
||||||
SDL_Renderer* renderer_{nullptr};
|
SDL_Renderer* renderer_{nullptr};
|
||||||
SDL_Texture* texture_{nullptr}; // 320x200 streaming, ARGB8888
|
SDL_Texture* texture_{nullptr}; // 320x200 streaming, ABGR8888 (fallback SDL_Renderer)
|
||||||
|
|
||||||
|
// Backend GPU (nullptr si no disponible o desactivat)
|
||||||
|
std::unique_ptr<Rendering::ShaderBackend> shader_backend_;
|
||||||
|
|
||||||
int zoom_{3};
|
int zoom_{3};
|
||||||
int max_zoom_{6};
|
int max_zoom_{6};
|
||||||
|
|||||||
10362
source/core/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
10362
source/core/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
4254
source/core/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
4254
source/core/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
11718
source/core/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
11718
source/core/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1450
source/core/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
1450
source/core/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1333
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
1333
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
183
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
183
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include "core/rendering/shader_backend.hpp"
|
||||||
|
|
||||||
|
// PostFX uniforms pushed to fragment stage each frame.
|
||||||
|
// Must match the MSL struct and GLSL uniform block layout.
|
||||||
|
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||||
|
struct PostFXUniforms {
|
||||||
|
float vignette_strength; // 0 = none, ~0.8 = subtle
|
||||||
|
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
|
||||||
|
float scanline_strength; // 0 = off, 1 = full
|
||||||
|
float screen_height; // logical height in pixels (used by bleeding effect)
|
||||||
|
float mask_strength; // 0 = off, 1 = full phosphor dot mask
|
||||||
|
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction
|
||||||
|
float curvature; // 0 = flat, 1 = max barrel distortion
|
||||||
|
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
|
||||||
|
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||||
|
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
||||||
|
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||||
|
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
|
||||||
|
};
|
||||||
|
|
||||||
|
// CrtPi uniforms pushed to fragment stage each frame.
|
||||||
|
// Must match the MSL struct and GLSL uniform block layout.
|
||||||
|
// 14 fields (8 floats + 6 ints) + 2 floats (texture size) = 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||||
|
struct CrtPiUniforms {
|
||||||
|
// vec4 #0
|
||||||
|
float scanline_weight; // Ajuste gaussiano (default 6.0)
|
||||||
|
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||||
|
float bloom_factor; // Factor brillo zonas iluminadas (default 3.5)
|
||||||
|
float input_gamma; // Gamma de entrada (default 2.4)
|
||||||
|
// vec4 #1
|
||||||
|
float output_gamma; // Gamma de salida (default 2.2)
|
||||||
|
float mask_brightness; // Brillo sub-píxeles máscara (default 0.80)
|
||||||
|
float curvature_x; // Distorsión barrel X (default 0.05)
|
||||||
|
float curvature_y; // Distorsión barrel Y (default 0.10)
|
||||||
|
// vec4 #2
|
||||||
|
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
int enable_scanlines; // 0 = off, 1 = on
|
||||||
|
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico)
|
||||||
|
int enable_gamma; // 0 = off, 1 = on
|
||||||
|
// vec4 #3
|
||||||
|
int enable_curvature; // 0 = off, 1 = on
|
||||||
|
int enable_sharper; // 0 = off, 1 = on
|
||||||
|
float texture_width; // Ancho del canvas en píxeles (inyectado en render)
|
||||||
|
float texture_height; // Alto del canvas en píxeles (inyectado en render)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Downscale uniforms pushed to the Lanczos downscale fragment stage.
|
||||||
|
// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment.
|
||||||
|
struct DownscaleUniforms {
|
||||||
|
int algorithm; // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3)
|
||||||
|
float pad0;
|
||||||
|
float pad1;
|
||||||
|
float pad2;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||||
|
*
|
||||||
|
* Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
|
||||||
|
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||||
|
* → PostFX render pass → swapchain → present
|
||||||
|
*/
|
||||||
|
class SDL3GPUShader : public ShaderBackend {
|
||||||
|
public:
|
||||||
|
SDL3GPUShader() = default;
|
||||||
|
~SDL3GPUShader() override;
|
||||||
|
|
||||||
|
auto init(SDL_Window* window,
|
||||||
|
SDL_Texture* texture,
|
||||||
|
const std::string& vertex_source,
|
||||||
|
const std::string& fragment_source) -> bool override;
|
||||||
|
|
||||||
|
void render() override;
|
||||||
|
void setTextureSize(float width, float height) override {}
|
||||||
|
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
|
||||||
|
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
|
||||||
|
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||||
|
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||||
|
|
||||||
|
// Establece el driver GPU preferido (vacío = auto). Debe llamarse antes de init().
|
||||||
|
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||||
|
|
||||||
|
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
|
||||||
|
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||||
|
|
||||||
|
// Actualiza los parámetros de intensidad de los efectos PostFX
|
||||||
|
void setPostFXParams(const PostFXParams& p) override;
|
||||||
|
|
||||||
|
// Activa/desactiva VSync en el swapchain
|
||||||
|
void setVSync(bool vsync) override;
|
||||||
|
|
||||||
|
// Activa/desactiva escalado entero (integer scale)
|
||||||
|
void setScaleMode(bool integer_scale) override;
|
||||||
|
|
||||||
|
// Establece factor de supersampling (1 = off, 3 = 3×SS)
|
||||||
|
void setOversample(int factor) override;
|
||||||
|
|
||||||
|
// Activa/desactiva interpolación LINEAR en el upscale (false = NEAREST)
|
||||||
|
void setLinearUpscale(bool linear) override;
|
||||||
|
|
||||||
|
// Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
|
||||||
|
void setDownscaleAlgo(int algo) override;
|
||||||
|
|
||||||
|
// Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado)
|
||||||
|
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||||
|
|
||||||
|
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
|
||||||
|
void setActiveShader(ShaderType type) override;
|
||||||
|
|
||||||
|
// Actualiza los parámetros del shader CRT-Pi
|
||||||
|
void setCrtPiParams(const CrtPiParams& p) override;
|
||||||
|
|
||||||
|
// Devuelve el shader activo
|
||||||
|
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||||
|
|
||||||
|
// Estirament vertical 4:3 (320x200 → 320x240 visual al viewport)
|
||||||
|
void setStretch4_3(bool enabled) override { stretch_4_3_ = enabled; }
|
||||||
|
[[nodiscard]] auto isStretch4_3() const -> bool override { return stretch_4_3_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||||
|
const char* msl_source,
|
||||||
|
const char* entrypoint,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
Uint32 num_samplers,
|
||||||
|
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||||
|
|
||||||
|
static auto createShaderSPIRV(SDL_GPUDevice* device,
|
||||||
|
const uint8_t* spv_code,
|
||||||
|
size_t spv_size,
|
||||||
|
const char* entrypoint,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
Uint32 num_samplers,
|
||||||
|
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||||
|
|
||||||
|
auto createPipeline() -> bool;
|
||||||
|
auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi
|
||||||
|
auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_
|
||||||
|
auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado
|
||||||
|
static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3)
|
||||||
|
// Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC
|
||||||
|
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||||
|
|
||||||
|
SDL_Window* window_ = nullptr;
|
||||||
|
SDL_GPUDevice* device_ = nullptr;
|
||||||
|
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass (→ swapchain o → postfx_texture_)
|
||||||
|
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass (→ swapchain directo, sin SS)
|
||||||
|
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr; // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos)
|
||||||
|
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale pass (solo con SS)
|
||||||
|
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr; // Lanczos downscale (solo con SS + algo > 0)
|
||||||
|
SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_)
|
||||||
|
SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor), solo con SS
|
||||||
|
SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolución escalada, solo con Lanczos
|
||||||
|
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||||
|
SDL_GPUSampler* sampler_ = nullptr; // NEAREST
|
||||||
|
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR
|
||||||
|
|
||||||
|
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 200.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||||
|
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
|
||||||
|
ShaderType active_shader_ = ShaderType::POSTFX; // Shader de post-procesado activo
|
||||||
|
|
||||||
|
int game_width_ = 0; // Dimensiones originales del canvas
|
||||||
|
int game_height_ = 0;
|
||||||
|
int ss_factor_ = 0; // Factor SS activo (3, 6, 9...) o 0 si SS desactivado
|
||||||
|
int oversample_ = 1; // SS on/off (1 = off, >1 = on)
|
||||||
|
int downscale_algo_ = 1; // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3
|
||||||
|
std::string driver_name_;
|
||||||
|
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
|
||||||
|
bool is_initialized_ = false;
|
||||||
|
bool vsync_ = true;
|
||||||
|
bool integer_scale_ = false;
|
||||||
|
bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true)
|
||||||
|
bool stretch_4_3_ = false; // Estirament vertical 4:3 al viewport
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
634
source/core/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
634
source/core/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
static const uint8_t kupscale_frag_spv[] = {
|
||||||
|
0x03,
|
||||||
|
0x02,
|
||||||
|
0x23,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x14,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x53,
|
||||||
|
0x4c,
|
||||||
|
0x2e,
|
||||||
|
0x73,
|
||||||
|
0x74,
|
||||||
|
0x64,
|
||||||
|
0x2e,
|
||||||
|
0x34,
|
||||||
|
0x35,
|
||||||
|
0x30,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6d,
|
||||||
|
0x61,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xc2,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x5f,
|
||||||
|
0x47,
|
||||||
|
0x4f,
|
||||||
|
0x4f,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x45,
|
||||||
|
0x5f,
|
||||||
|
0x63,
|
||||||
|
0x70,
|
||||||
|
0x70,
|
||||||
|
0x5f,
|
||||||
|
0x73,
|
||||||
|
0x74,
|
||||||
|
0x79,
|
||||||
|
0x6c,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x6c,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x64,
|
||||||
|
0x69,
|
||||||
|
0x72,
|
||||||
|
0x65,
|
||||||
|
0x63,
|
||||||
|
0x74,
|
||||||
|
0x69,
|
||||||
|
0x76,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x5f,
|
||||||
|
0x47,
|
||||||
|
0x4f,
|
||||||
|
0x4f,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x45,
|
||||||
|
0x5f,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x63,
|
||||||
|
0x6c,
|
||||||
|
0x75,
|
||||||
|
0x64,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x64,
|
||||||
|
0x69,
|
||||||
|
0x72,
|
||||||
|
0x65,
|
||||||
|
0x63,
|
||||||
|
0x74,
|
||||||
|
0x69,
|
||||||
|
0x76,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6d,
|
||||||
|
0x61,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6f,
|
||||||
|
0x75,
|
||||||
|
0x74,
|
||||||
|
0x5f,
|
||||||
|
0x63,
|
||||||
|
0x6f,
|
||||||
|
0x6c,
|
||||||
|
0x6f,
|
||||||
|
0x72,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x73,
|
||||||
|
0x63,
|
||||||
|
0x65,
|
||||||
|
0x6e,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x76,
|
||||||
|
0x5f,
|
||||||
|
0x75,
|
||||||
|
0x76,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x21,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x22,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x21,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x16,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x17,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x19,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1b,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0c,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0c,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x17,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x36,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xf8,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3d,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3d,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x12,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x57,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x12,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3e,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xfd,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x38,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
};
|
||||||
|
static const size_t kupscale_frag_spv_size = 628;
|
||||||
182
source/core/rendering/shader_backend.hpp
Normal file
182
source/core/rendering/shader_backend.hpp
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
/** @brief Identificador del shader de post-procesado activo */
|
||||||
|
enum class ShaderType { POSTFX,
|
||||||
|
CRTPI };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parámetros de intensidad de los efectos PostFX
|
||||||
|
* Definido a nivel de namespace para facilitar el uso desde subclases y screen.cpp
|
||||||
|
*/
|
||||||
|
struct PostFXParams {
|
||||||
|
float vignette = 0.0F; // Intensidad de la viñeta
|
||||||
|
float scanlines = 0.0F; // Intensidad de las scanlines
|
||||||
|
float chroma = 0.0F; // Aberración cromática
|
||||||
|
float mask = 0.0F; // Máscara de fósforo RGB
|
||||||
|
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
|
||||||
|
float curvature = 0.0F; // Curvatura barrel CRT
|
||||||
|
float bleeding = 0.0F; // Sangrado de color NTSC
|
||||||
|
float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||||
|
* Diferente al PostFX: usa pesos gaussianos por distancia subpixel y bloom.
|
||||||
|
*/
|
||||||
|
struct CrtPiParams {
|
||||||
|
float scanline_weight{6.0F}; // Ajuste gaussiano (mayor = scanlines más estrechas)
|
||||||
|
float scanline_gap_brightness{0.12F}; // Brillo mínimo en las ranuras entre scanlines
|
||||||
|
float bloom_factor{3.5F}; // Factor de brillo para zonas iluminadas
|
||||||
|
float input_gamma{2.4F}; // Gamma de entrada (linealización)
|
||||||
|
float output_gamma{2.2F}; // Gamma de salida (codificación)
|
||||||
|
float mask_brightness{0.80F}; // Sub-píxeles tenues en la máscara de fósforo
|
||||||
|
float curvature_x{0.05F}; // Distorsión barrel eje X
|
||||||
|
float curvature_y{0.10F}; // Distorsión barrel eje Y
|
||||||
|
int mask_type{2}; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
bool enable_scanlines{true}; // Activar efecto de scanlines
|
||||||
|
bool enable_multisample{true}; // Antialiasing analítico de scanlines
|
||||||
|
bool enable_gamma{true}; // Corrección gamma
|
||||||
|
bool enable_curvature{false}; // Distorsión barrel CRT
|
||||||
|
bool enable_sharper{false}; // Submuestreo más nítido (modo SHARPER)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||||
|
*
|
||||||
|
* Esta interfaz define el contrato que todos los backends de shaders
|
||||||
|
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
|
||||||
|
*/
|
||||||
|
class ShaderBackend {
|
||||||
|
public:
|
||||||
|
virtual ~ShaderBackend() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inicializa el backend de shaders
|
||||||
|
* @param window Ventana SDL
|
||||||
|
* @param texture Textura de backbuffer a la que aplicar shaders
|
||||||
|
* @param vertex_source Código fuente del vertex shader
|
||||||
|
* @param fragment_source Código fuente del fragment shader
|
||||||
|
* @return true si la inicialización fue exitosa
|
||||||
|
*/
|
||||||
|
virtual auto init(SDL_Window* window,
|
||||||
|
SDL_Texture* texture,
|
||||||
|
const std::string& vertex_source,
|
||||||
|
const std::string& fragment_source) -> bool = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renderiza la textura con los shaders aplicados
|
||||||
|
*/
|
||||||
|
virtual void render() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el tamaño de la textura como parámetro del shader
|
||||||
|
* @param width Ancho de la textura
|
||||||
|
* @param height Alto de la textura
|
||||||
|
*/
|
||||||
|
virtual void setTextureSize(float width, float height) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Limpia y libera recursos del backend
|
||||||
|
*/
|
||||||
|
virtual void cleanup() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sube píxeles ARGB8888 desde la CPU al backend de shaders
|
||||||
|
* Usado por SDL3GPUShader para evitar pasar por SDL_Texture
|
||||||
|
*/
|
||||||
|
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece los parámetros de intensidad de los efectos PostFX
|
||||||
|
* @param p Struct con todos los parámetros PostFX
|
||||||
|
*/
|
||||||
|
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa o desactiva VSync en el swapchain del GPU device
|
||||||
|
*/
|
||||||
|
virtual void setVSync(bool /*vsync*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa o desactiva el escalado entero (integer scale)
|
||||||
|
*/
|
||||||
|
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el factor de supersampling (1 = off, 3 = 3× SS)
|
||||||
|
* Con factor > 1, la textura GPU se crea a game×factor resolución y
|
||||||
|
* las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR.
|
||||||
|
*/
|
||||||
|
virtual void setOversample(int /*factor*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa/desactiva interpolación LINEAR en el paso de upscale (SS).
|
||||||
|
* Por defecto NEAREST (false). Solo tiene efecto con supersampling activo.
|
||||||
|
*/
|
||||||
|
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||||
|
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selecciona el algoritmo de downscale tras el PostFX (SS activo).
|
||||||
|
* 0 = bilinear legacy (comportamiento actual, sin textura intermedia),
|
||||||
|
* 1 = Lanczos2 (ventana 2, ~25 muestras), 2 = Lanczos3 (ventana 3, ~49 muestras).
|
||||||
|
*/
|
||||||
|
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||||
|
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Devuelve las dimensiones de la textura de supersampling.
|
||||||
|
* @return Par (ancho, alto) en píxeles; (0, 0) si SS está desactivado.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verifica si el backend está usando aceleración por hardware
|
||||||
|
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Nombre del driver GPU activo (p.ej. "vulkan", "metal", "direct3d12")
|
||||||
|
* @return Cadena vacía si no disponible
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el driver GPU preferido antes de init().
|
||||||
|
* Vacío = selección automática de SDL. Implementado en SDL3GPUShader.
|
||||||
|
*/
|
||||||
|
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selecciona el shader de post-procesado activo (POSTFX o CRTPI).
|
||||||
|
* Debe llamarse antes de render(). No recrea pipelines.
|
||||||
|
*/
|
||||||
|
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece los parámetros del shader CRT-Pi.
|
||||||
|
*/
|
||||||
|
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Devuelve el shader de post-procesado activo.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa/desactiva estirament vertical 4:3 (200→240 línies efectives).
|
||||||
|
* Només afecta el viewport, no les textures ni els shaders.
|
||||||
|
*/
|
||||||
|
virtual void setStretch4_3(bool /*enabled*/) {}
|
||||||
|
[[nodiscard]] virtual auto isStretch4_3() const -> bool { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
@@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
// Tecles GUI (capa de presentació — finestra, zoom, etc.)
|
// Tecles GUI (capa de presentació — finestra, zoom, shaders, etc.)
|
||||||
namespace Defaults::KeysGUI {
|
namespace Defaults::KeysGUI {
|
||||||
constexpr SDL_Scancode DEC_ZOOM = SDL_SCANCODE_F1;
|
constexpr SDL_Scancode DEC_ZOOM = SDL_SCANCODE_F1;
|
||||||
constexpr SDL_Scancode INC_ZOOM = SDL_SCANCODE_F2;
|
constexpr SDL_Scancode INC_ZOOM = SDL_SCANCODE_F2;
|
||||||
constexpr SDL_Scancode FULLSCREEN = SDL_SCANCODE_F3;
|
constexpr SDL_Scancode FULLSCREEN = SDL_SCANCODE_F3;
|
||||||
|
constexpr SDL_Scancode TOGGLE_SHADER = SDL_SCANCODE_F4;
|
||||||
|
constexpr SDL_Scancode TOGGLE_ASPECT_RATIO = SDL_SCANCODE_F5;
|
||||||
|
constexpr SDL_Scancode TOGGLE_SUPERSAMPLING = SDL_SCANCODE_F6;
|
||||||
|
constexpr SDL_Scancode NEXT_SHADER = SDL_SCANCODE_F7;
|
||||||
|
constexpr SDL_Scancode NEXT_SHADER_PRESET = SDL_SCANCODE_F8;
|
||||||
} // namespace Defaults::KeysGUI
|
} // namespace Defaults::KeysGUI
|
||||||
|
|
||||||
// Tecles de joc (moviment del personatge, accions)
|
// Tecles de joc (moviment del personatge, accions)
|
||||||
@@ -18,6 +23,16 @@ namespace Defaults::KeysGame {
|
|||||||
constexpr SDL_Scancode EXIT = SDL_SCANCODE_ESCAPE;
|
constexpr SDL_Scancode EXIT = SDL_SCANCODE_ESCAPE;
|
||||||
} // namespace Defaults::KeysGame
|
} // namespace Defaults::KeysGame
|
||||||
|
|
||||||
|
namespace Defaults::Video {
|
||||||
|
constexpr bool GPU_ACCELERATION = true;
|
||||||
|
constexpr bool SHADER_ENABLED = false;
|
||||||
|
constexpr bool SUPERSAMPLING = false;
|
||||||
|
constexpr bool INTEGER_SCALE = true;
|
||||||
|
constexpr bool ASPECT_RATIO_4_3 = true; // CRT original estira 200→240
|
||||||
|
constexpr int DOWNSCALE_ALGO = 1; // 0=bilinear, 1=Lanczos2, 2=Lanczos3
|
||||||
|
constexpr bool LINEAR_UPSCALE = false;
|
||||||
|
} // namespace Defaults::Video
|
||||||
|
|
||||||
namespace Defaults::Audio {
|
namespace Defaults::Audio {
|
||||||
constexpr float VOLUME = 1.0F;
|
constexpr float VOLUME = 1.0F;
|
||||||
constexpr bool MUSIC_ENABLED = true;
|
constexpr bool MUSIC_ENABLED = true;
|
||||||
|
|||||||
@@ -41,6 +41,26 @@ namespace Options {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void loadVideoConfigFromYaml(const fkyaml::node& yaml) {
|
||||||
|
if (!yaml.contains("video")) return;
|
||||||
|
const auto& node = yaml["video"];
|
||||||
|
|
||||||
|
if (node.contains("gpu_acceleration"))
|
||||||
|
video.gpu_acceleration = node["gpu_acceleration"].get_value<bool>();
|
||||||
|
if (node.contains("shader_enabled"))
|
||||||
|
video.shader_enabled = node["shader_enabled"].get_value<bool>();
|
||||||
|
if (node.contains("supersampling"))
|
||||||
|
video.supersampling = node["supersampling"].get_value<bool>();
|
||||||
|
if (node.contains("integer_scale"))
|
||||||
|
video.integer_scale = node["integer_scale"].get_value<bool>();
|
||||||
|
if (node.contains("aspect_ratio_4_3"))
|
||||||
|
video.aspect_ratio_4_3 = node["aspect_ratio_4_3"].get_value<bool>();
|
||||||
|
if (node.contains("downscale_algo"))
|
||||||
|
video.downscale_algo = node["downscale_algo"].get_value<int>();
|
||||||
|
if (node.contains("linear_upscale"))
|
||||||
|
video.linear_upscale = node["linear_upscale"].get_value<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
static void loadWindowConfigFromYaml(const fkyaml::node& yaml) {
|
static void loadWindowConfigFromYaml(const fkyaml::node& yaml) {
|
||||||
if (!yaml.contains("window")) return;
|
if (!yaml.contains("window")) return;
|
||||||
const auto& node = yaml["window"];
|
const auto& node = yaml["window"];
|
||||||
@@ -94,6 +114,7 @@ namespace Options {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadVideoConfigFromYaml(yaml);
|
||||||
loadWindowConfigFromYaml(yaml);
|
loadWindowConfigFromYaml(yaml);
|
||||||
loadAudioConfigFromYaml(yaml);
|
loadAudioConfigFromYaml(yaml);
|
||||||
loadGameConfigFromYaml(yaml);
|
loadGameConfigFromYaml(yaml);
|
||||||
@@ -129,6 +150,18 @@ namespace Options {
|
|||||||
file << "version: \"" << Texts::VERSION << "\"\n";
|
file << "version: \"" << Texts::VERSION << "\"\n";
|
||||||
file << "\n";
|
file << "\n";
|
||||||
|
|
||||||
|
// VIDEO
|
||||||
|
file << "# VIDEO\n";
|
||||||
|
file << "video:\n";
|
||||||
|
file << " gpu_acceleration: " << (video.gpu_acceleration ? "true" : "false") << "\n";
|
||||||
|
file << " shader_enabled: " << (video.shader_enabled ? "true" : "false") << "\n";
|
||||||
|
file << " supersampling: " << (video.supersampling ? "true" : "false") << "\n";
|
||||||
|
file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n";
|
||||||
|
file << " aspect_ratio_4_3: " << (video.aspect_ratio_4_3 ? "true" : "false") << "\n";
|
||||||
|
file << " downscale_algo: " << video.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n";
|
||||||
|
file << " linear_upscale: " << (video.linear_upscale ? "true" : "false") << "\n";
|
||||||
|
file << "\n";
|
||||||
|
|
||||||
// WINDOW
|
// WINDOW
|
||||||
file << "# WINDOW\n";
|
file << "# WINDOW\n";
|
||||||
file << "window:\n";
|
file << "window:\n";
|
||||||
|
|||||||
@@ -7,11 +7,16 @@
|
|||||||
|
|
||||||
namespace Options {
|
namespace Options {
|
||||||
|
|
||||||
// Tecles GUI (finestra, zoom)
|
// Tecles GUI (finestra, zoom, shaders)
|
||||||
struct KeysGUI {
|
struct KeysGUI {
|
||||||
SDL_Scancode dec_zoom{Defaults::KeysGUI::DEC_ZOOM};
|
SDL_Scancode dec_zoom{Defaults::KeysGUI::DEC_ZOOM};
|
||||||
SDL_Scancode inc_zoom{Defaults::KeysGUI::INC_ZOOM};
|
SDL_Scancode inc_zoom{Defaults::KeysGUI::INC_ZOOM};
|
||||||
SDL_Scancode fullscreen{Defaults::KeysGUI::FULLSCREEN};
|
SDL_Scancode fullscreen{Defaults::KeysGUI::FULLSCREEN};
|
||||||
|
SDL_Scancode toggle_shader{Defaults::KeysGUI::TOGGLE_SHADER};
|
||||||
|
SDL_Scancode toggle_aspect_ratio{Defaults::KeysGUI::TOGGLE_ASPECT_RATIO};
|
||||||
|
SDL_Scancode toggle_supersampling{Defaults::KeysGUI::TOGGLE_SUPERSAMPLING};
|
||||||
|
SDL_Scancode next_shader{Defaults::KeysGUI::NEXT_SHADER};
|
||||||
|
SDL_Scancode next_shader_preset{Defaults::KeysGUI::NEXT_SHADER_PRESET};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tecles de joc (moviment, accions)
|
// Tecles de joc (moviment, accions)
|
||||||
@@ -23,6 +28,17 @@ namespace Options {
|
|||||||
SDL_Scancode exit{Defaults::KeysGame::EXIT};
|
SDL_Scancode exit{Defaults::KeysGame::EXIT};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Opcions de vídeo
|
||||||
|
struct Video {
|
||||||
|
bool gpu_acceleration{Defaults::Video::GPU_ACCELERATION};
|
||||||
|
bool shader_enabled{Defaults::Video::SHADER_ENABLED};
|
||||||
|
bool supersampling{Defaults::Video::SUPERSAMPLING};
|
||||||
|
bool integer_scale{Defaults::Video::INTEGER_SCALE};
|
||||||
|
bool aspect_ratio_4_3{Defaults::Video::ASPECT_RATIO_4_3};
|
||||||
|
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO};
|
||||||
|
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE};
|
||||||
|
};
|
||||||
|
|
||||||
// Opcions d'àudio
|
// Opcions d'àudio
|
||||||
struct Audio {
|
struct Audio {
|
||||||
bool music_enabled{Defaults::Audio::MUSIC_ENABLED};
|
bool music_enabled{Defaults::Audio::MUSIC_ENABLED};
|
||||||
@@ -49,6 +65,7 @@ namespace Options {
|
|||||||
inline std::string version{};
|
inline std::string version{};
|
||||||
inline KeysGUI keys_gui{};
|
inline KeysGUI keys_gui{};
|
||||||
inline KeysGame keys_game{};
|
inline KeysGame keys_game{};
|
||||||
|
inline Video video{};
|
||||||
inline Audio audio{};
|
inline Audio audio{};
|
||||||
inline Window window{};
|
inline Window window{};
|
||||||
inline Game game{};
|
inline Game game{};
|
||||||
|
|||||||
101
tools/shaders/compile_spirv.cmake
Normal file
101
tools/shaders/compile_spirv.cmake
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# compile_spirv.cmake
|
||||||
|
# Compila shaders GLSL a SPIR-V y genera headers C++ embebibles.
|
||||||
|
# Multiplataforma: Windows, macOS, Linux (no requiere bash, xxd ni /tmp/).
|
||||||
|
#
|
||||||
|
# Invocado por CMakeLists.txt con:
|
||||||
|
# cmake -D GLSLC=<path> -D SHADERS_DIR=<path> -D HEADERS_DIR=<path> -P compile_spirv.cmake
|
||||||
|
#
|
||||||
|
# También puede ejecutarse manualmente desde la raíz del proyecto:
|
||||||
|
# cmake -D GLSLC=glslc -D SHADERS_DIR=data/shaders -D HEADERS_DIR=source/core/rendering/sdl3gpu -P tools/shaders/compile_spirv.cmake
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
cmake_policy(SET CMP0007 NEW)
|
||||||
|
|
||||||
|
# Lista de shaders: fuente relativa a SHADERS_DIR
|
||||||
|
set(SHADER_SOURCES
|
||||||
|
"postfx.vert"
|
||||||
|
"postfx.frag"
|
||||||
|
"upscale.frag"
|
||||||
|
"downscale.frag"
|
||||||
|
"crtpi_frag.glsl"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Nombre de la variable C++ para cada shader (mismo orden)
|
||||||
|
set(SHADER_VARS
|
||||||
|
"kpostfx_vert_spv"
|
||||||
|
"kpostfx_frag_spv"
|
||||||
|
"kupscale_frag_spv"
|
||||||
|
"kdownscale_frag_spv"
|
||||||
|
"kcrtpi_frag_spv"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Flags extra de glslc para cada shader (vacío si no hay)
|
||||||
|
set(SHADER_FLAGS
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
"-fshader-stage=frag"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(LENGTH SHADER_SOURCES NUM_SHADERS)
|
||||||
|
math(EXPR LAST_IDX "${NUM_SHADERS} - 1")
|
||||||
|
|
||||||
|
foreach(IDX RANGE ${LAST_IDX})
|
||||||
|
list(GET SHADER_SOURCES ${IDX} SRC_NAME)
|
||||||
|
list(GET SHADER_VARS ${IDX} VAR)
|
||||||
|
list(GET SHADER_FLAGS ${IDX} EXTRA_FLAG)
|
||||||
|
|
||||||
|
# Derivar nombre del header desde la variable: kpostfx_vert_spv → postfx_vert_spv.h
|
||||||
|
string(REGEX REPLACE "^k" "" HDR_BASE "${VAR}")
|
||||||
|
set(SRC "${SHADERS_DIR}/${SRC_NAME}")
|
||||||
|
set(SPV "${HEADERS_DIR}/${HDR_BASE}.spv")
|
||||||
|
set(HDR "${HEADERS_DIR}/${HDR_BASE}.h")
|
||||||
|
|
||||||
|
message(STATUS "Compilando ${SRC} ...")
|
||||||
|
|
||||||
|
if(EXTRA_FLAG)
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GLSLC}" "${EXTRA_FLAG}" "${SRC}" -o "${SPV}"
|
||||||
|
RESULT_VARIABLE GLSLC_RESULT
|
||||||
|
ERROR_VARIABLE GLSLC_ERROR
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GLSLC}" "${SRC}" -o "${SPV}"
|
||||||
|
RESULT_VARIABLE GLSLC_RESULT
|
||||||
|
ERROR_VARIABLE GLSLC_ERROR
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT GLSLC_RESULT EQUAL 0)
|
||||||
|
message(FATAL_ERROR "glslc falló para ${SRC}:\n${GLSLC_ERROR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Leer binario SPV como hex (sin separadores: "0302230700...")
|
||||||
|
file(READ "${SPV}" HEX_DATA HEX)
|
||||||
|
# Dividir en pares de caracteres hex → lista de bytes
|
||||||
|
string(REGEX MATCHALL ".." BYTES "${HEX_DATA}")
|
||||||
|
list(LENGTH BYTES NUM_BYTES)
|
||||||
|
|
||||||
|
# Construir el cuerpo del array C++ con un byte por línea
|
||||||
|
set(ARRAY_BODY "")
|
||||||
|
foreach(BYTE ${BYTES})
|
||||||
|
string(APPEND ARRAY_BODY " 0x${BYTE},\n")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
file(WRITE "${HDR}"
|
||||||
|
"#pragma once\n"
|
||||||
|
"#include <cstddef>\n"
|
||||||
|
"#include <cstdint>\n"
|
||||||
|
"static const uint8_t ${VAR}[] = {\n"
|
||||||
|
"${ARRAY_BODY}"
|
||||||
|
"};\n"
|
||||||
|
"static const size_t ${VAR}_size = ${NUM_BYTES};\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
file(REMOVE "${SPV}")
|
||||||
|
message(STATUS " -> ${HDR} (${NUM_BYTES} bytes)")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
message(STATUS "Shaders SPIR-V compilados correctamente.")
|
||||||
Reference in New Issue
Block a user