From 1ecb4271066b2d4cd323fd0e24f4ccda311de506 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 22 Mar 2026 21:55:18 +0100 Subject: [PATCH] supersampling implementat --- data/locale/ca.yaml | 2 + data/locale/en.yaml | 2 + source/core/input/global_inputs.cpp | 18 +++++++-- source/core/input/input_types.hpp | 1 + source/core/rendering/screen.cpp | 17 ++++++-- source/core/rendering/screen.hpp | 5 ++- source/core/rendering/shader_backend.hpp | 1 - source/game/defaults.hpp | 1 + source/game/options.cpp | 50 ++++++++++++++---------- source/game/options.hpp | 2 +- 10 files changed, 67 insertions(+), 32 deletions(-) diff --git a/data/locale/ca.yaml b/data/locale/ca.yaml index ac8d34e..0f7eb19 100644 --- a/data/locale/ca.yaml +++ b/data/locale/ca.yaml @@ -111,6 +111,8 @@ ui: postfx_enabled: "POSTFX ACTIVAT" postfx_disabled: "POSTFX DESACTIVAT" postfx: "POSTFX" + supersampling_enabled: "SUPERMOSTREIG ACTIVAT" + supersampling_disabled: "SUPERMOSTREIG DESACTIVAT" palette: "PALETA" integer_scale_enabled: "ESCALAT SENCER ACTIVAT" integer_scale_disabled: "ESCALAT SENCER DESACTIVAT" diff --git a/data/locale/en.yaml b/data/locale/en.yaml index 5b56f3d..843393f 100644 --- a/data/locale/en.yaml +++ b/data/locale/en.yaml @@ -111,6 +111,8 @@ ui: postfx_enabled: "POSTFX ENABLED" postfx_disabled: "POSTFX DISABLED" postfx: "POSTFX" + supersampling_enabled: "SUPERSAMPLING ON" + supersampling_disabled: "SUPERSAMPLING OFF" palette: "PALETTE" integer_scale_enabled: "INTEGER SCALE ENABLED" integer_scale_disabled: "INTEGER SCALE DISABLED" diff --git a/source/core/input/global_inputs.cpp b/source/core/input/global_inputs.cpp index 76a06bf..1f47bfc 100644 --- a/source/core/input/global_inputs.cpp +++ b/source/core/input/global_inputs.cpp @@ -93,6 +93,11 @@ namespace GlobalInputs { Notifier::get()->show({Locale::get()->get(Options::video.postfx ? "ui.postfx_enabled" : "ui.postfx_disabled")}); } + void handleToggleSupersampling() { + Screen::get()->toggleSupersampling(); + Notifier::get()->show({Locale::get()->get(Options::video.supersampling ? "ui.supersampling_enabled" : "ui.supersampling_disabled")}); + } + void handleNextPostFXPreset() { if (!Options::postfx_presets.empty()) { Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast(Options::postfx_presets.size()); @@ -146,10 +151,13 @@ namespace GlobalInputs { } } if (Input::get()->checkAction(InputAction::TOGGLE_POSTFX, Input::DO_NOT_ALLOW_REPEAT)) { - if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) { - return InputAction::NEXT_POSTFX_PRESET; + if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) { + return InputAction::TOGGLE_SUPERSAMPLING; // Ctrl+F4 } - return InputAction::TOGGLE_POSTFX; + if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) { + return InputAction::NEXT_POSTFX_PRESET; // Shift+F4 + } + return InputAction::TOGGLE_POSTFX; // F4 } if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) { return InputAction::NEXT_PALETTE; @@ -225,6 +233,10 @@ namespace GlobalInputs { handleNextPostFXPreset(); break; + case InputAction::TOGGLE_SUPERSAMPLING: + handleToggleSupersampling(); + break; + case InputAction::NEXT_PALETTE: handleNextPalette(); break; diff --git a/source/core/input/input_types.hpp b/source/core/input/input_types.hpp index 8414186..8847cb8 100644 --- a/source/core/input/input_types.hpp +++ b/source/core/input/input_types.hpp @@ -26,6 +26,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego TOGGLE_INTEGER_SCALE, TOGGLE_POSTFX, NEXT_POSTFX_PRESET, + TOGGLE_SUPERSAMPLING, TOGGLE_BORDER, TOGGLE_MUSIC, NEXT_PALETTE, diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 830d9c2..8bd31cb 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -459,14 +459,23 @@ auto loadData(const std::string& filepath) -> std::vector { return Resource::Helper::loadFile(filepath); } +// Activa/desactiva el supersampling global (Ctrl+F4) +void Screen::toggleSupersampling() { + Options::video.supersampling = !Options::video.supersampling; + if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) { + applyCurrentPostFXPreset(); + } +} + // Aplica los parámetros del preset actual al backend de shaders void Screen::applyCurrentPostFXPreset() { if (shader_backend_ && !Options::postfx_presets.empty()) { const auto& p = Options::postfx_presets[static_cast(Options::current_postfx_preset)]; - // setOversample primero: puede recrear texturas y debe conocer el factor - // antes de que setPostFXParams decida si hornear scanlines en CPU o GPU. - shader_backend_->setOversample(p.supersampling ? 3 : 1); - Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding, .supersampling = p.supersampling}; + // Supersampling es un toggle global (Options::video.supersampling), no por preset. + // 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 ? 3 : 1); + Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding}; shader_backend_->setPostFXParams(params); } } diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index 24d95c4..bd1eb74 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -56,8 +56,9 @@ class Screen { void nextPalette(); // Cambia a la siguiente paleta void previousPalette(); // Cambia a la paleta anterior void setPalete(); // Establece la paleta actual - void togglePostFX(); // Cambia el estado del PostFX - void reloadPostFX(); // Recarga el shader del preset actual sin toggle + void togglePostFX(); // Cambia el estado del PostFX + void toggleSupersampling(); // Activa/desactiva el supersampling global + 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/rendering/shader_backend.hpp b/source/core/rendering/shader_backend.hpp index f21b6b4..85c3dae 100644 --- a/source/core/rendering/shader_backend.hpp +++ b/source/core/rendering/shader_backend.hpp @@ -18,7 +18,6 @@ namespace Rendering { float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full) float curvature = 0.0F; // Curvatura barrel CRT float bleeding = 0.0F; // Sangrado de color NTSC - bool supersampling{false}; // Supersampling 3×: scanlines horneadas en CPU + sampler LINEAR }; /** diff --git a/source/game/defaults.hpp b/source/game/defaults.hpp index f0672a0..79d483e 100644 --- a/source/game/defaults.hpp +++ b/source/game/defaults.hpp @@ -26,6 +26,7 @@ namespace Defaults::Video { constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro por defecto constexpr bool VERTICAL_SYNC = true; // Vsync activado por defecto constexpr bool POSTFX = false; // PostFX desactivado por defecto + constexpr bool SUPERSAMPLING = false; // Supersampling 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 f3100a8..9e6bf3d 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -337,6 +337,22 @@ namespace Options { } } + if (vid.contains("supersampling")) { + try { + video.supersampling = vid["supersampling"].get_value(); + } catch (...) { + video.supersampling = Defaults::Video::SUPERSAMPLING; + } + } + + if (vid.contains("current_postfx_preset")) { + try { + current_postfx_preset = vid["current_postfx_preset"].get_value(); + } catch (...) { + current_postfx_preset = 0; + } + } + if (vid.contains("vertical_sync")) { try { video.vertical_sync = vid["vertical_sync"].get_value(); @@ -623,6 +639,8 @@ namespace Options { file << " fullscreen: " << (video.fullscreen ? "true" : "false") << "\n"; file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n"; file << " postfx: " << (video.postfx ? "true" : "false") << "\n"; + file << " supersampling: " << (video.supersampling ? "true" : "false") << "\n"; + file << " current_postfx_preset: " << current_postfx_preset << "\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"; @@ -688,7 +706,6 @@ namespace Options { // 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()) { @@ -718,13 +735,20 @@ namespace Options { parseFloatField(p, "gamma", preset.gamma); parseFloatField(p, "curvature", preset.curvature); parseFloatField(p, "bleeding", preset.bleeding); - if (p.contains("supersampling")) { - try { preset.supersampling = p["supersampling"].get_value(); } catch (...) {} - } + // Nota: 'supersampling' era un campo por-preset (eliminado). Si existe + // en el fichero del usuario se ignora silenciosamente (compatible). postfx_presets.push_back(preset); } } + // Preservar el índice cargado desde config.yaml; clampar al rango válido. + if (!postfx_presets.empty()) { + current_postfx_preset = std::clamp( + current_postfx_preset, 0, static_cast(postfx_presets.size()) - 1); + } else { + current_postfx_preset = 0; + } + if (console) { std::cout << "PostFX file loaded: " << postfx_presets.size() << " preset(s)\n"; } @@ -762,9 +786,7 @@ namespace Options { file << "# gamma: gamma correction input 2.4 / output 2.2\n"; file << "# curvature: CRT barrel distortion\n"; file << "# bleeding: NTSC horizontal colour bleeding\n"; - file << "# supersampling: 3x internal resolution, scanlines baked in CPU + linear filter\n"; - file << "# true = consistent 33% scanlines at any zoom (slight softening at non-3x)\n"; - file << "# false = sharp pixel art, scanlines depend on zoom (33% at 3x, 25% at 4x)\n"; + file << "# Note: supersampling is a global toggle in config.yaml, not per-preset.\n"; file << "\n"; file << "presets:\n"; file << " - name: \"CRT\"\n"; @@ -775,16 +797,6 @@ namespace Options { file << " gamma: 0.8\n"; file << " curvature: 0.0\n"; file << " bleeding: 0.0\n"; - file << " supersampling: false\n"; - file << " - name: \"CRT-SS\"\n"; - file << " vignette: 0.6\n"; - file << " scanlines: 0.7\n"; - file << " chroma: 0.15\n"; - file << " mask: 0.6\n"; - file << " gamma: 0.8\n"; - file << " curvature: 0.0\n"; - file << " bleeding: 0.0\n"; - file << " supersampling: true\n"; file << " - name: \"NTSC\"\n"; file << " vignette: 0.4\n"; file << " scanlines: 0.5\n"; @@ -793,7 +805,6 @@ namespace Options { file << " gamma: 0.5\n"; file << " curvature: 0.0\n"; file << " bleeding: 0.6\n"; - file << " supersampling: false\n"; file << " - name: \"CURVED\"\n"; file << " vignette: 0.5\n"; file << " scanlines: 0.6\n"; @@ -802,7 +813,6 @@ namespace Options { file << " gamma: 0.7\n"; file << " curvature: 0.8\n"; file << " bleeding: 0.0\n"; - file << " supersampling: false\n"; file << " - name: \"SCANLINES\"\n"; file << " vignette: 0.0\n"; file << " scanlines: 0.8\n"; @@ -811,7 +821,6 @@ namespace Options { file << " gamma: 0.0\n"; file << " curvature: 0.0\n"; file << " bleeding: 0.0\n"; - file << " supersampling: false\n"; file << " - name: \"SUBTLE\"\n"; file << " vignette: 0.3\n"; file << " scanlines: 0.4\n"; @@ -820,7 +829,6 @@ namespace Options { file << " gamma: 0.3\n"; file << " curvature: 0.0\n"; file << " bleeding: 0.0\n"; - file << " supersampling: false\n"; file.close(); diff --git a/source/game/options.hpp b/source/game/options.hpp index 1c29143..c7bbcd1 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -81,6 +81,7 @@ namespace Options { 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 postfx{Defaults::Video::POSTFX}; // Indica si se van a usar efectos PostFX o no + bool supersampling{Defaults::Video::SUPERSAMPLING}; // Indica si el supersampling 3× está activo 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 @@ -124,7 +125,6 @@ namespace Options { float gamma{0.0F}; // Corrección gamma input 2.4 / output 2.2 (0.0 = off, 1.0 = plena) float curvature{0.0F}; // Distorsión barrel CRT (0.0 = plana, 1.0 = máxima curvatura) float bleeding{0.0F}; // Sangrado de color NTSC horizontal Y/C (0.0 = off, 1.0 = máximo) - bool supersampling{false}; // 3x supersampling: scanlines horneadas en CPU + sampler LINEAR }; // --- Variables globales ---