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>
This commit is contained in:
@@ -76,12 +76,56 @@ auto GpuFrameRenderer::beginFrame(float clear_r, float clear_g, float clear_b) -
|
||||
cmd_buffer_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
applyViewport();
|
||||
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::setViewport(float x, float y, float w, float h) {
|
||||
viewport_x_ = x;
|
||||
viewport_y_ = y;
|
||||
viewport_w_ = w;
|
||||
viewport_h_ = h;
|
||||
// Si estamos en medio de un frame, aplicar inmediatamente.
|
||||
if (render_pass_ != nullptr) {
|
||||
applyViewport();
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::setVSync(bool enabled) {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr || device_.window() == nullptr) {
|
||||
return;
|
||||
}
|
||||
const SDL_GPUPresentMode MODE = enabled
|
||||
? SDL_GPU_PRESENTMODE_VSYNC
|
||||
: SDL_GPU_PRESENTMODE_IMMEDIATE;
|
||||
// Composition por defecto: SDR sin HDR.
|
||||
if (!SDL_SetGPUSwapchainParameters(dev, device_.window(),
|
||||
SDL_GPU_SWAPCHAINCOMPOSITION_SDR, MODE)) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_SetGPUSwapchainParameters: " << SDL_GetError() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::applyViewport() {
|
||||
if (render_pass_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (viewport_w_ <= 0.0F || viewport_h_ <= 0.0F) {
|
||||
return; // full window por defecto, no setear nada
|
||||
}
|
||||
SDL_GPUViewport vp{};
|
||||
vp.x = viewport_x_;
|
||||
vp.y = viewport_y_;
|
||||
vp.w = viewport_w_;
|
||||
vp.h = viewport_h_;
|
||||
vp.min_depth = 0.0F;
|
||||
vp.max_depth = 1.0F;
|
||||
SDL_SetGPUViewport(render_pass_, &vp);
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::pushLine(float x1, float y1, float x2, float y2, float thickness,
|
||||
float r, float g, float b, float a) {
|
||||
// Extrusión perpendicular en CPU: por cada línea generamos 4 vértices que
|
||||
@@ -167,6 +211,7 @@ void GpuFrameRenderer::flushBatch() {
|
||||
color_target.load_op = SDL_GPU_LOADOP_LOAD;
|
||||
color_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &color_target, 1, nullptr);
|
||||
applyViewport();
|
||||
|
||||
// Bind pipeline + buffers + uniforms.
|
||||
SDL_BindGPUGraphicsPipeline(render_pass_, pipeline_.get());
|
||||
|
||||
@@ -48,6 +48,13 @@ class GpuFrameRenderer {
|
||||
// endFrame: sube el VBO, ejecuta el draw, cierra render pass y presenta.
|
||||
void endFrame();
|
||||
|
||||
// Viewport en píxeles físicos de la swapchain. Si w<=0 o h<=0, se
|
||||
// usa el tamaño completo de la ventana (sin letterbox).
|
||||
void setViewport(float x, float y, float w, float h);
|
||||
|
||||
// Activa/desactiva VSync. true = SDL_GPU_PRESENTMODE_VSYNC, false = IMMEDIATE.
|
||||
void setVSync(bool enabled);
|
||||
|
||||
// Acceso a internals (necesario para SDLManager y futuros sistemas).
|
||||
[[nodiscard]] auto device() -> GpuDevice& { return device_; }
|
||||
[[nodiscard]] auto isInsideFrame() const -> bool { return cmd_buffer_ != nullptr; }
|
||||
@@ -60,6 +67,12 @@ class GpuFrameRenderer {
|
||||
float logical_w_{1280.0F};
|
||||
float logical_h_{720.0F};
|
||||
|
||||
// Viewport en píxeles físicos. <0 = full window.
|
||||
float viewport_x_{0.0F};
|
||||
float viewport_y_{0.0F};
|
||||
float viewport_w_{-1.0F};
|
||||
float viewport_h_{-1.0F};
|
||||
|
||||
// Batch del frame en curso.
|
||||
std::vector<LineVertex> vertices_;
|
||||
std::vector<uint16_t> indices_;
|
||||
@@ -71,6 +84,7 @@ class GpuFrameRenderer {
|
||||
|
||||
// Helpers internos.
|
||||
void flushBatch();
|
||||
void applyViewport();
|
||||
};
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
@@ -1,38 +1,51 @@
|
||||
// line_renderer.cpp - Implementació de renderizado de línies
|
||||
// line_renderer.cpp - Implementación de renderizado de líneas (SDL3 GPU)
|
||||
// © 1999 Visente i Sergi (versión Pascal)
|
||||
// © 2025 Port a C++20 con SDL3
|
||||
|
||||
#include "core/rendering/line_renderer.hpp"
|
||||
|
||||
#include "core/rendering/coordinate_transform.hpp"
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
// Color global compartit (actualitzat per ColorOscillator via SDLManager)
|
||||
// Color global compartido (actualizado por ColorOscillator via SDLManager).
|
||||
SDL_Color g_current_line_color = {255, 255, 255, 255};
|
||||
|
||||
void linea(SDL_Renderer* renderer, int x1, int y1, int x2, int y2, float brightness) {
|
||||
// Grosor global por defecto. Configurable via setLineThickness.
|
||||
// 1.5 da una línea visible y crujiente; 1.0 se ve demasiado fino en pantallas grandes.
|
||||
float g_current_line_thickness = 1.5F;
|
||||
|
||||
void linea(Renderer* renderer,
|
||||
int x1, int y1, int x2, int y2,
|
||||
float brightness,
|
||||
float thickness) {
|
||||
if (renderer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Transformar coordenades lògiques (640x480) a físiques (resolució real)
|
||||
const float SCALE = g_current_scale_factor;
|
||||
const int PX1 = transform_x(x1, SCALE);
|
||||
const int PY1 = transform_y(y1, SCALE);
|
||||
const int PX2 = transform_x(x2, SCALE);
|
||||
const int PY2 = transform_y(y2, SCALE);
|
||||
// Coords lógicas (1280×720). El shader hace el mapeo a NDC; el viewport
|
||||
// del SDLManager hace el letterbox a píxeles físicos.
|
||||
const float FX1 = static_cast<float>(x1);
|
||||
const float FY1 = static_cast<float>(y1);
|
||||
const float FX2 = static_cast<float>(x2);
|
||||
const float FY2 = static_cast<float>(y2);
|
||||
|
||||
// Aplicar brightness al color oscil·lat global
|
||||
const auto R = static_cast<uint8_t>(g_current_line_color.r * brightness);
|
||||
const auto G = static_cast<uint8_t>(g_current_line_color.g * brightness);
|
||||
const auto B = static_cast<uint8_t>(g_current_line_color.b * brightness);
|
||||
// Color: aplicar brightness al color oscilatorio global. Convertir 0..255 → 0..1.
|
||||
const float R = (static_cast<float>(g_current_line_color.r) * brightness) / 255.0F;
|
||||
const float G = (static_cast<float>(g_current_line_color.g) * brightness) / 255.0F;
|
||||
const float B = (static_cast<float>(g_current_line_color.b) * brightness) / 255.0F;
|
||||
|
||||
SDL_SetRenderDrawColor(renderer, R, G, B, 255);
|
||||
SDL_RenderLine(renderer, static_cast<float>(PX1), static_cast<float>(PY1),
|
||||
static_cast<float>(PX2), static_cast<float>(PY2));
|
||||
const float W = (thickness > 0.0F) ? thickness : g_current_line_thickness;
|
||||
|
||||
renderer->pushLine(FX1, FY1, FX2, FY2, W, R, G, B, 1.0F);
|
||||
}
|
||||
|
||||
void setLineColor(SDL_Color color) { g_current_line_color = color; }
|
||||
|
||||
void setLineThickness(float thickness) {
|
||||
if (thickness > 0.0F) {
|
||||
g_current_line_thickness = thickness;
|
||||
}
|
||||
}
|
||||
|
||||
auto getLineThickness() -> float { return g_current_line_thickness; }
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
// line_renderer.hpp - Renderizado de línies
|
||||
// line_renderer.hpp - Renderizado de líneas vectoriales (SDL3 GPU)
|
||||
// © 1999 Visente i Sergi (versión Pascal)
|
||||
// © 2025 Port a C++20 con SDL3
|
||||
//
|
||||
// El dibujo de líneas pasa por el pipeline GPU. Las coordenadas (x1,y1,x2,y2)
|
||||
// son lógicas (1280×720); el shader las mapea a NDC y el viewport del SDLManager
|
||||
// hace el letterbox a píxeles físicos. El brillo modula el color global de
|
||||
// línea (lo gestiona ColorOscillator). El grosor es configurable por línea
|
||||
// (parámetro thickness>0) o global (g_current_line_thickness vía setLineThickness).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/rendering/render_context.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
// Dibuixa una línia entre dos points en coordenades lògiques (640x480).
|
||||
// brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness).
|
||||
void linea(SDL_Renderer* renderer, int x1, int y1, int x2, int y2, float brightness = 1.0F);
|
||||
// Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720).
|
||||
// brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo).
|
||||
// thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness.
|
||||
void linea(Renderer* renderer,
|
||||
int x1, int y1, int x2, int y2,
|
||||
float brightness = 1.0F,
|
||||
float thickness = 0.0F);
|
||||
|
||||
// Estableix el color global de las línies (utilitzat per ColorOscillator).
|
||||
// Color global de las líneas (lo actualiza ColorOscillator vía SDLManager).
|
||||
void setLineColor(SDL_Color color);
|
||||
|
||||
// Grosor global por defecto (en píxeles lógicos). Default: 1.5.
|
||||
void setLineThickness(float thickness);
|
||||
[[nodiscard]] auto getLineThickness() -> float;
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// render_context.hpp - Alias del contexto de rendering del juego
|
||||
// © 2025 Orni Attack
|
||||
//
|
||||
// Punto único de indireción entre el resto del código y el backend de
|
||||
// rendering. El juego habla con un `Rendering::Renderer*` opaco; el alias
|
||||
// apunta a la implementación concreta (SDL3 GPU vía GpuFrameRenderer).
|
||||
//
|
||||
// Antes (BETA 3.0): se propagaba un `SDL_Renderer*` por todas las firmas.
|
||||
// Ahora: `Rendering::Renderer*`. El cambio de backend (Vulkan→Metal vía
|
||||
// SDL_gpu) no toca el código del juego.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
// Tipo único del contexto de dibujo que se pasa a las entidades, scenes,
|
||||
// effects, etc. Las llamadas concretas viven en GpuFrameRenderer.
|
||||
using Renderer = GPU::GpuFrameRenderer;
|
||||
|
||||
} // namespace Rendering
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "sdl_manager.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
|
||||
@@ -14,9 +16,45 @@
|
||||
#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),
|
||||
renderer_(nullptr),
|
||||
fps_accumulator_(0.0F),
|
||||
fps_frame_count_(0),
|
||||
fps_display_(0),
|
||||
@@ -29,58 +67,27 @@ SDLManager::SDLManager()
|
||||
windowed_width_(Defaults::Window::WIDTH),
|
||||
windowed_height_(Defaults::Window::HEIGHT),
|
||||
max_zoom_(1.0F) {
|
||||
// Inicialitzar SDL3
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// Calcular mida màxima des del display
|
||||
calculateMaxWindowSize();
|
||||
|
||||
// Construir título dinàmic
|
||||
std::string window_title = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
|
||||
|
||||
// Crear finestra CENTRADA (SDL ho hace automàticament con CENTERED)
|
||||
finestra_ =
|
||||
SDL_CreateWindow(window_title.c_str(), current_width_, current_height_,
|
||||
SDL_WINDOW_RESIZABLE // Permetre resize manual también
|
||||
);
|
||||
|
||||
if (finestra_ == nullptr) {
|
||||
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
|
||||
if (!initWindowAndGpu(&finestra_, gpu_renderer_, current_width_, current_height_, false)) {
|
||||
SDL_Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// IMPORTANT: Centrar explícitament la finestra
|
||||
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
// Crear renderer con aceleración
|
||||
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
|
||||
|
||||
if (renderer_ == nullptr) {
|
||||
std::cerr << "Error creant renderer: " << SDL_GetError() << '\n';
|
||||
SDL_DestroyWindow(finestra_);
|
||||
SDL_Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Aplicar configuración de V-Sync
|
||||
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
||||
|
||||
// CRÍTIC: Configurar viewport scaling
|
||||
updateLogicalPresentation();
|
||||
updateViewport();
|
||||
|
||||
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
|
||||
<< " (logic: " << Defaults::Game::WIDTH << "x"
|
||||
<< Defaults::Game::HEIGHT << ")" << '\n';
|
||||
}
|
||||
|
||||
// Constructor con configuración
|
||||
SDLManager::SDLManager(int width, int height, bool fullscreen)
|
||||
: finestra_(nullptr),
|
||||
renderer_(nullptr),
|
||||
fps_accumulator_(0.0F),
|
||||
fps_frame_count_(0),
|
||||
fps_display_(0),
|
||||
@@ -93,56 +100,21 @@ SDLManager::SDLManager(int width, int height, bool fullscreen)
|
||||
windowed_width_(width),
|
||||
windowed_height_(height),
|
||||
max_zoom_(1.0F) {
|
||||
// Inicialitzar SDL3
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// Calcular mida màxima des del display
|
||||
calculateMaxWindowSize();
|
||||
|
||||
// Construir título dinàmic
|
||||
std::string window_title = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
|
||||
|
||||
// Configurar flags de la finestra
|
||||
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
|
||||
if (is_fullscreen_) {
|
||||
flags = static_cast<SDL_WindowFlags>(flags | SDL_WINDOW_FULLSCREEN);
|
||||
}
|
||||
|
||||
// Crear finestra
|
||||
finestra_ = SDL_CreateWindow(window_title.c_str(), current_width_, current_height_, flags);
|
||||
|
||||
if (finestra_ == nullptr) {
|
||||
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
|
||||
if (!initWindowAndGpu(&finestra_, gpu_renderer_, current_width_, current_height_, is_fullscreen_)) {
|
||||
SDL_Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Centrar explícitament la finestra (si no es fullscreen)
|
||||
if (!is_fullscreen_) {
|
||||
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
}
|
||||
updateViewport();
|
||||
|
||||
// Crear renderer con aceleración
|
||||
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
|
||||
|
||||
if (renderer_ == nullptr) {
|
||||
std::cerr << "Error creant renderer: " << SDL_GetError() << '\n';
|
||||
SDL_DestroyWindow(finestra_);
|
||||
SDL_Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Aplicar configuración de V-Sync
|
||||
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
||||
|
||||
// Configurar viewport scaling
|
||||
updateLogicalPresentation();
|
||||
|
||||
// Inicialitzar sistema de cursor
|
||||
// En fullscreen: forzar ocultació permanent
|
||||
// En fullscreen: forzar ocultació permanent del cursor.
|
||||
if (is_fullscreen_) {
|
||||
Mouse::setForceHidden(true);
|
||||
}
|
||||
@@ -157,10 +129,7 @@ SDLManager::SDLManager(int width, int height, bool fullscreen)
|
||||
}
|
||||
|
||||
SDLManager::~SDLManager() {
|
||||
if (renderer_ != nullptr) {
|
||||
SDL_DestroyRenderer(renderer_);
|
||||
renderer_ = nullptr;
|
||||
}
|
||||
gpu_renderer_.destroy();
|
||||
|
||||
if (finestra_ != nullptr) {
|
||||
SDL_DestroyWindow(finestra_);
|
||||
@@ -183,29 +152,20 @@ void SDLManager::calculateMaxWindowSize() {
|
||||
<< " (max finestra: " << max_width_ << "x" << max_height_ << ")"
|
||||
<< '\n';
|
||||
} else {
|
||||
// Fallback conservador
|
||||
max_width_ = 1920;
|
||||
max_height_ = 1080;
|
||||
std::cerr << "No s'ha pogut detectar el display, usant fallback: "
|
||||
<< max_width_ << "x" << max_height_ << '\n';
|
||||
}
|
||||
|
||||
// Calculate max zoom immediately after determining max size
|
||||
calculateMaxZoom();
|
||||
}
|
||||
|
||||
void SDLManager::calculateMaxZoom() {
|
||||
// Maximum zoom limited by BOTH width and height (preserves 4:3)
|
||||
float max_zoom_width = static_cast<float>(max_width_) / Defaults::Window::WIDTH;
|
||||
float max_zoom_height = static_cast<float>(max_height_) / Defaults::Window::HEIGHT;
|
||||
|
||||
// Take smaller constraint
|
||||
float max_zoom_unrounded = std::min(max_zoom_width, max_zoom_height);
|
||||
|
||||
// Round DOWN to nearest 0.1 increment (user preference)
|
||||
max_zoom_ = std::floor(max_zoom_unrounded / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
|
||||
|
||||
// Safety clamp
|
||||
max_zoom_ = std::max(max_zoom_, Defaults::Window::MIN_ZOOM);
|
||||
|
||||
std::cout << "Max zoom: " << max_zoom_ << "x (display: "
|
||||
@@ -213,37 +173,25 @@ void SDLManager::calculateMaxZoom() {
|
||||
}
|
||||
|
||||
void SDLManager::applyZoom(float new_zoom) {
|
||||
// Clamp to valid range
|
||||
new_zoom = std::max(Defaults::Window::MIN_ZOOM,
|
||||
std::min(new_zoom, max_zoom_));
|
||||
|
||||
// Round to nearest 0.1 increment
|
||||
new_zoom = std::round(new_zoom / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
|
||||
|
||||
// No change?
|
||||
if (std::abs(new_zoom - zoom_factor_) < 0.01F) {
|
||||
return;
|
||||
}
|
||||
|
||||
zoom_factor_ = new_zoom;
|
||||
|
||||
// Calculate physical dimensions (4:3 maintained automatically)
|
||||
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_));
|
||||
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_));
|
||||
|
||||
// Apply to window (centers via applyWindowSize)
|
||||
applyWindowSize(new_width, new_height);
|
||||
|
||||
// Update viewport for new zoom
|
||||
updateViewport();
|
||||
|
||||
// Update windowed size cache
|
||||
windowed_width_ = new_width;
|
||||
windowed_height_ = new_height;
|
||||
|
||||
// Persist
|
||||
Options::window.width = new_width;
|
||||
Options::window.height = new_height;
|
||||
Options::window.zoom_factor = zoom_factor_;
|
||||
@@ -252,30 +200,23 @@ void SDLManager::applyZoom(float new_zoom) {
|
||||
<< new_width << "x" << new_height << ")" << '\n';
|
||||
}
|
||||
|
||||
void SDLManager::updateLogicalPresentation() {
|
||||
// CANVIAT: Ya no usem SDL_SetRenderLogicalPresentation
|
||||
// Ara renderitzem directament a resolució física per evitar pixelació irregular
|
||||
// El viewport con letterbox es configura a updateViewport()
|
||||
updateViewport();
|
||||
}
|
||||
|
||||
void SDLManager::updateViewport() {
|
||||
// Calcular dimensions físiques basades en el zoom
|
||||
// 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));
|
||||
|
||||
// Cálculo de letterbox (centrar l'àrea scaled)
|
||||
int offset_x = (current_width_ - scaled_width) / 2;
|
||||
int offset_y = (current_height_ - scaled_height) / 2;
|
||||
|
||||
// Evitar offsets negatius
|
||||
offset_x = std::max(offset_x, 0);
|
||||
offset_y = std::max(offset_y, 0);
|
||||
|
||||
// Configurar viewport per al renderizado
|
||||
SDL_Rect viewport = {offset_x, offset_y, scaled_width, scaled_height};
|
||||
SDL_SetRenderViewport(renderer_, &viewport);
|
||||
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 << "]"
|
||||
@@ -283,7 +224,6 @@ void SDLManager::updateViewport() {
|
||||
}
|
||||
|
||||
void SDLManager::updateRenderingContext() const {
|
||||
// Actualitzar el factor de scale global para todas las funciones de renderizado
|
||||
Rendering::g_current_scale_factor = zoom_factor_;
|
||||
}
|
||||
|
||||
@@ -291,10 +231,8 @@ 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';
|
||||
}
|
||||
|
||||
@@ -302,15 +240,12 @@ 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) {
|
||||
// Obtenir posición actual ABANS del resize
|
||||
int old_x;
|
||||
int old_y;
|
||||
SDL_GetWindowPosition(finestra_, &old_x, &old_y);
|
||||
@@ -318,76 +253,56 @@ void SDLManager::applyWindowSize(int new_width, int new_height) {
|
||||
int old_width = current_width_;
|
||||
int old_height = current_height_;
|
||||
|
||||
// Actualitzar mida
|
||||
SDL_SetWindowSize(finestra_, new_width, new_height);
|
||||
current_width_ = new_width;
|
||||
current_height_ = new_height;
|
||||
|
||||
// CENTRADO INTEL·LIGENT (algoritme de pollo)
|
||||
// Calcular nueva posición per mantenir la finestra centrada sobre si misma
|
||||
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);
|
||||
|
||||
// Evitar que la finestra surti de la pantalla
|
||||
constexpr int TITLEBAR_HEIGHT = 35; // Alçada aproximada de la barra de título
|
||||
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);
|
||||
|
||||
// Actualitzar viewport después del resize
|
||||
updateViewport();
|
||||
}
|
||||
|
||||
void SDLManager::toggleFullscreen() {
|
||||
if (!is_fullscreen_) {
|
||||
// ENTERING 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 {
|
||||
// EXITING FULLSCREEN
|
||||
is_fullscreen_ = false;
|
||||
SDL_SetWindowFullscreen(finestra_, false);
|
||||
|
||||
// CRITICAL: Explicitly restore windowed size
|
||||
applyWindowSize(windowed_width_, windowed_height_);
|
||||
|
||||
std::cout << "F3: Fullscreen desactivat (restaurada: "
|
||||
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
|
||||
}
|
||||
|
||||
Options::window.fullscreen = is_fullscreen_;
|
||||
|
||||
// Notificar al mòdul Mouse: Fullscreen requereix ocultació permanent del cursor.
|
||||
// Cuando es surt de fullscreen, restaurar el comportament normal de auto-ocultació.
|
||||
Mouse::setForceHidden(is_fullscreen_);
|
||||
}
|
||||
|
||||
bool SDLManager::handleWindowEvent(const SDL_Event& event) {
|
||||
auto SDLManager::handleWindowEvent(const SDL_Event& event) -> bool {
|
||||
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
||||
SDL_GetWindowSize(finestra_, ¤t_width_, ¤t_height_);
|
||||
|
||||
// Calculate zoom from actual size (may not align to 0.1 increments)
|
||||
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_));
|
||||
|
||||
// Update windowed cache (if not in fullscreen)
|
||||
if (!is_fullscreen_) {
|
||||
windowed_width_ = current_width_;
|
||||
windowed_height_ = current_height_;
|
||||
}
|
||||
|
||||
// Actualitzar viewport después del resize manual
|
||||
updateViewport();
|
||||
|
||||
std::cout << "Finestra redimensionada: " << current_width_
|
||||
@@ -399,48 +314,35 @@ bool SDLManager::handleWindowEvent(const SDL_Event& event) {
|
||||
}
|
||||
|
||||
void SDLManager::clear(uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (renderer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// [MODIFICAT] Usar color oscil·lat del fons en lloc dels parámetros
|
||||
// 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; // Suprimir warnings
|
||||
(void)b;
|
||||
SDL_Color bg = color_oscillator_.getCurrentBackgroundColor();
|
||||
SDL_SetRenderDrawColor(renderer_, bg.r, bg.g, bg.b, 255);
|
||||
SDL_RenderClear(renderer_);
|
||||
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() {
|
||||
if (renderer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer_);
|
||||
gpu_renderer_.endFrame();
|
||||
}
|
||||
|
||||
// [NUEVO] Actualitzar colors con oscil·lació
|
||||
void SDLManager::updateColors(float delta_time) {
|
||||
color_oscillator_.update(delta_time);
|
||||
|
||||
// Actualitzar color global de línies
|
||||
Rendering::setLineColor(color_oscillator_.getCurrentLineColor());
|
||||
}
|
||||
|
||||
// [NUEVO] Actualitzar counter de FPS
|
||||
void SDLManager::updateFPS(float delta_time) {
|
||||
// Acumular time i frames
|
||||
fps_accumulator_ += delta_time;
|
||||
fps_frame_count_++;
|
||||
|
||||
// Actualitzar display cada 0.5 segons
|
||||
if (fps_accumulator_ >= 0.5F) {
|
||||
fps_display_ = static_cast<int>(fps_frame_count_ / fps_accumulator_);
|
||||
fps_frame_count_ = 0;
|
||||
fps_accumulator_ = 0.0F;
|
||||
|
||||
// Actualitzar título de la finestra
|
||||
std::string vsync_state = (Options::rendering.vsync == 1) ? "ON" : "OFF";
|
||||
std::string title = std::format("{} v{} ({}) - {} FPS - VSync: {}",
|
||||
Project::LONG_NAME,
|
||||
@@ -455,27 +357,18 @@ void SDLManager::updateFPS(float delta_time) {
|
||||
}
|
||||
}
|
||||
|
||||
// [NUEVO] Actualitzar título de la finestra
|
||||
void SDLManager::setWindowTitle(const std::string& title) {
|
||||
if (finestra_ != nullptr) {
|
||||
SDL_SetWindowTitle(finestra_, title.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// [NUEVO] Toggle V-Sync (F4)
|
||||
void SDLManager::toggleVSync() {
|
||||
// Toggle: 1 → 0 → 1
|
||||
Options::rendering.vsync = (Options::rendering.vsync == 1) ? 0 : 1;
|
||||
gpu_renderer_.setVSync(Options::rendering.vsync != 0);
|
||||
|
||||
// Aplicar a SDL
|
||||
if (renderer_ != nullptr) {
|
||||
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
||||
}
|
||||
|
||||
// Reset FPS counter para evitar valores mixtos entre regímenes
|
||||
fps_accumulator_ = 0.0F;
|
||||
fps_frame_count_ = 0;
|
||||
|
||||
// Guardar configuración
|
||||
Options::saveToFile();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// sdl_manager.hpp - Gestor de inicialización de SDL3
|
||||
// © 2025 Port a C++20 con SDL3
|
||||
//
|
||||
// Tras la Fase 7 de la migración, el rendering ya no usa SDL_Renderer:
|
||||
// SDLManager posee un GpuFrameRenderer (SDL3 GPU) que es el contexto único
|
||||
// de dibujo del juego. El resto del código accede vía getRenderer() →
|
||||
// Rendering::Renderer* (alias del GpuFrameRenderer).
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -9,6 +14,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "core/rendering/color_oscillator.hpp"
|
||||
#include "core/rendering/render_context.hpp"
|
||||
|
||||
class SDLManager {
|
||||
public:
|
||||
@@ -18,14 +24,14 @@ class SDLManager {
|
||||
|
||||
// No permetre còpia ni assignació
|
||||
SDLManager(const SDLManager&) = delete;
|
||||
SDLManager& operator=(const SDLManager&) = delete;
|
||||
auto operator=(const SDLManager&) -> SDLManager& = delete;
|
||||
|
||||
// [NUEVO] Gestió de finestra dinàmica
|
||||
void increaseWindowSize(); // F2: +100px
|
||||
void decreaseWindowSize(); // F1: -100px
|
||||
void toggleFullscreen(); // F3
|
||||
void toggleVSync(); // F4
|
||||
bool handleWindowEvent(const SDL_Event& event); // Per a SDL_EVENT_WINDOW_RESIZED
|
||||
auto handleWindowEvent(const SDL_Event& event) -> bool; // Per a SDL_EVENT_WINDOW_RESIZED
|
||||
|
||||
// Funciones principals (renderizado)
|
||||
void clear(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0);
|
||||
@@ -38,8 +44,8 @@ class SDLManager {
|
||||
void updateFPS(float delta_time);
|
||||
|
||||
// Getters
|
||||
SDL_Renderer* getRenderer() { return renderer_; }
|
||||
[[nodiscard]] float getScaleFactor() const { return zoom_factor_; }
|
||||
auto getRenderer() -> Rendering::Renderer* { return &gpu_renderer_; }
|
||||
[[nodiscard]] auto getScaleFactor() const -> float { return zoom_factor_; }
|
||||
|
||||
// [NUEVO] Actualitzar título de la finestra
|
||||
void setWindowTitle(const std::string& title);
|
||||
@@ -49,7 +55,7 @@ class SDLManager {
|
||||
|
||||
private:
|
||||
SDL_Window* finestra_;
|
||||
SDL_Renderer* renderer_;
|
||||
Rendering::Renderer gpu_renderer_; // GpuFrameRenderer (SDL3 GPU)
|
||||
|
||||
// [NUEVO] Variables FPS
|
||||
float fps_accumulator_;
|
||||
@@ -74,7 +80,6 @@ class SDLManager {
|
||||
void calculateMaxZoom(); // Calculate max zoom from display
|
||||
void applyZoom(float new_zoom); // Apply zoom and resize window
|
||||
void applyWindowSize(int width, int height); // Canviar mida + centrar
|
||||
void updateLogicalPresentation(); // Actualitzar viewport
|
||||
void updateViewport(); // Configurar viewport con letterbox
|
||||
|
||||
// [NUEVO] Oscil·lador de colors
|
||||
|
||||
@@ -72,7 +72,7 @@ static Vec2 transform_point(const Vec2& point, const Vec2& shape_centre, const V
|
||||
return {.x = rotated_x + position.x, .y = rotated_y + position.y};
|
||||
}
|
||||
|
||||
void render_shape(SDL_Renderer* renderer,
|
||||
void render_shape(Rendering::Renderer* renderer,
|
||||
const std::shared_ptr<Graphics::Shape>& shape,
|
||||
const Vec2& position,
|
||||
float angle,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/rendering/render_context.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory>
|
||||
@@ -40,7 +42,7 @@ struct Rotation3D {
|
||||
// - scale: factor de scale (1.0 = mida original)
|
||||
// - progress: progrés de l'animación (0.0-1.0, default 1.0 = tot visible)
|
||||
// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness)
|
||||
void render_shape(SDL_Renderer* renderer,
|
||||
void render_shape(Rendering::Renderer* renderer,
|
||||
const std::shared_ptr<Graphics::Shape>& shape,
|
||||
const Vec2& position,
|
||||
float angle,
|
||||
|
||||
Reference in New Issue
Block a user