migrat, amb ajuda de claude, a sdl3gpu (postfx i crtpi) igual que el JDD

This commit is contained in:
2026-04-03 15:08:06 +02:00
parent 3c2a5c9b37
commit 93fe17c3b2
34 changed files with 20728 additions and 3312 deletions

103
CLAUDE.md Normal file
View 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/`.

View File

@@ -123,32 +123,43 @@ message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
if(NOT APPLE)
find_program(GLSLC_EXE NAMES glslc)
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
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_FRAG_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_frag_spv.h")
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
set(SHADER_CRTPI_SRC "${CMAKE_SOURCE_DIR}/data/shaders/crtpi_frag.glsl")
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)
add_custom_command(
OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}"
OUTPUT ${ALL_SHADER_HEADERS}
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}"
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")
else()
if(NOT EXISTS "${SHADER_VERT_H}" OR NOT EXISTS "${SHADER_FRAG_H}")
message(FATAL_ERROR
"glslc no encontrado y headers SPIR-V no existen.\n"
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
" 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()
foreach(_h IN LISTS ALL_SHADER_HEADERS)
if(NOT EXISTS "${_h}")
message(FATAL_ERROR
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
" choco install vulkan-sdk (Windows)\n"
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
)
endif()
endforeach()
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
endif()
else()
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")

View File

@@ -7,6 +7,7 @@
DATA|${SYSTEM_FOLDER}/config.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute
DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/crtpi.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
# Archivos de configuración del juego

View 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);
}

View 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
View 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);
}

View File

@@ -57,13 +57,13 @@ class AnimatedSprite : public MovingSprite {
void update(float delta_time) override; // Actualiza la animación (time-based)
// --- Control de animaciones ---
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 resetAnimation(); // Reinicia la animación actual
void setAnimationSpeed(float value); // Establece la velocidad de la animación
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 resetAnimation(); // Reinicia la animación actual
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
void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación
void animationResume() { animations_[current_animation_].paused = false; } // Reanuda 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
[[nodiscard]] auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual
// --- Consultas ---

View File

@@ -34,7 +34,7 @@ class Asset {
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
[[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 getListByType(Type type) const -> std::vector<std::string>;
[[nodiscard]] auto exists(const std::string& filename) const -> bool; // Nueva función para verificar existencia

View File

@@ -1,7 +1,6 @@
#include "balloon_formations.hpp"
#include <algorithm> // Para max, min, copy
#include <utility> // Para std::cmp_less
#include <array> // Para array
#include <cctype> // Para isdigit
#include <cstddef> // Para size_t
@@ -11,6 +10,7 @@
#include <map> // Para map, operator==, _Rb_tree_iterator
#include <sstream> // Para basic_istringstream
#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 "balloon.hpp" // Para Balloon

View File

@@ -186,8 +186,11 @@ namespace Defaults::Video {
constexpr bool FULLSCREEN = false;
constexpr bool VSYNC = true;
constexpr bool INTEGER_SCALE = true;
constexpr bool POSTFX = false;
constexpr int SUPERSAMPLING = 1;
constexpr bool GPU_ACCELERATION = true;
constexpr bool SHADER_ENABLED = false;
constexpr bool SUPERSAMPLING = false;
constexpr bool LINEAR_UPSCALE = false;
constexpr int DOWNSCALE_ALGO = 1;
} // namespace Defaults::Video
namespace Defaults::Music {

View File

@@ -103,13 +103,15 @@ void Director::init() {
Input::init(Asset::get()->getPath("gamecontrollerdb.txt"), Asset::get()->getPath("controllers.json")); // Carga configuración de controles
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::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX
Options::loadFromFile(); // Carga el archivo de configuración
Options::loadPostFXFromFile(); // Carga los presets PostFX
loadParams(); // Carga los parámetros del programa
loadScoreFile(); // Carga el archivo de puntuaciones
Options::setCrtPiFile(Asset::get()->getPath("crtpi.yaml")); // Establece el fichero de presets CrtPi
Options::loadFromFile(); // Carga el archivo de configuración
Options::loadPostFXFromFile(); // Carga los presets PostFX
Options::loadCrtPiFromFile(); // Carga los presets CrtPi
loadParams(); // Carga los parámetros del programa
loadScoreFile(); // Carga el archivo de puntuaciones
// Inicialización de subsistemas principales
Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma

View File

@@ -33,8 +33,8 @@ class EnterName {
private:
// --- Variables de estado ---
std::string character_list_{"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{"}; // Lista de caracteres permitidos
std::string name_; // Nombre en proceso
size_t selected_index_ = 0; // Índice del carácter seleccionado en "character_list_"
std::string name_; // Nombre en proceso
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
static auto getRandomName() -> std::string; // Devuelve un nombre al azar

View File

@@ -72,7 +72,7 @@ class Fade {
int num_squares_width_; // Cuadrados en horizontal
int num_squares_height_; // Cuadrados en vertical
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
int post_duration_ = 0; // Duración posterior en milisegundos
Uint32 post_start_time_ = 0; // Tiempo de inicio del estado POST

View File

@@ -54,7 +54,7 @@ namespace GlobalEvents {
break;
case SDL_EVENT_WINDOW_RESIZED:
Screen::initPostFX();
Screen::initShaders();
break;
default:

View File

@@ -62,25 +62,36 @@ namespace GlobalInputs {
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 14") + " " + boolToOnOff(Options::video.vsync)});
}
// Activa o desactiva los efectos PostFX
void togglePostFX() {
Screen::togglePostFX();
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.postfx)});
// Activa o desactiva los shaders
void toggleShaders() {
Screen::toggleShaders();
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.shader.enabled)});
}
// Avanza al siguiente preset PostFX
void nextPostFXPreset() {
Screen::nextPostFXPreset();
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset)).name;
Notifier::get()->show({"PostFX: " + name});
// Cambia entre PostFX y CrtPi
void nextShader() {
Screen::nextShader();
const std::string SHADER_NAME = (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX";
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() {
Screen::toggleSupersampling();
const int SS = Options::video.supersampling;
const std::string SS_LABEL = (SS <= 1) ? "OFF" : (std::to_string(SS) + "\xC3\x97");
Notifier::get()->show({"SS: " + SS_LABEL});
Notifier::get()->show({"SS: " + std::string(Options::video.supersampling.enabled ? "ON" : "OFF")});
}
// 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)) {
togglePostFX();
toggleShaders();
return true;
}
if (Input::get()->checkAction(Input::Action::NEXT_SHADER, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
nextShader();
return true;
}
if (Input::get()->checkAction(Input::Action::NEXT_POSTFX_PRESET, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
nextPostFXPreset();
nextPreset();
return true;
}
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {

View File

@@ -82,6 +82,7 @@ class Input {
{Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)},
{Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)},
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
{Action::NEXT_SHADER, KeyState(SDL_SCANCODE_8)},
{Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
{Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},

View File

@@ -22,6 +22,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::WINDOW_INC_SIZE, "WINDOW_INC_SIZE"},
{InputAction::WINDOW_DEC_SIZE, "WINDOW_DEC_SIZE"},
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
{InputAction::NEXT_SHADER, "NEXT_SHADER"},
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
{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_DEC_SIZE", InputAction::WINDOW_DEC_SIZE},
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
{"NEXT_SHADER", InputAction::NEXT_SHADER},
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},

View File

@@ -32,6 +32,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
WINDOW_INC_SIZE,
WINDOW_DEC_SIZE,
TOGGLE_VIDEO_POSTFX,
NEXT_SHADER,
NEXT_POSTFX_PRESET,
TOGGLE_SUPERSAMPLING,
TOGGLE_VIDEO_INTEGER_SCALE,

View File

@@ -8,7 +8,7 @@
#include <string> // Para string
#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 "input.hpp" // Para Input
#include "lang.hpp" // Para getText, Code
@@ -24,16 +24,17 @@ namespace Options {
GamepadManager gamepad_manager; // Opciones de mando para cada jugador
Keyboard keyboard; // Opciones para el teclado
PendingChanges pending_changes; // Opciones que se aplican al cerrar
std::vector<PostFXPreset> postfx_presets = { // Lista de presets de PostFX
{"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},
{"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},
{"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},
std::vector<PostFXPreset> postfx_presets = {
{"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},
{"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},
{"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},
};
int current_postfx_preset = 0; // Índice del preset PostFX activo
std::string postfx_file_path; // Ruta al fichero de presets PostFX
std::string postfx_file_path;
std::vector<CrtPiPreset> crtpi_presets;
std::string crtpi_file_path;
// Establece el fichero de configuración
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
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
static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
if (node.contains(key)) {
@@ -89,11 +93,21 @@ namespace Options {
}
if (!postfx_presets.empty()) {
current_postfx_preset = std::clamp(
current_postfx_preset, 0,
// Resolver nombre → índice
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);
} 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());
@@ -190,17 +204,141 @@ namespace Options {
// Cargar los presets recién escritos
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({"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({"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({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F});
current_postfx_preset = 0;
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({"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({"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});
video.shader.current_postfx_preset = 0;
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
void init() {
// Dificultades
@@ -234,55 +372,109 @@ namespace Options {
const auto& vid = yaml["video"];
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")) {
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")) {
try { video.vsync = vid["vsync"].get_value<bool>(); } catch (...) {}
try {
video.vsync = vid["vsync"].get_value<bool>();
} catch (...) {}
}
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 (...) {}
}
// Nuevo formato: supersampling (bool) + supersampling_amount (int)
// Backward compat: si solo existe supersampling como int, también funciona
{
bool ss_enabled = false;
int ss_amount = 3;
if (vid.contains("supersampling")) {
// --- GPU ---
if (vid.contains("gpu")) {
const auto& gpu_node = vid["gpu"];
if (gpu_node.contains("acceleration")) {
try {
const auto& node = vid["supersampling"];
if (node.is_boolean()) {
ss_enabled = node.get_value<bool>();
video.gpu.acceleration = gpu_node["acceleration"].get_value<bool>();
} catch (...) {}
}
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 {
// Formato antiguo: int directamente
int factor = node.get_value<int>();
ss_enabled = factor >= 2;
ss_amount = (factor >= 2) ? factor : 3;
int factor = ss_node.get_value<int>();
video.supersampling.enabled = factor >= 2;
}
} 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"];
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")) {
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")) {
const auto& mus = aud["music"];
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")) {
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")) {
const auto& snd = aud["sound"];
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")) {
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 (...) {}
}
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")) {
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")) {
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) {
if (i >= GamepadManager::size()) { break; }
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")) {
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")) {
try {
@@ -379,7 +593,9 @@ namespace Options {
if (!yaml.contains("keyboard")) { return; }
const auto& kb = yaml["keyboard"];
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 << " vsync: " << boolToString(video.vsync) << "\n";
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
file << " postfx: " << boolToString(video.postfx) << "\n";
file << " postfx_preset: " << current_postfx_preset << "\n";
file << " supersampling: " << boolToString(video.supersampling > 1) << "\n";
file << " supersampling_amount: " << std::max(2, video.supersampling) << "\n";
file << " gpu:\n";
file << " acceleration: " << boolToString(video.gpu.acceleration) << "\n";
file << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\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";
// AUDIO

View File

@@ -14,11 +14,12 @@
#include <vector> // Para vector
#include "defaults.hpp"
#include "difficulty.hpp" // for Code
#include "input.hpp" // for Input
#include "lang.hpp" // for Code
#include "manage_hiscore_table.hpp" // for ManageHiScoreTable, Table
#include "player.hpp" // for Player
#include "difficulty.hpp" // for Code
#include "input.hpp" // for Input
#include "lang.hpp" // for Code
#include "manage_hiscore_table.hpp" // for ManageHiScoreTable, Table
#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 {
@@ -36,20 +37,59 @@ namespace Options {
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 {
std::string caption = Defaults::Window::CAPTION; // Texto que aparece en la barra de título de la ventana
int zoom = Defaults::Window::ZOOM; // Valor por el que se multiplica el tamaño de la ventana
int max_zoom = Defaults::Window::MAX_ZOOM; // Tamaño máximo para que la ventana no sea mayor que la pantalla
std::string caption = Defaults::Window::CAPTION;
int zoom = Defaults::Window::ZOOM;
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 {
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE; // Filtro usado para el escalado de la imagen
bool fullscreen = Defaults::Video::FULLSCREEN; // Indica si se usa pantalla completa
bool vsync = Defaults::Video::VSYNC; // Indica si se usa vsync
bool integer_scale = Defaults::Video::INTEGER_SCALE; // Indica si se usa escalado entero
bool postfx = Defaults::Video::POSTFX; // Indica si se usan efectos PostFX
int supersampling = Defaults::Video::SUPERSAMPLING; // Factor de supersampling: 1=off, 2=2×, 3=3×
std::string info; // Información sobre el modo de vídeo
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
bool fullscreen = Defaults::Video::FULLSCREEN;
bool vsync = Defaults::Video::VSYNC;
bool integer_scale = Defaults::Video::INTEGER_SCALE;
std::string info;
GPU gpu{};
Supersampling supersampling{};
ShaderConfig shader{};
};
struct Music {
@@ -63,8 +103,8 @@ namespace Options {
};
struct Audio {
Music music; // Opciones para la música
Sound sound; // Opciones para los efectos de sonido
Music music; // Opciones para la música
Sound sound; // Opciones para los efectos de sonido
bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no
int volume = Defaults::Audio::VOLUME; // Volumen general del audio
};
@@ -292,16 +332,19 @@ namespace Options {
extern Keyboard keyboard; // Opciones para el teclado
extern PendingChanges pending_changes; // Opciones que se aplican al cerrar
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::vector<CrtPiPreset> crtpi_presets; // Lista de presets de CrtPi
extern std::string crtpi_file_path; // Ruta al fichero de presets CrtPi
// --- Funciones ---
void init(); // Inicializa las opciones del programa
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 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 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 saveToFile() -> bool; // Guarda el fichero de configuración
void setKeyboardToPlayer(Player::Id player_id); // Asigna el teclado al jugador

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,24 +3,56 @@
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include <string>
#include <utility>
#include "rendering/shader_backend.hpp"
// PostFX uniforms pushed to fragment stage each frame.
// Must match the MSL struct and GLSL uniform block layout.
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
struct PostFXUniforms {
float vignette_strength; // 0 = none, ~0.8 = subtle
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
float scanline_strength; // 0 = off, 1 = full
float screen_height; // logical height in pixels (used by bleeding effect)
float mask_strength; // 0 = off, 1 = full phosphor dot mask
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction
float curvature; // 0 = flat, 1 = max barrel distortion
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
float vignette_strength;
float chroma_strength;
float scanline_strength;
float screen_height;
float mask_strength;
float gamma_strength;
float curvature;
float bleeding;
float pixel_scale;
float time;
float oversample;
float flicker;
};
// 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 {
@@ -28,9 +60,8 @@ namespace Rendering {
/**
* @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)
* → PostFX render pass → swapchain → present
* → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
*/
class SDL3GPUShader : public ShaderBackend {
public:
@@ -44,25 +75,28 @@ namespace Rendering {
void render() override;
void setTextureSize(float width, float height) override {}
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
void cleanup() final;
void destroy();
[[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;
// Actualiza los parámetros de intensidad de los efectos PostFX
void setPostFXParams(const PostFXParams& p) override;
// Activa/desactiva VSync en el swapchain
void setVSync(bool vsync) override;
// Activa/desactiva escalado entero (integer scale)
void setScaleMode(bool integer_scale) override;
// Establece factor de supersampling (1 = off, 3 = 3×SS)
void setOversample(int factor) override;
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:
static auto createShaderMSL(SDL_GPUDevice* device,
const char* msl_source,
@@ -80,27 +114,41 @@ namespace Rendering {
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
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_GPUDevice* device_ = 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* scaled_texture_ = nullptr;
SDL_GPUTexture* postfx_texture_ = nullptr;
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling
SDL_GPUSampler* sampler_ = nullptr;
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};
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 tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_)
int tex_height_ = 0;
int oversample_ = 1; // Factor SS actual (1 o 3)
float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU
int ss_factor_ = 0;
int oversample_ = 1;
int downscale_algo_ = 1;
std::string driver_name_;
std::string preferred_driver_;
bool is_initialized_ = false;
bool vsync_ = true;
bool integer_scale_ = false;
bool linear_upscale_ = false;
};
} // namespace Rendering

View 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;

View File

@@ -3,9 +3,14 @@
#include <SDL3/SDL.h>
#include <string>
#include <utility>
namespace Rendering {
/** @brief Identificador del shader de post-procesado activo */
enum class ShaderType { POSTFX,
CRTPI };
/**
* @brief Parámetros de intensidad de los efectos PostFX
*/
@@ -20,57 +25,64 @@ namespace Rendering {
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
*
* Esta interfaz define el contrato que todos los backends de shaders
* deben cumplir (Metal, Vulkan, etc.)
*/
class ShaderBackend {
public:
virtual ~ShaderBackend() = default;
/**
* @brief Inicializa el backend de shaders
* @param window Ventana SDL
* @param texture Textura de backbuffer a la que aplicar shaders
* @param vertex_source Código fuente del vertex shader
* @param fragment_source Código fuente del fragment shader
* @return true si la inicialización fue exitosa
*/
virtual auto init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) -> bool = 0;
/**
* @brief Renderiza la textura con los shaders aplicados
*/
virtual void render() = 0;
/**
* @brief Establece el tamaño de la textura como parámetro del shader
* @param width Ancho de la textura
* @param height Alto de la textura
*/
virtual void setTextureSize(float width, float height) = 0;
/**
* @brief Limpia y libera recursos del backend
*/
virtual void cleanup() = 0;
/**
* @brief 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 setPostFXParams(const PostFXParams& /*p*/) {}
virtual void setVSync(bool /*vsync*/) {}
virtual void setScaleMode(bool /*integer_scale*/) {}
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

View File

@@ -8,16 +8,16 @@
#include <string> // Para basic_string, operator+, char_traits, to_string, string
#include <vector> // Para vector
#include "asset.hpp" // Para Asset
#include "mouse.hpp" // Para updateCursorVisibility
#include "options.hpp" // Para Video, video, Window, window
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
#include "asset.hpp" // Para Asset
#include "mouse.hpp" // Para updateCursorVisibility
#include "options.hpp" // Para Video, video, Window, window
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
#include "text.hpp" // Para Text
#include "texture.hpp" // Para Texture
#include "ui/logger.hpp" // Para info
#include "ui/notifier.hpp" // Para Notifier
#include "ui/service_menu.hpp" // Para ServiceMenu
#include "text.hpp" // Para Text
#include "texture.hpp" // Para Texture
#include "ui/logger.hpp" // Para info
#include "ui/notifier.hpp" // Para Notifier
#include "ui/service_menu.hpp" // Para ServiceMenu
// Singleton
Screen* Screen::instance = nullptr;
@@ -25,7 +25,7 @@ Screen* Screen::instance = nullptr;
// Inicializa la instancia única del singleton
void Screen::init() {
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
@@ -68,7 +68,6 @@ Screen::Screen()
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_RenderClear(renderer_);
SDL_RenderPresent(renderer_);
}
// Destructor
@@ -112,13 +111,11 @@ void Screen::renderPresent() {
SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr);
if (surface != nullptr) {
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
std::memcpy(pixel_buffer_.data(), surface->pixels,
pixel_buffer_.size() * sizeof(Uint32));
std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
} else {
SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
if (converted != nullptr) {
std::memcpy(pixel_buffer_.data(), converted->pixels,
pixel_buffer_.size() * sizeof(Uint32));
std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
SDL_DestroySurface(converted);
}
}
@@ -256,26 +253,35 @@ void Screen::renderInfo() const {
}
}
#endif
// Inicializa PostFX (SDL3GPU)
void Screen::initPostFX() {
// Inicializa shaders (SDL3GPU)
void Screen::initShaders() {
#ifndef NO_SHADERS
auto* self = Screen::get();
if (self == nullptr) {
SDL_Log("Screen::initPostFX: instance is null, skipping");
SDL_Log("Screen::initShaders: instance is null, skipping");
return;
}
if (!self->shader_backend_) {
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()) {
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
}
@@ -438,11 +444,34 @@ void Screen::getDisplayInfo() {
}
}
// Alterna entre activar y desactivar los efectos PostFX
void Screen::togglePostFX() {
Options::video.postfx = !Options::video.postfx;
// Alterna activar/desactivar shaders
void Screen::toggleShaders() {
Options::video.shader.enabled = !Options::video.shader.enabled;
auto* self = Screen::get();
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();
}
}
@@ -450,49 +479,87 @@ void Screen::togglePostFX() {
// Avanza al siguiente preset PostFX
void Screen::nextPostFXPreset() {
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();
if (self != nullptr) {
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() {
Options::video.supersampling = (Options::video.supersampling % 3) + 1;
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
auto* self = Screen::get();
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
void Screen::applyCurrentPostFXPreset() {
if (!shader_backend_) { return; }
// setOversample PRIMERO: puede recrear texturas antes de que setPostFXParams
// decida si hornear scanlines en CPU o aplicarlas en GPU.
shader_backend_->setOversample(Options::video.supersampling);
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
Rendering::PostFXParams p{};
if (Options::video.postfx && !Options::postfx_presets.empty()) {
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset));
p.vignette = preset.vignette;
if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
p.vignette = preset.vignette;
p.scanlines = preset.scanlines;
p.chroma = preset.chroma;
p.mask = preset.mask;
p.gamma = preset.gamma;
p.chroma = preset.chroma;
p.mask = preset.mask;
p.gamma = preset.gamma;
p.curvature = preset.curvature;
p.bleeding = preset.bleeding;
p.flicker = preset.flicker;
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f ss=%d×",
preset.name.c_str(), p.scanlines, p.vignette, p.chroma, Options::video.supersampling);
} else {
SDL_Log("Screen::applyCurrentPostFXPreset: PostFX=%s presets=%d → passthrough",
Options::video.postfx ? "ON" : "OFF",
static_cast<int>(Options::postfx_presets.size()));
p.bleeding = preset.bleeding;
p.flicker = preset.flicker;
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f",
preset.name.c_str(),
p.scanlines,
p.vignette,
p.chroma);
}
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
void Screen::toggleIntegerScale() {
Options::video.integer_scale = !Options::video.integer_scale;

View File

@@ -6,18 +6,15 @@
#include <string> // Para string
#include <vector> // Para vector
#include "color.hpp" // Para Color
#include "options.hpp" // Para VideoOptions, video
#include "color.hpp" // Para Color
#include "options.hpp" // Para VideoOptions, video
#include "rendering/shader_backend.hpp" // Para Rendering::ShaderType
// Forward declarations
class Notifier;
class ServiceMenu;
class Text;
namespace Rendering {
class ShaderBackend;
}
// --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) ---
class Screen {
public:
@@ -41,18 +38,20 @@ class Screen {
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
void applySettings(); // Aplica los valores de las opciones
static void initPostFX(); // Inicializa PostFX (SDL3GPU)
static void initShaders(); // Inicializa shaders (SDL3GPU)
// --- 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 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)
static void togglePostFX(); // Alterna entre activar y desactivar los efectos PostFX
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
static void toggleSupersampling(); // Alterna entre activar y desactivar el supersampling 3x
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
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
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); }
static void toggleShaders(); // Alterna activar/desactivar shaders
static void nextShader(); // Cambia entre PostFX y CrtPi
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
static void toggleSupersampling(); // Alterna supersampling
void toggleIntegerScale();
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 ---
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)
// --- Variables de estado ---
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 src_rect_; // Coordenadas de origen 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
FPS fps_; // Gestión de frames por segundo
FlashEffect flash_effect_; // Efecto de flash en pantalla
ShakeEffect shake_effect_; // Efecto de agitar la pantalla
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
DisplayMonitor display_monitor_; // Información del monitor actual
FPS fps_; // Gestión de frames por segundo
FlashEffect flash_effect_; // Efecto de flash en pantalla
ShakeEffect shake_effect_; // Efecto de agitar la pantalla
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
DisplayMonitor display_monitor_; // Información del monitor actual
#ifdef _DEBUG
Debug debug_info_; // Información de debug
#endif
// --- Métodos internos ---
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void renderFlash(); // Dibuja el efecto de flash en la pantalla
void renderShake(); // Aplica el efecto de agitar la pantalla
void renderInfo() const; // Muestra información por pantalla
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
void adjustWindowSize(); // Calcula el tamaño de la ventana
void getDisplayInfo(); // Obtiene información sobre la pantalla
void renderOverlays(); // Renderiza todos los overlays y efectos
void renderAttenuate(); // Atenúa la pantalla
void createText(); // Crea el objeto de texto
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void renderFlash(); // Dibuja el efecto de flash en la pantalla
void renderShake(); // Aplica el efecto de agitar la pantalla
void renderInfo() const; // Muestra información por pantalla
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
void adjustWindowSize(); // Calcula el tamaño de la ventana
void getDisplayInfo(); // Obtiene información sobre la pantalla
void renderOverlays(); // Renderiza todos los overlays y efectos
void renderAttenuate(); // Atenúa la pantalla
void createText(); // Crea el objeto de texto
// --- Constructores y destructor privados (singleton) ---
Screen(); // Constructor privado

View File

@@ -93,15 +93,15 @@ class Game {
// --- Estructuras ---
struct Helper {
bool need_coffee{false}; // Indica si se necesitan cafes
bool need_coffee_machine{false}; // Indica si se necesita PowerUp
bool need_power_ball{false}; // Indica si se necesita una PowerBall
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_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_clock_odds{ITEM_CLOCK_ODDS}; // Probabilidad de aparición del objeto
int item_coffee_odds{ITEM_COFFEE_ODDS}; // Probabilidad de aparición del objeto
bool need_coffee{false}; // Indica si se necesitan cafes
bool need_coffee_machine{false}; // Indica si se necesita PowerUp
bool need_power_ball{false}; // Indica si se necesita una PowerBall
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_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_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_machine_odds{ITEM_COFFEE_MACHINE_ODDS}; // Probabilidad de aparición del objeto
};

View File

@@ -71,15 +71,15 @@ class Instructions {
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
// --- Variables ---
float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos)
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos)
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
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
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
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)
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
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
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)
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
// --- Métodos internos ---
void update(float delta_time); // Actualiza las variables

View File

@@ -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 <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream
#include <utility> // Para std::cmp_less_equal
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream
#include <iostream> // Para cerr
#include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error
#include <string_view> // Para string_view
#include <utility> // Para std::cmp_less_equal
#include <vector> // Para vector
#include "color.hpp" // Para Color

View File

@@ -366,19 +366,20 @@ void ServiceMenu::initializeOptions() {
options_.push_back(std::make_unique<BoolOption>(
Lang::getText("[SERVICE_MENU] POSTFX"),
SettingsGroup::VIDEO,
&Options::video.postfx));
&Options::video.shader.enabled));
options_.push_back(std::make_unique<IntOption>(
Lang::getText("[SERVICE_MENU] POSTFX_PRESET"),
SettingsGroup::VIDEO,
&Options::current_postfx_preset,
0, static_cast<int>(Options::postfx_presets.size()) - 1, 1));
&Options::video.shader.current_postfx_preset,
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"),
SettingsGroup::VIDEO,
&Options::video.supersampling,
1, 3, 1));
&Options::video.supersampling.enabled));
options_.push_back(std::make_unique<BoolOption>(
Lang::getText("[SERVICE_MENU] VSYNC"),

View File

@@ -20,6 +20,9 @@ echo "Compiling SPIR-V shaders..."
glslc "${SHADERS_DIR}/postfx.vert" -o /tmp/postfx.vert.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..."
@@ -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/' \
> "${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
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"
done
echo "Done. Headers updated in ${HEADERS_DIR}/"
echo " postfx_vert_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."