fa7da4ca58
El runtime de rendering pasa a SDL3 GPU. SDL_Renderer eliminado por completo del proyecto: SDLManager posee un GpuFrameRenderer y todo el resto del codigo habla con un Rendering::Renderer* opaco (alias del GpuFrameRenderer). Cambios principales: - core/rendering/render_context.hpp: alias central `using Rendering::Renderer = GPU::GpuFrameRenderer;` — punto unico de indireccion entre el juego y el backend de dibujo. - core/rendering/sdl_manager.hpp/cpp: deja de tener SDL_Renderer*; contiene un Rendering::Renderer gpu_renderer_. iniciar() ahora hace GpuDevice::init + pipeline; clear() llama beginFrame; present() llama endFrame. Letterbox se aplica via setViewport tras cada begin del render pass. toggleVSync() usa SDL_SetGPUSwapchainParameters. - core/rendering/line_renderer.hpp/cpp: la firma cambia a `linea(Renderer*, x1,y1,x2,y2, brightness, thickness)`. La implementacion deja de usar SDL_RenderLine: empuja la linea como quad extrudido al batch del GpuFrameRenderer. Se anade un grosor global configurable via setLineThickness (default 1.5 px). Ya no se aplica transform_x/y porque el shader hace logical->NDC y el viewport hace el letterbox. - gpu_frame_renderer: anade setViewport (aplicable mid-frame), setVSync (PRESENTMODE_VSYNC/IMMEDIATE) y applyViewport interno que re-aplica el viewport tras reabrir el render pass en flushBatch. - Sed sweep masivo en 19 archivos: SDL_Renderer* -> Rendering::Renderer* en headers y .cpp de entities, effects, graphics y title. Los archivos solo propagan el puntero — solo line_renderer consume sus metodos. SDL_Renderer queda eliminado del proyecto. Smoke test xvfb: backend Vulkan detectado, binario arranca, carga todos los shapes/audio/title, TitleScene inicializa, termina limpio con "Adeu!". stderr vacio. Validacion visual pendiente en hardware real (xvfb VMware sin 3D no muestra el swapchain Vulkan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
375 lines
12 KiB
C++
375 lines
12 KiB
C++
// 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/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 {
|
||
// Construir título dinámico
|
||
const std::string TITLE = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
|
||
|
||
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);
|
||
*out_window = window;
|
||
return true;
|
||
}
|
||
} // namespace
|
||
|
||
SDLManager::SDLManager()
|
||
: finestra_(nullptr),
|
||
fps_accumulator_(0.0F),
|
||
fps_frame_count_(0),
|
||
fps_display_(0),
|
||
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),
|
||
fps_accumulator_(0.0F),
|
||
fps_frame_count_(0),
|
||
fps_display_(0),
|
||
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_, ¤t_width_, ¤t_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) {
|
||
// Usar el color oscilatorio de fondo (sustituye los parámetros pasados,
|
||
// que solo existían por compatibilidad con la API anterior).
|
||
(void)r;
|
||
(void)g;
|
||
(void)b;
|
||
SDL_Color bg = color_oscillator_.getCurrentBackgroundColor();
|
||
gpu_renderer_.beginFrame(static_cast<float>(bg.r) / 255.0F,
|
||
static_cast<float>(bg.g) / 255.0F,
|
||
static_cast<float>(bg.b) / 255.0F);
|
||
}
|
||
|
||
void SDLManager::present() {
|
||
gpu_renderer_.endFrame();
|
||
}
|
||
|
||
void SDLManager::updateColors(float delta_time) {
|
||
color_oscillator_.update(delta_time);
|
||
Rendering::setLineColor(color_oscillator_.getCurrentLineColor());
|
||
}
|
||
|
||
void SDLManager::updateFPS(float delta_time) {
|
||
fps_accumulator_ += delta_time;
|
||
fps_frame_count_++;
|
||
|
||
if (fps_accumulator_ >= 0.5F) {
|
||
fps_display_ = static_cast<int>(fps_frame_count_ / fps_accumulator_);
|
||
fps_frame_count_ = 0;
|
||
fps_accumulator_ = 0.0F;
|
||
|
||
std::string vsync_state = (Options::rendering.vsync == 1) ? "ON" : "OFF";
|
||
std::string title = std::format("{} v{} ({}) - {} FPS - VSync: {}",
|
||
Project::LONG_NAME,
|
||
Project::VERSION,
|
||
Project::COPYRIGHT,
|
||
fps_display_,
|
||
vsync_state);
|
||
|
||
if (finestra_ != nullptr) {
|
||
SDL_SetWindowTitle(finestra_, title.c_str());
|
||
}
|
||
}
|
||
}
|
||
|
||
void SDLManager::setWindowTitle(const std::string& title) {
|
||
if (finestra_ != nullptr) {
|
||
SDL_SetWindowTitle(finestra_, title.c_str());
|
||
}
|
||
}
|
||
|
||
void SDLManager::toggleVSync() {
|
||
Options::rendering.vsync = (Options::rendering.vsync == 1) ? 0 : 1;
|
||
gpu_renderer_.setVSync(Options::rendering.vsync != 0);
|
||
|
||
fps_accumulator_ = 0.0F;
|
||
fps_frame_count_ = 0;
|
||
|
||
Options::saveToFile();
|
||
}
|