diff --git a/source/core/entities/entity.hpp b/source/core/entities/entity.hpp index 83cdd68..47d3d13 100644 --- a/source/core/entities/entity.hpp +++ b/source/core/entities/entity.hpp @@ -13,12 +13,11 @@ #pragma once -#include - #include #include "core/graphics/shape.hpp" #include "core/physics/rigid_body.hpp" +#include "core/rendering/render_context.hpp" #include "core/types.hpp" namespace Entities { @@ -54,7 +53,7 @@ class Entity { protected: // Estado común (acceso directo, sin overhead) - SDL_Renderer* renderer_; + Rendering::Renderer* renderer_; std::shared_ptr shape_; Vec2 center_; float angle_{0.0F}; @@ -65,7 +64,7 @@ class Entity { Physics::RigidBody body_; // Constructor protegido (clase abstracta) - Entity(SDL_Renderer* renderer = nullptr) + Entity(Rendering::Renderer* renderer = nullptr) : renderer_(renderer), center_({.x = 0.0F, .y = 0.0F}) {} }; diff --git a/source/core/graphics/starfield.cpp b/source/core/graphics/starfield.cpp index ce0fdff..632fa91 100644 --- a/source/core/graphics/starfield.cpp +++ b/source/core/graphics/starfield.cpp @@ -14,7 +14,7 @@ namespace Graphics { // Constructor -Starfield::Starfield(SDL_Renderer* renderer, +Starfield::Starfield(Rendering::Renderer* renderer, const Vec2& punt_fuga, const SDL_FRect& area, int densitat) diff --git a/source/core/graphics/starfield.hpp b/source/core/graphics/starfield.hpp index c95de8d..30ae441 100644 --- a/source/core/graphics/starfield.hpp +++ b/source/core/graphics/starfield.hpp @@ -3,6 +3,8 @@ #pragma once +#include "core/rendering/render_context.hpp" + #include #include @@ -29,7 +31,7 @@ class Starfield { // - punt_fuga: point de origin/fuga des de on surten las estrelles // - area: rectangle on actuen las estrelles (SDL_FRect) // - densitat: nombre total de estrelles (es divideix entre capes) - Starfield(SDL_Renderer* renderer, + Starfield(Rendering::Renderer* renderer, const Vec2& punt_fuga, const SDL_FRect& area, int densitat = 150); @@ -69,7 +71,7 @@ class Starfield { std::vector estrelles_; std::vector capes_; // Configuración de las 3 capes std::shared_ptr shape_estrella_; - SDL_Renderer* renderer_; + Rendering::Renderer* renderer_; // Configuración Vec2 punt_fuga_; // Vec2 de origin de las estrelles diff --git a/source/core/graphics/vector_text.cpp b/source/core/graphics/vector_text.cpp index 1648e97..9c283c6 100644 --- a/source/core/graphics/vector_text.cpp +++ b/source/core/graphics/vector_text.cpp @@ -15,7 +15,7 @@ namespace Graphics { constexpr float char_width = 20.0F; // Amplada base del caràcter constexpr float char_height = 40.0F; // Altura base del caràcter -VectorText::VectorText(SDL_Renderer* renderer) +VectorText::VectorText(Rendering::Renderer* renderer) : renderer_(renderer) { load_charset(); } diff --git a/source/core/graphics/vector_text.hpp b/source/core/graphics/vector_text.hpp index 5650fa3..6352f7b 100644 --- a/source/core/graphics/vector_text.hpp +++ b/source/core/graphics/vector_text.hpp @@ -3,6 +3,8 @@ #pragma once +#include "core/rendering/render_context.hpp" + #include #include @@ -16,7 +18,7 @@ namespace Graphics { class VectorText { public: - VectorText(SDL_Renderer* renderer); + VectorText(Rendering::Renderer* renderer); // Renderizar string completo // - text: cadena a renderizar (soporta: A-Z, a-z, 0-9, '.', ',', '-', ':', @@ -45,7 +47,7 @@ class VectorText { [[nodiscard]] bool is_supported(char c) const; private: - SDL_Renderer* renderer_; + Rendering::Renderer* renderer_; std::unordered_map> chars_; void load_charset(); diff --git a/source/core/rendering/gpu/gpu_frame_renderer.cpp b/source/core/rendering/gpu/gpu_frame_renderer.cpp index 4cdb5c4..d716749 100644 --- a/source/core/rendering/gpu/gpu_frame_renderer.cpp +++ b/source/core/rendering/gpu/gpu_frame_renderer.cpp @@ -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()); diff --git a/source/core/rendering/gpu/gpu_frame_renderer.hpp b/source/core/rendering/gpu/gpu_frame_renderer.hpp index 9a5ddd6..8505d19 100644 --- a/source/core/rendering/gpu/gpu_frame_renderer.hpp +++ b/source/core/rendering/gpu/gpu_frame_renderer.hpp @@ -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 vertices_; std::vector indices_; @@ -71,6 +84,7 @@ class GpuFrameRenderer { // Helpers internos. void flushBatch(); + void applyViewport(); }; } // namespace Rendering::GPU diff --git a/source/core/rendering/line_renderer.cpp b/source/core/rendering/line_renderer.cpp index 6d50258..a421ff8 100644 --- a/source/core/rendering/line_renderer.cpp +++ b/source/core/rendering/line_renderer.cpp @@ -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(x1); + const float FY1 = static_cast(y1); + const float FX2 = static_cast(x2); + const float FY2 = static_cast(y2); - // Aplicar brightness al color oscil·lat global - const auto R = static_cast(g_current_line_color.r * brightness); - const auto G = static_cast(g_current_line_color.g * brightness); - const auto B = static_cast(g_current_line_color.b * brightness); + // Color: aplicar brightness al color oscilatorio global. Convertir 0..255 → 0..1. + const float R = (static_cast(g_current_line_color.r) * brightness) / 255.0F; + const float G = (static_cast(g_current_line_color.g) * brightness) / 255.0F; + const float B = (static_cast(g_current_line_color.b) * brightness) / 255.0F; - SDL_SetRenderDrawColor(renderer, R, G, B, 255); - SDL_RenderLine(renderer, static_cast(PX1), static_cast(PY1), - static_cast(PX2), static_cast(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 diff --git a/source/core/rendering/line_renderer.hpp b/source/core/rendering/line_renderer.hpp index c800781..59b4843 100644 --- a/source/core/rendering/line_renderer.hpp +++ b/source/core/rendering/line_renderer.hpp @@ -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 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 diff --git a/source/core/rendering/render_context.hpp b/source/core/rendering/render_context.hpp new file mode 100644 index 0000000..c127b55 --- /dev/null +++ b/source/core/rendering/render_context.hpp @@ -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 diff --git a/source/core/rendering/sdl_manager.cpp b/source/core/rendering/sdl_manager.cpp index 0e82f98..03a576f 100644 --- a/source/core/rendering/sdl_manager.cpp +++ b/source/core/rendering/sdl_manager.cpp @@ -4,6 +4,8 @@ #include "sdl_manager.hpp" #include +#include +#include #include #include @@ -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(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(Defaults::Game::WIDTH), + static_cast(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(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(max_width_) / Defaults::Window::WIDTH; float max_zoom_height = static_cast(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(std::round( - Defaults::Window::WIDTH * zoom_factor_)); - int new_height = static_cast(std::round( - Defaults::Window::HEIGHT * zoom_factor_)); + int new_width = static_cast(std::round(Defaults::Window::WIDTH * zoom_factor_)); + int new_height = static_cast(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(std::round(Defaults::Game::WIDTH * scale)); int scaled_height = static_cast(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(offset_x), + static_cast(offset_y), + static_cast(scaled_width), + static_cast(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(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(bg.r) / 255.0F, + static_cast(bg.g) / 255.0F, + static_cast(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(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(); } diff --git a/source/core/rendering/sdl_manager.hpp b/source/core/rendering/sdl_manager.hpp index 9bd8ab1..960dafa 100644 --- a/source/core/rendering/sdl_manager.hpp +++ b/source/core/rendering/sdl_manager.hpp @@ -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 #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 diff --git a/source/core/rendering/shape_renderer.cpp b/source/core/rendering/shape_renderer.cpp index af81e56..c5a23f0 100644 --- a/source/core/rendering/shape_renderer.cpp +++ b/source/core/rendering/shape_renderer.cpp @@ -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& shape, const Vec2& position, float angle, diff --git a/source/core/rendering/shape_renderer.hpp b/source/core/rendering/shape_renderer.hpp index f894c98..6952b5a 100644 --- a/source/core/rendering/shape_renderer.hpp +++ b/source/core/rendering/shape_renderer.hpp @@ -3,6 +3,8 @@ #pragma once +#include "core/rendering/render_context.hpp" + #include #include @@ -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& shape, const Vec2& position, float angle, diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index fcdf2bc..c43cbf1 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -36,7 +36,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}; } -DebrisManager::DebrisManager(SDL_Renderer* renderer) +DebrisManager::DebrisManager(Rendering::Renderer* renderer) : renderer_(renderer) { // Inicialitzar todos los debris como inactius for (auto& debris : debris_pool_) { diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 1f79d88..0793569 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -2,6 +2,8 @@ // © 2025 Port a C++20 con SDL3 #pragma once + +#include "core/rendering/render_context.hpp" #include #include @@ -18,7 +20,7 @@ namespace Effects { // Manté un pool de objectes Debris i gestiona el seu cicle de vida class DebrisManager { public: - explicit DebrisManager(SDL_Renderer* renderer); + explicit DebrisManager(Rendering::Renderer* renderer); // Crear explosión a partir de una shape // - shape: shape vectorial a explode @@ -54,7 +56,7 @@ class DebrisManager { [[nodiscard]] int getActiveCount() const; private: - SDL_Renderer* renderer_; + Rendering::Renderer* renderer_; // Pool de fragments (màxim concurrent) // Un pentágono té 5 línies, 15 enemigos = 75 línies diff --git a/source/game/effects/floating_score_manager.cpp b/source/game/effects/floating_score_manager.cpp index dc8ca56..a03e24f 100644 --- a/source/game/effects/floating_score_manager.cpp +++ b/source/game/effects/floating_score_manager.cpp @@ -7,7 +7,7 @@ namespace Effects { -FloatingScoreManager::FloatingScoreManager(SDL_Renderer* renderer) +FloatingScoreManager::FloatingScoreManager(Rendering::Renderer* renderer) : text_(renderer) { // Inicialitzar todos los slots como inactius for (auto& pf : pool_) { diff --git a/source/game/effects/floating_score_manager.hpp b/source/game/effects/floating_score_manager.hpp index 45d7964..2b37eae 100644 --- a/source/game/effects/floating_score_manager.hpp +++ b/source/game/effects/floating_score_manager.hpp @@ -3,6 +3,8 @@ #pragma once +#include "core/rendering/render_context.hpp" + #include #include @@ -18,7 +20,7 @@ namespace Effects { // Manté un pool de FloatingScore i gestiona el seu cicle de vida class FloatingScoreManager { public: - explicit FloatingScoreManager(SDL_Renderer* renderer); + explicit FloatingScoreManager(Rendering::Renderer* renderer); // Crear número flotante // - points: value numèric (100, 150, 200) diff --git a/source/game/entities/bullet.cpp b/source/game/entities/bullet.cpp index b3a854b..3f3abe3 100644 --- a/source/game/entities/bullet.cpp +++ b/source/game/entities/bullet.cpp @@ -23,7 +23,7 @@ namespace { constexpr float BULLET_SPEED = 140.0F; } // namespace -Bullet::Bullet(SDL_Renderer* renderer) +Bullet::Bullet(Rendering::Renderer* renderer) : Entity(renderer), esta_(false), owner_id_(0), diff --git a/source/game/entities/bullet.hpp b/source/game/entities/bullet.hpp index 8b95410..b41337e 100644 --- a/source/game/entities/bullet.hpp +++ b/source/game/entities/bullet.hpp @@ -15,7 +15,7 @@ class Bullet : public Entities::Entity { public: Bullet() : Entity(nullptr) {} - Bullet(SDL_Renderer* renderer); + Bullet(Rendering::Renderer* renderer); void init() override; void disparar(const Vec2& position, float angle, uint8_t owner_id); diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index 6e1e731..32dbdc8 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -39,7 +39,7 @@ auto velocityToAngle(const Vec2& velocity) -> float { } // namespace -Enemy::Enemy(SDL_Renderer* renderer) +Enemy::Enemy(Rendering::Renderer* renderer) : Entity(renderer), drotacio_(0.0F), rotacio_(0.0F), diff --git a/source/game/entities/enemy.hpp b/source/game/entities/enemy.hpp index ccfebaf..71fe9a3 100644 --- a/source/game/entities/enemy.hpp +++ b/source/game/entities/enemy.hpp @@ -38,7 +38,7 @@ class Enemy : public Entities::Entity { public: Enemy() : Entity(nullptr) {} - Enemy(SDL_Renderer* renderer); + Enemy(Rendering::Renderer* renderer); void init() override { init(EnemyType::PENTAGON, nullptr); } void init(EnemyType type, const Vec2* ship_pos = nullptr); diff --git a/source/game/entities/ship.cpp b/source/game/entities/ship.cpp index 0f1cb13..184ccc5 100644 --- a/source/game/entities/ship.cpp +++ b/source/game/entities/ship.cpp @@ -20,7 +20,7 @@ #include "core/types.hpp" #include "game/constants.hpp" -Ship::Ship(SDL_Renderer* renderer, const char* shape_file) +Ship::Ship(Rendering::Renderer* renderer, const char* shape_file) : Entity(renderer), is_hit_(false), invulnerable_timer_(0.0F) { diff --git a/source/game/entities/ship.hpp b/source/game/entities/ship.hpp index 5e1ade5..8dc8448 100644 --- a/source/game/entities/ship.hpp +++ b/source/game/entities/ship.hpp @@ -15,7 +15,7 @@ class Ship : public Entities::Entity { public: Ship() : Entity(nullptr) {} - Ship(SDL_Renderer* renderer, const char* shape_file = "ship.shp"); + Ship(Rendering::Renderer* renderer, const char* shape_file = "ship.shp"); void init() override { init(nullptr, false); } void init(const Vec2* spawn_point, bool activar_invulnerabilitat = false); diff --git a/source/game/title/ship_animator.cpp b/source/game/title/ship_animator.cpp index 4ad4cb2..ad15a55 100644 --- a/source/game/title/ship_animator.cpp +++ b/source/game/title/ship_animator.cpp @@ -13,7 +13,7 @@ namespace Title { -ShipAnimator::ShipAnimator(SDL_Renderer* renderer) +ShipAnimator::ShipAnimator(Rendering::Renderer* renderer) : renderer_(renderer) { } diff --git a/source/game/title/ship_animator.hpp b/source/game/title/ship_animator.hpp index 47c5f8c..751957b 100644 --- a/source/game/title/ship_animator.hpp +++ b/source/game/title/ship_animator.hpp @@ -3,6 +3,8 @@ #pragma once +#include "core/rendering/render_context.hpp" + #include #include @@ -61,7 +63,7 @@ struct TitleShip { // Gestor de animación de naves para l'escena de título class ShipAnimator { public: - explicit ShipAnimator(SDL_Renderer* renderer); + explicit ShipAnimator(Rendering::Renderer* renderer); // Cicle de vida void init(); @@ -80,7 +82,7 @@ class ShipAnimator { [[nodiscard]] bool is_visible() const; // Comprova si alguna ship es visible private: - SDL_Renderer* renderer_; + Rendering::Renderer* renderer_; std::array ships_; // Naves P1 i P2 // Métodos de animación