Files
orni-attack/source/core/rendering/sdl_manager.cpp
T
JailDesigner fdd34eb943 refactor(#28): SDLManager rep Config::EngineConfig + on_persist callback
Pas 4/N del hallazgo #28.

SDLManager deixa d'incloure game/options.hpp. El ctor accepta ara una
Config::EngineConfig& (per llegir/mutar window i rendering) i un
opcional std::function<void()> on_persist callback.

Canvis funcionals:
- Es mantenen les mutacions de window.{width,height,zoom_factor,fullscreen}
  però ara sobre cfg_->window en lloc d'Options::window. Comportament
  idèntic perquè Options::window és un alias a engine_config.window.
- toggleVSync deixa de cridar Options::saveToFile() directament i
  invoca on_persist_ si està connectat. El Director li passa una lambda
  que fa la persistència (mantenint sdl_manager agnòstic).
- initWindowAndGpu (free function) rep el vsync inicial per paràmetre.
- Eliminat el ctor per defecte (SDLManager()) que no era cridat des de
  cap call-site del projecte.

Cleanup preexistent surfat per clang-tidy en treure el ctor default:
- finestra_, max_width_, max_height_, max_zoom_ passen a tindre
  default member initializers; eliminat el seu ctor mem-init redundant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:35:35 +02:00

313 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// sdl_manager.cpp - Implementació del gestor SDL3
// © 2026 JailDesigner
#include "sdl_manager.hpp"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <format>
#include <iostream>
#include "core/config/postfx_config.hpp"
#include "core/defaults.hpp"
#include "core/input/mouse.hpp"
#include "core/rendering/coordinate_transform.hpp"
#include "project.h"
namespace {
auto initWindowAndGpu(SDL_Window** out_window,
Rendering::Renderer& gpu_renderer,
int width,
int height,
bool fullscreen,
int initial_vsync) -> bool {
// Título estático estilo CCAE. El FPS y el estado de VSync los muestra
// el DebugOverlay (toggle F11), no la barra de título.
const std::string TITLE = std::format("© 2026 {} — JailDesigner",
Project::LONG_NAME);
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
if (fullscreen) {
flags = static_cast<SDL_WindowFlags>(flags | SDL_WINDOW_FULLSCREEN);
}
SDL_Window* window = SDL_CreateWindow(TITLE.c_str(), width, height, flags);
if (window == nullptr) {
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
return false;
}
if (!fullscreen) {
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
// Inicializar el FrameRenderer (claim del window + pipeline de líneas).
if (!gpu_renderer.init(window,
static_cast<float>(Defaults::Game::WIDTH),
static_cast<float>(Defaults::Game::HEIGHT))) {
std::cerr << "Error inicialitzant GpuFrameRenderer\n";
SDL_DestroyWindow(window);
return false;
}
gpu_renderer.setVSync(initial_vsync != 0);
// Cargar parámetros del postpro desde el resource pack. Si el YAML falta
// o falla, el loader devuelve los defaults built-in (bloom suave + flicker
// sutil + background verde tenue).
gpu_renderer.setPostFx(Config::PostFx::load("config/postfx.yaml"));
*out_window = window;
return true;
}
} // namespace
SDLManager::SDLManager(int width, int height, bool fullscreen, Config::EngineConfig& cfg, std::function<void()> on_persist)
: cfg_(&cfg),
on_persist_(std::move(on_persist)),
current_width_(width),
current_height_(height),
is_fullscreen_(fullscreen),
zoom_factor_(static_cast<float>(width) / Defaults::Window::WIDTH),
windowed_width_(width),
windowed_height_(height) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
return;
}
calculateMaxWindowSize();
if (!initWindowAndGpu(&finestra_, gpu_renderer_, current_width_, current_height_, is_fullscreen_, cfg_->rendering.vsync)) {
SDL_Quit();
return;
}
updateViewport();
// En fullscreen: forzar ocultació permanent del cursor.
if (is_fullscreen_) {
Mouse::setForceHidden(true);
}
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
<< " (logic: " << Defaults::Game::WIDTH << "x"
<< Defaults::Game::HEIGHT << ")";
if (is_fullscreen_) {
std::cout << " [FULLSCREEN]";
}
std::cout << '\n';
}
SDLManager::~SDLManager() {
gpu_renderer_.destroy();
if (finestra_ != nullptr) {
SDL_DestroyWindow(finestra_);
finestra_ = nullptr;
}
SDL_Quit();
std::cout << "SDL3 netejat correctament" << '\n';
}
void SDLManager::calculateMaxWindowSize() {
SDL_DisplayID display = SDL_GetPrimaryDisplay();
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
if (mode != nullptr) {
// Deixar marge de 100px para decoracions de l'OS
max_width_ = mode->w - 100;
max_height_ = mode->h - 100;
std::cout << "Display detectat: " << mode->w << "x" << mode->h
<< " (max finestra: " << max_width_ << "x" << max_height_ << ")"
<< '\n';
} else {
max_width_ = 1920;
max_height_ = 1080;
std::cerr << "No s'ha pogut detectar el display, usant fallback: "
<< max_width_ << "x" << max_height_ << '\n';
}
calculateMaxZoom();
}
void SDLManager::calculateMaxZoom() {
float max_zoom_width = static_cast<float>(max_width_) / Defaults::Window::WIDTH;
float max_zoom_height = static_cast<float>(max_height_) / Defaults::Window::HEIGHT;
float max_zoom_unrounded = std::min(max_zoom_width, max_zoom_height);
max_zoom_ = std::floor(max_zoom_unrounded / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
max_zoom_ = std::max(max_zoom_, Defaults::Window::MIN_ZOOM);
std::cout << "Max zoom: " << max_zoom_ << "x (display: "
<< max_width_ << "x" << max_height_ << ")" << '\n';
}
void SDLManager::applyZoom(float new_zoom) {
new_zoom = std::max(Defaults::Window::MIN_ZOOM,
std::min(new_zoom, max_zoom_));
new_zoom = std::round(new_zoom / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
if (std::abs(new_zoom - zoom_factor_) < 0.01F) {
return;
}
zoom_factor_ = new_zoom;
int new_width = static_cast<int>(std::round(Defaults::Window::WIDTH * zoom_factor_));
int new_height = static_cast<int>(std::round(Defaults::Window::HEIGHT * zoom_factor_));
applyWindowSize(new_width, new_height);
updateViewport();
windowed_width_ = new_width;
windowed_height_ = new_height;
cfg_->window.width = new_width;
cfg_->window.height = new_height;
cfg_->window.zoom_factor = zoom_factor_;
std::cout << "Zoom: " << zoom_factor_ << "x ("
<< new_width << "x" << new_height << ")" << '\n';
}
void SDLManager::updateViewport() {
// Cálculo de letterbox: el juego se renderiza a 1280×720 lógicos, pero
// la swapchain tiene el tamaño físico de la ventana. Aplicamos un viewport
// centrado con la proporción 16:9 para preservar aspect ratio.
float scale = zoom_factor_;
int scaled_width = static_cast<int>(std::round(Defaults::Game::WIDTH * scale));
int scaled_height = static_cast<int>(std::round(Defaults::Game::HEIGHT * scale));
int offset_x = (current_width_ - scaled_width) / 2;
int offset_y = (current_height_ - scaled_height) / 2;
offset_x = std::max(offset_x, 0);
offset_y = std::max(offset_y, 0);
gpu_renderer_.setViewport(static_cast<float>(offset_x),
static_cast<float>(offset_y),
static_cast<float>(scaled_width),
static_cast<float>(scaled_height));
std::cout << "Viewport: " << scaled_width << "x" << scaled_height
<< " @ (" << offset_x << "," << offset_y << ") [scale=" << scale << "]"
<< '\n';
}
void SDLManager::updateRenderingContext() const {
Rendering::g_current_scale_factor = zoom_factor_;
}
void SDLManager::increaseWindowSize() {
if (is_fullscreen_) {
return;
}
float new_zoom = zoom_factor_ + Defaults::Window::ZOOM_INCREMENT;
applyZoom(new_zoom);
std::cout << "F2: Zoom aumentat a " << zoom_factor_ << "x" << '\n';
}
void SDLManager::decreaseWindowSize() {
if (is_fullscreen_) {
return;
}
float new_zoom = zoom_factor_ - Defaults::Window::ZOOM_INCREMENT;
applyZoom(new_zoom);
std::cout << "F1: Zoom reduït a " << zoom_factor_ << "x" << '\n';
}
void SDLManager::applyWindowSize(int new_width, int new_height) {
int old_x;
int old_y;
SDL_GetWindowPosition(finestra_, &old_x, &old_y);
int old_width = current_width_;
int old_height = current_height_;
SDL_SetWindowSize(finestra_, new_width, new_height);
current_width_ = new_width;
current_height_ = new_height;
int delta_width = old_width - new_width;
int delta_height = old_height - new_height;
int new_x = old_x + (delta_width / 2);
int new_y = old_y + (delta_height / 2);
constexpr int TITLEBAR_HEIGHT = 35;
new_x = std::max(new_x, 0);
new_y = std::max(new_y, TITLEBAR_HEIGHT);
SDL_SetWindowPosition(finestra_, new_x, new_y);
updateViewport();
}
void SDLManager::toggleFullscreen() {
if (!is_fullscreen_) {
windowed_width_ = current_width_;
windowed_height_ = current_height_;
is_fullscreen_ = true;
SDL_SetWindowFullscreen(finestra_, true);
std::cout << "F3: Fullscreen activat (guardada: "
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
} else {
is_fullscreen_ = false;
SDL_SetWindowFullscreen(finestra_, false);
applyWindowSize(windowed_width_, windowed_height_);
std::cout << "F3: Fullscreen desactivat (restaurada: "
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
}
cfg_->window.fullscreen = is_fullscreen_;
Mouse::setForceHidden(is_fullscreen_);
}
auto SDLManager::handleWindowEvent(const SDL_Event& event) -> bool {
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
SDL_GetWindowSize(finestra_, &current_width_, &current_height_);
float new_zoom = static_cast<float>(current_width_) / Defaults::Window::WIDTH;
zoom_factor_ = std::max(Defaults::Window::MIN_ZOOM,
std::min(new_zoom, max_zoom_));
if (!is_fullscreen_) {
windowed_width_ = current_width_;
windowed_height_ = current_height_;
}
updateViewport();
std::cout << "Finestra redimensionada: " << current_width_
<< "x" << current_height_ << " (zoom ≈" << zoom_factor_ << "x)"
<< '\n';
return true;
}
return false;
}
auto SDLManager::clear(uint8_t r, uint8_t g, uint8_t b) -> bool {
// El fondo lo dibuja ahora el shader de postpro (background pulse). El
// offscreen se limpia en negro dentro de beginFrame. Los argumentos r/g/b
// se mantienen por compatibilidad de API.
(void)r;
(void)g;
(void)b;
// beginFrame devuelve false si la swapchain no está disponible (ventana
// minimizada, por ejemplo). Propagamos el bool al caller para que pueda
// saltarse draw+present ese frame; si no, los vértices se acumulan en
// el batch interno sin que nadie los consuma.
return gpu_renderer_.beginFrame(0.0F, 0.0F, 0.0F);
}
void SDLManager::present() {
gpu_renderer_.endFrame();
}
void SDLManager::toggleVSync() {
cfg_->rendering.vsync = (cfg_->rendering.vsync == 1) ? 0 : 1;
gpu_renderer_.setVSync(cfg_->rendering.vsync != 0);
if (on_persist_) {
on_persist_();
}
}