Fase 7a: infraestructura SDL3 GPU (dormida, sin tocar runtime)
Preparacion del pipeline GPU. Codigo nuevo aislado en
core/rendering/gpu/; el runtime sigue usando SDL_Renderer hasta
Fase 7b. Tras 7a el juego sigue funcionando identico.
Shaders (shaders/):
- line.vert.glsl: vertex shader, transforma de pixeles logicos a NDC
via uniform buffer LineUniforms{viewport_w, viewport_h}.
- line.frag.glsl: pinta el color RGBA interpolado.
Build:
- CMakeLists.txt: step nuevo que compila *.glsl a build/shaders/*.spv
con glslc. ALL depende del target 'shaders' para incluirlo en cada
build. Falla en cmake config si glslc no esta instalado.
Wrappers C++ (source/core/rendering/gpu/):
- gpu_device.hpp/cpp: GpuDevice, claim del window, loadShader desde
.spv. Backends solicitados: Vulkan + Metal (sin DirectX).
- gpu_line_pipeline.hpp/cpp: GpuLinePipeline. Vertex layout
(vec2 pos + vec4 color), primitive TRIANGLELIST (lineas como
quads), alpha blending estandar, sin culling ni depth.
- gpu_frame_renderer.hpp/cpp: GpuFrameRenderer, API alto nivel:
beginFrame / pushLine / endFrame. Extrusion perpendicular en CPU
por linea (thickness libre por linea). Un draw call por frame
con vertex+index buffers transitorios.
Plan: 7b swap del SDL_Renderer al GpuFrameRenderer en SDLManager.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,46 @@ add_custom_command(
|
|||||||
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
||||||
add_dependencies(${PROJECT_NAME} resource_pack)
|
add_dependencies(${PROJECT_NAME} resource_pack)
|
||||||
|
|
||||||
|
# --- COMPILACIÓ DE SHADERS GLSL → SPIR-V ---
|
||||||
|
# Compila tots els shaders .glsl a SPIR-V (Vulkan/Linux/Windows).
|
||||||
|
# macOS necessitarà MSL en el futur (Metal) — es generen amb spirv-cross
|
||||||
|
# o glslang amb target distint en una etapa posterior.
|
||||||
|
# Sortida: build/shaders/*.spv
|
||||||
|
find_program(GLSLC_EXE NAMES glslc HINTS ${Vulkan_GLSLC_EXECUTABLE})
|
||||||
|
if(GLSLC_EXE)
|
||||||
|
file(GLOB SHADER_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/shaders/*.glsl")
|
||||||
|
set(COMPILED_SHADERS "")
|
||||||
|
foreach(SHADER ${SHADER_SOURCES})
|
||||||
|
get_filename_component(SHADER_NAME ${SHADER} NAME)
|
||||||
|
# Detectar stage del nom: line.vert.glsl → vert, line.frag.glsl → frag
|
||||||
|
if(SHADER_NAME MATCHES "\\.vert\\.glsl$")
|
||||||
|
set(SHADER_STAGE "vert")
|
||||||
|
string(REPLACE ".glsl" ".spv" SPV_NAME ${SHADER_NAME})
|
||||||
|
elseif(SHADER_NAME MATCHES "\\.frag\\.glsl$")
|
||||||
|
set(SHADER_STAGE "frag")
|
||||||
|
string(REPLACE ".glsl" ".spv" SPV_NAME ${SHADER_NAME})
|
||||||
|
else()
|
||||||
|
message(WARNING "Shader sense stage detectat: ${SHADER_NAME} (esperat .vert.glsl o .frag.glsl)")
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
set(SPV_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${SPV_NAME}")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${SPV_OUTPUT}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/shaders"
|
||||||
|
COMMAND ${GLSLC_EXE} -fshader-stage=${SHADER_STAGE} -O ${SHADER} -o ${SPV_OUTPUT}
|
||||||
|
DEPENDS ${SHADER}
|
||||||
|
COMMENT "Compilant shader ${SHADER_NAME} → ${SPV_NAME}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND COMPILED_SHADERS ${SPV_OUTPUT})
|
||||||
|
endforeach()
|
||||||
|
add_custom_target(shaders ALL DEPENDS ${COMPILED_SHADERS})
|
||||||
|
add_dependencies(${PROJECT_NAME} shaders)
|
||||||
|
message(STATUS "Shaders trobats: ${SHADER_SOURCES}")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "glslc no trobat: instal·la 'shaderc' o 'vulkan-sdk' per compilar shaders SPIR-V")
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- STATIC ANALYSIS / FORMAT TARGETS ---
|
# --- STATIC ANALYSIS / FORMAT TARGETS ---
|
||||||
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
||||||
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
||||||
|
|||||||
+31
-21
@@ -36,36 +36,46 @@ Tag de seguridad: **`beta-3.0`** (snapshot de `main` antes de empezar).
|
|||||||
| 6a+b — `body_` en Entity + `world_` en GameScene | ✅ | `0574077` |
|
| 6a+b — `body_` en Entity + `world_` en GameScene | ✅ | `0574077` |
|
||||||
| 6c — Migrar Ship | ✅ | `2fe22ff` |
|
| 6c — Migrar Ship | ✅ | `2fe22ff` |
|
||||||
| 6d — Migrar Enemy | ✅ | `27242f5` |
|
| 6d — Migrar Enemy | ✅ | `27242f5` |
|
||||||
| **6e — Migrar Bullet** | ✅ | (este commit) |
|
| 6e — Migrar Bullet | ✅ | `9993b2d` |
|
||||||
| 7 — Migración a SDL3 GPU (sin fallback) | 🔲 **siguiente** | — |
|
| **7a — Infra GPU (shaders + wrappers, runtime dormido)** | ✅ | (este commit) |
|
||||||
|
| 7b — Swap SDL_Renderer → SDL_GPUDevice (clear/present por GPU) | 🔲 | — |
|
||||||
|
| 7c — Pipeline de líneas (quads con thickness configurable) | 🔲 | — |
|
||||||
|
| 7d — Refactor de firmas (SDL_Renderer* → FrameContext*) | 🔲 | — |
|
||||||
|
| 7e — Cleanup + smoke test final | 🔲 | — |
|
||||||
| 8 — Postprocesado, color, paleta por tipo | 🔲 | — |
|
| 8 — Postprocesado, color, paleta por tipo | 🔲 | — |
|
||||||
| 9 — Refactor de GameScene (2.877 LOC → módulos) | 🔲 | — |
|
| 9 — Refactor de GameScene (2.877 LOC → módulos) | 🔲 | — |
|
||||||
| 10 — Tuning final de masa/restitución/damping | 🔲 | — |
|
| 10 — Tuning final de masa/restitución/damping | 🔲 | — |
|
||||||
|
|
||||||
## Lo que queda inmediato (Fase 7)
|
## Lo que queda inmediato (Fase 7b)
|
||||||
|
|
||||||
**Migración del renderizado a SDL3 GPU sin fallback.**
|
**Swap del runtime de SDL_Renderer a SDL_GPUDevice.**
|
||||||
|
|
||||||
Fase 6e completada: las balas son cinemáticas dentro del PhysicsWorld (radius=0
|
Fase 7a completada: infraestructura GPU dormida (no llamada en runtime aún).
|
||||||
en el world → no colisionan físicamente, solo gameplay-level via check_collision).
|
- `shaders/line.{vert,frag}.glsl` → compilados en build a `build/shaders/*.spv`
|
||||||
Body con mass=0.5, restitution=0, damping=0. `disparar()` setea
|
(CMake step con `glslc`). Vertex layout: vec2 position + vec4 color.
|
||||||
`body_.position` + `body_.velocity` cartesiana; `update()` detecta salida de
|
Transformación a NDC vía uniform buffer `LineUniforms{viewport_w, viewport_h}`.
|
||||||
PLAYAREA y desactiva; `postUpdate()` sincroniza `center_` desde body. Smoke
|
- `core/rendering/gpu/`:
|
||||||
test xvfb pasa sin errores.
|
- `GpuDevice`: claim del window, loadShader desde .spv.
|
||||||
|
- `GpuLinePipeline`: pipeline TRIANGLELIST con vertex layout + alpha blending.
|
||||||
|
- `GpuFrameRenderer`: API `beginFrame / pushLine / endFrame`.
|
||||||
|
Extrusión perpendicular en CPU por línea (thickness configurable libre).
|
||||||
|
Un draw call por frame con un vertex/index buffer transitorio.
|
||||||
|
|
||||||
Decisión técnica del proyecto: **no fallback a SDL_Renderer**
|
Backend: Vulkan (Linux/Windows) y Metal (macOS). Sin DirectX.
|
||||||
([memoria](./.claude/projects/-home-sergio-gitea-orni-attack/memory/project_no_sdl_fallback.md)).
|
|
||||||
La fase 7 reemplaza completamente el pipeline actual de SDL_Renderer
|
|
||||||
(`SDL_RenderLine`, `SDL_RenderGeometry`) por SDL3 GPU.
|
|
||||||
|
|
||||||
Tareas previstas Fase 7:
|
Fase 7b siguiente:
|
||||||
- Sustituir `SDL_Renderer*` por `SDL_GPUDevice*` en SDLManager.
|
1. En `SDLManager::iniciar`, sustituir `SDL_CreateRenderer` por
|
||||||
- Setup de swapchain + shaders básicos (vertex + fragment) para líneas vectoriales.
|
`Rendering::GPU::GpuFrameRenderer::init(window, 1280, 720)`.
|
||||||
- Migrar `Rendering::render_shape` a un pipeline GPU que reciba vertex buffers.
|
2. `SDLManager::neteja(r,g,b)` → `gpu_renderer_.beginFrame(r/255, g/255, b/255)`.
|
||||||
- Postprocesado mínimo (Fase 8): bloom/glow, paleta por tipo de entidad.
|
3. `SDLManager::presenta()` → `gpu_renderer_.endFrame()`.
|
||||||
|
4. Borrar `renderer_` (SDL_Renderer*) del SDLManager.
|
||||||
|
5. Validar arranque con xvfb: pantalla negra (sin líneas todavía), sin crash.
|
||||||
|
|
||||||
**Fase 10 (tuning)** queda pendiente para después de SDL3 GPU + postpro. El usuario
|
Después de 7b el juego se verá NEGRO porque ningún sistema sabe pintar líneas
|
||||||
prefiere terminar la migración técnica antes de tunear feel.
|
todavía. Eso se arregla en 7c (line_renderer apunta al pipeline GPU) y 7d
|
||||||
|
(refactor de firmas SDL_Renderer* → FrameContext*).
|
||||||
|
|
||||||
|
**Fase 10 (tuning)** queda pendiente para después de SDL3 GPU + postpro.
|
||||||
|
|
||||||
## Memoria del proyecto
|
## Memoria del proyecto
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Fragment shader para líneas vectoriales.
|
||||||
|
// Pinta el color interpolado per-vertex que viene del vertex shader.
|
||||||
|
// (Postprocesado / bloom / glow son responsabilidad de la Fase 8.)
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 frag_color;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = frag_color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vertex shader para líneas vectoriales.
|
||||||
|
// Las líneas se proveen ya extrudidas en CPU como quads (2 triángulos por línea)
|
||||||
|
// con grosor configurable. El vertex shader solo:
|
||||||
|
// 1. Transforma de píxeles lógicos (0..viewport_size) a clip-space (-1..+1).
|
||||||
|
// 2. Pasa el color RGBA al fragment shader.
|
||||||
|
//
|
||||||
|
// Slot de uniform buffer 0 (vertex): viewport size para la transformación.
|
||||||
|
// Convención SDL_gpu: SDL_PushGPUVertexUniformData(cmd, 0, &ubo, sizeof(ubo)).
|
||||||
|
|
||||||
|
layout(set = 1, binding = 0) uniform UBO {
|
||||||
|
vec2 viewport_size; // ancho y alto en píxeles lógicos (ej. 1280, 720)
|
||||||
|
vec2 _padding; // alineamiento a 16 bytes
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 in_position; // píxeles lógicos
|
||||||
|
layout(location = 1) in vec4 in_color; // RGBA 0..1
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Píxeles lógicos -> NDC (-1..+1)
|
||||||
|
vec2 ndc = (in_position / ubo.viewport_size) * 2.0 - 1.0;
|
||||||
|
// Y flip: SDL screen-Y va hacia abajo, clip-Y hacia arriba.
|
||||||
|
ndc.y = -ndc.y;
|
||||||
|
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||||
|
frag_color = in_color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// gpu_device.cpp - Implementación del wrapper de SDL_GPUDevice
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_device.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/utils/path_utils.hpp"
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
GpuDevice::~GpuDevice() { destroy(); }
|
||||||
|
|
||||||
|
auto GpuDevice::init(SDL_Window* window) -> bool {
|
||||||
|
window_ = window;
|
||||||
|
|
||||||
|
// Solicitar backends en orden de preferencia: Vulkan (Linux/Windows),
|
||||||
|
// Metal (macOS). Sin DirectX según decisión de proyecto.
|
||||||
|
// SDL_GPU_SHADERFORMAT_SPIRV: shaders compilados con glslc.
|
||||||
|
// SDL_GPU_SHADERFORMAT_MSL: pendiente para macOS (Fase futura).
|
||||||
|
constexpr SDL_GPUShaderFormat SHADER_FORMATS =
|
||||||
|
SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL;
|
||||||
|
|
||||||
|
device_ = SDL_CreateGPUDevice(SHADER_FORMATS, /*debug=*/true, /*name=*/nullptr);
|
||||||
|
if (device_ == nullptr) {
|
||||||
|
std::cerr << "[GpuDevice] SDL_CreateGPUDevice falló: " << SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* driver = SDL_GetGPUDeviceDriver(device_);
|
||||||
|
std::cout << "[GpuDevice] Backend GPU: " << (driver != nullptr ? driver : "?") << '\n';
|
||||||
|
|
||||||
|
if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
|
||||||
|
std::cerr << "[GpuDevice] SDL_ClaimWindowForGPUDevice falló: " << SDL_GetError() << '\n';
|
||||||
|
SDL_DestroyGPUDevice(device_);
|
||||||
|
device_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapchain_format_ = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuDevice::destroy() {
|
||||||
|
if (device_ != nullptr) {
|
||||||
|
if (window_ != nullptr) {
|
||||||
|
SDL_ReleaseWindowFromGPUDevice(device_, window_);
|
||||||
|
}
|
||||||
|
SDL_DestroyGPUDevice(device_);
|
||||||
|
device_ = nullptr;
|
||||||
|
}
|
||||||
|
window_ = nullptr;
|
||||||
|
swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GpuDevice::loadShader(const std::string& spv_filename,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
uint32_t num_uniform_buffers) const -> SDL_GPUShader* {
|
||||||
|
if (device_ == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Los .spv viven en build/shaders/ junto al binario.
|
||||||
|
const std::string PATH = Utils::getExecutableDirectory() + "/shaders/" + spv_filename;
|
||||||
|
|
||||||
|
std::ifstream file(PATH, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "[GpuDevice] No s'ha pogut obrir el shader: " << PATH << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const std::streamsize SIZE = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
std::vector<uint8_t> buffer(static_cast<size_t>(SIZE));
|
||||||
|
if (!file.read(reinterpret_cast<char*>(buffer.data()), SIZE)) {
|
||||||
|
std::cerr << "[GpuDevice] Error llegint shader: " << PATH << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUShaderCreateInfo info{};
|
||||||
|
info.code = buffer.data();
|
||||||
|
info.code_size = buffer.size();
|
||||||
|
info.entrypoint = "main";
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||||
|
info.stage = stage;
|
||||||
|
info.num_uniform_buffers = num_uniform_buffers;
|
||||||
|
info.num_samplers = 0;
|
||||||
|
info.num_storage_buffers = 0;
|
||||||
|
info.num_storage_textures = 0;
|
||||||
|
|
||||||
|
SDL_GPUShader* shader = SDL_CreateGPUShader(device_, &info);
|
||||||
|
if (shader == nullptr) {
|
||||||
|
std::cerr << "[GpuDevice] SDL_CreateGPUShader (" << spv_filename << "): " << SDL_GetError() << '\n';
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// gpu_device.hpp - Wrapper de SDL_GPUDevice
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Ownership del SDL_GPUDevice y del claim del window. Backend preferido:
|
||||||
|
// Vulkan (Linux, Windows) y Metal (macOS). Sin DirectX.
|
||||||
|
//
|
||||||
|
// Uso:
|
||||||
|
// GpuDevice device;
|
||||||
|
// if (!device.init(window)) return -1; // claim del window
|
||||||
|
// ... renderer setup ...
|
||||||
|
// device.destroy(); // unclaim + destroy device
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
class GpuDevice {
|
||||||
|
public:
|
||||||
|
GpuDevice() = default;
|
||||||
|
~GpuDevice();
|
||||||
|
|
||||||
|
// No copia / move (RAII propietario del device).
|
||||||
|
GpuDevice(const GpuDevice&) = delete;
|
||||||
|
auto operator=(const GpuDevice&) -> GpuDevice& = delete;
|
||||||
|
GpuDevice(GpuDevice&&) = delete;
|
||||||
|
auto operator=(GpuDevice&&) -> GpuDevice& = delete;
|
||||||
|
|
||||||
|
// Crea el device y claim el window. Devuelve false si no hay backend
|
||||||
|
// soportado o si el driver no permite el claim.
|
||||||
|
[[nodiscard]] auto init(SDL_Window* window) -> bool;
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
// Carga un shader SPIR-V desde build/shaders/{name}.spv. Devuelve un
|
||||||
|
// SDL_GPUShader* del que ahora es responsable el caller (libera con
|
||||||
|
// SDL_ReleaseGPUShader). Retorna nullptr si falla.
|
||||||
|
//
|
||||||
|
// num_uniform_buffers: nº de uniform buffers que usa el shader (slot 0..N-1).
|
||||||
|
[[nodiscard]] auto loadShader(const std::string& spv_filename,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
uint32_t num_uniform_buffers) const -> SDL_GPUShader*;
|
||||||
|
|
||||||
|
[[nodiscard]] auto get() const -> SDL_GPUDevice* { return device_; }
|
||||||
|
[[nodiscard]] auto window() const -> SDL_Window* { return window_; }
|
||||||
|
[[nodiscard]] auto swapchainFormat() const -> SDL_GPUTextureFormat { return swapchain_format_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_GPUDevice* device_{nullptr};
|
||||||
|
SDL_Window* window_{nullptr};
|
||||||
|
SDL_GPUTextureFormat swapchain_format_{SDL_GPU_TEXTUREFORMAT_INVALID};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
// gpu_frame_renderer.cpp - Implementación del FrameRenderer
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
GpuFrameRenderer::~GpuFrameRenderer() { destroy(); }
|
||||||
|
|
||||||
|
auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h) -> bool {
|
||||||
|
logical_w_ = logical_w;
|
||||||
|
logical_h_ = logical_h;
|
||||||
|
|
||||||
|
if (!device_.init(window)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!pipeline_.init(device_)) {
|
||||||
|
device_.destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::destroy() {
|
||||||
|
pipeline_.destroy();
|
||||||
|
device_.destroy();
|
||||||
|
vertices_.clear();
|
||||||
|
indices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GpuFrameRenderer::beginFrame(float clear_r, float clear_g, float clear_b) -> bool {
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
if (dev == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_buffer_ = SDL_AcquireGPUCommandBuffer(dev);
|
||||||
|
if (cmd_buffer_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SDL_AcquireGPUCommandBuffer: " << SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmd_buffer_, device_.window(),
|
||||||
|
&swapchain_texture_, nullptr, nullptr)) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] WaitAndAcquire: " << SDL_GetError() << '\n';
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swapchain_texture_ == nullptr) {
|
||||||
|
// Ventana minimizada o swapchain no disponible: solo submit y salir.
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUColorTargetInfo color_target{};
|
||||||
|
color_target.texture = swapchain_texture_;
|
||||||
|
color_target.clear_color = SDL_FColor{.r = clear_r, .g = clear_g, .b = clear_b, .a = 1.0F};
|
||||||
|
color_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
|
color_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
|
color_target.cycle = false;
|
||||||
|
|
||||||
|
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &color_target, 1, nullptr);
|
||||||
|
if (render_pass_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SDL_BeginGPURenderPass: " << SDL_GetError() << '\n';
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices_.clear();
|
||||||
|
indices_.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// forman un quad (2 triángulos = 6 índices).
|
||||||
|
const float DX = x2 - x1;
|
||||||
|
const float DY = y2 - y1;
|
||||||
|
const float LEN = std::sqrt((DX * DX) + (DY * DY));
|
||||||
|
if (LEN < 1e-6F) {
|
||||||
|
return; // línea degenerada, saltar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vector unitario perpendicular (90° CCW): (-DY, DX) / LEN.
|
||||||
|
const float HALF = thickness * 0.5F;
|
||||||
|
const float NX = -DY / LEN * HALF;
|
||||||
|
const float NY = DX / LEN * HALF;
|
||||||
|
|
||||||
|
const auto BASE_INDEX = static_cast<uint16_t>(vertices_.size());
|
||||||
|
|
||||||
|
// 4 vértices del quad: top-start, bottom-start, top-end, bottom-end.
|
||||||
|
vertices_.push_back({x1 + NX, y1 + NY, r, g, b, a}); // 0: top-start
|
||||||
|
vertices_.push_back({x1 - NX, y1 - NY, r, g, b, a}); // 1: bottom-start
|
||||||
|
vertices_.push_back({x2 + NX, y2 + NY, r, g, b, a}); // 2: top-end
|
||||||
|
vertices_.push_back({x2 - NX, y2 - NY, r, g, b, a}); // 3: bottom-end
|
||||||
|
|
||||||
|
// 2 triángulos: 0-1-2 y 1-3-2 (CCW)
|
||||||
|
indices_.push_back(BASE_INDEX + 0);
|
||||||
|
indices_.push_back(BASE_INDEX + 1);
|
||||||
|
indices_.push_back(BASE_INDEX + 2);
|
||||||
|
indices_.push_back(BASE_INDEX + 1);
|
||||||
|
indices_.push_back(BASE_INDEX + 3);
|
||||||
|
indices_.push_back(BASE_INDEX + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::flushBatch() {
|
||||||
|
if (vertices_.empty() || indices_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
|
||||||
|
// Crear buffers transitorios para este frame.
|
||||||
|
const uint32_t VBO_SIZE = static_cast<uint32_t>(vertices_.size() * sizeof(LineVertex));
|
||||||
|
const uint32_t IBO_SIZE = static_cast<uint32_t>(indices_.size() * sizeof(uint16_t));
|
||||||
|
|
||||||
|
SDL_GPUBufferCreateInfo vbo_info{};
|
||||||
|
vbo_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||||
|
vbo_info.size = VBO_SIZE;
|
||||||
|
SDL_GPUBuffer* vbo = SDL_CreateGPUBuffer(dev, &vbo_info);
|
||||||
|
|
||||||
|
SDL_GPUBufferCreateInfo ibo_info{};
|
||||||
|
ibo_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
||||||
|
ibo_info.size = IBO_SIZE;
|
||||||
|
SDL_GPUBuffer* ibo = SDL_CreateGPUBuffer(dev, &ibo_info);
|
||||||
|
|
||||||
|
// Transfer buffer combinado (vertex + index) para subir los datos.
|
||||||
|
SDL_GPUTransferBufferCreateInfo tbo_info{};
|
||||||
|
tbo_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||||
|
tbo_info.size = VBO_SIZE + IBO_SIZE;
|
||||||
|
SDL_GPUTransferBuffer* tbo = SDL_CreateGPUTransferBuffer(dev, &tbo_info);
|
||||||
|
|
||||||
|
auto* mapped = static_cast<uint8_t*>(SDL_MapGPUTransferBuffer(dev, tbo, false));
|
||||||
|
std::memcpy(mapped, vertices_.data(), VBO_SIZE);
|
||||||
|
std::memcpy(mapped + VBO_SIZE, indices_.data(), IBO_SIZE);
|
||||||
|
SDL_UnmapGPUTransferBuffer(dev, tbo);
|
||||||
|
|
||||||
|
// Copy pass: subir transfer buffer → device buffers.
|
||||||
|
// Importante: el copy pass debe ejecutarse FUERA del render pass.
|
||||||
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
|
render_pass_ = nullptr;
|
||||||
|
|
||||||
|
SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd_buffer_);
|
||||||
|
SDL_GPUTransferBufferLocation vbo_src{.transfer_buffer = tbo, .offset = 0};
|
||||||
|
SDL_GPUBufferRegion vbo_dst{.buffer = vbo, .offset = 0, .size = VBO_SIZE};
|
||||||
|
SDL_UploadToGPUBuffer(copy_pass, &vbo_src, &vbo_dst, false);
|
||||||
|
SDL_GPUTransferBufferLocation ibo_src{.transfer_buffer = tbo, .offset = VBO_SIZE};
|
||||||
|
SDL_GPUBufferRegion ibo_dst{.buffer = ibo, .offset = 0, .size = IBO_SIZE};
|
||||||
|
SDL_UploadToGPUBuffer(copy_pass, &ibo_src, &ibo_dst, false);
|
||||||
|
SDL_EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
|
// Reabrir render pass (load_op = LOAD para preservar lo ya pintado).
|
||||||
|
SDL_GPUColorTargetInfo color_target{};
|
||||||
|
color_target.texture = swapchain_texture_;
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Bind pipeline + buffers + uniforms.
|
||||||
|
SDL_BindGPUGraphicsPipeline(render_pass_, pipeline_.get());
|
||||||
|
|
||||||
|
LineUniforms ubo{.viewport_width = logical_w_,
|
||||||
|
.viewport_height = logical_h_,
|
||||||
|
.padding_0 = 0.0F,
|
||||||
|
.padding_1 = 0.0F};
|
||||||
|
SDL_PushGPUVertexUniformData(cmd_buffer_, 0, &ubo, sizeof(ubo));
|
||||||
|
|
||||||
|
SDL_GPUBufferBinding vbo_binding{.buffer = vbo, .offset = 0};
|
||||||
|
SDL_BindGPUVertexBuffers(render_pass_, 0, &vbo_binding, 1);
|
||||||
|
|
||||||
|
SDL_GPUBufferBinding ibo_binding{.buffer = ibo, .offset = 0};
|
||||||
|
SDL_BindGPUIndexBuffer(render_pass_, &ibo_binding, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
SDL_DrawGPUIndexedPrimitives(render_pass_,
|
||||||
|
static_cast<uint32_t>(indices_.size()),
|
||||||
|
1, // num_instances
|
||||||
|
0, // first_index
|
||||||
|
0, // vertex_offset
|
||||||
|
0); // first_instance
|
||||||
|
|
||||||
|
// Liberar buffers transitorios (SDL los retiene hasta que el cmd buffer termine).
|
||||||
|
SDL_ReleaseGPUBuffer(dev, vbo);
|
||||||
|
SDL_ReleaseGPUBuffer(dev, ibo);
|
||||||
|
SDL_ReleaseGPUTransferBuffer(dev, tbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::endFrame() {
|
||||||
|
if (cmd_buffer_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flushBatch();
|
||||||
|
if (render_pass_ != nullptr) {
|
||||||
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
|
render_pass_ = nullptr;
|
||||||
|
}
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
swapchain_texture_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// gpu_frame_renderer.hpp - Renderer de alto nivel basado en SDL_GPU
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// API por frame:
|
||||||
|
// 1. beginFrame(clear_color) — acquire swapchain + begin render pass
|
||||||
|
// 2. pushLine(x1, y1, x2, y2, thickness, r, g, b, a) — encola la línea
|
||||||
|
// 3. endFrame() — flush del batch + submit + presenta
|
||||||
|
//
|
||||||
|
// Internamente: extruye cada línea como un quad (4 vértices, 6 índices) en CPU
|
||||||
|
// y construye un vertex buffer único por frame. Un solo draw call por frame.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_device.hpp"
|
||||||
|
#include "core/rendering/gpu/gpu_line_pipeline.hpp"
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
class GpuFrameRenderer {
|
||||||
|
public:
|
||||||
|
GpuFrameRenderer() = default;
|
||||||
|
~GpuFrameRenderer();
|
||||||
|
|
||||||
|
GpuFrameRenderer(const GpuFrameRenderer&) = delete;
|
||||||
|
auto operator=(const GpuFrameRenderer&) -> GpuFrameRenderer& = delete;
|
||||||
|
GpuFrameRenderer(GpuFrameRenderer&&) = delete;
|
||||||
|
auto operator=(GpuFrameRenderer&&) -> GpuFrameRenderer& = delete;
|
||||||
|
|
||||||
|
// Crea device + pipeline. logical_w/h = tamaño en píxeles lógicos del
|
||||||
|
// juego (1280×720) — se usa como transformación a NDC en el shader.
|
||||||
|
[[nodiscard]] auto init(SDL_Window* window, float logical_w, float logical_h) -> bool;
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
// beginFrame: adquiere swapchain, abre render pass, hace clear.
|
||||||
|
// Devuelve false si no hay textura disponible (ventana minimizada).
|
||||||
|
[[nodiscard]] auto beginFrame(float clear_r, float clear_g, float clear_b) -> bool;
|
||||||
|
|
||||||
|
// Encola una línea con grosor configurable (px). Color RGBA en [0..1].
|
||||||
|
void pushLine(float x1, float y1, float x2, float y2, float thickness,
|
||||||
|
float r, float g, float b, float a);
|
||||||
|
|
||||||
|
// endFrame: sube el VBO, ejecuta el draw, cierra render pass y presenta.
|
||||||
|
void endFrame();
|
||||||
|
|
||||||
|
// Acceso a internals (necesario para SDLManager y futuros sistemas).
|
||||||
|
[[nodiscard]] auto device() -> GpuDevice& { return device_; }
|
||||||
|
[[nodiscard]] auto isInsideFrame() const -> bool { return cmd_buffer_ != nullptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GpuDevice device_;
|
||||||
|
GpuLinePipeline pipeline_;
|
||||||
|
|
||||||
|
// Tamaño lógico del juego (para transformación NDC en el shader).
|
||||||
|
float logical_w_{1280.0F};
|
||||||
|
float logical_h_{720.0F};
|
||||||
|
|
||||||
|
// Batch del frame en curso.
|
||||||
|
std::vector<LineVertex> vertices_;
|
||||||
|
std::vector<uint16_t> indices_;
|
||||||
|
|
||||||
|
// Estado del frame en curso.
|
||||||
|
SDL_GPUCommandBuffer* cmd_buffer_{nullptr};
|
||||||
|
SDL_GPUTexture* swapchain_texture_{nullptr};
|
||||||
|
SDL_GPURenderPass* render_pass_{nullptr};
|
||||||
|
|
||||||
|
// Helpers internos.
|
||||||
|
void flushBatch();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// gpu_line_pipeline.cpp - Implementación del pipeline de líneas
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_line_pipeline.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_device.hpp"
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
GpuLinePipeline::~GpuLinePipeline() { destroy(); }
|
||||||
|
|
||||||
|
auto GpuLinePipeline::init(const GpuDevice& device) -> bool {
|
||||||
|
owner_ = device.get();
|
||||||
|
if (owner_ == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUShader* vert = device.loadShader("line.vert.spv",
|
||||||
|
SDL_GPU_SHADERSTAGE_VERTEX,
|
||||||
|
/*num_uniform_buffers=*/1);
|
||||||
|
SDL_GPUShader* frag = device.loadShader("line.frag.spv",
|
||||||
|
SDL_GPU_SHADERSTAGE_FRAGMENT,
|
||||||
|
/*num_uniform_buffers=*/0);
|
||||||
|
if ((vert == nullptr) || (frag == nullptr)) {
|
||||||
|
if (vert != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(owner_, vert);
|
||||||
|
}
|
||||||
|
if (frag != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(owner_, frag);
|
||||||
|
}
|
||||||
|
std::cerr << "[GpuLinePipeline] Error cargando shaders\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertex layout: pos (vec2) + color (vec4) → 6 floats por vertex.
|
||||||
|
SDL_GPUVertexBufferDescription vbo_desc{};
|
||||||
|
vbo_desc.slot = 0;
|
||||||
|
vbo_desc.pitch = sizeof(LineVertex);
|
||||||
|
vbo_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||||
|
vbo_desc.instance_step_rate = 0;
|
||||||
|
|
||||||
|
SDL_GPUVertexAttribute attrs[2];
|
||||||
|
attrs[0].location = 0; // in_position
|
||||||
|
attrs[0].buffer_slot = 0;
|
||||||
|
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||||
|
attrs[0].offset = 0;
|
||||||
|
attrs[1].location = 1; // in_color
|
||||||
|
attrs[1].buffer_slot = 0;
|
||||||
|
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||||
|
attrs[1].offset = sizeof(float) * 2;
|
||||||
|
|
||||||
|
SDL_GPUVertexInputState vertex_input{};
|
||||||
|
vertex_input.vertex_buffer_descriptions = &vbo_desc;
|
||||||
|
vertex_input.num_vertex_buffers = 1;
|
||||||
|
vertex_input.vertex_attributes = attrs;
|
||||||
|
vertex_input.num_vertex_attributes = 2;
|
||||||
|
|
||||||
|
// Color target = swapchain. Blending alpha estándar.
|
||||||
|
SDL_GPUColorTargetDescription color_target{};
|
||||||
|
color_target.format = device.swapchainFormat();
|
||||||
|
color_target.blend_state.enable_blend = true;
|
||||||
|
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||||
|
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||||
|
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||||
|
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||||
|
color_target.blend_state.color_write_mask =
|
||||||
|
SDL_GPU_COLORCOMPONENT_R | SDL_GPU_COLORCOMPONENT_G |
|
||||||
|
SDL_GPU_COLORCOMPONENT_B | SDL_GPU_COLORCOMPONENT_A;
|
||||||
|
|
||||||
|
SDL_GPUGraphicsPipelineTargetInfo target_info{};
|
||||||
|
target_info.color_target_descriptions = &color_target;
|
||||||
|
target_info.num_color_targets = 1;
|
||||||
|
target_info.has_depth_stencil_target = false;
|
||||||
|
|
||||||
|
SDL_GPUGraphicsPipelineCreateInfo info{};
|
||||||
|
info.vertex_shader = vert;
|
||||||
|
info.fragment_shader = frag;
|
||||||
|
info.vertex_input_state = vertex_input;
|
||||||
|
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||||
|
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
||||||
|
info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE; // No backface culling para 2D
|
||||||
|
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||||
|
info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
info.depth_stencil_state = {};
|
||||||
|
info.target_info = target_info;
|
||||||
|
|
||||||
|
pipeline_ = SDL_CreateGPUGraphicsPipeline(owner_, &info);
|
||||||
|
|
||||||
|
// Los shaders se pueden liberar tras crear el pipeline (SDL los retiene
|
||||||
|
// internamente mientras el pipeline esté vivo).
|
||||||
|
SDL_ReleaseGPUShader(owner_, vert);
|
||||||
|
SDL_ReleaseGPUShader(owner_, frag);
|
||||||
|
|
||||||
|
if (pipeline_ == nullptr) {
|
||||||
|
std::cerr << "[GpuLinePipeline] SDL_CreateGPUGraphicsPipeline: " << SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuLinePipeline::destroy() {
|
||||||
|
if ((pipeline_ != nullptr) && (owner_ != nullptr)) {
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_);
|
||||||
|
}
|
||||||
|
pipeline_ = nullptr;
|
||||||
|
owner_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// gpu_line_pipeline.hpp - Pipeline gráfico para dibujar líneas vectoriales
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Las líneas se renderizan como quads (2 triángulos = 6 índices) ya extrudidos
|
||||||
|
// en CPU según el grosor pedido por línea. Vertex layout: position (vec2) + color (vec4).
|
||||||
|
// Primitive type: TRIANGLELIST. Sin depth test (juego 2D), blending alpha estándar.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
class GpuDevice;
|
||||||
|
|
||||||
|
// Vertex layout (debe coincidir con shaders/line.vert.glsl).
|
||||||
|
struct LineVertex {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float a;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Uniform buffer del vertex shader (debe coincidir con UBO en line.vert.glsl).
|
||||||
|
struct LineUniforms {
|
||||||
|
float viewport_width;
|
||||||
|
float viewport_height;
|
||||||
|
float padding_0;
|
||||||
|
float padding_1; // Alineamiento a 16 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpuLinePipeline {
|
||||||
|
public:
|
||||||
|
GpuLinePipeline() = default;
|
||||||
|
~GpuLinePipeline();
|
||||||
|
|
||||||
|
GpuLinePipeline(const GpuLinePipeline&) = delete;
|
||||||
|
auto operator=(const GpuLinePipeline&) -> GpuLinePipeline& = delete;
|
||||||
|
GpuLinePipeline(GpuLinePipeline&&) = delete;
|
||||||
|
auto operator=(GpuLinePipeline&&) -> GpuLinePipeline& = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] auto init(const GpuDevice& device) -> bool;
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
[[nodiscard]] auto get() const -> SDL_GPUGraphicsPipeline* { return pipeline_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_GPUDevice* owner_{nullptr}; // No-owning; el GpuDevice es el dueño
|
||||||
|
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
Reference in New Issue
Block a user