presentation: bool integer_scale -> enum PresentationMode (integer_scale|letterbox|stretched|overscan) amb migracio de configs antics

This commit is contained in:
2026-05-19 20:29:22 +02:00
parent ac997c185d
commit 20325ddd5a
8 changed files with 214 additions and 69 deletions
+80 -33
View File
@@ -326,16 +326,27 @@ void Screen::detectMaxZoom() {
#endif #endif
} }
// Establece el escalado entero // Estableix el mode de presentacio del canvas i reaplica el layout
void Screen::setIntegerScale(bool enabled) { void Screen::setPresentationMode(Options::PresentationMode mode) {
if (Options::video.integer_scale == enabled) { return; } if (Options::video.presentation_mode == mode) { return; }
Options::video.integer_scale = enabled; Options::video.presentation_mode = mode;
#ifndef NO_SHADERS
if (shader_backend_) {
shader_backend_->setPresentationMode(static_cast<Rendering::ShaderBackend::PresentationMode>(mode));
}
#endif
setVideoMode(Options::video.fullscreen); setVideoMode(Options::video.fullscreen);
} }
// Alterna el escalado entero // Cicla integer_scale -> letterbox -> stretched -> overscan -> integer_scale
void Screen::toggleIntegerScale() { void Screen::nextPresentationMode() {
setIntegerScale(!Options::video.integer_scale); setPresentationMode(Options::nextPresentationMode(Options::video.presentation_mode));
}
// Nom curt del mode actual (per a notificacions). Static perque no necessita
// estat d'instancia: nomes consulta Options::video.
auto Screen::getPresentationModeName() -> const char * {
return Options::presentationModeToString(Options::video.presentation_mode);
} }
// Establece el V-Sync // Establece el V-Sync
@@ -407,39 +418,75 @@ void Screen::applyFullscreenLayout() {
computeFullscreenGameRect(); computeFullscreenGameRect();
} }
// Calcula el rectángulo dest para fullscreen: integer_scale / aspect ratio // Calcula el rectangle dest segons el PresentationMode actiu.
// INTEGER_SCALE: x sencera maxima (1x, 2x, 3x...) centrada amb barres.
// LETTERBOX: mante aspect ratio, ajusta al menor dels eixos, barres.
// STRETCHED: omple tota la finestra deformant la relacio d'aspecte.
// OVERSCAN: mante aspect ratio omplint la finestra (retalla el sobrant).
void Screen::computeFullscreenGameRect() { void Screen::computeFullscreenGameRect() {
if (Options::video.integer_scale) { const float CANVAS_RATIO = static_cast<float>(game_canvas_width_) / static_cast<float>(game_canvas_height_);
// Calcula el tamaño de la escala máxima const float WINDOW_RATIO = static_cast<float>(window_width_) / static_cast<float>(window_height_);
int scale = 0;
while (((game_canvas_width_ * (scale + 1)) <= window_width_) && ((game_canvas_height_ * (scale + 1)) <= window_height_)) {
scale++;
}
dest_.w = game_canvas_width_ * scale; switch (Options::video.presentation_mode) {
dest_.h = game_canvas_height_ * scale; case Options::PresentationMode::INTEGER_SCALE: {
dest_.x = (window_width_ - dest_.w) / 2; int scale = 0;
dest_.y = (window_height_ - dest_.h) / 2; while (((game_canvas_width_ * (scale + 1)) <= window_width_) && ((game_canvas_height_ * (scale + 1)) <= window_height_)) {
} else { scale++;
// Manté la relació d'aspecte sense escalat enter (letterbox/pillarbox). }
float ratio = (float)game_canvas_width_ / (float)game_canvas_height_; dest_.w = game_canvas_width_ * scale;
if ((window_width_ - game_canvas_width_) >= (window_height_ - game_canvas_height_)) { dest_.h = game_canvas_height_ * scale;
dest_.h = window_height_; break;
dest_.w = static_cast<int>(std::lround(window_height_ * ratio)); }
dest_.x = (window_width_ - dest_.w) / 2; case Options::PresentationMode::LETTERBOX: {
dest_.y = (window_height_ - dest_.h) / 2; if (WINDOW_RATIO >= CANVAS_RATIO) {
} else { dest_.h = window_height_;
dest_.w = static_cast<int>(std::lround(window_height_ * CANVAS_RATIO));
} else {
dest_.w = window_width_;
dest_.h = static_cast<int>(std::lround(window_width_ / CANVAS_RATIO));
}
break;
}
case Options::PresentationMode::STRETCHED: {
dest_.w = window_width_; dest_.w = window_width_;
dest_.h = static_cast<int>(std::lround(window_width_ / ratio)); dest_.h = window_height_;
dest_.x = (window_width_ - dest_.w) / 2; break;
dest_.y = (window_height_ - dest_.h) / 2; }
case Options::PresentationMode::OVERSCAN: {
// Mante aspect: dimensiona al major dels eixos (l'altre desborda).
if (WINDOW_RATIO >= CANVAS_RATIO) {
dest_.w = window_width_;
dest_.h = static_cast<int>(std::lround(window_width_ / CANVAS_RATIO));
} else {
dest_.h = window_height_;
dest_.w = static_cast<int>(std::lround(window_height_ * CANVAS_RATIO));
}
break;
} }
} }
dest_.x = (window_width_ - dest_.w) / 2;
dest_.y = (window_height_ - dest_.h) / 2;
} }
// Aplica la logical presentation y persiste el estado en options // Aplica la logical presentation segons el PresentationMode actiu (ruta SDL_Renderer fallback).
// La ruta GPU calcula el viewport ella mateixa via computeViewport().
void Screen::applyLogicalPresentation(bool fullscreen) { void Screen::applyLogicalPresentation(bool fullscreen) {
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, SDL_LOGICAL_PRESENTATION_LETTERBOX); SDL_RendererLogicalPresentation lp = SDL_LOGICAL_PRESENTATION_LETTERBOX;
switch (Options::video.presentation_mode) {
case Options::PresentationMode::INTEGER_SCALE:
lp = SDL_LOGICAL_PRESENTATION_INTEGER_SCALE;
break;
case Options::PresentationMode::LETTERBOX:
lp = SDL_LOGICAL_PRESENTATION_LETTERBOX;
break;
case Options::PresentationMode::STRETCHED:
lp = SDL_LOGICAL_PRESENTATION_STRETCH;
break;
case Options::PresentationMode::OVERSCAN:
lp = SDL_LOGICAL_PRESENTATION_OVERSCAN;
break;
}
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, lp);
Options::video.fullscreen = fullscreen; Options::video.fullscreen = fullscreen;
} }
@@ -545,7 +592,7 @@ void Screen::initShaders() {
} }
} }
if (shader_backend_->isHardwareAccelerated()) { if (shader_backend_->isHardwareAccelerated()) {
shader_backend_->setScaleMode(Options::video.integer_scale); shader_backend_->setPresentationMode(static_cast<Rendering::ShaderBackend::PresentationMode>(Options::video.presentation_mode));
shader_backend_->setVSync(Options::video.vsync); shader_backend_->setVSync(Options::video.vsync);
// Resol els índexs de preset a partir del nom emmagatzemat al config. // Resol els índexs de preset a partir del nom emmagatzemat al config.
+14 -12
View File
@@ -6,7 +6,8 @@
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "utils/utils.h" // for Color #include "game/options.hpp" // for Options::PresentationMode
#include "utils/utils.h" // for Color
#ifndef NO_SHADERS #ifndef NO_SHADERS
#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType #include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType
@@ -51,17 +52,18 @@ class Screen {
void blit(); // Vuelca el contenido del renderizador en pantalla void blit(); // Vuelca el contenido del renderizador en pantalla
// Video y ventana // Video y ventana
void setVideoMode(bool fullscreen); // Establece el modo de video void setVideoMode(bool fullscreen); // Establece el modo de video
void toggleVideoMode(); // Cambia entre pantalla completa y ventana void toggleVideoMode(); // Cambia entre pantalla completa y ventana
void handleCanvasResized(); // En Emscripten, reaplica setVideoMode tras un cambio del navegador (salida de fullscreen con Esc, rotación). No-op fuera de Emscripten void handleCanvasResized(); // En Emscripten, reaplica setVideoMode tras un cambio del navegador (salida de fullscreen con Esc, rotación). No-op fuera de Emscripten
static void syncFullscreenFlagFromBrowser(bool is_fullscreen); // Sincroniza el flag interno de fullscreen con el estado real del navegador. Debe llamarse antes de diferir handleCanvasResized. No-op fuera de Emscripten static void syncFullscreenFlagFromBrowser(bool is_fullscreen); // Sincroniza el flag interno de fullscreen con el estado real del navegador. Debe llamarse antes de diferir handleCanvasResized. No-op fuera de Emscripten
void toggleIntegerScale(); // Alterna el escalado entero void nextPresentationMode(); // Cicla integer_scale -> letterbox -> stretched -> overscan
void setIntegerScale(bool enabled); // Establece el escalado entero void setPresentationMode(Options::PresentationMode mode); // Estableix el mode de presentacio del canvas
void toggleVSync(); // Alterna el V-Sync [[nodiscard]] static auto getPresentationModeName() -> const char *; // Nom curt del mode actual (per a notificacions)
void setVSync(bool enabled); // Establece el V-Sync void toggleVSync(); // Alterna el V-Sync
auto decWindowZoom() -> bool; // Reduce el zoom de la ventana (devuelve true si cambió) void setVSync(bool enabled); // Establece el V-Sync
auto incWindowZoom() -> bool; // Aumenta el zoom de la ventana (devuelve true si cambió) auto decWindowZoom() -> bool; // Reduce el zoom de la ventana (devuelve true si cambió)
auto setWindowZoom(int zoom) -> bool; // Establece el zoom de la ventana (devuelve true si cambió) auto incWindowZoom() -> bool; // Aumenta el zoom de la ventana (devuelve true si cambió)
auto setWindowZoom(int zoom) -> bool; // Establece el zoom de la ventana (devuelve true si cambió)
// Borde // Borde
void setBorderColor(Color color); // Cambia el color del borde void setBorderColor(Color color); // Cambia el color del borde
@@ -292,21 +292,48 @@ namespace Rendering {
// computeViewport — dimensions lògiques del canvas dins del swapchain (letterbox) // computeViewport — dimensions lògiques del canvas dins del swapchain (letterbox)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
auto SDL3GPUShader::computeViewport(Uint32 sw, Uint32 sh) const -> Viewport { auto SDL3GPUShader::computeViewport(Uint32 sw, Uint32 sh) const -> Viewport {
const auto SWF = static_cast<float>(sw);
const auto SHF = static_cast<float>(sh);
const float CANVAS_RATIO = static_cast<float>(game_width_) / static_cast<float>(game_height_);
const float WINDOW_RATIO = SWF / SHF;
float vw = 0.0F; float vw = 0.0F;
float vh = 0.0F; float vh = 0.0F;
if (integer_scale_) { switch (presentation_mode_) {
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_)); case PresentationMode::INTEGER_SCALE: {
vw = static_cast<float>(game_width_ * SCALE); const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
vh = static_cast<float>(game_height_ * SCALE); vw = static_cast<float>(game_width_ * SCALE);
} else { vh = static_cast<float>(game_height_ * SCALE);
const float SCALE = std::min( break;
static_cast<float>(sw) / static_cast<float>(game_width_), }
static_cast<float>(sh) / static_cast<float>(game_height_)); case PresentationMode::LETTERBOX: {
vw = static_cast<float>(game_width_) * SCALE; if (WINDOW_RATIO >= CANVAS_RATIO) {
vh = static_cast<float>(game_height_) * SCALE; vh = SHF;
vw = SHF * CANVAS_RATIO;
} else {
vw = SWF;
vh = SWF / CANVAS_RATIO;
}
break;
}
case PresentationMode::STRETCHED: {
vw = SWF;
vh = SHF;
break;
}
case PresentationMode::OVERSCAN: {
if (WINDOW_RATIO >= CANVAS_RATIO) {
vw = SWF;
vh = SWF / CANVAS_RATIO;
} else {
vh = SHF;
vw = SHF * CANVAS_RATIO;
}
break;
}
} }
const float VX = std::floor((static_cast<float>(sw) - vw) * 0.5F); const float VX = std::floor((SWF - vw) * 0.5F);
const float VY = std::floor((static_cast<float>(sh) - vh) * 0.5F); const float VY = std::floor((SHF - vh) * 0.5F);
return {.x = VX, .y = VY, .w = vw, .h = vh}; return {.x = VX, .y = VY, .w = vw, .h = vh};
} }
@@ -569,8 +596,8 @@ namespace Rendering {
} }
} }
void SDL3GPUShader::setScaleMode(bool integer_scale) { void SDL3GPUShader::setPresentationMode(PresentationMode mode) {
integer_scale_ = integer_scale; presentation_mode_ = mode;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -97,8 +97,8 @@ namespace Rendering {
// Activa/desactiva VSync en el swapchain // Activa/desactiva VSync en el swapchain
void setVSync(bool vsync) override; void setVSync(bool vsync) override;
// Activa/desactiva escalado entero (integer scale) // Estableix el mode de presentacio del canvas
void setScaleMode(bool integer_scale) override; void setPresentationMode(PresentationMode mode) override;
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI) // Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
void setActiveShader(ShaderType type) override; void setActiveShader(ShaderType type) override;
@@ -195,7 +195,7 @@ namespace Rendering {
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige) std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
bool is_initialized_ = false; bool is_initialized_ = false;
bool vsync_ = true; bool vsync_ = true;
bool integer_scale_ = false; PresentationMode presentation_mode_ = PresentationMode::INTEGER_SCALE;
}; };
} // namespace Rendering } // namespace Rendering
+9 -2
View File
@@ -112,9 +112,16 @@ namespace Rendering {
virtual void setVSync(bool /*vsync*/) {} virtual void setVSync(bool /*vsync*/) {}
/** /**
* @brief Activa o desactiva el escalado entero (integer scale) * @brief Estableix el mode de presentacio del canvas dins del swapchain.
* El backend calcula el viewport en consequencia.
*/ */
virtual void setScaleMode(bool /*integer_scale*/) {} enum class PresentationMode : std::uint8_t {
INTEGER_SCALE,
LETTERBOX,
STRETCHED,
OVERSCAN
};
virtual void setPresentationMode(PresentationMode /*mode*/) {}
/** /**
* @brief Verifica si el backend está usando aceleración por hardware * @brief Verifica si el backend está usando aceleración por hardware
+2 -1
View File
@@ -19,7 +19,8 @@ namespace Defaults::Video {
constexpr SDL_ScaleMode SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST; constexpr SDL_ScaleMode SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
constexpr bool FULLSCREEN = false; constexpr bool FULLSCREEN = false;
constexpr bool VSYNC = true; constexpr bool VSYNC = true;
constexpr bool INTEGER_SCALE = true; // INTEGER_SCALE eliminat: ara es part de PresentationMode (a options.hpp).
// El default es defineix literal alli: PresentationMode::INTEGER_SCALE.
constexpr bool GPU_ACCELERATION = true; constexpr bool GPU_ACCELERATION = true;
constexpr const char *GPU_PREFERRED_DRIVER = ""; constexpr const char *GPU_PREFERRED_DRIVER = "";
constexpr bool SHADER_ENABLED = false; constexpr bool SHADER_ENABLED = false;
+49 -3
View File
@@ -31,6 +31,42 @@ namespace Options {
std::string crtpi_file_path; std::string crtpi_file_path;
int current_crtpi_preset = 0; int current_crtpi_preset = 0;
// Conversions PresentationMode <-> string per a config.yaml i UI
auto presentationModeToString(PresentationMode m) -> const char * {
switch (m) {
case PresentationMode::INTEGER_SCALE:
return "integer_scale";
case PresentationMode::LETTERBOX:
return "letterbox";
case PresentationMode::STRETCHED:
return "stretched";
case PresentationMode::OVERSCAN:
return "overscan";
}
return "integer_scale";
}
auto presentationModeFromString(const std::string &s) -> PresentationMode {
if (s == "letterbox") { return PresentationMode::LETTERBOX; }
if (s == "stretched") { return PresentationMode::STRETCHED; }
if (s == "overscan") { return PresentationMode::OVERSCAN; }
return PresentationMode::INTEGER_SCALE;
}
auto nextPresentationMode(PresentationMode m) -> PresentationMode {
switch (m) {
case PresentationMode::INTEGER_SCALE:
return PresentationMode::LETTERBOX;
case PresentationMode::LETTERBOX:
return PresentationMode::STRETCHED;
case PresentationMode::STRETCHED:
return PresentationMode::OVERSCAN;
case PresentationMode::OVERSCAN:
return PresentationMode::INTEGER_SCALE;
}
return PresentationMode::INTEGER_SCALE;
}
// Lectura tolerant d'un camp YAML: assigna a `target` el valor del camp // Lectura tolerant d'un camp YAML: assigna a `target` el valor del camp
// si existeix i el tipus encaixa. Si la clau no hi és o el tipus YAML // si existeix i el tipus encaixa. Si la clau no hi és o el tipus YAML
// no és compatible amb T, conserva el valor previ de `target` (default). // no és compatible amb T, conserva el valor previ de `target` (default).
@@ -82,7 +118,16 @@ namespace Options {
parseBoolField(vid, "fullscreen", video.fullscreen); parseBoolField(vid, "fullscreen", video.fullscreen);
parseBoolField(vid, "vsync", video.vsync); parseBoolField(vid, "vsync", video.vsync);
parseBoolField(vid, "integer_scale", video.integer_scale); // presentation_mode (nou): prefereix string explicit; cau a integer_scale legacy (bool) si no.
std::string pm_str;
if (tryGet<std::string>(vid, "presentation_mode", pm_str)) {
video.presentation_mode = presentationModeFromString(pm_str);
} else {
bool legacy_integer_scale = true;
if (tryGet<bool>(vid, "integer_scale", legacy_integer_scale)) {
video.presentation_mode = legacy_integer_scale ? PresentationMode::INTEGER_SCALE : PresentationMode::LETTERBOX;
}
}
int scale_mode_int = static_cast<int>(video.scale_mode); int scale_mode_int = static_cast<int>(video.scale_mode);
if (tryGet<int>(vid, "scale_mode", scale_mode_int)) { if (tryGet<int>(vid, "scale_mode", scale_mode_int)) {
video.scale_mode = static_cast<SDL_ScaleMode>(scale_mode_int); video.scale_mode = static_cast<SDL_ScaleMode>(scale_mode_int);
@@ -197,7 +242,7 @@ namespace Options {
// En Emscripten la ventana la gestiona el navegador // En Emscripten la ventana la gestiona el navegador
window.zoom = 4; window.zoom = 4;
video.fullscreen = false; video.fullscreen = false;
video.integer_scale = true; video.presentation_mode = PresentationMode::INTEGER_SCALE;
#endif #endif
// Dispositius d'entrada per defecte // Dispositius d'entrada per defecte
@@ -283,7 +328,8 @@ namespace Options {
file << "video:\n"; file << "video:\n";
file << " fullscreen: " << boolToString(video.fullscreen) << "\n"; file << " fullscreen: " << boolToString(video.fullscreen) << "\n";
file << " vsync: " << boolToString(video.vsync) << "\n"; file << " vsync: " << boolToString(video.vsync) << "\n";
file << " integer_scale: " << boolToString(video.integer_scale) << "\n"; file << " presentation_mode: " << presentationModeToString(video.presentation_mode)
<< " # integer_scale | letterbox | stretched | overscan\n";
file << " scale_mode: " << static_cast<int>(video.scale_mode) file << " scale_mode: " << static_cast<int>(video.scale_mode)
<< " # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, " << " # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, "
<< static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n"; << static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n";
+16 -1
View File
@@ -18,6 +18,16 @@
namespace Options { namespace Options {
// Modes de presentacio del canvas virtual a la finestra. Tot fullscreen i
// windowed amb zoom no-fit el respecta; en windowed amb zoom exacte (1x/2x/3x)
// l'efecte es null perque el canvas ja encaixa amb la finestra.
enum class PresentationMode : std::uint8_t {
INTEGER_SCALE, // Multiple enter (1x, 2x, 3x...), centrat, amb barres
LETTERBOX, // Mante aspect ratio, centrat, amb barres
STRETCHED, // Omple tota la finestra, deforma l'aspect ratio
OVERSCAN // Mante aspect ratio i omple la finestra retallant el contingut fora
};
struct Window { struct Window {
std::string caption = Defaults::Window::CAPTION; std::string caption = Defaults::Window::CAPTION;
int zoom = Defaults::Window::ZOOM; int zoom = Defaults::Window::ZOOM;
@@ -42,11 +52,16 @@ namespace Options {
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE; SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
bool fullscreen = Defaults::Video::FULLSCREEN; bool fullscreen = Defaults::Video::FULLSCREEN;
bool vsync = Defaults::Video::VSYNC; bool vsync = Defaults::Video::VSYNC;
bool integer_scale = Defaults::Video::INTEGER_SCALE; PresentationMode presentation_mode = PresentationMode::INTEGER_SCALE;
GPU gpu; GPU gpu;
ShaderConfig shader; ShaderConfig shader;
}; };
// Conversions string <-> PresentationMode per a config.yaml i notificacions
auto presentationModeToString(PresentationMode m) -> const char *;
auto presentationModeFromString(const std::string &s) -> PresentationMode;
auto nextPresentationMode(PresentationMode m) -> PresentationMode;
struct Music { struct Music {
bool enabled = Defaults::Music::ENABLED; bool enabled = Defaults::Music::ENABLED;
float volume = Defaults::Music::VOLUME; float volume = Defaults::Music::VOLUME;