From 96763847fb7b6d6613dacbb31c681a73064a46a0 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 17 May 2026 15:06:42 +0200 Subject: [PATCH] PostFXParams/Preset amb chroma_min/max + scan_*; presets AEE migrats --- source/core/rendering/screen.cpp | 41 +++------- .../core/rendering/sdl3gpu/sdl3gpu_shader.hpp | 77 +++++++------------ source/core/rendering/shader_backend.hpp | 30 ++------ source/game/options.cpp | 44 +++++------ source/game/options.hpp | 10 ++- 5 files changed, 74 insertions(+), 128 deletions(-) diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 91ab710..ff20583 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -179,12 +179,6 @@ void Screen::initShaders() { shader_backend_->setVSync(Options::video.vsync); shader_backend_->setTextureFilter(Options::video.texture_filter); shader_backend_->setStretch43(Options::video.aspect_ratio_4_3); - shader_backend_->setDownscaleAlgo(Options::video.downscale_algo); - - if (Options::video.supersampling) { - shader_backend_->setOversample(3); - } - shader_backend_->setInternalResolution(Options::video.internal_resolution); // Resol el shader actiu des del config @@ -325,24 +319,6 @@ void Screen::toggleShaders() { } } -auto Screen::toggleSupersampling() -> bool { - // SS només té sentit amb shaders on i pipeline PostFX (el Lanczos downscale - // i el camí SS s'apliquen al pas de PostFX; CRTPI fa el seu propi - // submostreig intern i no usa aquesta via). - if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { - return false; - } - if (!Options::video.shader_enabled) { - return false; - } - if (shader_backend_->getActiveShader() != Rendering::ShaderType::POSTFX) { - return false; - } - Options::video.supersampling = !Options::video.supersampling; - shader_backend_->setOversample(Options::video.supersampling ? 3 : 1); - return true; -} - void Screen::toggleAspectRatio() { Options::video.aspect_ratio_4_3 = !Options::video.aspect_ratio_4_3; if (shader_backend_) { @@ -516,12 +492,16 @@ void Screen::applyCurrentPostFXPreset() { Rendering::PostFXParams p; p.vignette = preset.vignette; p.scanlines = preset.scanlines; - p.chroma = preset.chroma; + p.chroma_min = preset.chroma_min; + p.chroma_max = preset.chroma_max; p.mask = preset.mask; p.gamma = preset.gamma; p.curvature = preset.curvature; p.bleeding = preset.bleeding; p.flicker = preset.flicker; + p.scan_dark_ratio = preset.scan_dark_ratio; + p.scan_dark_floor = preset.scan_dark_floor; + p.scan_edge_soft = preset.scan_edge_soft; shader_backend_->setPostFXParams(p); } @@ -572,10 +552,7 @@ void Screen::updateRenderInfo() { shader_seg = " - " + toLower(getActiveShaderName()) + " " + toLower(getCurrentPresetName()); } - // Segment 2: supersampling indicator - const char* ss_seg = (Options::video.shader_enabled && Options::video.supersampling) ? " (ss)" : nullptr; - - // Segment 3: hora (només si show_time) + // Segment 2: hora (només si show_time) char time_buf[32] = {0}; if (Options::render_info.show_time) { Uint32 elapsed = SDL_GetTicks() - START_TICKS; @@ -585,14 +562,14 @@ void Screen::updateRenderInfo() { snprintf(time_buf, sizeof(time_buf), " - %d:%02d.%02d", minutes, seconds, centis); } - // Dígits en mono a FPS (segment 0) i TEMPS (segment 3): els dígits canvien + // Dígits en mono a FPS (segment 0) i TEMPS (segment 2): els dígits canvien // contínuament mentre els símbols del voltant ("fps", ":", ".", " - ") no Overlay::setRenderInfoSegments( fps_driver.c_str(), shader_seg.empty() ? nullptr : shader_seg.c_str(), - ss_seg, (time_buf[0] != 0) ? time_buf : nullptr, - 0b1001); + nullptr, + 0b0101); } void Screen::applyFallbackPresentation() { diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp index 61d6796..b0cf031 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp @@ -7,20 +7,28 @@ // 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. +// 16 floats = 64 bytes (4 × vec4) — meets Metal/Vulkan 16-byte alignment. struct PostFXUniforms { + // vec4 #0 float vignette_strength; // 0 = none, ~0.8 = subtle - float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration + float chroma_min; // aberració cromàtica mínima (sempre present) 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) + // vec4 #1 + 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 + // vec4 #2 + float pixel_scale; // physical pixels per logical pixel (vh / tex_height_) + float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f) + float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz + float chroma_max; // si == chroma_min queda estàtic; si != pulsa sinusoidalment + // vec4 #3 — paràmetres de forma de les scanlines (exposats per preset) + float scan_dark_ratio; // fracció de subfila fosca (1/3 = 0.333 per defecte) + float scan_dark_floor; // brillantor de la subfila fosca (0.42 per defecte) + float scan_edge_soft; // suavitzat de la transició (0 = step dur, 1 = 1px físic) + float pad3; }; // CrtPi uniforms pushed to fragment stage each frame. @@ -49,15 +57,6 @@ struct CrtPiUniforms { float texture_height; // Alto del canvas en píxeles (inyectado en render) }; -// Downscale uniforms pushed to the Lanczos downscale fragment stage. -// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment. -struct DownscaleUniforms { - int algorithm; // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3) - float pad0; - float pad1; - float pad2; -}; - namespace Rendering { /** @@ -99,15 +98,6 @@ namespace Rendering { // Selecciona el mode de presentació lògica (DISABLED/STRETCH/LETTERBOX/OVERSCAN/INTEGER) void setScalingMode(Options::ScalingMode mode) override; - // Establece factor de supersampling (1 = off, 3 = 3×SS) - void setOversample(int factor) override; - - // Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3 - void setDownscaleAlgo(int algo) override; - - // Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado) - [[nodiscard]] auto getSsTextureSize() const -> std::pair override; - // Selecciona el shader de post-procesado activo (POSTFX o CRTPI) void setActiveShader(ShaderType type) override; @@ -146,39 +136,30 @@ namespace Rendering { Uint32 num_uniform_buffers) -> SDL_GPUShader*; auto createPipeline() -> bool; - auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi - auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_ - auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado - auto recreateInternalTexture() -> bool; // Recrea internal_texture_ (res interna × N) - static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3) + auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi + auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_ + auto recreateInternalTexture() -> bool; // Recrea internal_texture_ (res interna × N) // Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC [[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode; SDL_Window* window_ = nullptr; SDL_GPUDevice* device_ = nullptr; - SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass (→ swapchain o → postfx_texture_) - SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass (→ swapchain directo, sin SS) - SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr; // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos) - SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale pass (solo con SS) - SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr; // Lanczos downscale (solo con SS + algo > 0) - SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del joc (game_width_ × game_height_) - SDL_GPUTexture* internal_texture_ = nullptr; // Resolució interna ampliada (game·N × game·N), si N>1 - SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor, amb 4:3 si actiu) - SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolució escalada, sols amb Lanczos + SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass → swapchain + SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass → swapchain + SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale per al pas de resolució interna + SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del joc (game_width_ × game_height_) + SDL_GPUTexture* internal_texture_ = nullptr; // Resolució interna ampliada (game·N × game·N), si N>1 SDL_GPUTransferBuffer* upload_buffer_ = nullptr; SDL_GPUSampler* sampler_ = nullptr; // NEAREST - SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR + SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR (per texture_filter_linear_) - PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 200.0F, .pixel_scale = 1.0F, .oversample = 1.0F}; + PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_min = 0.15F, .scanline_strength = 0.7F, .screen_height = 200.0F, .pixel_scale = 1.0F, .chroma_max = 0.15F, .scan_dark_ratio = 0.333F, .scan_dark_floor = 0.42F, .scan_edge_soft = 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; // Shader de post-procesado activo int game_width_ = 0; // Dimensions originals del canvas int game_height_ = 0; - int ss_factor_ = 0; // Factor SS activo (3, 6, 9...) o 0 si SS desactivado - int oversample_ = 1; // SS on/off (1 = off, >1 = on) - int downscale_algo_ = 1; // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3 - int internal_res_ = 1; // Multiplicador de resolució interna (1 = off) + int internal_res_ = 1; // Multiplicador de resolució interna (1 = off) std::string driver_name_; std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige) bool is_initialized_ = false; diff --git a/source/core/rendering/shader_backend.hpp b/source/core/rendering/shader_backend.hpp index 7c3cb52..7395182 100644 --- a/source/core/rendering/shader_backend.hpp +++ b/source/core/rendering/shader_backend.hpp @@ -21,12 +21,19 @@ namespace Rendering { struct PostFXParams { float vignette = 0.0F; // Intensidad de la viñeta float scanlines = 0.0F; // Intensidad de las scanlines - float chroma = 0.0F; // Aberración cromática + // Aberració cromàtica — varia entre min i max via sinusoidal; si coincideixen + // queda estàtica. min > 0 garanteix que la imatge mai sigui lliure de chroma. + float chroma_min = 0.0F; + float chroma_max = 0.0F; float mask = 0.0F; // Máscara de fósforo RGB 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 float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz + // Forma de les scanlines — 3 subpíxels per fila lògica per defecte. + float scan_dark_ratio = 0.333F; // fracció obscura (1/3) + float scan_dark_floor = 0.42F; // brillantor subfila fosca + float scan_edge_soft = 1.0F; // 0 = step dur; 1 = suavitzat 1 px físic }; /** @@ -112,27 +119,6 @@ namespace Rendering { */ virtual void setScalingMode(Options::ScalingMode /*mode*/) {} - /** - * @brief Establece el factor de supersampling (1 = off, 3 = 3× SS) - * Con factor > 1, la textura GPU se crea a game×factor resolución y - * las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR. - */ - virtual void setOversample(int /*factor*/) {} - - /** - * @brief Selecciona el algoritmo de downscale tras el PostFX (SS activo). - * 0 = bilinear legacy (comportamiento actual, sin textura intermedia), - * 1 = Lanczos2 (ventana 2, ~25 muestras), 2 = Lanczos3 (ventana 3, ~49 muestras). - */ - virtual void setDownscaleAlgo(int /*algo*/) {} - [[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; } - - /** - * @brief Devuelve las dimensiones de la textura de supersampling. - * @return Par (ancho, alto) en píxeles; (0, 0) si SS está desactivado. - */ - [[nodiscard]] virtual auto getSsTextureSize() const -> std::pair { return {0, 0}; } - /** * @brief Verifica si el backend está usando aceleración por hardware * @return true si usa aceleración (OpenGL/Metal/Vulkan) diff --git a/source/game/options.cpp b/source/game/options.cpp index 4923451..683271b 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -145,9 +145,6 @@ namespace Options { if (node.contains("shader_enabled")) { video.shader_enabled = node["shader_enabled"].get_value(); } - if (node.contains("supersampling")) { - video.supersampling = node["supersampling"].get_value(); - } if (node.contains("scaling_mode")) { auto s = node["scaling_mode"].get_value(); if (s == "disabled") { @@ -172,9 +169,6 @@ namespace Options { auto s = node["texture_filter"].get_value(); video.texture_filter = (s == "linear") ? TextureFilter::LINEAR : TextureFilter::NEAREST; } - if (node.contains("downscale_algo")) { - video.downscale_algo = node["downscale_algo"].get_value(); - } if (node.contains("internal_resolution")) { video.internal_resolution = node["internal_resolution"].get_value(); video.internal_resolution = std::max(video.internal_resolution, 1); @@ -344,7 +338,6 @@ namespace Options { file << "video:\n"; file << " gpu_acceleration: " << (video.gpu_acceleration ? "true" : "false") << "\n"; file << " shader_enabled: " << (video.shader_enabled ? "true" : "false") << "\n"; - file << " supersampling: " << (video.supersampling ? "true" : "false") << "\n"; { const char* m = nullptr; switch (video.scaling_mode) { @@ -370,7 +363,6 @@ namespace Options { file << " vsync: " << (video.vsync ? "true" : "false") << "\n"; file << " aspect_ratio_4_3: " << (video.aspect_ratio_4_3 ? "true" : "false") << "\n"; file << " texture_filter: " << (video.texture_filter == TextureFilter::LINEAR ? "linear" : "nearest") << " # nearest|linear\n"; - file << " downscale_algo: " << video.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n"; file << " internal_resolution: " << video.internal_resolution << " # multiplicador enter font, clampat a max_zoom\n"; file << " current_shader: " << video.current_shader << "\n"; file << " current_postfx_preset: " << video.current_postfx_preset << "\n"; @@ -481,22 +473,24 @@ namespace Options { // Escriure defaults std::ofstream out(postfx_file_path); if (out.is_open()) { - out << "# Aventures En Egipte - PostFX Shader Presets\n\n"; + out << "# Aventures En Egipte - PostFX Shader Presets\n"; + out << "# chroma_min/max: min == max → estàtic; min != max → pulsa sinusoïdalment.\n"; + out << "# scan_dark_ratio/floor/edge_soft: forma analítica de les scanlines.\n\n"; out << "presets:\n"; - out << " - name: \"CRT\"\n vignette: 0.6\n scanlines: 0.7\n chroma: 0.15\n mask: 0.6\n gamma: 0.8\n"; - out << " - name: \"NTSC\"\n vignette: 0.4\n scanlines: 0.5\n chroma: 0.2\n mask: 0.4\n gamma: 0.5\n bleeding: 0.6\n"; - out << " - name: \"CURVED\"\n vignette: 0.5\n scanlines: 0.6\n chroma: 0.1\n mask: 0.5\n gamma: 0.7\n curvature: 0.8\n"; - out << " - name: \"SCANLINES\"\n scanlines: 0.8\n vignette: 0.0\n chroma: 0.0\n"; - out << " - name: \"SUBTLE\"\n vignette: 0.3\n scanlines: 0.4\n chroma: 0.05\n gamma: 0.3\n"; - out << " - name: \"CRT LIVE\"\n vignette: 0.5\n scanlines: 0.6\n chroma: 0.3\n mask: 0.3\n gamma: 0.4\n curvature: 0.3\n bleeding: 0.4\n flicker: 0.8\n"; + out << " - name: \"CRT\"\n vignette: 0.6\n scanlines: 0.7\n chroma_min: 0.15\n chroma_max: 0.15\n mask: 0.6\n gamma: 0.8\n"; + out << " - name: \"NTSC\"\n vignette: 0.4\n scanlines: 0.5\n chroma_min: 0.2\n chroma_max: 0.2\n mask: 0.4\n gamma: 0.5\n bleeding: 0.6\n"; + out << " - name: \"CURVED\"\n vignette: 0.5\n scanlines: 0.6\n chroma_min: 0.1\n chroma_max: 0.1\n mask: 0.5\n gamma: 0.7\n curvature: 0.8\n"; + out << " - name: \"SCANLINES\"\n scanlines: 0.8\n vignette: 0.0\n chroma_min: 0.0\n chroma_max: 0.0\n"; + out << " - name: \"SUBTLE\"\n vignette: 0.3\n scanlines: 0.4\n chroma_min: 0.05\n chroma_max: 0.05\n gamma: 0.3\n"; + out << " - name: \"CRT LIVE\"\n vignette: 0.5\n scanlines: 0.6\n chroma_min: 0.3\n chroma_max: 0.3\n mask: 0.3\n gamma: 0.4\n curvature: 0.3\n bleeding: 0.4\n flicker: 0.8\n"; out.close(); } - postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.6F, 0.8F}); - postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.4F, 0.5F, 0.0F, 0.6F}); - postfx_presets.push_back({"CURVED", 0.5F, 0.6F, 0.1F, 0.5F, 0.7F, 0.8F}); - postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F}); - postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F}); - postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F}); + postfx_presets.push_back({.name = "CRT", .vignette = 0.6F, .scanlines = 0.7F, .chroma_min = 0.15F, .chroma_max = 0.15F, .mask = 0.6F, .gamma = 0.8F}); + postfx_presets.push_back({.name = "NTSC", .vignette = 0.4F, .scanlines = 0.5F, .chroma_min = 0.2F, .chroma_max = 0.2F, .mask = 0.4F, .gamma = 0.5F, .bleeding = 0.6F}); + postfx_presets.push_back({.name = "CURVED", .vignette = 0.5F, .scanlines = 0.6F, .chroma_min = 0.1F, .chroma_max = 0.1F, .mask = 0.5F, .gamma = 0.7F, .curvature = 0.8F}); + postfx_presets.push_back({.name = "SCANLINES", .vignette = 0.0F, .scanlines = 0.8F, .chroma_min = 0.0F, .chroma_max = 0.0F}); + postfx_presets.push_back({.name = "SUBTLE", .vignette = 0.3F, .scanlines = 0.4F, .chroma_min = 0.05F, .chroma_max = 0.05F, .gamma = 0.3F}); + postfx_presets.push_back({.name = "CRT LIVE", .vignette = 0.5F, .scanlines = 0.6F, .chroma_min = 0.3F, .chroma_max = 0.3F, .mask = 0.3F, .gamma = 0.4F, .curvature = 0.3F, .bleeding = 0.4F, .flicker = 0.8F}); current_postfx_preset = 0; return true; } @@ -514,12 +508,16 @@ namespace Options { } parseFloatField(p, "vignette", preset.vignette); parseFloatField(p, "scanlines", preset.scanlines); - parseFloatField(p, "chroma", preset.chroma); + parseFloatField(p, "chroma_min", preset.chroma_min); + parseFloatField(p, "chroma_max", preset.chroma_max); parseFloatField(p, "mask", preset.mask); parseFloatField(p, "gamma", preset.gamma); parseFloatField(p, "curvature", preset.curvature); parseFloatField(p, "bleeding", preset.bleeding); parseFloatField(p, "flicker", preset.flicker); + parseFloatField(p, "scan_dark_ratio", preset.scan_dark_ratio); + parseFloatField(p, "scan_dark_floor", preset.scan_dark_floor); + parseFloatField(p, "scan_edge_soft", preset.scan_edge_soft); postfx_presets.push_back(preset); } } @@ -528,7 +526,7 @@ namespace Options { return true; } catch (const fkyaml::exception& e) { std::cerr << "Error parsing PostFX YAML: " << e.what() << '\n'; - postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.6F, 0.8F}); + postfx_presets.push_back({.name = "CRT", .vignette = 0.6F, .scanlines = 0.7F, .chroma_min = 0.15F, .chroma_max = 0.15F, .mask = 0.6F, .gamma = 0.8F}); current_postfx_preset = 0; return false; } diff --git a/source/game/options.hpp b/source/game/options.hpp index e5116e4..57a6e2a 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -40,12 +40,10 @@ namespace Options { struct Video { bool gpu_acceleration{Defaults::Video::GPU_ACCELERATION}; bool shader_enabled{Defaults::Video::SHADER_ENABLED}; - bool supersampling{Defaults::Video::SUPERSAMPLING}; ScalingMode scaling_mode{ScalingMode::INTEGER}; bool vsync{Defaults::Video::VSYNC}; bool aspect_ratio_4_3{Defaults::Video::ASPECT_RATIO_4_3}; TextureFilter texture_filter{TextureFilter::NEAREST}; - int downscale_algo{Defaults::Video::DOWNSCALE_ALGO}; int internal_resolution{Defaults::Video::INTERNAL_RESOLUTION}; // Multiplicador enter ≥ 1, clampat a max_zoom std::string current_shader{"postfx"}; // "postfx" o "crtpi" std::string current_postfx_preset{"CRT"}; // Nom del preset PostFX actiu @@ -99,12 +97,18 @@ namespace Options { std::string name; float vignette{0.6F}; float scanlines{0.7F}; - float chroma{0.15F}; + // Aberració cromàtica: min == max → estàtic; min != max → pulsa sinusoïdalment. + float chroma_min{0.15F}; + float chroma_max{0.15F}; float mask{0.0F}; float gamma{0.0F}; float curvature{0.0F}; float bleeding{0.0F}; float flicker{0.0F}; + // Forma de les scanlines — 3 subpíxels per fila lògica per defecte. + float scan_dark_ratio{0.333F}; + float scan_dark_floor{0.42F}; + float scan_edge_soft{1.0F}; }; // Preset CrtPi