Files
orni-attack/source/core/rendering/sdl_manager.cpp
T
JailDesigner 0573022b7c Debug overlay (FPS + VSync) toggleable con F11
Crea core/system/DebugOverlay como sistema global propiedad del Director
que muestra FPS y estado de VSync en la esquina superior izquierda usando
VectorText. Visible por defecto en builds debug, oculto en release; F11
alterna.

Cambios:

- Nuevo DebugOverlay con su propio contador de FPS interno (cadencia 0.5s).
  El cálculo que vivía en SDLManager::updateFPS se mueve aquí.
- Director construye el overlay una vez y lo pasa a runFrameLoop. F11 se
  intercepta directamente en el event loop del Director (no en
  GlobalEvents para no acoplar la firma a la presencia del overlay).
- Limpieza de SDLManager: fuera updateFPS, updateColors (era no-op desde
  Fase 8c), setWindowTitle (no se usaba) y los campos fps_*.
- Título de ventana estático estilo CCAE:
    © 2026 Orni Attack — JailDesigner
  Ya no se reescribe cada 0.5s con FPS y VSync; ese estado vive en el
  overlay.

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

338 lines
11 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
// © 2025 Port a C++20 con SDL3
#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 "core/rendering/line_renderer.hpp"
#include "game/options.hpp"
#include "project.h"
namespace {
auto initWindowAndGpu(SDL_Window** out_window,
Rendering::Renderer& gpu_renderer,
int width, int height, bool fullscreen) -> 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(Options::rendering.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()
: finestra_(nullptr),
current_width_(Defaults::Window::WIDTH),
current_height_(Defaults::Window::HEIGHT),
is_fullscreen_(false),
max_width_(1920),
max_height_(1080),
zoom_factor_(Defaults::Window::BASE_ZOOM),
windowed_width_(Defaults::Window::WIDTH),
windowed_height_(Defaults::Window::HEIGHT),
max_zoom_(1.0F) {
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_, false)) {
SDL_Quit();
return;
}
updateViewport();
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
<< " (logic: " << Defaults::Game::WIDTH << "x"
<< Defaults::Game::HEIGHT << ")" << '\n';
}
SDLManager::SDLManager(int width, int height, bool fullscreen)
: finestra_(nullptr),
current_width_(width),
current_height_(height),
is_fullscreen_(fullscreen),
max_width_(1920),
max_height_(1080),
zoom_factor_(static_cast<float>(width) / Defaults::Window::WIDTH),
windowed_width_(width),
windowed_height_(height),
max_zoom_(1.0F) {
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_)) {
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;
Options::window.width = new_width;
Options::window.height = new_height;
Options::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';
}
Options::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;
}
void SDLManager::clear(uint8_t r, uint8_t g, uint8_t b) {
// 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;
gpu_renderer_.beginFrame(0.0F, 0.0F, 0.0F);
}
void SDLManager::present() {
gpu_renderer_.endFrame();
}
void SDLManager::toggleVSync() {
Options::rendering.vsync = (Options::rendering.vsync == 1) ? 0 : 1;
gpu_renderer_.setVSync(Options::rendering.vsync != 0);
Options::saveToFile();
}