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:
2026-05-19 14:12:34 +02:00
parent ba6fd00b54
commit fa7da4ca58
26 changed files with 245 additions and 226 deletions
@@ -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