modernitzat el sistema d'opcions

This commit is contained in:
2026-04-17 19:36:40 +02:00
parent 1bb0ebdef8
commit 7f703390f9
12 changed files with 690 additions and 553 deletions

View File

@@ -11,6 +11,8 @@
#include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
#include "core/resources/asset.h" // for Asset
#include "core/resources/resource.h"
#include "game/defaults.hpp" // for GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT
#include "game/options.hpp" // for Options::video, Options::settings
#ifndef NO_SHADERS
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // for Rendering::SDL3GPUShader
@@ -59,17 +61,14 @@ namespace {
#endif // __EMSCRIPTEN__
// Constructor
Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options) {
Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset) {
// Inicializa variables
this->window = window;
this->renderer = renderer;
this->options = options;
this->asset = asset;
gameCanvasWidth = options->gameWidth;
gameCanvasHeight = options->gameHeight;
borderWidth = options->borderWidth * 2;
borderHeight = options->borderHeight * 2;
gameCanvasWidth = GAMECANVAS_WIDTH;
gameCanvasHeight = GAMECANVAS_HEIGHT;
// Define el color del borde para el modo de pantalla completa
borderColor = {0x00, 0x00, 0x00};
@@ -80,7 +79,7 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
// Mirror del pattern de jaildoctors_dilemma (que usa exactament 256×192 i
// funciona) on `initSDLVideo` configura la presentation abans de crear cap
// textura.
setVideoMode(options->videoMode != 0);
setVideoMode(Options::video.fullscreen);
// Força al window manager a completar el resize/posicionat abans de passar
// la ventana al dispositiu GPU. Sense açò en Linux/X11 hi ha un race
@@ -92,10 +91,10 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
// ARGB8888 per simplificar el readback cap al pipeline SDL3 GPU.
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
if (gameCanvas != nullptr) {
SDL_SetTextureScaleMode(gameCanvas, options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
SDL_SetTextureScaleMode(gameCanvas, Options::video.scale_mode);
}
if (gameCanvas == nullptr) {
if (options->console) {
if (Options::settings.console) {
std::cout << "gameCanvas could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
}
@@ -182,7 +181,7 @@ void Screen::blit() {
}
SDL_SetRenderTarget(renderer, nullptr);
if (options->videoShaderEnabled) {
if (Options::video.shader.enabled) {
// Ruta normal: shader amb els seus params.
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
shader_backend_->render();
@@ -237,54 +236,54 @@ void Screen::setVideoMode(bool fullscreen) {
// Cambia entre pantalla completa y ventana
void Screen::toggleVideoMode() {
setVideoMode(options->videoMode == 0);
setVideoMode(!Options::video.fullscreen);
}
// Reduce el zoom de la ventana
auto Screen::decWindowZoom() -> bool {
if (options->videoMode != 0) { return false; }
const int PREV = options->windowSize;
options->windowSize = std::max(options->windowSize - 1, WINDOW_ZOOM_MIN);
if (options->windowSize == PREV) { return false; }
if (Options::video.fullscreen) { return false; }
const int PREV = Options::window.zoom;
Options::window.zoom = std::max(Options::window.zoom - 1, WINDOW_ZOOM_MIN);
if (Options::window.zoom == PREV) { return false; }
setVideoMode(false);
return true;
}
// Aumenta el zoom de la ventana
auto Screen::incWindowZoom() -> bool {
if (options->videoMode != 0) { return false; }
const int PREV = options->windowSize;
options->windowSize = std::min(options->windowSize + 1, WINDOW_ZOOM_MAX);
if (options->windowSize == PREV) { return false; }
if (Options::video.fullscreen) { return false; }
const int PREV = Options::window.zoom;
Options::window.zoom = std::min(Options::window.zoom + 1, WINDOW_ZOOM_MAX);
if (Options::window.zoom == PREV) { return false; }
setVideoMode(false);
return true;
}
// Establece el zoom de la ventana directamente
auto Screen::setWindowZoom(int zoom) -> bool {
if (options->videoMode != 0) { return false; }
if (Options::video.fullscreen) { return false; }
if (zoom < WINDOW_ZOOM_MIN || zoom > WINDOW_ZOOM_MAX) { return false; }
if (zoom == options->windowSize) { return false; }
options->windowSize = zoom;
if (zoom == Options::window.zoom) { return false; }
Options::window.zoom = zoom;
setVideoMode(false);
return true;
}
// Establece el escalado entero
void Screen::setIntegerScale(bool enabled) {
if (options->integerScale == enabled) { return; }
options->integerScale = enabled;
setVideoMode(options->videoMode != 0);
if (Options::video.integer_scale == enabled) { return; }
Options::video.integer_scale = enabled;
setVideoMode(Options::video.fullscreen);
}
// Alterna el escalado entero
void Screen::toggleIntegerScale() {
setIntegerScale(!options->integerScale);
setIntegerScale(!Options::video.integer_scale);
}
// Establece el V-Sync
void Screen::setVSync(bool enabled) {
options->vSync = enabled;
Options::video.vsync = enabled;
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
#ifndef NO_SHADERS
if (shader_backend_) {
@@ -295,7 +294,7 @@ void Screen::setVSync(bool enabled) {
// Alterna el V-Sync
void Screen::toggleVSync() {
setVSync(!options->vSync);
setVSync(!Options::video.vsync);
}
// Cambia el color del borde
@@ -322,15 +321,9 @@ void Screen::applyFullscreen(bool fullscreen) {
// Calcula windowWidth/Height/dest para el modo ventana y aplica SDL_SetWindowSize
void Screen::applyWindowedLayout() {
if (options->borderEnabled) {
windowWidth = gameCanvasWidth + borderWidth;
windowHeight = gameCanvasHeight + borderHeight;
dest = {0 + (borderWidth / 2), 0 + (borderHeight / 2), gameCanvasWidth, gameCanvasHeight};
} else {
windowWidth = gameCanvasWidth;
windowHeight = gameCanvasHeight;
dest = {0, 0, gameCanvasWidth, gameCanvasHeight};
}
windowWidth = gameCanvasWidth;
windowHeight = gameCanvasHeight;
dest = {0, 0, gameCanvasWidth, gameCanvasHeight};
#ifdef __EMSCRIPTEN__
windowWidth *= WASM_RENDER_SCALE;
@@ -340,7 +333,7 @@ void Screen::applyWindowedLayout() {
#endif
// Modifica el tamaño de la ventana
SDL_SetWindowSize(window, windowWidth * options->windowSize, windowHeight * options->windowSize);
SDL_SetWindowSize(window, windowWidth * Options::window.zoom, windowHeight * Options::window.zoom);
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
@@ -350,9 +343,9 @@ void Screen::applyFullscreenLayout() {
computeFullscreenGameRect();
}
// Calcula el rectángulo dest para fullscreen: integerScale / keepAspect / stretched
// Calcula el rectángulo dest para fullscreen: integer_scale / aspect ratio
void Screen::computeFullscreenGameRect() {
if (options->integerScale) {
if (Options::video.integer_scale) {
// Calcula el tamaño de la escala máxima
int scale = 0;
while (((gameCanvasWidth * (scale + 1)) <= windowWidth) && ((gameCanvasHeight * (scale + 1)) <= windowHeight)) {
@@ -363,7 +356,8 @@ void Screen::computeFullscreenGameRect() {
dest.h = gameCanvasHeight * scale;
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
} else if (options->keepAspect) {
} else {
// Manté la relació d'aspecte sense escalat enter (letterbox/pillarbox).
float ratio = (float)gameCanvasWidth / (float)gameCanvasHeight;
if ((windowWidth - gameCanvasWidth) >= (windowHeight - gameCanvasHeight)) {
dest.h = windowHeight;
@@ -376,21 +370,13 @@ void Screen::computeFullscreenGameRect() {
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
}
} else {
dest.w = windowWidth;
dest.h = windowHeight;
dest.x = dest.y = 0;
}
}
// Aplica la logical presentation y persiste el estado en options
void Screen::applyLogicalPresentation(bool fullscreen) {
SDL_SetRenderLogicalPresentation(renderer, windowWidth, windowHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
// Actualiza las opciones
options->videoMode = fullscreen ? SDL_WINDOW_FULLSCREEN : 0;
options->screen.windowWidth = windowWidth;
options->screen.windowHeight = windowHeight;
Options::video.fullscreen = fullscreen;
}
// ============================================================================
@@ -436,13 +422,13 @@ void Screen::handleCanvasResized() {
// La crida a SDL_SetWindowFullscreen + SDL_SetRenderLogicalPresentation
// que fa setVideoMode és l'única manera de resincronitzar l'estat intern
// de SDL amb el canvas HTML real.
setVideoMode(options->videoMode != 0);
setVideoMode(Options::video.fullscreen);
#endif
}
void Screen::syncFullscreenFlagFromBrowser(bool isFullscreen) {
#ifdef __EMSCRIPTEN__
options->videoMode = isFullscreen ? SDL_WINDOW_FULLSCREEN : 0;
Options::video.fullscreen = isFullscreen;
#else
(void)isFullscreen;
#endif
@@ -474,10 +460,7 @@ void Screen::applyShaderParams() {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
return;
}
const Rendering::ShaderType ACTIVE = options->videoShaderType == 1
? Rendering::ShaderType::CRTPI
: Rendering::ShaderType::POSTFX;
shader_backend_->setActiveShader(ACTIVE);
shader_backend_->setActiveShader(Options::video.shader.current_shader);
// Preset per defecte (carregador YAML pendent). Valors estil "CRT" de CCAE.
Rendering::PostFXParams POSTFX;
@@ -497,17 +480,17 @@ void Screen::initShaders() {
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
const std::string FALLBACK_DRIVER = "none";
shader_backend_->setPreferredDriver(
options->videoGpuAcceleration ? options->videoGpuPreferredDriver : FALLBACK_DRIVER);
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
}
if (!shader_backend_->isHardwareAccelerated()) {
const bool ok = shader_backend_->init(window, gameCanvas, "", "");
if (options->console) {
if (Options::settings.console) {
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
}
}
if (shader_backend_->isHardwareAccelerated()) {
shader_backend_->setScaleMode(options->integerScale);
shader_backend_->setVSync(options->vSync);
shader_backend_->setScaleMode(Options::video.integer_scale);
shader_backend_->setVSync(Options::video.vsync);
applyShaderParams(); // aplica preset del shader actiu
}
#endif
@@ -526,8 +509,8 @@ void Screen::shutdownShaders() {
}
void Screen::setGpuAcceleration(bool enabled) {
if (options->videoGpuAcceleration == enabled) { return; }
options->videoGpuAcceleration = enabled;
if (Options::video.gpu.acceleration == enabled) { return; }
Options::video.gpu.acceleration = enabled;
// Soft toggle: el backend es manté viu (vegeu shutdownShaders). El canvi
// s'aplica al proper arrencada. S'emet una notificació perquè l'usuari
// sap que ha tocat la tecla però el canvi no és immediat.
@@ -538,7 +521,7 @@ void Screen::setGpuAcceleration(bool enabled) {
}
void Screen::toggleGpuAcceleration() {
setGpuAcceleration(!options->videoGpuAcceleration);
setGpuAcceleration(!Options::video.gpu.acceleration);
}
auto Screen::isGpuAccelerated() const -> bool {
@@ -550,8 +533,8 @@ auto Screen::isGpuAccelerated() const -> bool {
}
void Screen::setShaderEnabled(bool enabled) {
if (options->videoShaderEnabled == enabled) { return; }
options->videoShaderEnabled = enabled;
if (Options::video.shader.enabled == enabled) { return; }
Options::video.shader.enabled = enabled;
#ifndef NO_SHADERS
if (enabled) {
applyShaderParams(); // restaura preset del shader actiu
@@ -566,17 +549,17 @@ void Screen::setShaderEnabled(bool enabled) {
}
void Screen::toggleShaderEnabled() {
setShaderEnabled(!options->videoShaderEnabled);
setShaderEnabled(!Options::video.shader.enabled);
}
auto Screen::isShaderEnabled() const -> bool {
return options->videoShaderEnabled;
return Options::video.shader.enabled;
}
#ifndef NO_SHADERS
void Screen::setActiveShader(Rendering::ShaderType type) {
options->videoShaderType = type == Rendering::ShaderType::CRTPI ? 1 : 0;
if (options->videoShaderEnabled) {
Options::video.shader.current_shader = type;
if (Options::video.shader.enabled) {
applyShaderParams();
}
const color_t MAGENTA = {0xFF, 0x00, 0xFF};
@@ -586,7 +569,7 @@ void Screen::setActiveShader(Rendering::ShaderType type) {
}
auto Screen::getActiveShader() const -> Rendering::ShaderType {
return options->videoShaderType == 1 ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
return Options::video.shader.current_shader;
}
#endif
@@ -597,6 +580,8 @@ void Screen::toggleActiveShader() {
: Rendering::ShaderType::POSTFX;
setActiveShader(NEXT);
#else
options->videoShaderType = options->videoShaderType == 1 ? 0 : 1;
Options::video.shader.current_shader = Options::video.shader.current_shader == Rendering::ShaderType::POSTFX
? Rendering::ShaderType::CRTPI
: Rendering::ShaderType::POSTFX;
#endif
}

View File

@@ -18,10 +18,6 @@ namespace Rendering {
class Asset;
class Text;
// Tipos de filtro
constexpr int FILTER_NEAREST = 0;
constexpr int FILTER_LINEAL = 1;
class Screen {
public:
// Constantes
@@ -35,7 +31,7 @@ class Screen {
#endif
// Constructor y destructor
Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options);
Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset);
~Screen();
// Render loop
@@ -104,15 +100,12 @@ class Screen {
SDL_Renderer *renderer; // El renderizador de la ventana
Asset *asset; // Objeto con el listado de recursos
SDL_Texture *gameCanvas; // Textura para completar la ventana de juego hasta la pantalla completa
options_t *options; // Variable con todas las opciones del programa
// Variables
int windowWidth; // Ancho de la pantalla o ventana
int windowHeight; // Alto de la pantalla o ventana
int gameCanvasWidth; // Resolución interna del juego. Es el ancho de la textura donde se dibuja el juego
int gameCanvasHeight; // Resolución interna del juego. Es el alto de la textura donde se dibuja el juego
int borderWidth; // Anchura del borde
int borderHeight; // Altura del borde
SDL_Rect dest; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
color_t borderColor; // Color del borde añadido a la textura de juego para rellenar la pantalla