From 6996b3a82a0fdf8fc40d54503fb800fb7c7e7d2c Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 21 Mar 2026 13:57:18 +0100 Subject: [PATCH] presets en postfx --- config/assets.yaml | 4 ++ source/core/input/global_inputs.cpp | 29 ++++++-- source/core/input/input.cpp | 2 +- source/core/input/input_types.cpp | 6 +- source/core/input/input_types.hpp | 3 +- source/core/rendering/screen.cpp | 66 ++++++++++++------ source/core/rendering/screen.hpp | 5 +- source/core/system/director.cpp | 4 ++ source/game/defaults.hpp | 2 +- source/game/options.cpp | 101 ++++++++++++++++++++++++++-- source/game/options.hpp | 26 +++++-- 11 files changed, 206 insertions(+), 42 deletions(-) diff --git a/config/assets.yaml b/config/assets.yaml index 04541bc..55d3a8e 100644 --- a/config/assets.yaml +++ b/config/assets.yaml @@ -90,6 +90,10 @@ assets: path: ${SYSTEM_FOLDER}/cheevos.bin required: false absolute: true + - type: DATA + path: ${SYSTEM_FOLDER}/postfx.yaml + required: false + absolute: true # ROOMS rooms: diff --git a/source/core/input/global_inputs.cpp b/source/core/input/global_inputs.cpp index 2a3a601..2d5b510 100644 --- a/source/core/input/global_inputs.cpp +++ b/source/core/input/global_inputs.cpp @@ -91,9 +91,17 @@ void handleIncWindowZoom() { } } -void handleToggleShaders() { - Screen::get()->toggleShaders(); - Notifier::get()->show({"SHADERS " + std::string(Options::video.shaders ? "ENABLED" : "DISABLED")}); +void handleTogglePostFX() { + Screen::get()->togglePostFX(); + Notifier::get()->show({"POSTFX " + std::string(Options::video.postfx ? "ENABLED" : "DISABLED")}); +} + +void handleNextPostFXPreset() { + if (!Options::postfx_presets.empty()) { + Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast(Options::postfx_presets.size()); + Screen::get()->reloadPostFX(); + Notifier::get()->show({"POSTFX " + Options::postfx_presets[static_cast(Options::current_postfx_preset)].name}); + } } void handleNextPalette() { @@ -152,8 +160,11 @@ auto getPressedAction() -> InputAction { return InputAction::WINDOW_INC_ZOOM; } } - if (Input::get()->checkAction(InputAction::TOGGLE_SHADERS, Input::DO_NOT_ALLOW_REPEAT)) { - return InputAction::TOGGLE_SHADERS; + if (Input::get()->checkAction(InputAction::TOGGLE_POSTFX, Input::DO_NOT_ALLOW_REPEAT)) { + if (Options::video.postfx && (SDL_GetModState() & SDL_KMOD_SHIFT)) { + return InputAction::NEXT_POSTFX_PRESET; + } + return InputAction::TOGGLE_POSTFX; } if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) { return InputAction::NEXT_PALETTE; @@ -221,8 +232,12 @@ void handle() { handleIncWindowZoom(); break; - case InputAction::TOGGLE_SHADERS: - handleToggleShaders(); + case InputAction::TOGGLE_POSTFX: + handleTogglePostFX(); + break; + + case InputAction::NEXT_POSTFX_PRESET: + handleNextPostFXPreset(); break; case InputAction::NEXT_PALETTE: diff --git a/source/core/input/input.cpp b/source/core/input/input.cpp index 2d05162..60dbe77 100644 --- a/source/core/input/input.cpp +++ b/source/core/input/input.cpp @@ -43,7 +43,7 @@ Input::Input(std::string game_controller_db_path) {Action::WINDOW_DEC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F1}}, {Action::WINDOW_INC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F2}}, {Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}}, - {Action::TOGGLE_SHADERS, KeyState{.scancode = SDL_SCANCODE_F4}}, + {Action::TOGGLE_POSTFX, KeyState{.scancode = SDL_SCANCODE_F4}}, {Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}}, {Action::PREVIOUS_PALETTE, KeyState{.scancode = SDL_SCANCODE_F6}}, {Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}}, diff --git a/source/core/input/input_types.cpp b/source/core/input/input_types.cpp index 563bfe4..9f1297b 100644 --- a/source/core/input/input_types.cpp +++ b/source/core/input/input_types.cpp @@ -20,7 +20,8 @@ const std::unordered_map ACTION_TO_STRING = { {InputAction::TOGGLE_MUSIC, "TOGGLE_MUSIC"}, {InputAction::NEXT_PALETTE, "NEXT_PALETTE"}, {InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"}, - {InputAction::TOGGLE_SHADERS, "TOGGLE_SHADERS"}, + {InputAction::TOGGLE_POSTFX, "TOGGLE_POSTFX"}, + {InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"}, {InputAction::SHOW_DEBUG_INFO, "SHOW_DEBUG_INFO"}, {InputAction::TOGGLE_DEBUG, "TOGGLE_DEBUG"}, {InputAction::NONE, "NONE"}}; @@ -42,7 +43,8 @@ const std::unordered_map STRING_TO_ACTION = { {"TOGGLE_MUSIC", InputAction::TOGGLE_MUSIC}, {"NEXT_PALETTE", InputAction::NEXT_PALETTE}, {"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE}, - {"TOGGLE_SHADERS", InputAction::TOGGLE_SHADERS}, + {"TOGGLE_POSTFX", InputAction::TOGGLE_POSTFX}, + {"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET}, {"SHOW_DEBUG_INFO", InputAction::SHOW_DEBUG_INFO}, {"TOGGLE_DEBUG", InputAction::TOGGLE_DEBUG}, {"NONE", InputAction::NONE}}; diff --git a/source/core/input/input_types.hpp b/source/core/input/input_types.hpp index b1fc3f4..8414186 100644 --- a/source/core/input/input_types.hpp +++ b/source/core/input/input_types.hpp @@ -24,7 +24,8 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego TOGGLE_FULLSCREEN, TOGGLE_VSYNC, TOGGLE_INTEGER_SCALE, - TOGGLE_SHADERS, + TOGGLE_POSTFX, + NEXT_POSTFX_PRESET, TOGGLE_BORDER, TOGGLE_MUSIC, NEXT_PALETTE, diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 58010bc..0bc7c36 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -132,7 +132,7 @@ void Screen::render() { // En el path SDL3GPU, los píxeles se suben directamente desde la Surface. // En el path SDL_Renderer, primero copiamos la surface a la SDL_Texture. - if (!(Options::video.shaders && shader_backend_ && shader_backend_->isHardwareAccelerated())) { + if (!(Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated())) { surfaceToTexture(); } @@ -209,17 +209,26 @@ void Screen::renderNotifications() const { } } -// Cambia el estado de los shaders -void Screen::toggleShaders() { - Options::video.shaders = !Options::video.shaders; - if (!Options::video.shaders && shader_backend_) { - // Al desactivar shaders, limpiar el backend para liberar el swapchain de GPU +// Cambia el estado del PostFX +void Screen::togglePostFX() { + Options::video.postfx = !Options::video.postfx; + if (!Options::video.postfx && shader_backend_) { + // Al desactivar PostFX, limpiar el backend para liberar el swapchain de GPU shader_backend_->cleanup(); } else { initShaders(); } } +// Recarga el shader del preset actual sin toggle +void Screen::reloadPostFX() { + if (Options::video.postfx) { + vertex_shader_source_.clear(); + fragment_shader_source_.clear(); + initShaders(); + } +} + // Actualiza la lógica de la clase (versión nueva con delta_time para escenas migradas) void Screen::update(float delta_time) { fps_.calculate(SDL_GetTicks()); @@ -319,7 +328,7 @@ void Screen::surfaceToTexture() { void Screen::textureToRenderer() { SDL_Texture* texture_to_render = Options::video.border.enabled ? border_texture_ : game_texture_; - if (Options::video.shaders && shader_backend_ && shader_backend_->isHardwareAccelerated()) { + if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) { // ---- SDL3 GPU path: convertir Surface → ARGB → upload → PostFX → present ---- if (Options::video.border.enabled) { // El border_surface_ solo tiene el color de borde; hay que componer encima el game_surface_ @@ -439,16 +448,32 @@ auto loadData(const std::string& filepath) -> std::vector { // Carga el contenido de los archivos GLSL void Screen::loadShaders() { + // Obtener nombres de fichero desde el preset actual (o usar fallback) + std::string preset_vertex = "crtpi_vertex.glsl"; + std::string preset_fragment = "crtpi_fragment.glsl"; + if (!Options::postfx_presets.empty()) { + const auto& preset = Options::postfx_presets[static_cast(Options::current_postfx_preset)]; + if (!preset.vertex.empty()) { + preset_vertex = preset.vertex; + } + if (!preset.fragment.empty()) { + preset_fragment = preset.fragment; + } + } + if (vertex_shader_source_.empty()) { // Detectar si necesitamos OpenGL ES (Raspberry Pi) - // Intentar cargar versión ES primero si existe - std::string vertex_file = "crtpi_vertex_es.glsl"; - auto data = loadData(Resource::List::get()->get(vertex_file)); + // Intentar cargar versión ES primero si existe (reemplaza .glsl por _es.glsl) + std::string vertex_es = preset_vertex; + auto pos = vertex_es.rfind(".glsl"); + if (pos != std::string::npos) { + vertex_es.insert(pos, "_es"); + } + auto data = loadData(Resource::List::get()->get(vertex_es)); if (data.empty()) { // Si no existe versión ES, usar versión Desktop - vertex_file = "crtpi_vertex.glsl"; - data = loadData(Resource::List::get()->get(vertex_file)); + data = loadData(Resource::List::get()->get(preset_vertex)); std::cout << "Usando shaders OpenGL Desktop 3.3\n"; } else { std::cout << "Usando shaders OpenGL ES 3.0 (Raspberry Pi)\n"; @@ -460,13 +485,16 @@ void Screen::loadShaders() { } if (fragment_shader_source_.empty()) { // Intentar cargar versión ES primero si existe - std::string fragment_file = "crtpi_fragment_es.glsl"; - auto data = loadData(Resource::List::get()->get(fragment_file)); + std::string fragment_es = preset_fragment; + auto pos = fragment_es.rfind(".glsl"); + if (pos != std::string::npos) { + fragment_es.insert(pos, "_es"); + } + auto data = loadData(Resource::List::get()->get(fragment_es)); if (data.empty()) { // Si no existe versión ES, usar versión Desktop - fragment_file = "crtpi_fragment.glsl"; - data = loadData(Resource::List::get()->get(fragment_file)); + data = loadData(Resource::List::get()->get(preset_fragment)); } if (!data.empty()) { @@ -477,7 +505,7 @@ void Screen::loadShaders() { // Inicializa los shaders void Screen::initShaders() { - if (!Options::video.shaders) { + if (!Options::video.postfx) { return; } @@ -619,8 +647,8 @@ auto Screen::initSDLVideo() -> bool { return false; } // Sin OpenGL garantizado, deshabilitar shaders - Options::video.shaders = false; - std::cout << "WARNING: Shaders disabled (OpenGL not available)\n"; + Options::video.postfx = false; + std::cout << "WARNING: PostFX disabled (OpenGL not available)\n"; } // Configurar renderer diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index b9468fb..79dbd03 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -53,11 +53,12 @@ class Screen { static void setBorderEnabled(bool value); // Establece si se ha de ver el borde void toggleBorder(); // Cambia entre borde visible y no visible - // Paletas y shaders + // Paletas y PostFX void nextPalette(); // Cambia a la siguiente paleta void previousPalette(); // Cambia a la paleta anterior void setPalete(); // Establece la paleta actual - void toggleShaders(); // Cambia el estado de los shaders + void togglePostFX(); // Cambia el estado del PostFX + void reloadPostFX(); // Recarga el shader del preset actual sin toggle // Surfaces y notificaciones void setRendererSurface(const std::shared_ptr& surface = nullptr); // Establece el renderizador para las surfaces diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 78a9325..7dfac50 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -122,6 +122,10 @@ Director::Director(std::vector const& args) { Options::setConfigFile(Resource::List::get()->get("config.yaml")); Options::loadFromFile(); + // Configura la ruta y carga los presets de PostFX + Options::setPostFXFile(Resource::List::get()->get("postfx.yaml")); + Options::loadPostFXFromFile(); + // En mode quiosc, forçar pantalla completa independentment de la configuració if (Options::kiosk.enabled) { Options::video.fullscreen = true; diff --git a/source/game/defaults.hpp b/source/game/defaults.hpp index 70e0a1c..911d45f 100644 --- a/source/game/defaults.hpp +++ b/source/game/defaults.hpp @@ -31,7 +31,7 @@ namespace Video { constexpr bool FULLSCREEN = false; // Modo de pantalla completa por defecto (false = ventana) constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro por defecto constexpr bool VERTICAL_SYNC = true; // Vsync activado por defecto -constexpr bool SHADERS = false; // Shaders desactivados por defecto +constexpr bool POSTFX = false; // PostFX desactivado por defecto constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto diff --git a/source/game/options.cpp b/source/game/options.cpp index f05eaf5..9d44bc2 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -329,11 +329,11 @@ void loadBasicVideoFieldsFromYaml(const fkyaml::node& vid) { } } - if (vid.contains("shaders")) { + if (vid.contains("postfx")) { try { - video.shaders = vid["shaders"].get_value(); + video.postfx = vid["postfx"].get_value(); } catch (...) { - video.shaders = Defaults::Video::SHADERS; + video.postfx = Defaults::Video::POSTFX; } } @@ -606,7 +606,7 @@ auto saveToFile() -> bool { file << "video:\n"; file << " fullscreen: " << (video.fullscreen ? "true" : "false") << "\n"; file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n"; - file << " shaders: " << (video.shaders ? "true" : "false") << "\n"; + file << " postfx: " << (video.postfx ? "true" : "false") << "\n"; file << " vertical_sync: " << (video.vertical_sync ? "true" : "false") << "\n"; file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n"; file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n"; @@ -649,4 +649,97 @@ auto saveToFile() -> bool { return true; } +// Establece la ruta del fichero de PostFX +void setPostFXFile(const std::string& path) { + postfx_file_path = path; +} + +// Carga los presets de PostFX desde el fichero +auto loadPostFXFromFile() -> bool { + postfx_presets.clear(); + current_postfx_preset = 0; + + std::ifstream file(postfx_file_path); + if (!file.good()) { + if (console) { + std::cout << "PostFX file not found, creating default: " << postfx_file_path << '\n'; + } + return savePostFXToFile(); + } + + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); + + try { + auto yaml = fkyaml::node::deserialize(content); + + if (yaml.contains("presets")) { + const auto& presets = yaml["presets"]; + for (size_t i = 0; i < presets.size(); ++i) { + const auto& p = presets[i]; + PostFXPreset preset; + if (p.contains("name")) { + preset.name = p["name"].get_value(); + } + if (p.contains("vertex")) { + preset.vertex = p["vertex"].get_value(); + } + if (p.contains("fragment")) { + preset.fragment = p["fragment"].get_value(); + } + postfx_presets.push_back(preset); + } + } + + if (console) { + std::cout << "PostFX file loaded: " << postfx_presets.size() << " preset(s)\n"; + } + + return true; + + } catch (const fkyaml::exception& e) { + if (console) { + std::cerr << "Error parsing PostFX YAML: " << e.what() << '\n'; + } + return savePostFXToFile(); + } +} + +// Guarda los presets de PostFX por defecto +auto savePostFXToFile() -> bool { + if (postfx_file_path.empty()) { + return false; + } + + std::ofstream file(postfx_file_path); + if (!file.is_open()) { + if (console) { + std::cerr << "Error: Unable to open file " << postfx_file_path << " for writing\n"; + } + return false; + } + + file << "# JailDoctor's Dilemma - PostFX Presets\n"; + file << "# Add or modify presets to customize post-processing effects.\n"; + file << "# vertex and fragment reference shader filenames from the shaders directory.\n"; + file << "\n"; + file << "presets:\n"; + file << " - name: \"CRT\"\n"; + file << " vertex: \"crtpi_vertex.glsl\"\n"; + file << " fragment: \"crtpi_fragment.glsl\"\n"; + + file.close(); + + if (console) { + std::cout << "PostFX file created with defaults: " << postfx_file_path << '\n'; + } + + // Cargar los presets recién creados + postfx_presets.clear(); + postfx_presets.push_back({"CRT", "crtpi_vertex.glsl", "crtpi_fragment.glsl"}); + current_postfx_preset = 0; + + return true; +} + } // namespace Options diff --git a/source/game/options.hpp b/source/game/options.hpp index 2e67b59..42edfff 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -5,6 +5,7 @@ #include #include // Para string, basic_string #include +#include // Para vector #include "core/rendering/screen.hpp" // Para Screen::Filter #include "game/defaults.hpp" @@ -79,7 +80,7 @@ struct Video { bool fullscreen{Defaults::Video::FULLSCREEN}; // Contiene el valor del modo de pantalla completa Screen::Filter filter{Defaults::Video::FILTER}; // Filtro usado para el escalado de la imagen bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no - bool shaders{Defaults::Video::SHADERS}; // Indica si se van a usar shaders o no + bool postfx{Defaults::Video::POSTFX}; // Indica si se van a usar efectos PostFX o no bool integer_scale{Defaults::Video::INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa bool keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa Border border{}; // Borde de la pantalla @@ -113,6 +114,13 @@ struct Game { float height{Defaults::Canvas::HEIGHT}; // Alto de la resolucion del juego }; +// Estructura para un preset de PostFX +struct PostFXPreset { + std::string name; // Nombre del preset + std::string vertex; // Nombre del fichero vertex shader + std::string fragment; // Nombre del fichero fragment shader +}; + // --- Variables globales --- inline std::string version{}; // Versión del fichero de configuración. Sirve para saber si las opciones son compatibles inline bool console{false}; // Indica si ha de mostrar información por la consola de texto @@ -129,10 +137,18 @@ inline Kiosk kiosk{}; // Opciones del modo kiosko // Ruta completa del fichero de configuración (establecida mediante setConfigFile) inline std::string config_file_path{}; +// --- Variables PostFX --- +inline std::vector postfx_presets{}; // Lista de presets de PostFX +inline int current_postfx_preset{0}; // Índice del preset de PostFX actual +inline std::string postfx_file_path{}; // Ruta del fichero postfx.yaml + // --- Funciones públicas --- -void init(); // Crea e inicializa las opciones del programa -void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración -auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado -auto saveToFile() -> bool; // Guarda las opciones al fichero configurado +void init(); // Crea e inicializa las opciones del programa +void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración +auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado +auto saveToFile() -> bool; // Guarda las opciones al fichero configurado +void setPostFXFile(const std::string& path); // Establece la ruta del fichero de PostFX +auto loadPostFXFromFile() -> bool; // Carga los presets de PostFX desde el fichero +auto savePostFXToFile() -> bool; // Guarda los presets de PostFX por defecto } // namespace Options \ No newline at end of file