From 26b4d9e6102e37ca363540872fbbe31bff988ace Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 18 Apr 2026 23:02:06 +0200 Subject: [PATCH] Presets PostFX carregats de shaders/postfx.yaml; Screen aplica params (v0.5.0) --- source/core/rendering/screen.cpp | 6 ++ source/core/rendering/screen.hpp | 4 + source/core/system/director.cpp | 11 +++ source/game/options.cpp | 140 ++++++++++++++++++++++++++++++- source/game/options.hpp | 28 +++++++ source/utils/defaults.hpp | 7 +- source/utils/defines.hpp | 2 +- 7 files changed, 194 insertions(+), 4 deletions(-) diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index db8b093..6108f3b 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -136,6 +136,12 @@ void Screen::createSdl() { SDL_SetTextureScaleMode(game_texture_, toSdlScaleMode(config_.texture_filter)); } +void Screen::applyPostFXParams(const Rendering::PostFXParams& params) { + if (shader_backend_ != nullptr) { + shader_backend_->setPostFXParams(params); + } +} + void Screen::clear(std::uint8_t r, std::uint8_t g, std::uint8_t b) { const std::uint32_t COLOR = (0xFFU << 24) | (static_cast(r) << 16) | (static_cast(g) << 8) | static_cast(b); std::ranges::fill(canvas_, COLOR); diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index 3c4fe5e..09bd061 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -44,6 +44,10 @@ class Screen { // i tanca el subsistema de vídeo. El Director el crida ABANS de SDL_Quit. void shutdown(); + // Reenvia paràmetres PostFX al backend GPU. No-op si estem al path + // SDL_Renderer (fallback) o si encara no hi ha backend. + void applyPostFXParams(const Rendering::PostFXParams& params); + void clear(std::uint8_t r = 0, std::uint8_t g = 0, std::uint8_t b = 0); void present(); diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 7241f10..859c786 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -16,6 +16,7 @@ #include "core/system/event_buffer.hpp" #include "game/options.hpp" #include "game/scenes/game_scene.hpp" +#include "utils/defaults.hpp" Director::Director() { if (!SDL_Init(0)) { @@ -30,9 +31,17 @@ Director::Director() { std::cerr << "Director: no s'ha pogut carregar ni desar config.yaml a " << system_folder_ << '\n'; } + // Carrega els presets de PostFX des de shaders/postfx.yaml; si no existeix + // es crea amb presets per defecte (off, crt, ntsc). + Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE); + if (!Options::loadPostFXFromFile()) { + std::cerr << "Director: no s'ha pogut carregar ni desar postfx.yaml.\n"; + } + // Screen es configura ara (subsystem + finestra + GPU/renderer); la seua // destrucció la fa aquest Director (veure ~Director). Res de static dtor. Screen::get().configure(Options::buildScreenConfig()); + Screen::get().applyPostFXParams(Options::buildPostFXParams()); (void)Resource::Cache::get(); current_scene_ = std::make_unique(); @@ -65,6 +74,8 @@ void Director::createSystemFolder() { std::error_code error; std::filesystem::create_directories(system_folder_, error); + // Subcarpeta per als presets de shaders. std::filesystem és tolerant si ja existeix. + std::filesystem::create_directories(system_folder_ + "shaders/", error); if (error) { std::cerr << "Director: no s'ha pogut crear " << system_folder_ << ": " << error.message() << '\n'; } diff --git a/source/game/options.cpp b/source/game/options.cpp index 47d6c82..1f6ea12 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -1,5 +1,6 @@ #include "game/options.hpp" +#include #include #include #include @@ -71,6 +72,15 @@ namespace { if (node.contains("preferred_driver")) { Options::video.gpu.preferred_driver = node["preferred_driver"].get_value(); } } + void parseShader(const fkyaml::node& video_node) { + if (!video_node.contains("shader")) { return; } + const auto& node = video_node["shader"]; + if (node.contains("enabled")) { Options::video.shader.enabled = node["enabled"].get_value(); } + if (node.contains("current_postfx_preset")) { + Options::video.shader.current_postfx_preset = node["current_postfx_preset"].get_value(); + } + } + void parseVideo(const fkyaml::node& yaml) { if (!yaml.contains("video")) { return; } const auto& node = yaml["video"]; @@ -82,6 +92,7 @@ namespace { Options::video.texture_filter = filterFromString(node["texture_filter"].get_value()); } parseGpu(node); + parseShader(node); } void parseMusic(const fkyaml::node& audio_node) { @@ -107,6 +118,55 @@ namespace { parseSound(node); } + // Presets per defecte de PostFX, usats quan el fitxer postfx.yaml no existeix. + auto defaultPostFXPresets() -> std::vector { + return { + {.name = "off"}, // passthrough (tot a 0) + {.name = "crt", + .vignette = 0.6F, + .scanlines = 0.7F, + .chroma = 0.15F, + .mask = 0.6F, + .gamma = 0.8F, + .curvature = 0.0F, + .bleeding = 0.0F, + .flicker = 0.0F}, + {.name = "ntsc", + .vignette = 0.4F, + .scanlines = 0.5F, + .chroma = 0.25F, + .mask = 0.3F, + .gamma = 0.6F, + .curvature = 0.0F, + .bleeding = 0.4F, + .flicker = 0.1F}, + }; + } + + void parsePostFXPreset(const fkyaml::node& node, Options::PostFXPreset& preset) { + if (node.contains("name")) { preset.name = node["name"].get_value(); } + if (node.contains("vignette")) { preset.vignette = node["vignette"].get_value(); } + if (node.contains("scanlines")) { preset.scanlines = node["scanlines"].get_value(); } + if (node.contains("chroma")) { preset.chroma = node["chroma"].get_value(); } + if (node.contains("mask")) { preset.mask = node["mask"].get_value(); } + if (node.contains("gamma")) { preset.gamma = node["gamma"].get_value(); } + if (node.contains("curvature")) { preset.curvature = node["curvature"].get_value(); } + if (node.contains("bleeding")) { preset.bleeding = node["bleeding"].get_value(); } + if (node.contains("flicker")) { preset.flicker = node["flicker"].get_value(); } + } + + void writePostFXPreset(std::ostringstream& out, const Options::PostFXPreset& preset) { + out << " - name: \"" << preset.name << "\"\n"; + out << " vignette: " << preset.vignette << "\n"; + out << " scanlines: " << preset.scanlines << "\n"; + out << " chroma: " << preset.chroma << "\n"; + out << " mask: " << preset.mask << "\n"; + out << " gamma: " << preset.gamma << "\n"; + out << " curvature: " << preset.curvature << "\n"; + out << " bleeding: " << preset.bleeding << "\n"; + out << " flicker: " << preset.flicker << "\n"; + } + } // namespace namespace Options { @@ -166,7 +226,10 @@ namespace Options { out << " texture_filter: \"" << filterToString(video.texture_filter) << "\"\n"; out << " gpu:\n"; out << " acceleration: " << (video.gpu.acceleration ? "true" : "false") << "\n"; - out << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\n\n"; + out << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\n"; + out << " shader:\n"; + out << " enabled: " << (video.shader.enabled ? "true" : "false") << "\n"; + out << " current_postfx_preset: \"" << video.shader.current_postfx_preset << "\"\n\n"; out << "audio:\n"; out << " enabled: " << (audio.enabled ? "true" : "false") << "\n"; @@ -193,4 +256,79 @@ namespace Options { return config; } + void setPostFXFile(const std::string& path) { + postfx_file_path = path; + } + + auto loadPostFXFromFile() -> bool { + if (postfx_file_path.empty()) { return false; } + + const std::string CONTENT = readFileOrEmpty(postfx_file_path); + if (CONTENT.empty()) { + postfx_presets = defaultPostFXPresets(); + return savePostFXToFile(); + } + + try { + auto yaml = fkyaml::node::deserialize(CONTENT); + if (!yaml.contains("presets") || !yaml["presets"].is_sequence()) { + postfx_presets = defaultPostFXPresets(); + return savePostFXToFile(); + } + + postfx_presets.clear(); + for (const auto& preset_node : yaml["presets"]) { + PostFXPreset preset; + parsePostFXPreset(preset_node, preset); + postfx_presets.push_back(preset); + } + + if (postfx_presets.empty()) { + postfx_presets = defaultPostFXPresets(); + return savePostFXToFile(); + } + return true; + } catch (const std::exception& e) { + std::cerr << "Options: error parsejant postfx.yaml: " << e.what() << '\n'; + std::cerr << "Options: reescrivint el fitxer amb presets per defecte.\n"; + postfx_presets = defaultPostFXPresets(); + return savePostFXToFile(); + } + } + + auto savePostFXToFile() -> bool { + if (postfx_file_path.empty()) { return false; } + + std::ostringstream out; + out << "# Presets PostFX per a AEE 2026. Editable a mà.\n"; + out << "# Els noms han de coincidir amb config.yaml > video.shader.current_postfx_preset.\n"; + out << "presets:\n"; + for (const auto& preset : postfx_presets) { + writePostFXPreset(out, preset); + } + return writeFile(postfx_file_path, out.str()); + } + + auto buildPostFXParams() -> Rendering::PostFXParams { + // Si el shader està deshabilitat, retornem params a zero → passthrough. + if (!video.shader.enabled) { return {}; } + + const auto IT = std::ranges::find_if(postfx_presets, + [](const PostFXPreset& preset) { return preset.name == video.shader.current_postfx_preset; }); + if (IT == postfx_presets.end()) { + std::cerr << "Options: preset PostFX \"" << video.shader.current_postfx_preset << "\" no trobat; aplicant passthrough.\n"; + return {}; + } + return { + .vignette = IT->vignette, + .scanlines = IT->scanlines, + .chroma = IT->chroma, + .mask = IT->mask, + .gamma = IT->gamma, + .curvature = IT->curvature, + .bleeding = IT->bleeding, + .flicker = IT->flicker, + }; + } + } // namespace Options diff --git a/source/game/options.hpp b/source/game/options.hpp index 39d3f4e..187bc21 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -1,8 +1,10 @@ #pragma once #include +#include #include "core/rendering/screen.hpp" +#include "core/rendering/shader_backend.hpp" #include "utils/defaults.hpp" // Gestor de configuració del joc. És l'ÚNIC lloc que coneix YAML i la ruta @@ -21,11 +23,30 @@ namespace Options { std::string preferred_driver{Defaults::Video::GPU_PREFERRED_DRIVER}; }; + struct Shader { + bool enabled{Defaults::Video::SHADER_ENABLED}; + std::string current_postfx_preset{Defaults::Video::POSTFX_DEFAULT_PRESET}; + }; + struct Video { bool vsync{Defaults::Video::VSYNC}; Defaults::ScalingMode scaling_mode{Defaults::Video::SCALING_MODE}; Defaults::TextureFilter texture_filter{Defaults::Video::TEXTURE_FILTER}; Gpu gpu{}; + Shader shader{}; + }; + + // Preset d'efectes PostFX (un per entrada a postfx.yaml). + struct PostFXPreset { + std::string name; + float vignette{0.0F}; + float scanlines{0.0F}; + float chroma{0.0F}; + float mask{0.0F}; + float gamma{0.0F}; + float curvature{0.0F}; + float bleeding{0.0F}; + float flicker{0.0F}; }; struct Music { @@ -49,16 +70,23 @@ namespace Options { // el programa sense necessitar un .cpp extra per a cada una) --- inline std::string config_version{}; inline std::string config_file_path{}; + inline std::string postfx_file_path{}; inline Window window{}; inline Video video{}; inline Audio audio{}; + inline std::vector postfx_presets{}; // --- API pública --- void setConfigFile(const std::string& path); auto loadFromFile() -> bool; auto saveToFile() -> bool; + void setPostFXFile(const std::string& path); + auto loadPostFXFromFile() -> bool; + auto savePostFXToFile() -> bool; + // --- Builders per als subsistemes --- [[nodiscard]] auto buildScreenConfig() -> Screen::Config; + [[nodiscard]] auto buildPostFXParams() -> Rendering::PostFXParams; } // namespace Options diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index 7be7804..e641ca6 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -32,8 +32,11 @@ namespace Defaults { constexpr bool VSYNC = true; constexpr ScalingMode SCALING_MODE = ScalingMode::LETTERBOX; constexpr TextureFilter TEXTURE_FILTER = TextureFilter::NEAREST; - constexpr bool GPU_ACCELERATION = true; // Path SDL3 GPU per defecte; fallback a SDL_Renderer si falla. - constexpr const char* GPU_PREFERRED_DRIVER = ""; // Buit = auto. Aplica a l'arrencada. + constexpr bool GPU_ACCELERATION = true; // Path SDL3 GPU per defecte; fallback a SDL_Renderer si falla. + constexpr const char* GPU_PREFERRED_DRIVER = ""; // Buit = auto. Aplica a l'arrencada. + constexpr bool SHADER_ENABLED = true; // Aplica PostFX; si false, sempre passthrough. + constexpr const char* POSTFX_DEFAULT_PRESET = "off"; // Preset inicial a config.yaml. + constexpr const char* POSTFX_FILE = "shaders/postfx.yaml"; // Relatiu a la carpeta de sistema. } // namespace Video namespace Audio { diff --git a/source/utils/defines.hpp b/source/utils/defines.hpp index ddb003a..8cab53d 100644 --- a/source/utils/defines.hpp +++ b/source/utils/defines.hpp @@ -3,7 +3,7 @@ namespace Texts { constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte — JailDesigner"; constexpr const char* COPYRIGHT = "© 2026 JailDesigner"; - constexpr const char* VERSION = "0.4.0"; // Single source of truth — Makefile el grep + constexpr const char* VERSION = "0.5.0"; // Single source of truth — Makefile el grep } // namespace Texts namespace GameCanvas {