Files
orni-attack/source/core/rendering/sdl_manager.cpp
T
JailDesigner fa7da4ca58 Fase 7b+c: swap atomico a SDL3 GPU (Vulkan/Metal, sin SDL_Renderer)
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>
2026-05-19 14:12:34 +02:00

375 lines
12 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/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_, &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) {
// 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();
}