migrat, amb ajuda de claude, a sdl3gpu (postfx i crtpi) igual que el JDD
This commit is contained in:
103
CLAUDE.md
Normal file
103
CLAUDE.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Coffee Crisis Arcade Edition is a 2-player cooperative arcade shooter built with C++20 and SDL3. Players defend coffee against giant balloons. The game targets Windows, Linux, macOS (Intel/Apple Silicon), Raspberry Pi, and Anbernic handhelds.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
The project uses both CMake and a top-level Makefile. The Makefile is the primary build interface.
|
||||||
|
|
||||||
|
### CMake (generates compile_commands.json for IDE/linter support)
|
||||||
|
```bash
|
||||||
|
cmake -B build -DCMAKE_BUILD_TYPE=Debug # configure
|
||||||
|
cmake --build build # build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Makefile (direct compilation, platform-specific targets)
|
||||||
|
```bash
|
||||||
|
make linux # build for Linux
|
||||||
|
make linux_debug # debug build with -DDEBUG -DVERBOSE
|
||||||
|
make linux_release # release tar.gz with resources.pack
|
||||||
|
make windows # build for Windows (cross-compile or native)
|
||||||
|
make windows_debug # Windows debug build
|
||||||
|
make macos # build for macOS (arm64)
|
||||||
|
make raspi # build for Raspberry Pi
|
||||||
|
make anbernic # build for Anbernic (no shaders, arcade mode)
|
||||||
|
make no_audio # build without audio system
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tools & Resources
|
||||||
|
```bash
|
||||||
|
make pack_tool # compile resource packer
|
||||||
|
make resources.pack # pack data/ into resources.pack (required for release builds)
|
||||||
|
make spirv # compile GLSL shaders to SPIR-V headers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
```bash
|
||||||
|
make format # run clang-format on all sources (or: cmake --build build --target format)
|
||||||
|
make format-check # check formatting without modifying
|
||||||
|
make tidy # run clang-tidy static analysis (cmake --build build --target tidy)
|
||||||
|
make tidy-fix # run clang-tidy with auto-fix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Singletons (core systems)
|
||||||
|
- **Director** (`source/director.hpp`) — Application state machine, orchestrates scene transitions (Logo → Intro → Title → Game → Credits/HiScore → Title)
|
||||||
|
- **Screen** (`source/screen.hpp`) — Window management, SDL3 GPU rendering pipeline, post-processing effects
|
||||||
|
- **Resource** (`source/resource.hpp`) — Asset loading/caching with PRELOAD and LAZY_LOAD modes, reads from `resources.pack`
|
||||||
|
- **Audio** (`source/audio.hpp`) — Music and SFX management
|
||||||
|
- **Input** (`source/input.hpp`) — Keyboard and gamepad input handling
|
||||||
|
|
||||||
|
### Scenes (source/sections/)
|
||||||
|
Each scene is a self-contained class with update/render lifecycle. Scene flow is managed by Director.
|
||||||
|
|
||||||
|
### Entity Managers
|
||||||
|
- `BalloonManager` / `BulletManager` — Object pool-based entity management
|
||||||
|
- `Player` — Two-player support (player 1: keyboard, player 2: gamepad)
|
||||||
|
|
||||||
|
### Rendering Pipeline
|
||||||
|
- SDL3 GPU API (Vulkan/Metal/D3D12 backends)
|
||||||
|
- SPIR-V shaders compiled offline from GLSL (`data/shaders/`) via `glslc`
|
||||||
|
- Compiled shader headers embedded in `source/rendering/sdl3gpu/postfx_*_spv.h`
|
||||||
|
- macOS uses Metal (no SPIR-V compilation needed)
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- Game parameters: `config/param_320x240.txt`, `config/param_320x256.txt`
|
||||||
|
- Asset manifest: `config/assets.txt`
|
||||||
|
- Balloon formations: `config/formations.txt`
|
||||||
|
- Level definitions: `config/stages.txt`
|
||||||
|
- Gamepad mappings: `config/gamecontrollerdb.txt`
|
||||||
|
|
||||||
|
### External Libraries (header-only/vendored in source/external/)
|
||||||
|
- nlohmann/json, fkyaml (YAML), stb_image, stb_vorbis, jail_audio
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
Enforced via `.clang-format` (Google-based) and `.clang-tidy`:
|
||||||
|
|
||||||
|
- **Naming conventions**: Classes/structs `CamelCase`, methods/functions `camelBack`, variables/params `snake_case`, private/protected members `snake_case_` (trailing underscore), constants/constexpr `UPPER_CASE`, namespaces `CamelCase`, enum values `UPPER_CASE`
|
||||||
|
- 4-space indentation, no column limit, braces attach to statement
|
||||||
|
- clang-tidy treats all warnings as errors
|
||||||
|
|
||||||
|
## Conditional Compilation Defines
|
||||||
|
|
||||||
|
| Define | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `WINDOWS_BUILD` / `LINUX_BUILD` / `MACOS_BUILD` | Platform selection |
|
||||||
|
| `DEBUG` / `VERBOSE` | Debug output |
|
||||||
|
| `RELEASE_BUILD` | Release-specific code paths |
|
||||||
|
| `RECORDING` | Demo recording mode |
|
||||||
|
| `NO_SHADERS` | Disable shader pipeline (Anbernic) |
|
||||||
|
| `NO_AUDIO` | Build without audio |
|
||||||
|
| `ARCADE` | Arcade-specific mode |
|
||||||
|
| `MACOS_BUNDLE` | macOS .app bundle paths |
|
||||||
|
| `ANBERNIC` | Anbernic handheld build |
|
||||||
|
|
||||||
|
## Language
|
||||||
|
|
||||||
|
Code comments are in Spanish/Catalan. Game UI supports multiple languages via JSON files in `data/lang/`.
|
||||||
@@ -123,32 +123,43 @@ message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
|||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
find_program(GLSLC_EXE NAMES glslc)
|
find_program(GLSLC_EXE NAMES glslc)
|
||||||
|
|
||||||
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
|
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
|
||||||
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
|
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
|
||||||
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_vert_spv.h")
|
set(SHADER_CRTPI_SRC "${CMAKE_SOURCE_DIR}/data/shaders/crtpi_frag.glsl")
|
||||||
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_frag_spv.h")
|
set(SHADER_UPSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/upscale.frag")
|
||||||
|
set(SHADER_DOWNSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/downscale.frag")
|
||||||
|
|
||||||
|
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_vert_spv.h")
|
||||||
|
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_frag_spv.h")
|
||||||
|
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/crtpi_frag_spv.h")
|
||||||
|
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/upscale_frag_spv.h")
|
||||||
|
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/downscale_frag_spv.h")
|
||||||
|
|
||||||
|
set(ALL_SHADER_SOURCES "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" "${SHADER_CRTPI_SRC}" "${SHADER_UPSCALE_SRC}" "${SHADER_DOWNSCALE_SRC}")
|
||||||
|
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
|
||||||
|
|
||||||
if(GLSLC_EXE)
|
if(GLSLC_EXE)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}"
|
OUTPUT ${ALL_SHADER_HEADERS}
|
||||||
COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh"
|
COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh"
|
||||||
DEPENDS "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}"
|
DEPENDS ${ALL_SHADER_SOURCES}
|
||||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
COMMENT "Compilando shaders SPIR-V..."
|
COMMENT "Compilando shaders SPIR-V..."
|
||||||
)
|
)
|
||||||
add_custom_target(shaders DEPENDS "${SHADER_VERT_H}" "${SHADER_FRAG_H}")
|
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
|
||||||
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
||||||
else()
|
else()
|
||||||
if(NOT EXISTS "${SHADER_VERT_H}" OR NOT EXISTS "${SHADER_FRAG_H}")
|
foreach(_h IN LISTS ALL_SHADER_HEADERS)
|
||||||
message(FATAL_ERROR
|
if(NOT EXISTS "${_h}")
|
||||||
"glslc no encontrado y headers SPIR-V no existen.\n"
|
message(FATAL_ERROR
|
||||||
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
|
||||||
" choco install vulkan-sdk (Windows)\n"
|
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
||||||
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
|
" choco install vulkan-sdk (Windows)\n"
|
||||||
)
|
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
|
||||||
else()
|
)
|
||||||
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
|
endif()
|
||||||
endif()
|
endforeach()
|
||||||
|
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")
|
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
DATA|${SYSTEM_FOLDER}/config.yaml|optional,absolute
|
DATA|${SYSTEM_FOLDER}/config.yaml|optional,absolute
|
||||||
DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute
|
DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute
|
||||||
DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute
|
DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute
|
||||||
|
DATA|${SYSTEM_FOLDER}/crtpi.yaml|optional,absolute
|
||||||
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
|
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
|
||||||
|
|
||||||
# Archivos de configuración del juego
|
# Archivos de configuración del juego
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
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);
|
||||||
|
}
|
||||||
@@ -57,13 +57,13 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
void update(float delta_time) override; // Actualiza la animación (time-based)
|
void update(float delta_time) override; // Actualiza la animación (time-based)
|
||||||
|
|
||||||
// --- Control de animaciones ---
|
// --- Control de animaciones ---
|
||||||
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
|
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
|
||||||
void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice
|
void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice
|
||||||
void resetAnimation(); // Reinicia la animación actual
|
void resetAnimation(); // Reinicia la animación actual
|
||||||
void setAnimationSpeed(float value); // Establece la velocidad de la animación
|
void setAnimationSpeed(float value); // Establece la velocidad de la animación
|
||||||
[[nodiscard]] auto getAnimationSpeed() const -> float { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual
|
[[nodiscard]] auto getAnimationSpeed() const -> float { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual
|
||||||
void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación
|
void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación
|
||||||
void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación
|
void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación
|
||||||
[[nodiscard]] auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual
|
[[nodiscard]] auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual
|
||||||
|
|
||||||
// --- Consultas ---
|
// --- Consultas ---
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class Asset {
|
|||||||
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
|
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
|
||||||
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
|
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
|
||||||
[[nodiscard]] auto getPath(const std::string& filename) const -> std::string;
|
[[nodiscard]] auto getPath(const std::string& filename) const -> std::string;
|
||||||
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
|
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
|
||||||
[[nodiscard]] auto check() const -> bool;
|
[[nodiscard]] auto check() const -> bool;
|
||||||
[[nodiscard]] auto getListByType(Type type) const -> std::vector<std::string>;
|
[[nodiscard]] auto getListByType(Type type) const -> std::vector<std::string>;
|
||||||
[[nodiscard]] auto exists(const std::string& filename) const -> bool; // Nueva función para verificar existencia
|
[[nodiscard]] auto exists(const std::string& filename) const -> bool; // Nueva función para verificar existencia
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "balloon_formations.hpp"
|
#include "balloon_formations.hpp"
|
||||||
|
|
||||||
#include <algorithm> // Para max, min, copy
|
#include <algorithm> // Para max, min, copy
|
||||||
#include <utility> // Para std::cmp_less
|
|
||||||
#include <array> // Para array
|
#include <array> // Para array
|
||||||
#include <cctype> // Para isdigit
|
#include <cctype> // Para isdigit
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
@@ -11,6 +10,7 @@
|
|||||||
#include <map> // Para map, operator==, _Rb_tree_iterator
|
#include <map> // Para map, operator==, _Rb_tree_iterator
|
||||||
#include <sstream> // Para basic_istringstream
|
#include <sstream> // Para basic_istringstream
|
||||||
#include <string> // Para string, char_traits, allocator, operator==, stoi, getline, operator<=>, basic_string
|
#include <string> // Para string, char_traits, allocator, operator==, stoi, getline, operator<=>, basic_string
|
||||||
|
#include <utility> // Para std::cmp_less
|
||||||
|
|
||||||
#include "asset.hpp" // Para Asset
|
#include "asset.hpp" // Para Asset
|
||||||
#include "balloon.hpp" // Para Balloon
|
#include "balloon.hpp" // Para Balloon
|
||||||
|
|||||||
@@ -186,8 +186,11 @@ namespace Defaults::Video {
|
|||||||
constexpr bool FULLSCREEN = false;
|
constexpr bool FULLSCREEN = false;
|
||||||
constexpr bool VSYNC = true;
|
constexpr bool VSYNC = true;
|
||||||
constexpr bool INTEGER_SCALE = true;
|
constexpr bool INTEGER_SCALE = true;
|
||||||
constexpr bool POSTFX = false;
|
constexpr bool GPU_ACCELERATION = true;
|
||||||
constexpr int SUPERSAMPLING = 1;
|
constexpr bool SHADER_ENABLED = false;
|
||||||
|
constexpr bool SUPERSAMPLING = false;
|
||||||
|
constexpr bool LINEAR_UPSCALE = false;
|
||||||
|
constexpr int DOWNSCALE_ALGO = 1;
|
||||||
} // namespace Defaults::Video
|
} // namespace Defaults::Video
|
||||||
|
|
||||||
namespace Defaults::Music {
|
namespace Defaults::Music {
|
||||||
|
|||||||
@@ -103,13 +103,15 @@ void Director::init() {
|
|||||||
Input::init(Asset::get()->getPath("gamecontrollerdb.txt"), Asset::get()->getPath("controllers.json")); // Carga configuración de controles
|
Input::init(Asset::get()->getPath("gamecontrollerdb.txt"), Asset::get()->getPath("controllers.json")); // Carga configuración de controles
|
||||||
|
|
||||||
Logger::section("INIT CONFIG");
|
Logger::section("INIT CONFIG");
|
||||||
Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración
|
Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración
|
||||||
Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos
|
Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos
|
||||||
Options::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX
|
Options::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX
|
||||||
Options::loadFromFile(); // Carga el archivo de configuración
|
Options::setCrtPiFile(Asset::get()->getPath("crtpi.yaml")); // Establece el fichero de presets CrtPi
|
||||||
Options::loadPostFXFromFile(); // Carga los presets PostFX
|
Options::loadFromFile(); // Carga el archivo de configuración
|
||||||
loadParams(); // Carga los parámetros del programa
|
Options::loadPostFXFromFile(); // Carga los presets PostFX
|
||||||
loadScoreFile(); // Carga el archivo de puntuaciones
|
Options::loadCrtPiFromFile(); // Carga los presets CrtPi
|
||||||
|
loadParams(); // Carga los parámetros del programa
|
||||||
|
loadScoreFile(); // Carga el archivo de puntuaciones
|
||||||
|
|
||||||
// Inicialización de subsistemas principales
|
// Inicialización de subsistemas principales
|
||||||
Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma
|
Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class EnterName {
|
|||||||
private:
|
private:
|
||||||
// --- Variables de estado ---
|
// --- Variables de estado ---
|
||||||
std::string character_list_{"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{"}; // Lista de caracteres permitidos
|
std::string character_list_{"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{"}; // Lista de caracteres permitidos
|
||||||
std::string name_; // Nombre en proceso
|
std::string name_; // Nombre en proceso
|
||||||
size_t selected_index_ = 0; // Índice del carácter seleccionado en "character_list_"
|
size_t selected_index_ = 0; // Índice del carácter seleccionado en "character_list_"
|
||||||
|
|
||||||
[[nodiscard]] auto sanitizeName(const std::string& name) const -> std::string; // Valida y limpia el nombre
|
[[nodiscard]] auto sanitizeName(const std::string& name) const -> std::string; // Valida y limpia el nombre
|
||||||
static auto getRandomName() -> std::string; // Devuelve un nombre al azar
|
static auto getRandomName() -> std::string; // Devuelve un nombre al azar
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class Fade {
|
|||||||
int num_squares_width_; // Cuadrados en horizontal
|
int num_squares_width_; // Cuadrados en horizontal
|
||||||
int num_squares_height_; // Cuadrados en vertical
|
int num_squares_height_; // Cuadrados en vertical
|
||||||
int square_transition_duration_; // Duración de transición de cada cuadrado en ms
|
int square_transition_duration_; // Duración de transición de cada cuadrado en ms
|
||||||
int fading_duration_{0}; // Duración del estado FADING en milisegundos
|
int fading_duration_{0}; // Duración del estado FADING en milisegundos
|
||||||
Uint32 fading_start_time_ = 0; // Tiempo de inicio del estado FADING
|
Uint32 fading_start_time_ = 0; // Tiempo de inicio del estado FADING
|
||||||
int post_duration_ = 0; // Duración posterior en milisegundos
|
int post_duration_ = 0; // Duración posterior en milisegundos
|
||||||
Uint32 post_start_time_ = 0; // Tiempo de inicio del estado POST
|
Uint32 post_start_time_ = 0; // Tiempo de inicio del estado POST
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace GlobalEvents {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_EVENT_WINDOW_RESIZED:
|
case SDL_EVENT_WINDOW_RESIZED:
|
||||||
Screen::initPostFX();
|
Screen::initShaders();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -62,25 +62,36 @@ namespace GlobalInputs {
|
|||||||
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 14") + " " + boolToOnOff(Options::video.vsync)});
|
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 14") + " " + boolToOnOff(Options::video.vsync)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activa o desactiva los efectos PostFX
|
// Activa o desactiva los shaders
|
||||||
void togglePostFX() {
|
void toggleShaders() {
|
||||||
Screen::togglePostFX();
|
Screen::toggleShaders();
|
||||||
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.postfx)});
|
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.shader.enabled)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avanza al siguiente preset PostFX
|
// Cambia entre PostFX y CrtPi
|
||||||
void nextPostFXPreset() {
|
void nextShader() {
|
||||||
Screen::nextPostFXPreset();
|
Screen::nextShader();
|
||||||
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset)).name;
|
const std::string SHADER_NAME = (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX";
|
||||||
Notifier::get()->show({"PostFX: " + name});
|
Notifier::get()->show({"Shader: " + SHADER_NAME});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activa o desactiva el supersampling 3x
|
// Avanza al siguiente preset PostFX o CrtPi según shader activo
|
||||||
|
void nextPreset() {
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||||
|
Screen::nextCrtPiPreset();
|
||||||
|
const std::string name = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||||
|
Notifier::get()->show({"CrtPi: " + name});
|
||||||
|
} else {
|
||||||
|
Screen::nextPostFXPreset();
|
||||||
|
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||||
|
Notifier::get()->show({"PostFX: " + name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activa o desactiva el supersampling
|
||||||
void toggleSupersampling() {
|
void toggleSupersampling() {
|
||||||
Screen::toggleSupersampling();
|
Screen::toggleSupersampling();
|
||||||
const int SS = Options::video.supersampling;
|
Notifier::get()->show({"SS: " + std::string(Options::video.supersampling.enabled ? "ON" : "OFF")});
|
||||||
const std::string SS_LABEL = (SS <= 1) ? "OFF" : (std::to_string(SS) + "\xC3\x97");
|
|
||||||
Notifier::get()->show({"SS: " + SS_LABEL});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cambia al siguiente idioma
|
// Cambia al siguiente idioma
|
||||||
@@ -202,11 +213,15 @@ namespace GlobalInputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Input::get()->checkAction(Input::Action::TOGGLE_VIDEO_POSTFX, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
if (Input::get()->checkAction(Input::Action::TOGGLE_VIDEO_POSTFX, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||||
togglePostFX();
|
toggleShaders();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkAction(Input::Action::NEXT_SHADER, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||||
|
nextShader();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (Input::get()->checkAction(Input::Action::NEXT_POSTFX_PRESET, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
if (Input::get()->checkAction(Input::Action::NEXT_POSTFX_PRESET, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||||
nextPostFXPreset();
|
nextPreset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class Input {
|
|||||||
{Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)},
|
{Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)},
|
||||||
{Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)},
|
{Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)},
|
||||||
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
|
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
|
||||||
|
{Action::NEXT_SHADER, KeyState(SDL_SCANCODE_8)},
|
||||||
{Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
|
{Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
|
||||||
{Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
|
{Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
|
||||||
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
|
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
|||||||
{InputAction::WINDOW_INC_SIZE, "WINDOW_INC_SIZE"},
|
{InputAction::WINDOW_INC_SIZE, "WINDOW_INC_SIZE"},
|
||||||
{InputAction::WINDOW_DEC_SIZE, "WINDOW_DEC_SIZE"},
|
{InputAction::WINDOW_DEC_SIZE, "WINDOW_DEC_SIZE"},
|
||||||
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
|
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
|
||||||
|
{InputAction::NEXT_SHADER, "NEXT_SHADER"},
|
||||||
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
|
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
|
||||||
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
|
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
|
||||||
{InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"},
|
{InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"},
|
||||||
@@ -54,6 +55,7 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
|||||||
{"WINDOW_INC_SIZE", InputAction::WINDOW_INC_SIZE},
|
{"WINDOW_INC_SIZE", InputAction::WINDOW_INC_SIZE},
|
||||||
{"WINDOW_DEC_SIZE", InputAction::WINDOW_DEC_SIZE},
|
{"WINDOW_DEC_SIZE", InputAction::WINDOW_DEC_SIZE},
|
||||||
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
|
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
|
||||||
|
{"NEXT_SHADER", InputAction::NEXT_SHADER},
|
||||||
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
|
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
|
||||||
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
|
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
|
||||||
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},
|
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
|
|||||||
WINDOW_INC_SIZE,
|
WINDOW_INC_SIZE,
|
||||||
WINDOW_DEC_SIZE,
|
WINDOW_DEC_SIZE,
|
||||||
TOGGLE_VIDEO_POSTFX,
|
TOGGLE_VIDEO_POSTFX,
|
||||||
|
NEXT_SHADER,
|
||||||
NEXT_POSTFX_PRESET,
|
NEXT_POSTFX_PRESET,
|
||||||
TOGGLE_SUPERSAMPLING,
|
TOGGLE_SUPERSAMPLING,
|
||||||
TOGGLE_VIDEO_INTEGER_SCALE,
|
TOGGLE_VIDEO_INTEGER_SCALE,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "difficulty.hpp" // Para Code, init
|
#include "difficulty.hpp" // Para Code, init
|
||||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||||
#include "input.hpp" // Para Input
|
#include "input.hpp" // Para Input
|
||||||
#include "lang.hpp" // Para getText, Code
|
#include "lang.hpp" // Para getText, Code
|
||||||
@@ -24,16 +24,17 @@ namespace Options {
|
|||||||
GamepadManager gamepad_manager; // Opciones de mando para cada jugador
|
GamepadManager gamepad_manager; // Opciones de mando para cada jugador
|
||||||
Keyboard keyboard; // Opciones para el teclado
|
Keyboard keyboard; // Opciones para el teclado
|
||||||
PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
||||||
std::vector<PostFXPreset> postfx_presets = { // Lista de presets de PostFX
|
std::vector<PostFXPreset> postfx_presets = {
|
||||||
{"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F},
|
{"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F},
|
||||||
{"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F},
|
{"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F},
|
||||||
{"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F},
|
{"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F},
|
||||||
{"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F},
|
{"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F},
|
||||||
{"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F},
|
{"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F},
|
||||||
{"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F},
|
{"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F},
|
||||||
};
|
};
|
||||||
int current_postfx_preset = 0; // Índice del preset PostFX activo
|
std::string postfx_file_path;
|
||||||
std::string postfx_file_path; // Ruta al fichero de presets PostFX
|
std::vector<CrtPiPreset> crtpi_presets;
|
||||||
|
std::string crtpi_file_path;
|
||||||
|
|
||||||
// Establece el fichero de configuración
|
// Establece el fichero de configuración
|
||||||
void setConfigFile(const std::string& file_path) { settings.config_file = file_path; }
|
void setConfigFile(const std::string& file_path) { settings.config_file = file_path; }
|
||||||
@@ -44,6 +45,9 @@ namespace Options {
|
|||||||
// Establece la ruta del fichero de PostFX
|
// Establece la ruta del fichero de PostFX
|
||||||
void setPostFXFile(const std::string& path) { postfx_file_path = path; }
|
void setPostFXFile(const std::string& path) { postfx_file_path = path; }
|
||||||
|
|
||||||
|
// Establece la ruta del fichero de CrtPi
|
||||||
|
void setCrtPiFile(const std::string& path) { crtpi_file_path = path; }
|
||||||
|
|
||||||
// Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión
|
// Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión
|
||||||
static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
|
static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
|
||||||
if (node.contains(key)) {
|
if (node.contains(key)) {
|
||||||
@@ -89,11 +93,21 @@ namespace Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!postfx_presets.empty()) {
|
if (!postfx_presets.empty()) {
|
||||||
current_postfx_preset = std::clamp(
|
// Resolver nombre → índice
|
||||||
current_postfx_preset, 0,
|
if (!video.shader.current_postfx_preset_name.empty()) {
|
||||||
|
for (int i = 0; i < static_cast<int>(postfx_presets.size()); ++i) {
|
||||||
|
if (postfx_presets[static_cast<size_t>(i)].name == video.shader.current_postfx_preset_name) {
|
||||||
|
video.shader.current_postfx_preset = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
video.shader.current_postfx_preset = std::clamp(
|
||||||
|
video.shader.current_postfx_preset,
|
||||||
|
0,
|
||||||
static_cast<int>(postfx_presets.size()) - 1);
|
static_cast<int>(postfx_presets.size()) - 1);
|
||||||
} else {
|
} else {
|
||||||
current_postfx_preset = 0;
|
video.shader.current_postfx_preset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "PostFX file loaded: %zu preset(s)", postfx_presets.size());
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "PostFX file loaded: %zu preset(s)", postfx_presets.size());
|
||||||
@@ -190,17 +204,141 @@ namespace Options {
|
|||||||
|
|
||||||
// Cargar los presets recién escritos
|
// Cargar los presets recién escritos
|
||||||
postfx_presets.clear();
|
postfx_presets.clear();
|
||||||
postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F});
|
postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F});
|
||||||
postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F});
|
postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F});
|
||||||
postfx_presets.push_back({"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F});
|
postfx_presets.push_back({"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F});
|
||||||
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
|
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
|
||||||
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F});
|
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F});
|
||||||
postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F});
|
postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F});
|
||||||
current_postfx_preset = 0;
|
video.shader.current_postfx_preset = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: extrae un campo bool de un nodo YAML si existe, ignorando errores
|
||||||
|
static void parseBoolField(const fkyaml::node& node, const std::string& key, bool& target) {
|
||||||
|
if (node.contains(key)) {
|
||||||
|
try {
|
||||||
|
target = node[key].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: extrae un campo int de un nodo YAML si existe, ignorando errores
|
||||||
|
static void parseIntField(const fkyaml::node& node, const std::string& key, int& target) {
|
||||||
|
if (node.contains(key)) {
|
||||||
|
try {
|
||||||
|
target = node[key].get_value<int>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rellena los presets CrtPi por defecto
|
||||||
|
static void populateDefaultCrtPiPresets() {
|
||||||
|
crtpi_presets.clear();
|
||||||
|
crtpi_presets.push_back({"DEFAULT", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false});
|
||||||
|
crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false});
|
||||||
|
crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true});
|
||||||
|
crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escribe los presets CrtPi por defecto al fichero
|
||||||
|
static auto saveCrtPiDefaults() -> bool {
|
||||||
|
if (crtpi_file_path.empty()) { return false; }
|
||||||
|
std::ofstream file(crtpi_file_path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: %s can't be opened for writing", crtpi_file_path.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file << "# Coffee Crisis Arcade Edition - CrtPi Shader Presets\n";
|
||||||
|
file << "# scanline_weight: gaussian adjustment (higher = narrower scanlines, default 6.0)\n";
|
||||||
|
file << "# scanline_gap_brightness: min brightness between scanlines (0.0-1.0, default 0.12)\n";
|
||||||
|
file << "# bloom_factor: brightness for bright areas (default 3.5)\n";
|
||||||
|
file << "# input_gamma: input gamma - linearization (default 2.4)\n";
|
||||||
|
file << "# output_gamma: output gamma - encoding (default 2.2)\n";
|
||||||
|
file << "# mask_brightness: sub-pixel brightness (default 0.80)\n";
|
||||||
|
file << "# curvature_x/y: barrel CRT distortion (0.0 = flat)\n";
|
||||||
|
file << "# mask_type: 0=none, 1=green/magenta, 2=RGB phosphor\n";
|
||||||
|
file << "# enable_scanlines/multisample/gamma/curvature/sharper: true/false\n";
|
||||||
|
file << "\npresets:\n";
|
||||||
|
file << " - name: \"DEFAULT\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: true\n enable_gamma: true\n enable_curvature: false\n enable_sharper: false\n";
|
||||||
|
file << " - name: \"CURVED\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: true\n enable_gamma: true\n enable_curvature: true\n enable_sharper: false\n";
|
||||||
|
file << " - name: \"SHARP\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: false\n enable_gamma: true\n enable_curvature: false\n enable_sharper: true\n";
|
||||||
|
file << " - name: \"MINIMAL\"\n scanline_weight: 8.0\n scanline_gap_brightness: 0.05\n bloom_factor: 2.0\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 1.00\n curvature_x: 0.0\n curvature_y: 0.0\n mask_type: 0\n enable_scanlines: true\n enable_multisample: false\n enable_gamma: false\n enable_curvature: false\n enable_sharper: false\n";
|
||||||
|
file.close();
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CrtPi file created with defaults: %s", crtpi_file_path.c_str());
|
||||||
|
populateDefaultCrtPiPresets();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carga los presets de CrtPi desde el fichero
|
||||||
|
auto loadCrtPiFromFile() -> bool {
|
||||||
|
crtpi_presets.clear();
|
||||||
|
|
||||||
|
std::ifstream file(crtpi_file_path);
|
||||||
|
if (!file.good()) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CrtPi file not found, creating default: %s", crtpi_file_path.c_str());
|
||||||
|
return saveCrtPiDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto yaml = fkyaml::node::deserialize(content);
|
||||||
|
|
||||||
|
if (yaml.contains("presets")) {
|
||||||
|
const auto& presets = yaml["presets"];
|
||||||
|
for (const auto& p : presets) {
|
||||||
|
CrtPiPreset preset;
|
||||||
|
if (p.contains("name")) {
|
||||||
|
preset.name = p["name"].get_value<std::string>();
|
||||||
|
}
|
||||||
|
parseFloatField(p, "scanline_weight", preset.scanline_weight);
|
||||||
|
parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness);
|
||||||
|
parseFloatField(p, "bloom_factor", preset.bloom_factor);
|
||||||
|
parseFloatField(p, "input_gamma", preset.input_gamma);
|
||||||
|
parseFloatField(p, "output_gamma", preset.output_gamma);
|
||||||
|
parseFloatField(p, "mask_brightness", preset.mask_brightness);
|
||||||
|
parseFloatField(p, "curvature_x", preset.curvature_x);
|
||||||
|
parseFloatField(p, "curvature_y", preset.curvature_y);
|
||||||
|
parseIntField(p, "mask_type", preset.mask_type);
|
||||||
|
parseBoolField(p, "enable_scanlines", preset.enable_scanlines);
|
||||||
|
parseBoolField(p, "enable_multisample", preset.enable_multisample);
|
||||||
|
parseBoolField(p, "enable_gamma", preset.enable_gamma);
|
||||||
|
parseBoolField(p, "enable_curvature", preset.enable_curvature);
|
||||||
|
parseBoolField(p, "enable_sharper", preset.enable_sharper);
|
||||||
|
crtpi_presets.push_back(preset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!crtpi_presets.empty()) {
|
||||||
|
// Resolver nombre → índice
|
||||||
|
if (!video.shader.current_crtpi_preset_name.empty()) {
|
||||||
|
for (int i = 0; i < static_cast<int>(crtpi_presets.size()); ++i) {
|
||||||
|
if (crtpi_presets[static_cast<size_t>(i)].name == video.shader.current_crtpi_preset_name) {
|
||||||
|
video.shader.current_crtpi_preset = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
video.shader.current_crtpi_preset = std::clamp(
|
||||||
|
video.shader.current_crtpi_preset,
|
||||||
|
0,
|
||||||
|
static_cast<int>(crtpi_presets.size()) - 1);
|
||||||
|
} else {
|
||||||
|
video.shader.current_crtpi_preset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CrtPi file loaded: %zu preset(s)", crtpi_presets.size());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const fkyaml::exception& e) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Error parsing CrtPi YAML: %s. Recreating defaults.", e.what());
|
||||||
|
return saveCrtPiDefaults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Inicializa las opciones del programa
|
// Inicializa las opciones del programa
|
||||||
void init() {
|
void init() {
|
||||||
// Dificultades
|
// Dificultades
|
||||||
@@ -234,55 +372,109 @@ namespace Options {
|
|||||||
const auto& vid = yaml["video"];
|
const auto& vid = yaml["video"];
|
||||||
|
|
||||||
if (vid.contains("fullscreen")) {
|
if (vid.contains("fullscreen")) {
|
||||||
try { video.fullscreen = vid["fullscreen"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
video.fullscreen = vid["fullscreen"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (vid.contains("scale_mode")) {
|
if (vid.contains("scale_mode")) {
|
||||||
try { video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>()); } catch (...) {}
|
try {
|
||||||
|
video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>());
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (vid.contains("vsync")) {
|
if (vid.contains("vsync")) {
|
||||||
try { video.vsync = vid["vsync"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
video.vsync = vid["vsync"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (vid.contains("integer_scale")) {
|
if (vid.contains("integer_scale")) {
|
||||||
try { video.integer_scale = vid["integer_scale"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
video.integer_scale = vid["integer_scale"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (vid.contains("postfx")) {
|
|
||||||
try { video.postfx = vid["postfx"].get_value<bool>(); } catch (...) {}
|
// --- GPU ---
|
||||||
}
|
if (vid.contains("gpu")) {
|
||||||
// Nuevo formato: supersampling (bool) + supersampling_amount (int)
|
const auto& gpu_node = vid["gpu"];
|
||||||
// Backward compat: si solo existe supersampling como int, también funciona
|
if (gpu_node.contains("acceleration")) {
|
||||||
{
|
|
||||||
bool ss_enabled = false;
|
|
||||||
int ss_amount = 3;
|
|
||||||
if (vid.contains("supersampling")) {
|
|
||||||
try {
|
try {
|
||||||
const auto& node = vid["supersampling"];
|
video.gpu.acceleration = gpu_node["acceleration"].get_value<bool>();
|
||||||
if (node.is_boolean()) {
|
} catch (...) {}
|
||||||
ss_enabled = node.get_value<bool>();
|
}
|
||||||
|
if (gpu_node.contains("preferred_driver")) {
|
||||||
|
try {
|
||||||
|
video.gpu.preferred_driver = gpu_node["preferred_driver"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Shader config ---
|
||||||
|
if (vid.contains("shader")) {
|
||||||
|
const auto& sh = vid["shader"];
|
||||||
|
if (sh.contains("enabled")) {
|
||||||
|
try {
|
||||||
|
video.shader.enabled = sh["enabled"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (sh.contains("current_shader")) {
|
||||||
|
try {
|
||||||
|
auto s = sh["current_shader"].get_value<std::string>();
|
||||||
|
video.shader.current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (sh.contains("postfx_preset")) {
|
||||||
|
try {
|
||||||
|
video.shader.current_postfx_preset_name = sh["postfx_preset"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (sh.contains("crtpi_preset")) {
|
||||||
|
try {
|
||||||
|
video.shader.current_crtpi_preset_name = sh["crtpi_preset"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
} else if (vid.contains("postfx")) {
|
||||||
|
// Backward compat: formato antiguo plano
|
||||||
|
try {
|
||||||
|
video.shader.enabled = vid["postfx"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
if (vid.contains("postfx_preset")) {
|
||||||
|
try {
|
||||||
|
int preset = vid["postfx_preset"].get_value<int>();
|
||||||
|
if (preset >= 0) { video.shader.current_postfx_preset = preset; }
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Supersampling ---
|
||||||
|
if (vid.contains("supersampling")) {
|
||||||
|
const auto& ss_node = vid["supersampling"];
|
||||||
|
if (ss_node.is_mapping()) {
|
||||||
|
// Nuevo formato anidado
|
||||||
|
if (ss_node.contains("enabled")) {
|
||||||
|
try {
|
||||||
|
video.supersampling.enabled = ss_node["enabled"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (ss_node.contains("linear_upscale")) {
|
||||||
|
try {
|
||||||
|
video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (ss_node.contains("downscale_algo")) {
|
||||||
|
try {
|
||||||
|
video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value<int>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Backward compat: bool o int
|
||||||
|
try {
|
||||||
|
if (ss_node.is_boolean()) {
|
||||||
|
video.supersampling.enabled = ss_node.get_value<bool>();
|
||||||
} else {
|
} else {
|
||||||
// Formato antiguo: int directamente
|
int factor = ss_node.get_value<int>();
|
||||||
int factor = node.get_value<int>();
|
video.supersampling.enabled = factor >= 2;
|
||||||
ss_enabled = factor >= 2;
|
|
||||||
ss_amount = (factor >= 2) ? factor : 3;
|
|
||||||
}
|
}
|
||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (vid.contains("supersampling_amount")) {
|
|
||||||
try {
|
|
||||||
int amount = vid["supersampling_amount"].get_value<int>();
|
|
||||||
if (amount >= 2) { ss_amount = amount; }
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
video.supersampling = ss_enabled ? ss_amount : 1;
|
|
||||||
}
|
|
||||||
if (vid.contains("postfx_preset")) {
|
|
||||||
try {
|
|
||||||
int preset = vid["postfx_preset"].get_value<int>();
|
|
||||||
// No validamos contra postfx_presets.size() aquí porque postfx.yaml
|
|
||||||
// aún no se ha cargado. El clamp se hace en loadPostFXFromFile().
|
|
||||||
if (preset >= 0) {
|
|
||||||
current_postfx_preset = preset;
|
|
||||||
}
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,27 +483,39 @@ namespace Options {
|
|||||||
const auto& aud = yaml["audio"];
|
const auto& aud = yaml["audio"];
|
||||||
|
|
||||||
if (aud.contains("enabled")) {
|
if (aud.contains("enabled")) {
|
||||||
try { audio.enabled = aud["enabled"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
audio.enabled = aud["enabled"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (aud.contains("volume")) {
|
if (aud.contains("volume")) {
|
||||||
try { audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100); } catch (...) {}
|
try {
|
||||||
|
audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100);
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (aud.contains("music")) {
|
if (aud.contains("music")) {
|
||||||
const auto& mus = aud["music"];
|
const auto& mus = aud["music"];
|
||||||
if (mus.contains("enabled")) {
|
if (mus.contains("enabled")) {
|
||||||
try { audio.music.enabled = mus["enabled"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
audio.music.enabled = mus["enabled"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (mus.contains("volume")) {
|
if (mus.contains("volume")) {
|
||||||
try { audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100); } catch (...) {}
|
try {
|
||||||
|
audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100);
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (aud.contains("sound")) {
|
if (aud.contains("sound")) {
|
||||||
const auto& snd = aud["sound"];
|
const auto& snd = aud["sound"];
|
||||||
if (snd.contains("enabled")) {
|
if (snd.contains("enabled")) {
|
||||||
try { audio.sound.enabled = snd["enabled"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
audio.sound.enabled = snd["enabled"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (snd.contains("volume")) {
|
if (snd.contains("volume")) {
|
||||||
try { audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100); } catch (...) {}
|
try {
|
||||||
|
audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100);
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,13 +542,19 @@ namespace Options {
|
|||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (game.contains("autofire")) {
|
if (game.contains("autofire")) {
|
||||||
try { settings.autofire = game["autofire"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
settings.autofire = game["autofire"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (game.contains("shutdown_enabled")) {
|
if (game.contains("shutdown_enabled")) {
|
||||||
try { settings.shutdown_enabled = game["shutdown_enabled"].get_value<bool>(); } catch (...) {}
|
try {
|
||||||
|
settings.shutdown_enabled = game["shutdown_enabled"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (game.contains("params_file")) {
|
if (game.contains("params_file")) {
|
||||||
try { settings.params_file = game["params_file"].get_value<std::string>(); } catch (...) {}
|
try {
|
||||||
|
settings.params_file = game["params_file"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,10 +566,14 @@ namespace Options {
|
|||||||
for (const auto& ctrl : controllers) {
|
for (const auto& ctrl : controllers) {
|
||||||
if (i >= GamepadManager::size()) { break; }
|
if (i >= GamepadManager::size()) { break; }
|
||||||
if (ctrl.contains("name")) {
|
if (ctrl.contains("name")) {
|
||||||
try { gamepad_manager[i].name = ctrl["name"].get_value<std::string>(); } catch (...) {}
|
try {
|
||||||
|
gamepad_manager[i].name = ctrl["name"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (ctrl.contains("path")) {
|
if (ctrl.contains("path")) {
|
||||||
try { gamepad_manager[i].path = ctrl["path"].get_value<std::string>(); } catch (...) {}
|
try {
|
||||||
|
gamepad_manager[i].path = ctrl["path"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (ctrl.contains("player")) {
|
if (ctrl.contains("player")) {
|
||||||
try {
|
try {
|
||||||
@@ -379,7 +593,9 @@ namespace Options {
|
|||||||
if (!yaml.contains("keyboard")) { return; }
|
if (!yaml.contains("keyboard")) { return; }
|
||||||
const auto& kb = yaml["keyboard"];
|
const auto& kb = yaml["keyboard"];
|
||||||
if (kb.contains("player")) {
|
if (kb.contains("player")) {
|
||||||
try { keyboard.player_id = static_cast<Player::Id>(kb["player"].get_value<int>()); } catch (...) {}
|
try {
|
||||||
|
keyboard.player_id = static_cast<Player::Id>(kb["player"].get_value<int>());
|
||||||
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,10 +668,26 @@ namespace Options {
|
|||||||
file << " scale_mode: " << static_cast<int>(video.scale_mode) << " # " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear\n";
|
file << " scale_mode: " << static_cast<int>(video.scale_mode) << " # " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear\n";
|
||||||
file << " vsync: " << boolToString(video.vsync) << "\n";
|
file << " vsync: " << boolToString(video.vsync) << "\n";
|
||||||
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
|
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
|
||||||
file << " postfx: " << boolToString(video.postfx) << "\n";
|
file << " gpu:\n";
|
||||||
file << " postfx_preset: " << current_postfx_preset << "\n";
|
file << " acceleration: " << boolToString(video.gpu.acceleration) << "\n";
|
||||||
file << " supersampling: " << boolToString(video.supersampling > 1) << "\n";
|
file << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\n";
|
||||||
file << " supersampling_amount: " << std::max(2, video.supersampling) << "\n";
|
file << " shader:\n";
|
||||||
|
file << " enabled: " << boolToString(video.shader.enabled) << "\n";
|
||||||
|
file << " current_shader: " << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
|
||||||
|
{
|
||||||
|
std::string postfx_name = (!postfx_presets.empty() && video.shader.current_postfx_preset < static_cast<int>(postfx_presets.size()))
|
||||||
|
? postfx_presets[static_cast<size_t>(video.shader.current_postfx_preset)].name
|
||||||
|
: "";
|
||||||
|
std::string crtpi_name = (!crtpi_presets.empty() && video.shader.current_crtpi_preset < static_cast<int>(crtpi_presets.size()))
|
||||||
|
? crtpi_presets[static_cast<size_t>(video.shader.current_crtpi_preset)].name
|
||||||
|
: "";
|
||||||
|
file << " postfx_preset: \"" << postfx_name << "\"\n";
|
||||||
|
file << " crtpi_preset: \"" << crtpi_name << "\"\n";
|
||||||
|
}
|
||||||
|
file << " supersampling:\n";
|
||||||
|
file << " enabled: " << boolToString(video.supersampling.enabled) << "\n";
|
||||||
|
file << " linear_upscale: " << boolToString(video.supersampling.linear_upscale) << "\n";
|
||||||
|
file << " downscale_algo: " << video.supersampling.downscale_algo << "\n";
|
||||||
file << "\n";
|
file << "\n";
|
||||||
|
|
||||||
// AUDIO
|
// AUDIO
|
||||||
|
|||||||
@@ -14,11 +14,12 @@
|
|||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "defaults.hpp"
|
#include "defaults.hpp"
|
||||||
#include "difficulty.hpp" // for Code
|
#include "difficulty.hpp" // for Code
|
||||||
#include "input.hpp" // for Input
|
#include "input.hpp" // for Input
|
||||||
#include "lang.hpp" // for Code
|
#include "lang.hpp" // for Code
|
||||||
#include "manage_hiscore_table.hpp" // for ManageHiScoreTable, Table
|
#include "manage_hiscore_table.hpp" // for ManageHiScoreTable, Table
|
||||||
#include "player.hpp" // for Player
|
#include "player.hpp" // for Player
|
||||||
|
#include "rendering/shader_backend.hpp" // for Rendering::ShaderType
|
||||||
|
|
||||||
// --- Namespace Options: gestión de configuración y opciones del juego ---
|
// --- Namespace Options: gestión de configuración y opciones del juego ---
|
||||||
namespace Options {
|
namespace Options {
|
||||||
@@ -36,20 +37,59 @@ namespace Options {
|
|||||||
float flicker{0.0F};
|
float flicker{0.0F};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CrtPiPreset {
|
||||||
|
std::string name;
|
||||||
|
float scanline_weight{6.0F};
|
||||||
|
float scanline_gap_brightness{0.12F};
|
||||||
|
float bloom_factor{3.5F};
|
||||||
|
float input_gamma{2.4F};
|
||||||
|
float output_gamma{2.2F};
|
||||||
|
float mask_brightness{0.80F};
|
||||||
|
float curvature_x{0.05F};
|
||||||
|
float curvature_y{0.10F};
|
||||||
|
int mask_type{2};
|
||||||
|
bool enable_scanlines{true};
|
||||||
|
bool enable_multisample{true};
|
||||||
|
bool enable_gamma{true};
|
||||||
|
bool enable_curvature{false};
|
||||||
|
bool enable_sharper{false};
|
||||||
|
};
|
||||||
|
|
||||||
struct Window {
|
struct Window {
|
||||||
std::string caption = Defaults::Window::CAPTION; // Texto que aparece en la barra de título de la ventana
|
std::string caption = Defaults::Window::CAPTION;
|
||||||
int zoom = Defaults::Window::ZOOM; // Valor por el que se multiplica el tamaño de la ventana
|
int zoom = Defaults::Window::ZOOM;
|
||||||
int max_zoom = Defaults::Window::MAX_ZOOM; // Tamaño máximo para que la ventana no sea mayor que la pantalla
|
int max_zoom = Defaults::Window::MAX_ZOOM;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPU {
|
||||||
|
bool acceleration{Defaults::Video::GPU_ACCELERATION};
|
||||||
|
std::string preferred_driver;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Supersampling {
|
||||||
|
bool enabled{Defaults::Video::SUPERSAMPLING};
|
||||||
|
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE};
|
||||||
|
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderConfig {
|
||||||
|
bool enabled{Defaults::Video::SHADER_ENABLED};
|
||||||
|
Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX};
|
||||||
|
std::string current_postfx_preset_name;
|
||||||
|
std::string current_crtpi_preset_name;
|
||||||
|
int current_postfx_preset{0};
|
||||||
|
int current_crtpi_preset{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Video {
|
struct Video {
|
||||||
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE; // Filtro usado para el escalado de la imagen
|
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
|
||||||
bool fullscreen = Defaults::Video::FULLSCREEN; // Indica si se usa pantalla completa
|
bool fullscreen = Defaults::Video::FULLSCREEN;
|
||||||
bool vsync = Defaults::Video::VSYNC; // Indica si se usa vsync
|
bool vsync = Defaults::Video::VSYNC;
|
||||||
bool integer_scale = Defaults::Video::INTEGER_SCALE; // Indica si se usa escalado entero
|
bool integer_scale = Defaults::Video::INTEGER_SCALE;
|
||||||
bool postfx = Defaults::Video::POSTFX; // Indica si se usan efectos PostFX
|
std::string info;
|
||||||
int supersampling = Defaults::Video::SUPERSAMPLING; // Factor de supersampling: 1=off, 2=2×, 3=3×
|
GPU gpu{};
|
||||||
std::string info; // Información sobre el modo de vídeo
|
Supersampling supersampling{};
|
||||||
|
ShaderConfig shader{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Music {
|
struct Music {
|
||||||
@@ -63,8 +103,8 @@ namespace Options {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Audio {
|
struct Audio {
|
||||||
Music music; // Opciones para la música
|
Music music; // Opciones para la música
|
||||||
Sound sound; // Opciones para los efectos de sonido
|
Sound sound; // Opciones para los efectos de sonido
|
||||||
bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no
|
bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no
|
||||||
int volume = Defaults::Audio::VOLUME; // Volumen general del audio
|
int volume = Defaults::Audio::VOLUME; // Volumen general del audio
|
||||||
};
|
};
|
||||||
@@ -292,16 +332,19 @@ namespace Options {
|
|||||||
extern Keyboard keyboard; // Opciones para el teclado
|
extern Keyboard keyboard; // Opciones para el teclado
|
||||||
extern PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
extern PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
||||||
extern std::vector<PostFXPreset> postfx_presets; // Lista de presets de PostFX
|
extern std::vector<PostFXPreset> postfx_presets; // Lista de presets de PostFX
|
||||||
extern int current_postfx_preset; // Índice del preset PostFX activo
|
|
||||||
extern std::string postfx_file_path; // Ruta al fichero de presets PostFX
|
extern std::string postfx_file_path; // Ruta al fichero de presets PostFX
|
||||||
|
extern std::vector<CrtPiPreset> crtpi_presets; // Lista de presets de CrtPi
|
||||||
|
extern std::string crtpi_file_path; // Ruta al fichero de presets CrtPi
|
||||||
|
|
||||||
// --- Funciones ---
|
// --- Funciones ---
|
||||||
void init(); // Inicializa las opciones del programa
|
void init(); // Inicializa las opciones del programa
|
||||||
void setConfigFile(const std::string& file_path); // Establece el fichero de configuración
|
void setConfigFile(const std::string& file_path); // Establece el fichero de configuración
|
||||||
void setControllersFile(const std::string& file_path); // Establece el fichero de configuración de mandos
|
void setControllersFile(const std::string& file_path); // Establece el fichero de configuración de mandos
|
||||||
void setPostFXFile(const std::string& path); // Establece el fichero de presets PostFX
|
void setPostFXFile(const std::string& path); // Establece el fichero de presets PostFX
|
||||||
|
void setCrtPiFile(const std::string& path); // Establece el fichero de presets CrtPi
|
||||||
auto loadPostFXFromFile() -> bool; // Carga los presets PostFX desde fichero
|
auto loadPostFXFromFile() -> bool; // Carga los presets PostFX desde fichero
|
||||||
auto savePostFXToFile() -> bool; // Guarda los presets PostFX por defecto al fichero
|
auto savePostFXToFile() -> bool; // Guarda los presets PostFX por defecto al fichero
|
||||||
|
auto loadCrtPiFromFile() -> bool; // Carga los presets CrtPi desde fichero
|
||||||
auto loadFromFile() -> bool; // Carga el fichero de configuración
|
auto loadFromFile() -> bool; // Carga el fichero de configuración
|
||||||
auto saveToFile() -> bool; // Guarda el fichero de configuración
|
auto saveToFile() -> bool; // Guarda el fichero de configuración
|
||||||
void setKeyboardToPlayer(Player::Id player_id); // Asigna el teclado al jugador
|
void setKeyboardToPlayer(Player::Id player_id); // Asigna el teclado al jugador
|
||||||
|
|||||||
10361
source/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
10361
source/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
4253
source/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
4253
source/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,24 +3,56 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3/SDL_gpu.h>
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "rendering/shader_backend.hpp"
|
#include "rendering/shader_backend.hpp"
|
||||||
|
|
||||||
// PostFX uniforms pushed to fragment stage each frame.
|
// 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.
|
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||||
struct PostFXUniforms {
|
struct PostFXUniforms {
|
||||||
float vignette_strength; // 0 = none, ~0.8 = subtle
|
float vignette_strength;
|
||||||
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
|
float chroma_strength;
|
||||||
float scanline_strength; // 0 = off, 1 = full
|
float scanline_strength;
|
||||||
float screen_height; // logical height in pixels (used by bleeding effect)
|
float screen_height;
|
||||||
float mask_strength; // 0 = off, 1 = full phosphor dot mask
|
float mask_strength;
|
||||||
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction
|
float gamma_strength;
|
||||||
float curvature; // 0 = flat, 1 = max barrel distortion
|
float curvature;
|
||||||
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
|
float bleeding;
|
||||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
float pixel_scale;
|
||||||
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
float time;
|
||||||
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
float oversample;
|
||||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
|
float flicker;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CrtPi uniforms pushed to fragment stage each frame.
|
||||||
|
// 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Downscale uniforms for Lanczos downscale fragment stage.
|
||||||
|
// 1 int + 3 floats = 16 bytes.
|
||||||
|
struct DownscaleUniforms {
|
||||||
|
int algorithm;
|
||||||
|
float pad0;
|
||||||
|
float pad1;
|
||||||
|
float pad2;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
@@ -28,9 +60,8 @@ namespace Rendering {
|
|||||||
/**
|
/**
|
||||||
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||||
*
|
*
|
||||||
* Backend de shaders PostFX para macOS (Metal) y Win/Linux (Vulkan/SPIR-V).
|
|
||||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||||
* → PostFX render pass → swapchain → present
|
* → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
|
||||||
*/
|
*/
|
||||||
class SDL3GPUShader : public ShaderBackend {
|
class SDL3GPUShader : public ShaderBackend {
|
||||||
public:
|
public:
|
||||||
@@ -44,25 +75,28 @@ namespace Rendering {
|
|||||||
|
|
||||||
void render() override;
|
void render() override;
|
||||||
void setTextureSize(float width, float height) override {}
|
void setTextureSize(float width, float height) override {}
|
||||||
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
|
void cleanup() final;
|
||||||
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
|
void destroy();
|
||||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||||
|
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||||
|
|
||||||
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
|
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||||
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
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;
|
void setPostFXParams(const PostFXParams& p) override;
|
||||||
|
|
||||||
// Activa/desactiva VSync en el swapchain
|
|
||||||
void setVSync(bool vsync) override;
|
void setVSync(bool vsync) override;
|
||||||
|
|
||||||
// Activa/desactiva escalado entero (integer scale)
|
|
||||||
void setScaleMode(bool integer_scale) override;
|
void setScaleMode(bool integer_scale) override;
|
||||||
|
|
||||||
// Establece factor de supersampling (1 = off, 3 = 3×SS)
|
|
||||||
void setOversample(int factor) override;
|
void setOversample(int factor) override;
|
||||||
|
|
||||||
|
void setLinearUpscale(bool linear) override;
|
||||||
|
[[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
|
||||||
|
void setDownscaleAlgo(int algo) override;
|
||||||
|
[[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
|
||||||
|
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||||
|
|
||||||
|
void setActiveShader(ShaderType type) override;
|
||||||
|
void setCrtPiParams(const CrtPiParams& p) override;
|
||||||
|
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||||
const char* msl_source,
|
const char* msl_source,
|
||||||
@@ -80,27 +114,41 @@ namespace Rendering {
|
|||||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||||
|
|
||||||
auto createPipeline() -> bool;
|
auto createPipeline() -> bool;
|
||||||
auto reinitTexturesAndBuffer() -> bool; // Recrea textura y buffer con oversample actual
|
auto createCrtPiPipeline() -> bool;
|
||||||
|
auto reinitTexturesAndBuffer() -> bool;
|
||||||
|
auto recreateScaledTexture(int factor) -> bool;
|
||||||
|
static auto calcSsFactor(float zoom) -> int;
|
||||||
|
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||||
|
|
||||||
SDL_Window* window_ = nullptr;
|
SDL_Window* window_ = nullptr;
|
||||||
SDL_GPUDevice* device_ = nullptr;
|
SDL_GPUDevice* device_ = nullptr;
|
||||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||||
|
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
|
||||||
|
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
|
||||||
|
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
|
||||||
|
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
|
||||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||||
|
SDL_GPUTexture* scaled_texture_ = nullptr;
|
||||||
|
SDL_GPUTexture* postfx_texture_ = nullptr;
|
||||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||||
SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling
|
SDL_GPUSampler* sampler_ = nullptr;
|
||||||
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling
|
SDL_GPUSampler* linear_sampler_ = nullptr;
|
||||||
|
|
||||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.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;
|
||||||
|
|
||||||
int game_width_ = 0; // Dimensiones originales del canvas (sin SS)
|
int game_width_ = 0;
|
||||||
int game_height_ = 0;
|
int game_height_ = 0;
|
||||||
int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_)
|
int ss_factor_ = 0;
|
||||||
int tex_height_ = 0;
|
int oversample_ = 1;
|
||||||
int oversample_ = 1; // Factor SS actual (1 o 3)
|
int downscale_algo_ = 1;
|
||||||
float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU
|
std::string driver_name_;
|
||||||
|
std::string preferred_driver_;
|
||||||
bool is_initialized_ = false;
|
bool is_initialized_ = false;
|
||||||
bool vsync_ = true;
|
bool vsync_ = true;
|
||||||
bool integer_scale_ = false;
|
bool integer_scale_ = false;
|
||||||
|
bool linear_upscale_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
633
source/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
633
source/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
#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;
|
||||||
@@ -3,9 +3,14 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
|
/** @brief Identificador del shader de post-procesado activo */
|
||||||
|
enum class ShaderType { POSTFX,
|
||||||
|
CRTPI };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parámetros de intensidad de los efectos PostFX
|
* @brief Parámetros de intensidad de los efectos PostFX
|
||||||
*/
|
*/
|
||||||
@@ -20,57 +25,64 @@ namespace Rendering {
|
|||||||
float flicker = 0.0F;
|
float flicker = 0.0F;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||||
|
*/
|
||||||
|
struct CrtPiParams {
|
||||||
|
float scanline_weight{6.0F};
|
||||||
|
float scanline_gap_brightness{0.12F};
|
||||||
|
float bloom_factor{3.5F};
|
||||||
|
float input_gamma{2.4F};
|
||||||
|
float output_gamma{2.2F};
|
||||||
|
float mask_brightness{0.80F};
|
||||||
|
float curvature_x{0.05F};
|
||||||
|
float curvature_y{0.10F};
|
||||||
|
int mask_type{2};
|
||||||
|
bool enable_scanlines{true};
|
||||||
|
bool enable_multisample{true};
|
||||||
|
bool enable_gamma{true};
|
||||||
|
bool enable_curvature{false};
|
||||||
|
bool enable_sharper{false};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Interfaz abstracta para backends de renderizado con shaders
|
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||||
*
|
|
||||||
* Esta interfaz define el contrato que todos los backends de shaders
|
|
||||||
* deben cumplir (Metal, Vulkan, etc.)
|
|
||||||
*/
|
*/
|
||||||
class ShaderBackend {
|
class ShaderBackend {
|
||||||
public:
|
public:
|
||||||
virtual ~ShaderBackend() = default;
|
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,
|
virtual auto init(SDL_Window* window,
|
||||||
SDL_Texture* texture,
|
SDL_Texture* texture,
|
||||||
const std::string& vertex_source,
|
const std::string& vertex_source,
|
||||||
const std::string& fragment_source) -> bool = 0;
|
const std::string& fragment_source) -> bool = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Renderiza la textura con los shaders aplicados
|
|
||||||
*/
|
|
||||||
virtual void render() = 0;
|
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;
|
virtual void setTextureSize(float width, float height) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Limpia y libera recursos del backend
|
|
||||||
*/
|
|
||||||
virtual void cleanup() = 0;
|
virtual void cleanup() = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Verifica si el backend está usando aceleración por hardware
|
|
||||||
* @return true si usa aceleración por hardware
|
|
||||||
*/
|
|
||||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
|
||||||
|
|
||||||
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||||
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||||
virtual void setVSync(bool /*vsync*/) {}
|
virtual void setVSync(bool /*vsync*/) {}
|
||||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||||
virtual void setOversample(int /*factor*/) {}
|
virtual void setOversample(int /*factor*/) {}
|
||||||
|
|
||||||
|
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||||
|
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||||
|
|
||||||
|
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||||
|
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||||
|
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||||
|
|
||||||
|
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||||
|
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||||
|
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
#include <string> // Para basic_string, operator+, char_traits, to_string, string
|
#include <string> // Para basic_string, operator+, char_traits, to_string, string
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "asset.hpp" // Para Asset
|
#include "asset.hpp" // Para Asset
|
||||||
#include "mouse.hpp" // Para updateCursorVisibility
|
#include "mouse.hpp" // Para updateCursorVisibility
|
||||||
#include "options.hpp" // Para Video, video, Window, window
|
#include "options.hpp" // Para Video, video, Window, window
|
||||||
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
|
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
|
||||||
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
||||||
#include "text.hpp" // Para Text
|
#include "text.hpp" // Para Text
|
||||||
#include "texture.hpp" // Para Texture
|
#include "texture.hpp" // Para Texture
|
||||||
#include "ui/logger.hpp" // Para info
|
#include "ui/logger.hpp" // Para info
|
||||||
#include "ui/notifier.hpp" // Para Notifier
|
#include "ui/notifier.hpp" // Para Notifier
|
||||||
#include "ui/service_menu.hpp" // Para ServiceMenu
|
#include "ui/service_menu.hpp" // Para ServiceMenu
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
Screen* Screen::instance = nullptr;
|
Screen* Screen::instance = nullptr;
|
||||||
@@ -25,7 +25,7 @@ Screen* Screen::instance = nullptr;
|
|||||||
// Inicializa la instancia única del singleton
|
// Inicializa la instancia única del singleton
|
||||||
void Screen::init() {
|
void Screen::init() {
|
||||||
Screen::instance = new Screen();
|
Screen::instance = new Screen();
|
||||||
Screen::initPostFX(); // Llamar aquí para que Screen::get() ya devuelva la instancia
|
Screen::initShaders(); // Llamar aquí para que Screen::get() ya devuelva la instancia
|
||||||
}
|
}
|
||||||
|
|
||||||
// Libera la instancia
|
// Libera la instancia
|
||||||
@@ -68,7 +68,6 @@ Screen::Screen()
|
|||||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||||
SDL_RenderClear(renderer_);
|
SDL_RenderClear(renderer_);
|
||||||
SDL_RenderPresent(renderer_);
|
SDL_RenderPresent(renderer_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
@@ -112,13 +111,11 @@ void Screen::renderPresent() {
|
|||||||
SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr);
|
SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr);
|
||||||
if (surface != nullptr) {
|
if (surface != nullptr) {
|
||||||
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
|
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
|
||||||
std::memcpy(pixel_buffer_.data(), surface->pixels,
|
std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||||
pixel_buffer_.size() * sizeof(Uint32));
|
|
||||||
} else {
|
} else {
|
||||||
SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
|
SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
|
||||||
if (converted != nullptr) {
|
if (converted != nullptr) {
|
||||||
std::memcpy(pixel_buffer_.data(), converted->pixels,
|
std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||||
pixel_buffer_.size() * sizeof(Uint32));
|
|
||||||
SDL_DestroySurface(converted);
|
SDL_DestroySurface(converted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,26 +253,35 @@ void Screen::renderInfo() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Inicializa PostFX (SDL3GPU)
|
// Inicializa shaders (SDL3GPU)
|
||||||
void Screen::initPostFX() {
|
void Screen::initShaders() {
|
||||||
#ifndef NO_SHADERS
|
#ifndef NO_SHADERS
|
||||||
auto* self = Screen::get();
|
auto* self = Screen::get();
|
||||||
if (self == nullptr) {
|
if (self == nullptr) {
|
||||||
SDL_Log("Screen::initPostFX: instance is null, skipping");
|
SDL_Log("Screen::initShaders: instance is null, skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!self->shader_backend_) {
|
if (!self->shader_backend_) {
|
||||||
self->shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
self->shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||||
|
const std::string FALLBACK_DRIVER = "none";
|
||||||
|
self->shader_backend_->setPreferredDriver(
|
||||||
|
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
|
||||||
}
|
}
|
||||||
if (!self->shader_backend_->isHardwareAccelerated()) {
|
if (!self->shader_backend_->isHardwareAccelerated()) {
|
||||||
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
||||||
SDL_Log("Screen::initPostFX: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED");
|
SDL_Log("Screen::initShaders: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED");
|
||||||
|
}
|
||||||
|
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||||
|
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
||||||
|
self->shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
|
||||||
|
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||||
|
self->shader_backend_->setActiveShader(Options::video.shader.current_shader);
|
||||||
|
}
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||||
|
self->applyCurrentCrtPiPreset();
|
||||||
|
} else {
|
||||||
|
self->applyCurrentPostFXPreset();
|
||||||
}
|
}
|
||||||
SDL_Log("Screen::initPostFX: presets=%d current=%d postfx=%s",
|
|
||||||
static_cast<int>(Options::postfx_presets.size()),
|
|
||||||
Options::current_postfx_preset,
|
|
||||||
Options::video.postfx ? "ON" : "OFF");
|
|
||||||
self->applyCurrentPostFXPreset();
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,11 +444,34 @@ void Screen::getDisplayInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alterna entre activar y desactivar los efectos PostFX
|
// Alterna activar/desactivar shaders
|
||||||
void Screen::togglePostFX() {
|
void Screen::toggleShaders() {
|
||||||
Options::video.postfx = !Options::video.postfx;
|
Options::video.shader.enabled = !Options::video.shader.enabled;
|
||||||
auto* self = Screen::get();
|
auto* self = Screen::get();
|
||||||
if (self != nullptr) {
|
if (self != nullptr) {
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||||
|
self->applyCurrentCrtPiPreset();
|
||||||
|
} else {
|
||||||
|
self->applyCurrentPostFXPreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cambia entre PostFX y CrtPi
|
||||||
|
void Screen::nextShader() {
|
||||||
|
auto* self = Screen::get();
|
||||||
|
if (self == nullptr || !self->shader_backend_ || !self->shader_backend_->isHardwareAccelerated()) { return; }
|
||||||
|
|
||||||
|
const auto NEXT = (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX)
|
||||||
|
? Rendering::ShaderType::CRTPI
|
||||||
|
: Rendering::ShaderType::POSTFX;
|
||||||
|
|
||||||
|
Options::video.shader.current_shader = NEXT;
|
||||||
|
self->shader_backend_->setActiveShader(NEXT);
|
||||||
|
|
||||||
|
if (NEXT == Rendering::ShaderType::CRTPI) {
|
||||||
|
self->applyCurrentCrtPiPreset();
|
||||||
|
} else {
|
||||||
self->applyCurrentPostFXPreset();
|
self->applyCurrentPostFXPreset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -450,49 +479,87 @@ void Screen::togglePostFX() {
|
|||||||
// Avanza al siguiente preset PostFX
|
// Avanza al siguiente preset PostFX
|
||||||
void Screen::nextPostFXPreset() {
|
void Screen::nextPostFXPreset() {
|
||||||
if (Options::postfx_presets.empty()) { return; }
|
if (Options::postfx_presets.empty()) { return; }
|
||||||
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
Options::video.shader.current_postfx_preset = (Options::video.shader.current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||||
auto* self = Screen::get();
|
auto* self = Screen::get();
|
||||||
if (self != nullptr) {
|
if (self != nullptr) {
|
||||||
self->applyCurrentPostFXPreset();
|
self->applyCurrentPostFXPreset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alterna entre activar y desactivar el supersampling 3x
|
// Avanza al siguiente preset CrtPi
|
||||||
|
void Screen::nextCrtPiPreset() {
|
||||||
|
if (Options::crtpi_presets.empty()) { return; }
|
||||||
|
Options::video.shader.current_crtpi_preset = (Options::video.shader.current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
|
||||||
|
auto* self = Screen::get();
|
||||||
|
if (self != nullptr) {
|
||||||
|
self->applyCurrentCrtPiPreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alterna supersampling
|
||||||
void Screen::toggleSupersampling() {
|
void Screen::toggleSupersampling() {
|
||||||
Options::video.supersampling = (Options::video.supersampling % 3) + 1;
|
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
|
||||||
auto* self = Screen::get();
|
auto* self = Screen::get();
|
||||||
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||||
self->applyCurrentPostFXPreset();
|
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||||
|
self->applyCurrentCrtPiPreset();
|
||||||
|
} else {
|
||||||
|
self->applyCurrentPostFXPreset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aplica el preset PostFX activo al backend
|
// Aplica el preset PostFX activo al backend
|
||||||
void Screen::applyCurrentPostFXPreset() {
|
void Screen::applyCurrentPostFXPreset() {
|
||||||
if (!shader_backend_) { return; }
|
if (!shader_backend_) { return; }
|
||||||
// setOversample PRIMERO: puede recrear texturas antes de que setPostFXParams
|
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||||
// decida si hornear scanlines en CPU o aplicarlas en GPU.
|
|
||||||
shader_backend_->setOversample(Options::video.supersampling);
|
|
||||||
Rendering::PostFXParams p{};
|
Rendering::PostFXParams p{};
|
||||||
if (Options::video.postfx && !Options::postfx_presets.empty()) {
|
if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
|
||||||
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset));
|
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
|
||||||
p.vignette = preset.vignette;
|
p.vignette = preset.vignette;
|
||||||
p.scanlines = preset.scanlines;
|
p.scanlines = preset.scanlines;
|
||||||
p.chroma = preset.chroma;
|
p.chroma = preset.chroma;
|
||||||
p.mask = preset.mask;
|
p.mask = preset.mask;
|
||||||
p.gamma = preset.gamma;
|
p.gamma = preset.gamma;
|
||||||
p.curvature = preset.curvature;
|
p.curvature = preset.curvature;
|
||||||
p.bleeding = preset.bleeding;
|
p.bleeding = preset.bleeding;
|
||||||
p.flicker = preset.flicker;
|
p.flicker = preset.flicker;
|
||||||
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f ss=%d×",
|
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f",
|
||||||
preset.name.c_str(), p.scanlines, p.vignette, p.chroma, Options::video.supersampling);
|
preset.name.c_str(),
|
||||||
} else {
|
p.scanlines,
|
||||||
SDL_Log("Screen::applyCurrentPostFXPreset: PostFX=%s presets=%d → passthrough",
|
p.vignette,
|
||||||
Options::video.postfx ? "ON" : "OFF",
|
p.chroma);
|
||||||
static_cast<int>(Options::postfx_presets.size()));
|
|
||||||
}
|
}
|
||||||
shader_backend_->setPostFXParams(p);
|
shader_backend_->setPostFXParams(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aplica el preset CrtPi activo al backend
|
||||||
|
void Screen::applyCurrentCrtPiPreset() {
|
||||||
|
if (!shader_backend_) { return; }
|
||||||
|
if (Options::video.shader.enabled && !Options::crtpi_presets.empty()) {
|
||||||
|
const auto& preset = Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset));
|
||||||
|
Rendering::CrtPiParams p{
|
||||||
|
.scanline_weight = preset.scanline_weight,
|
||||||
|
.scanline_gap_brightness = preset.scanline_gap_brightness,
|
||||||
|
.bloom_factor = preset.bloom_factor,
|
||||||
|
.input_gamma = preset.input_gamma,
|
||||||
|
.output_gamma = preset.output_gamma,
|
||||||
|
.mask_brightness = preset.mask_brightness,
|
||||||
|
.curvature_x = preset.curvature_x,
|
||||||
|
.curvature_y = preset.curvature_y,
|
||||||
|
.mask_type = preset.mask_type,
|
||||||
|
.enable_scanlines = preset.enable_scanlines,
|
||||||
|
.enable_multisample = preset.enable_multisample,
|
||||||
|
.enable_gamma = preset.enable_gamma,
|
||||||
|
.enable_curvature = preset.enable_curvature,
|
||||||
|
.enable_sharper = preset.enable_sharper,
|
||||||
|
};
|
||||||
|
shader_backend_->setCrtPiParams(p);
|
||||||
|
SDL_Log("Screen::applyCurrentCrtPiPreset: preset='%s'", preset.name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Alterna entre activar y desactivar el escalado entero
|
// Alterna entre activar y desactivar el escalado entero
|
||||||
void Screen::toggleIntegerScale() {
|
void Screen::toggleIntegerScale() {
|
||||||
Options::video.integer_scale = !Options::video.integer_scale;
|
Options::video.integer_scale = !Options::video.integer_scale;
|
||||||
|
|||||||
@@ -6,18 +6,15 @@
|
|||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "color.hpp" // Para Color
|
#include "color.hpp" // Para Color
|
||||||
#include "options.hpp" // Para VideoOptions, video
|
#include "options.hpp" // Para VideoOptions, video
|
||||||
|
#include "rendering/shader_backend.hpp" // Para Rendering::ShaderType
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class Notifier;
|
class Notifier;
|
||||||
class ServiceMenu;
|
class ServiceMenu;
|
||||||
class Text;
|
class Text;
|
||||||
|
|
||||||
namespace Rendering {
|
|
||||||
class ShaderBackend;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) ---
|
// --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) ---
|
||||||
class Screen {
|
class Screen {
|
||||||
public:
|
public:
|
||||||
@@ -41,18 +38,20 @@ class Screen {
|
|||||||
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
|
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
|
||||||
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
|
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
|
||||||
void applySettings(); // Aplica los valores de las opciones
|
void applySettings(); // Aplica los valores de las opciones
|
||||||
static void initPostFX(); // Inicializa PostFX (SDL3GPU)
|
static void initShaders(); // Inicializa shaders (SDL3GPU)
|
||||||
|
|
||||||
// --- Efectos visuales ---
|
// --- Efectos visuales ---
|
||||||
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); } // Agita la pantalla (tiempo en segundos)
|
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); }
|
||||||
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); } // Pone la pantalla de color (tiempo en segundos)
|
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
|
||||||
static void togglePostFX(); // Alterna entre activar y desactivar los efectos PostFX
|
static void toggleShaders(); // Alterna activar/desactivar shaders
|
||||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
static void nextShader(); // Cambia entre PostFX y CrtPi
|
||||||
static void toggleSupersampling(); // Alterna entre activar y desactivar el supersampling 3x
|
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||||
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
|
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
static void toggleSupersampling(); // Alterna supersampling
|
||||||
void setVSync(bool enabled); // Establece el estado del V-Sync
|
void toggleIntegerScale();
|
||||||
void attenuate(bool value) { attenuate_effect_ = value; } // Atenúa la pantalla
|
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||||
|
void setVSync(bool enabled); // Establece el estado del V-Sync
|
||||||
|
void attenuate(bool value) { attenuate_effect_ = value; } // Atenúa la pantalla
|
||||||
|
|
||||||
// --- Getters ---
|
// --- Getters ---
|
||||||
auto getRenderer() -> SDL_Renderer* { return renderer_; } // Obtiene el renderizador
|
auto getRenderer() -> SDL_Renderer* { return renderer_; } // Obtiene el renderizador
|
||||||
@@ -220,30 +219,31 @@ class Screen {
|
|||||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
|
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
|
||||||
|
|
||||||
// --- Variables de estado ---
|
// --- Variables de estado ---
|
||||||
SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego
|
SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego
|
||||||
SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego
|
SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego
|
||||||
std::vector<Uint32> pixel_buffer_; // Buffer de píxeles para SDL_RenderReadPixels
|
std::vector<Uint32> pixel_buffer_; // Buffer de píxeles para SDL_RenderReadPixels
|
||||||
FPS fps_; // Gestión de frames por segundo
|
FPS fps_; // Gestión de frames por segundo
|
||||||
FlashEffect flash_effect_; // Efecto de flash en pantalla
|
FlashEffect flash_effect_; // Efecto de flash en pantalla
|
||||||
ShakeEffect shake_effect_; // Efecto de agitar la pantalla
|
ShakeEffect shake_effect_; // Efecto de agitar la pantalla
|
||||||
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
|
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
|
||||||
DisplayMonitor display_monitor_; // Información del monitor actual
|
DisplayMonitor display_monitor_; // Información del monitor actual
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
Debug debug_info_; // Información de debug
|
Debug debug_info_; // Información de debug
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// --- Métodos internos ---
|
// --- Métodos internos ---
|
||||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||||
void renderInfo() const; // Muestra información por pantalla
|
void renderInfo() const; // Muestra información por pantalla
|
||||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||||
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
||||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||||
void renderAttenuate(); // Atenúa la pantalla
|
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||||
void createText(); // Crea el objeto de texto
|
void renderAttenuate(); // Atenúa la pantalla
|
||||||
|
void createText(); // Crea el objeto de texto
|
||||||
|
|
||||||
// --- Constructores y destructor privados (singleton) ---
|
// --- Constructores y destructor privados (singleton) ---
|
||||||
Screen(); // Constructor privado
|
Screen(); // Constructor privado
|
||||||
|
|||||||
@@ -93,15 +93,15 @@ class Game {
|
|||||||
|
|
||||||
// --- Estructuras ---
|
// --- Estructuras ---
|
||||||
struct Helper {
|
struct Helper {
|
||||||
bool need_coffee{false}; // Indica si se necesitan cafes
|
bool need_coffee{false}; // Indica si se necesitan cafes
|
||||||
bool need_coffee_machine{false}; // Indica si se necesita PowerUp
|
bool need_coffee_machine{false}; // Indica si se necesita PowerUp
|
||||||
bool need_power_ball{false}; // Indica si se necesita una PowerBall
|
bool need_power_ball{false}; // Indica si se necesita una PowerBall
|
||||||
float counter{HELP_COUNTER_S * 1000}; // Contador para no dar ayudas consecutivas
|
float counter{HELP_COUNTER_S * 1000}; // Contador para no dar ayudas consecutivas
|
||||||
int item_disk_odds{ITEM_POINTS_1_DISK_ODDS}; // Probabilidad de aparición del objeto
|
int item_disk_odds{ITEM_POINTS_1_DISK_ODDS}; // Probabilidad de aparición del objeto
|
||||||
int item_gavina_odds{ITEM_POINTS_2_GAVINA_ODDS}; // Probabilidad de aparición del objeto
|
int item_gavina_odds{ITEM_POINTS_2_GAVINA_ODDS}; // Probabilidad de aparición del objeto
|
||||||
int item_pacmar_odds{ITEM_POINTS_3_PACMAR_ODDS}; // Probabilidad de aparición del objeto
|
int item_pacmar_odds{ITEM_POINTS_3_PACMAR_ODDS}; // Probabilidad de aparición del objeto
|
||||||
int item_clock_odds{ITEM_CLOCK_ODDS}; // Probabilidad de aparición del objeto
|
int item_clock_odds{ITEM_CLOCK_ODDS}; // Probabilidad de aparición del objeto
|
||||||
int item_coffee_odds{ITEM_COFFEE_ODDS}; // Probabilidad de aparición del objeto
|
int item_coffee_odds{ITEM_COFFEE_ODDS}; // Probabilidad de aparición del objeto
|
||||||
int item_coffee_machine_odds{ITEM_COFFEE_MACHINE_ODDS}; // Probabilidad de aparición del objeto
|
int item_coffee_machine_odds{ITEM_COFFEE_MACHINE_ODDS}; // Probabilidad de aparición del objeto
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -71,15 +71,15 @@ class Instructions {
|
|||||||
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
|
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
|
||||||
|
|
||||||
// --- Variables ---
|
// --- Variables ---
|
||||||
float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos)
|
float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos)
|
||||||
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
|
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
|
||||||
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
|
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
|
||||||
SDL_FPoint sprite_pos_ = {.x = 0, .y = 0}; // Posición del primer sprite en la lista
|
SDL_FPoint sprite_pos_ = {.x = 0, .y = 0}; // Posición del primer sprite en la lista
|
||||||
float item_space_ = 2.0; // Espacio entre los items en pantalla
|
float item_space_ = 2.0; // Espacio entre los items en pantalla
|
||||||
std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla
|
std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla
|
||||||
bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla
|
bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla
|
||||||
float start_delay_timer_ = 0.0F; // Timer para retraso antes de mover líneas (segundos)
|
float start_delay_timer_ = 0.0F; // Timer para retraso antes de mover líneas (segundos)
|
||||||
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
|
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
|
||||||
|
|
||||||
// --- Métodos internos ---
|
// --- Métodos internos ---
|
||||||
void update(float delta_time); // Actualiza las variables
|
void update(float delta_time); // Actualiza las variables
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_FRect, Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess, SDL_GetTextureAlphaMod
|
#include <SDL3/SDL.h> // Para SDL_FRect, Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess, SDL_GetTextureAlphaMod
|
||||||
|
|
||||||
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream
|
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream
|
||||||
#include <utility> // Para std::cmp_less_equal
|
|
||||||
#include <iostream> // Para cerr
|
#include <iostream> // Para cerr
|
||||||
#include <sstream> // Para basic_istringstream
|
#include <sstream> // Para basic_istringstream
|
||||||
#include <stdexcept> // Para runtime_error
|
#include <stdexcept> // Para runtime_error
|
||||||
#include <string_view> // Para string_view
|
#include <string_view> // Para string_view
|
||||||
|
#include <utility> // Para std::cmp_less_equal
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "color.hpp" // Para Color
|
#include "color.hpp" // Para Color
|
||||||
|
|||||||
@@ -366,19 +366,20 @@ void ServiceMenu::initializeOptions() {
|
|||||||
options_.push_back(std::make_unique<BoolOption>(
|
options_.push_back(std::make_unique<BoolOption>(
|
||||||
Lang::getText("[SERVICE_MENU] POSTFX"),
|
Lang::getText("[SERVICE_MENU] POSTFX"),
|
||||||
SettingsGroup::VIDEO,
|
SettingsGroup::VIDEO,
|
||||||
&Options::video.postfx));
|
&Options::video.shader.enabled));
|
||||||
|
|
||||||
options_.push_back(std::make_unique<IntOption>(
|
options_.push_back(std::make_unique<IntOption>(
|
||||||
Lang::getText("[SERVICE_MENU] POSTFX_PRESET"),
|
Lang::getText("[SERVICE_MENU] POSTFX_PRESET"),
|
||||||
SettingsGroup::VIDEO,
|
SettingsGroup::VIDEO,
|
||||||
&Options::current_postfx_preset,
|
&Options::video.shader.current_postfx_preset,
|
||||||
0, static_cast<int>(Options::postfx_presets.size()) - 1, 1));
|
0,
|
||||||
|
static_cast<int>(Options::postfx_presets.size()) - 1,
|
||||||
|
1));
|
||||||
|
|
||||||
options_.push_back(std::make_unique<IntOption>(
|
options_.push_back(std::make_unique<BoolOption>(
|
||||||
Lang::getText("[SERVICE_MENU] SUPERSAMPLING"),
|
Lang::getText("[SERVICE_MENU] SUPERSAMPLING"),
|
||||||
SettingsGroup::VIDEO,
|
SettingsGroup::VIDEO,
|
||||||
&Options::video.supersampling,
|
&Options::video.supersampling.enabled));
|
||||||
1, 3, 1));
|
|
||||||
|
|
||||||
options_.push_back(std::make_unique<BoolOption>(
|
options_.push_back(std::make_unique<BoolOption>(
|
||||||
Lang::getText("[SERVICE_MENU] VSYNC"),
|
Lang::getText("[SERVICE_MENU] VSYNC"),
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ echo "Compiling SPIR-V shaders..."
|
|||||||
|
|
||||||
glslc "${SHADERS_DIR}/postfx.vert" -o /tmp/postfx.vert.spv
|
glslc "${SHADERS_DIR}/postfx.vert" -o /tmp/postfx.vert.spv
|
||||||
glslc "${SHADERS_DIR}/postfx.frag" -o /tmp/postfx.frag.spv
|
glslc "${SHADERS_DIR}/postfx.frag" -o /tmp/postfx.frag.spv
|
||||||
|
glslc -fshader-stage=fragment "${SHADERS_DIR}/crtpi_frag.glsl" -o /tmp/crtpi_frag.spv
|
||||||
|
glslc "${SHADERS_DIR}/upscale.frag" -o /tmp/upscale.frag.spv
|
||||||
|
glslc "${SHADERS_DIR}/downscale.frag" -o /tmp/downscale.frag.spv
|
||||||
|
|
||||||
echo "Generating C++ headers..."
|
echo "Generating C++ headers..."
|
||||||
|
|
||||||
@@ -33,12 +36,30 @@ xxd -i /tmp/postfx.frag.spv | \
|
|||||||
sed 's/unsigned int .*postfx_frag_spv_len/static const size_t kpostfx_frag_spv_size/' \
|
sed 's/unsigned int .*postfx_frag_spv_len/static const size_t kpostfx_frag_spv_size/' \
|
||||||
> "${HEADERS_DIR}/postfx_frag_spv.h"
|
> "${HEADERS_DIR}/postfx_frag_spv.h"
|
||||||
|
|
||||||
|
xxd -i /tmp/crtpi_frag.spv | \
|
||||||
|
sed 's/unsigned char .*crtpi_frag_spv\[\]/static const uint8_t kcrtpi_frag_spv[]/' | \
|
||||||
|
sed 's/unsigned int .*crtpi_frag_spv_len/static const size_t kcrtpi_frag_spv_size/' \
|
||||||
|
> "${HEADERS_DIR}/crtpi_frag_spv.h"
|
||||||
|
|
||||||
|
xxd -i /tmp/upscale.frag.spv | \
|
||||||
|
sed 's/unsigned char .*upscale_frag_spv\[\]/static const uint8_t kupscale_frag_spv[]/' | \
|
||||||
|
sed 's/unsigned int .*upscale_frag_spv_len/static const size_t kupscale_frag_spv_size/' \
|
||||||
|
> "${HEADERS_DIR}/upscale_frag_spv.h"
|
||||||
|
|
||||||
|
xxd -i /tmp/downscale.frag.spv | \
|
||||||
|
sed 's/unsigned char .*downscale_frag_spv\[\]/static const uint8_t kdownscale_frag_spv[]/' | \
|
||||||
|
sed 's/unsigned int .*downscale_frag_spv_len/static const size_t kdownscale_frag_spv_size/' \
|
||||||
|
> "${HEADERS_DIR}/downscale_frag_spv.h"
|
||||||
|
|
||||||
# Prepend required includes to the headers
|
# Prepend required includes to the headers
|
||||||
for f in "${HEADERS_DIR}/postfx_vert_spv.h" "${HEADERS_DIR}/postfx_frag_spv.h"; do
|
for f in "${HEADERS_DIR}/postfx_vert_spv.h" "${HEADERS_DIR}/postfx_frag_spv.h" "${HEADERS_DIR}/crtpi_frag_spv.h" "${HEADERS_DIR}/upscale_frag_spv.h" "${HEADERS_DIR}/downscale_frag_spv.h"; do
|
||||||
echo -e "#pragma once\n#include <cstdint>\n#include <cstddef>\n$(cat "$f")" > "$f"
|
echo -e "#pragma once\n#include <cstdint>\n#include <cstddef>\n$(cat "$f")" > "$f"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Done. Headers updated in ${HEADERS_DIR}/"
|
echo "Done. Headers updated in ${HEADERS_DIR}/"
|
||||||
echo " postfx_vert_spv.h"
|
echo " postfx_vert_spv.h"
|
||||||
echo " postfx_frag_spv.h"
|
echo " postfx_frag_spv.h"
|
||||||
|
echo " crtpi_frag_spv.h"
|
||||||
|
echo " upscale_frag_spv.h"
|
||||||
|
echo " downscale_frag_spv.h"
|
||||||
echo "Rebuild the project to use the new shaders."
|
echo "Rebuild the project to use the new shaders."
|
||||||
|
|||||||
Reference in New Issue
Block a user