diff --git a/source/core/input/global_inputs.cpp b/source/core/input/global_inputs.cpp index 84bc520..8a24d98 100644 --- a/source/core/input/global_inputs.cpp +++ b/source/core/input/global_inputs.cpp @@ -18,6 +18,7 @@ namespace GlobalInputs { static bool ss_prev = false; static bool next_shader_prev = false; static bool next_preset_prev = false; + static bool stretch_filter_prev = false; auto handle() -> bool { bool consumed = false; @@ -84,7 +85,9 @@ namespace GlobalInputs { bool next_shader = JI_KeyPressed(Options::keys_gui.next_shader); if (next_shader && !next_shader_prev) { Screen::get()->nextShaderPreset(); - Overlay::showNotification(Screen::get()->isHardwareAccelerated() ? "POSTFX / CRT-PI" : "SENSE GPU"); + char msg[32]; + snprintf(msg, sizeof(msg), "SHADER: %s", Screen::get()->getActiveShaderName()); + Overlay::showNotification(msg); } if (next_shader) consumed = true; next_shader_prev = next_shader; @@ -98,6 +101,15 @@ namespace GlobalInputs { if (next_preset) consumed = true; next_preset_prev = next_preset; + // F9 — Toggle filtre d'estirament 4:3 (NEAREST ↔ LINEAR) + bool stretch_filter = JI_KeyPressed(Options::keys_gui.toggle_stretch_filter); + if (stretch_filter && !stretch_filter_prev) { + Screen::get()->toggleStretchFilter(); + Overlay::showNotification(Options::video.stretch_filter_linear ? "FILTRE: LINEAR" : "FILTRE: NEAREST"); + } + if (stretch_filter) consumed = true; + stretch_filter_prev = stretch_filter; + return consumed; } diff --git a/source/core/jail/jinput.cpp b/source/core/jail/jinput.cpp index 6cd07d8..6e8146f 100644 --- a/source/core/jail/jinput.cpp +++ b/source/core/jail/jinput.cpp @@ -21,7 +21,8 @@ static bool isGuiKey(SDL_Scancode sc) { sc == Options::keys_gui.toggle_aspect_ratio || sc == Options::keys_gui.toggle_supersampling || sc == Options::keys_gui.next_shader || - sc == Options::keys_gui.next_shader_preset; + sc == Options::keys_gui.next_shader_preset || + sc == Options::keys_gui.toggle_stretch_filter; } void JI_DisableKeyboard(Uint32 time) { diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 6806ede..c474d74 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -88,6 +88,7 @@ void Screen::initShaders() { // Aplica opcions de vídeo shader_backend_->setScaleMode(Options::video.integer_scale); + shader_backend_->setStretchFilter(Options::video.stretch_filter_linear); shader_backend_->setStretch4_3(Options::video.aspect_ratio_4_3); shader_backend_->setLinearUpscale(Options::video.linear_upscale); shader_backend_->setDownscaleAlgo(Options::video.downscale_algo); @@ -179,6 +180,13 @@ void Screen::toggleIntegerScale() { } } +void Screen::toggleStretchFilter() { + Options::video.stretch_filter_linear = !Options::video.stretch_filter_linear; + if (shader_backend_) { + shader_backend_->setStretchFilter(Options::video.stretch_filter_linear); + } +} + void Screen::nextShaderPreset() { if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return; @@ -224,6 +232,11 @@ auto Screen::isHardwareAccelerated() const -> bool { return shader_backend_ && shader_backend_->isHardwareAccelerated(); } +auto Screen::getActiveShaderName() const -> const char* { + if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return "SENSE GPU"; + return shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX ? "POSTFX" : "CRT-PI"; +} + void Screen::adjustWindowSize() { int w = GAME_WIDTH * zoom_; // Si 4:3 actiu, l'alçada visual és 240 per zoom (200 * 1.2) diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index 9186e8e..bb8f63a 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -27,7 +27,8 @@ class Screen { void toggleSupersampling(); void toggleAspectRatio(); void toggleIntegerScale(); - void nextShaderPreset(); // Futur: ciclar presets + void toggleStretchFilter(); + void nextShaderPreset(); void setActiveShader(Rendering::ShaderType type); void applyCurrentPostFXPreset(); void applyCurrentCrtPiPreset(); @@ -36,6 +37,7 @@ class Screen { [[nodiscard]] auto isFullscreen() const -> bool { return fullscreen_; } [[nodiscard]] auto getZoom() const -> int { return zoom_; } [[nodiscard]] auto isHardwareAccelerated() const -> bool; + [[nodiscard]] auto getActiveShaderName() const -> const char*; [[nodiscard]] auto getWindow() -> SDL_Window* { return window_; } [[nodiscard]] auto getRenderer() -> SDL_Renderer* { return renderer_; } diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp index dbce7fc..8e9811c 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp @@ -812,23 +812,40 @@ namespace Rendering { SDL_EndGPUCopyPass(copy); } - // ---- Upscale pass: scene_texture_ → scaled_texture_ (NEAREST o LINEAR según linear_upscale_) ---- + // ---- Upscale pass: scene_texture_ → scaled_texture_ ---- + // Si 4:3 actiu, l'estirament s'aplica ací directament (320x200 → W*factor × H*factor*1.2) + // El filtre per al 4:3 és configurable (stretch_filter_linear_). + // L'effective_scene/height reflecteix la textura real que veuen els shaders. + // Sense SS ni stretch: scene_texture_ a game_height_. + // Amb SS o stretch: scaled_texture_ a l'alçada escalada (amb o sense 4:3). + SDL_GPUTexture* effective_scene = scene_texture_; + int effective_height = game_height_; + if (oversample_ > 1 && scaled_texture_ != nullptr && upscale_pipeline_ != nullptr) { SDL_GPUColorTargetInfo upscale_target = {}; upscale_target.texture = scaled_texture_; upscale_target.load_op = SDL_GPU_LOADOP_DONT_CARE; upscale_target.store_op = SDL_GPU_STOREOP_STORE; + // Triar filtre: si 4:3 actiu, usar el filtre configurable per a l'estirament. + // Si no, usar el filtre d'upscale normal (linear_upscale_). + bool use_linear = stretch_4_3_ ? stretch_filter_linear_ : linear_upscale_; + SDL_GPURenderPass* upass = SDL_BeginGPURenderPass(cmd, &upscale_target, 1, nullptr); if (upass != nullptr) { SDL_BindGPUGraphicsPipeline(upass, upscale_pipeline_); SDL_GPUTextureSamplerBinding ubinding = {}; ubinding.texture = scene_texture_; - ubinding.sampler = (linear_upscale_ && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_; + ubinding.sampler = (use_linear && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_; SDL_BindGPUFragmentSamplers(upass, 0, &ubinding, 1); SDL_DrawGPUPrimitives(upass, 3, 1, 0, 0); SDL_EndGPURenderPass(upass); } + effective_scene = scaled_texture_; + effective_height = stretch_4_3_ ? static_cast(static_cast(game_height_) * 1.2F) : game_height_; + } else if (stretch_4_3_) { + // Sense SS: el viewport s'encarrega de l'estirament geomètric + effective_height = static_cast(static_cast(game_height_) * 1.2F); } // ---- Acquire swapchain texture ---- @@ -846,10 +863,10 @@ namespace Rendering { return; } - // ---- Calcular viewport (dimensions lògiques del canvas, no de textura GPU) ---- - // Si stretch_4_3_ actiu, l'alçada lògica es multiplica per 1.2 (200→240) per simular 4:3 CRT + // ---- Calcular viewport (dimensions lògiques del canvas) ---- + // Si 4:3 actiu, effective_height ja és 240 (la textura estirada) const float logical_w = static_cast(game_width_); - const float logical_h = stretch_4_3_ ? static_cast(game_height_) * 1.2F : static_cast(game_height_); + const float logical_h = static_cast(effective_height); float vx = 0.0F; float vy = 0.0F; @@ -869,14 +886,15 @@ namespace Rendering { vx = std::floor((static_cast(sw) - vw) * 0.5F); vy = std::floor((static_cast(sh) - vh) * 0.5F); - // pixel_scale: subpíxeles por pixel lógico. - // Sin SS: vh/game_height (zoom de ventana). - // Con SS: ss_factor_ exacto (3, 6, 9...). + // pixel_scale: subpíxels per píxel lògic. + // Sense SS: vh/effective_height (zoom de finestra). + // Amb SS: ss_factor_ exacte (3, 6, 9...). if (oversample_ > 1 && ss_factor_ > 0) { uniforms_.pixel_scale = static_cast(ss_factor_); } else { - uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast(game_height_)) : 1.0F; + uniforms_.pixel_scale = (effective_height > 0) ? (vh / static_cast(effective_height)) : 1.0F; } + uniforms_.screen_height = static_cast(effective_height); uniforms_.time = static_cast(SDL_GetTicks()) / 1000.0F; uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0) ? static_cast(ss_factor_) @@ -897,13 +915,13 @@ namespace Rendering { SDL_SetGPUViewport(pass, &vp); SDL_GPUTextureSamplerBinding binding = {}; - binding.texture = scene_texture_; - binding.sampler = sampler_; // NEAREST: el shader CrtPi hace su propio filtrado analítico + binding.texture = effective_scene; + binding.sampler = sampler_; // NEAREST: el shader CrtPi fa el seu propi filtrat analític SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1); - // Inyectar texture_width/height antes del push + // Injectar texture_width/height abans del push crtpi_uniforms_.texture_width = static_cast(game_width_); - crtpi_uniforms_.texture_height = static_cast(game_height_); + crtpi_uniforms_.texture_height = static_cast(effective_height); SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms)); SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); @@ -973,10 +991,10 @@ namespace Rendering { SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F}; SDL_SetGPUViewport(pass, &vp); - // Con SS: leer de scaled_texture_ con LINEAR; sin SS: scene_texture_ con NEAREST. + // Amb SS: llegir de scaled_texture_ amb LINEAR; sense SS: effective_scene amb NEAREST. SDL_GPUTexture* input_texture = (oversample_ > 1 && scaled_texture_ != nullptr) ? scaled_texture_ - : scene_texture_; + : effective_scene; SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_; @@ -1179,6 +1197,17 @@ namespace Rendering { integer_scale_ = integer_scale; } + void SDL3GPUShader::setStretch4_3(bool enabled) { + stretch_4_3_ = enabled; + if (!is_initialized_ || device_ == nullptr) return; + + // Recrear scaled_texture_ perquè tinga les dimensions correctes (amb o sense 4:3) + if (oversample_ > 1 && ss_factor_ > 0) { + SDL_WaitForGPUIdle(device_); + recreateScaledTexture(ss_factor_); + } + } + // --------------------------------------------------------------------------- // setOversample — cambia el factor SS; recrea texturas si ya está inicializado // --------------------------------------------------------------------------- @@ -1292,7 +1321,10 @@ namespace Rendering { ss_factor_ = 0; const int W = game_width_ * factor; - const int H = game_height_ * factor; + // Si 4:3 actiu, l'alçada inclou l'estirament (200 * factor * 1.2) + const int H = stretch_4_3_ + ? static_cast(static_cast(game_height_) * 1.2F * static_cast(factor)) + : game_height_ * factor; SDL_GPUTextureCreateInfo info = {}; info.type = SDL_GPU_TEXTURETYPE_2D; diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp index 68f147b..e3b476c 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp @@ -120,9 +120,10 @@ namespace Rendering { // Devuelve el shader activo [[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; } - // Estirament vertical 4:3 (320x200 → 320x240 visual al viewport) - void setStretch4_3(bool enabled) override { stretch_4_3_ = enabled; } + // Estirament vertical 4:3 (fusionat amb l'upscale pass) + void setStretch4_3(bool enabled) override; [[nodiscard]] auto isStretch4_3() const -> bool override { return stretch_4_3_; } + void setStretchFilter(bool linear) override { stretch_filter_linear_ = linear; } private: static auto createShaderMSL(SDL_GPUDevice* device, @@ -155,9 +156,9 @@ namespace Rendering { 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 juego (game_width_ × game_height_) - SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor), solo con SS - SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolución escalada, solo con Lanczos + SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del joc (game_width_ × game_height_) + 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_GPUTransferBuffer* upload_buffer_ = nullptr; SDL_GPUSampler* sampler_ = nullptr; // NEAREST SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR @@ -166,7 +167,7 @@ namespace Rendering { 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; // Dimensiones originales del canvas + 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) @@ -176,8 +177,9 @@ namespace Rendering { bool is_initialized_ = false; bool vsync_ = true; bool integer_scale_ = false; - bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true) - bool stretch_4_3_ = false; // Estirament vertical 4:3 al viewport + bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true) + bool stretch_4_3_ = false; // Estirament vertical 4:3 + bool stretch_filter_linear_ = false; // Filtre per a l'estirament 4:3 (false=NEAREST, true=LINEAR) }; } // namespace Rendering diff --git a/source/core/rendering/shader_backend.hpp b/source/core/rendering/shader_backend.hpp index 1d6a837..de7fe9a 100644 --- a/source/core/rendering/shader_backend.hpp +++ b/source/core/rendering/shader_backend.hpp @@ -177,6 +177,11 @@ namespace Rendering { */ virtual void setStretch4_3(bool /*enabled*/) {} [[nodiscard]] virtual auto isStretch4_3() const -> bool { return false; } + + /** + * @brief Filtre per a l'estirament 4:3 (false=NEAREST, true=LINEAR). + */ + virtual void setStretchFilter(bool /*linear*/) {} }; } // namespace Rendering diff --git a/source/game/defaults.hpp b/source/game/defaults.hpp index 3a0a255..189909d 100644 --- a/source/game/defaults.hpp +++ b/source/game/defaults.hpp @@ -12,6 +12,7 @@ namespace Defaults::KeysGUI { constexpr SDL_Scancode TOGGLE_SUPERSAMPLING = SDL_SCANCODE_F6; constexpr SDL_Scancode NEXT_SHADER = SDL_SCANCODE_F7; constexpr SDL_Scancode NEXT_SHADER_PRESET = SDL_SCANCODE_F8; + constexpr SDL_Scancode TOGGLE_STRETCH_FILTER = SDL_SCANCODE_F9; } // namespace Defaults::KeysGUI // Tecles de joc (moviment del personatge, accions) @@ -28,8 +29,9 @@ namespace Defaults::Video { constexpr bool SHADER_ENABLED = false; constexpr bool SUPERSAMPLING = false; constexpr bool INTEGER_SCALE = true; - constexpr bool ASPECT_RATIO_4_3 = true; // CRT original estira 200→240 - constexpr int DOWNSCALE_ALGO = 1; // 0=bilinear, 1=Lanczos2, 2=Lanczos3 + constexpr bool ASPECT_RATIO_4_3 = true; // CRT original estira 200→240 + constexpr bool STRETCH_FILTER_LINEAR = false; // Filtre per a l'estirament 4:3 (false=NEAREST) + constexpr int DOWNSCALE_ALGO = 1; // 0=bilinear, 1=Lanczos2, 2=Lanczos3 constexpr bool LINEAR_UPSCALE = false; } // namespace Defaults::Video diff --git a/source/game/options.cpp b/source/game/options.cpp index 19b95a7..df3a72e 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -55,6 +55,8 @@ namespace Options { video.integer_scale = node["integer_scale"].get_value(); if (node.contains("aspect_ratio_4_3")) video.aspect_ratio_4_3 = node["aspect_ratio_4_3"].get_value(); + if (node.contains("stretch_filter_linear")) + video.stretch_filter_linear = node["stretch_filter_linear"].get_value(); if (node.contains("downscale_algo")) video.downscale_algo = node["downscale_algo"].get_value(); if (node.contains("linear_upscale")) @@ -158,6 +160,7 @@ namespace Options { file << " supersampling: " << (video.supersampling ? "true" : "false") << "\n"; file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n"; file << " aspect_ratio_4_3: " << (video.aspect_ratio_4_3 ? "true" : "false") << "\n"; + file << " stretch_filter_linear: " << (video.stretch_filter_linear ? "true" : "false") << " # filtre 4:3: false=nearest, true=linear\n"; file << " downscale_algo: " << video.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n"; file << " linear_upscale: " << (video.linear_upscale ? "true" : "false") << "\n"; file << "\n"; diff --git a/source/game/options.hpp b/source/game/options.hpp index e1aec60..3907a91 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -17,6 +17,7 @@ namespace Options { SDL_Scancode toggle_supersampling{Defaults::KeysGUI::TOGGLE_SUPERSAMPLING}; SDL_Scancode next_shader{Defaults::KeysGUI::NEXT_SHADER}; SDL_Scancode next_shader_preset{Defaults::KeysGUI::NEXT_SHADER_PRESET}; + SDL_Scancode toggle_stretch_filter{Defaults::KeysGUI::TOGGLE_STRETCH_FILTER}; }; // Tecles de joc (moviment, accions) @@ -35,6 +36,7 @@ namespace Options { bool supersampling{Defaults::Video::SUPERSAMPLING}; bool integer_scale{Defaults::Video::INTEGER_SCALE}; bool aspect_ratio_4_3{Defaults::Video::ASPECT_RATIO_4_3}; + bool stretch_filter_linear{Defaults::Video::STRETCH_FILTER_LINEAR}; int downscale_algo{Defaults::Video::DOWNSCALE_ALGO}; bool linear_upscale{Defaults::Video::LINEAR_UPSCALE}; };