fix(vsync): comprovar suport de present mode i loggejar el mode efectiu
setVSync demanava SDL_GPU_PRESENTMODE_IMMEDIATE sense comprovar suport. A SDL_GPU només VSYNC està garantit; IMMEDIATE i MAILBOX són opcionals. Si no estaven suportats (típicament Wayland/X11 amb compositor), SDL retornava error i la swapchain es quedava en VSYNC sense que ho sabéssim. Ara: - Consultem SDL_WindowSupportsGPUPresentMode abans de fer la crida. - En VSync OFF: provem IMMEDIATE → fallback a MAILBOX → si cap, ens quedem en VSYNC i avisem (driver/compositor força VSync). - Loggejem sempre el mode efectiu (no només els errors), perquè ara mateix no hi havia forma de saber des de fora si el toggle havia tingut efecte.
This commit is contained in:
@@ -12,389 +12,424 @@
|
||||
|
||||
namespace Rendering::GPU {
|
||||
|
||||
GpuFrameRenderer::~GpuFrameRenderer() { destroy(); }
|
||||
GpuFrameRenderer::~GpuFrameRenderer() { destroy(); }
|
||||
|
||||
auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h) -> bool {
|
||||
logical_w_ = logical_w;
|
||||
logical_h_ = logical_h;
|
||||
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 (!device_.init(window)) {
|
||||
return false;
|
||||
}
|
||||
// Pipeline de líneas: escribe sobre el offscreen (formato fijo).
|
||||
if (!line_pipeline_.init(device_, offscreen_format_)) {
|
||||
device_.destroy();
|
||||
return false;
|
||||
}
|
||||
// Pipeline de postpro: escribe sobre swapchain (formato del swapchain).
|
||||
if (!postfx_pipeline_.init(device_, device_.swapchainFormat())) {
|
||||
line_pipeline_.destroy();
|
||||
device_.destroy();
|
||||
return false;
|
||||
}
|
||||
if (!createOffscreen()) {
|
||||
postfx_pipeline_.destroy();
|
||||
line_pipeline_.destroy();
|
||||
device_.destroy();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Pipeline de líneas: escribe sobre el offscreen (formato fijo).
|
||||
if (!line_pipeline_.init(device_, offscreen_format_)) {
|
||||
device_.destroy();
|
||||
return false;
|
||||
|
||||
auto GpuFrameRenderer::createOffscreen() -> bool {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Textura offscreen del tamaño lógico del juego, COLOR_TARGET + SAMPLER.
|
||||
SDL_GPUTextureCreateInfo tex_info{};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = offscreen_format_;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.width = static_cast<uint32_t>(logical_w_);
|
||||
tex_info.height = static_cast<uint32_t>(logical_h_);
|
||||
tex_info.layer_count_or_depth = 1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
offscreen_texture_ = SDL_CreateGPUTexture(dev, &tex_info);
|
||||
if (offscreen_texture_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_CreateGPUTexture (offscreen): "
|
||||
<< SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sampler lineal con clamp-to-edge (evita sangrado en los bordes del bloom).
|
||||
SDL_GPUSamplerCreateInfo sampler_info{};
|
||||
sampler_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||
sampler_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||
sampler_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
|
||||
sampler_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
sampler_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
sampler_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
linear_sampler_ = SDL_CreateGPUSampler(dev, &sampler_info);
|
||||
if (linear_sampler_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_CreateGPUSampler: "
|
||||
<< SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Pipeline de postpro: escribe sobre swapchain (formato del swapchain).
|
||||
if (!postfx_pipeline_.init(device_, device_.swapchainFormat())) {
|
||||
line_pipeline_.destroy();
|
||||
device_.destroy();
|
||||
return false;
|
||||
|
||||
void GpuFrameRenderer::destroyOffscreen() {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr) {
|
||||
offscreen_texture_ = nullptr;
|
||||
linear_sampler_ = nullptr;
|
||||
return;
|
||||
}
|
||||
if (offscreen_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(dev, offscreen_texture_);
|
||||
offscreen_texture_ = nullptr;
|
||||
}
|
||||
if (linear_sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(dev, linear_sampler_);
|
||||
linear_sampler_ = nullptr;
|
||||
}
|
||||
}
|
||||
if (!createOffscreen()) {
|
||||
|
||||
void GpuFrameRenderer::destroy() {
|
||||
destroyOffscreen();
|
||||
postfx_pipeline_.destroy();
|
||||
line_pipeline_.destroy();
|
||||
device_.destroy();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto GpuFrameRenderer::createOffscreen() -> bool {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
}
|
||||
|
||||
// Textura offscreen del tamaño lógico del juego, COLOR_TARGET + SAMPLER.
|
||||
SDL_GPUTextureCreateInfo tex_info{};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = offscreen_format_;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.width = static_cast<uint32_t>(logical_w_);
|
||||
tex_info.height = static_cast<uint32_t>(logical_h_);
|
||||
tex_info.layer_count_or_depth = 1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
offscreen_texture_ = SDL_CreateGPUTexture(dev, &tex_info);
|
||||
if (offscreen_texture_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_CreateGPUTexture (offscreen): "
|
||||
<< SDL_GetError() << '\n';
|
||||
return false;
|
||||
auto GpuFrameRenderer::beginFrame(float clear_r, float clear_g, float clear_b) -> bool {
|
||||
// Los clear_* se ignoran: el fondo lo pinta el postpro. Mantenemos la
|
||||
// firma para no romper el SDLManager.
|
||||
(void)clear_r;
|
||||
(void)clear_g;
|
||||
(void)clear_b;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Abrir render pass sobre OFFSCREEN con clear a negro.
|
||||
SDL_GPUColorTargetInfo color_target{};
|
||||
color_target.texture = offscreen_texture_;
|
||||
color_target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .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 (offscreen): "
|
||||
<< SDL_GetError() << '\n';
|
||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||
cmd_buffer_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
// Sin SetGPUViewport: el offscreen se llena entero a tamaño lógico.
|
||||
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sampler lineal con clamp-to-edge (evita sangrado en los bordes del bloom).
|
||||
SDL_GPUSamplerCreateInfo sampler_info{};
|
||||
sampler_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||
sampler_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||
sampler_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
|
||||
sampler_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
sampler_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
sampler_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
linear_sampler_ = SDL_CreateGPUSampler(dev, &sampler_info);
|
||||
if (linear_sampler_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_CreateGPUSampler: "
|
||||
<< SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::destroyOffscreen() {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr) {
|
||||
offscreen_texture_ = nullptr;
|
||||
linear_sampler_ = nullptr;
|
||||
return;
|
||||
}
|
||||
if (offscreen_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(dev, offscreen_texture_);
|
||||
offscreen_texture_ = nullptr;
|
||||
}
|
||||
if (linear_sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(dev, linear_sampler_);
|
||||
linear_sampler_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::destroy() {
|
||||
destroyOffscreen();
|
||||
postfx_pipeline_.destroy();
|
||||
line_pipeline_.destroy();
|
||||
device_.destroy();
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
}
|
||||
|
||||
auto GpuFrameRenderer::beginFrame(float clear_r, float clear_g, float clear_b) -> bool {
|
||||
// Los clear_* se ignoran: el fondo lo pinta el postpro. Mantenemos la
|
||||
// firma para no romper el SDLManager.
|
||||
(void)clear_r;
|
||||
(void)clear_g;
|
||||
(void)clear_b;
|
||||
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
void GpuFrameRenderer::setViewport(float x, float y, float w, float h) {
|
||||
viewport_x_ = x;
|
||||
viewport_y_ = y;
|
||||
viewport_w_ = w;
|
||||
viewport_h_ = h;
|
||||
// El viewport solo se aplica en el pase final (composite). Si estamos
|
||||
// ya dentro del composite, lo aplicaríamos inmediatamente, pero la API
|
||||
// está pensada para llamarse antes de endFrame/al cambiar de ventana.
|
||||
}
|
||||
|
||||
cmd_buffer_ = SDL_AcquireGPUCommandBuffer(dev);
|
||||
if (cmd_buffer_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_AcquireGPUCommandBuffer: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
void GpuFrameRenderer::setVSync(bool enabled) {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
SDL_Window* win = device_.window();
|
||||
if (dev == nullptr || win == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A SDL_GPU, només VSYNC està garantit. IMMEDIATE i MAILBOX són opcionals
|
||||
// i poden no estar disponibles segons backend/driver/compositor (típicament
|
||||
// Wayland sense unredirect, X11 amb compositor agressiu, etc.). Si demanem
|
||||
// un mode no suportat, SDL retorna error i la swapchain queda igual.
|
||||
//
|
||||
// Estratègia: si l'usuari demana VSync OFF, provem IMMEDIATE i si no està
|
||||
// suportat fem fallback a MAILBOX (no és no-VSync però sí permet superar
|
||||
// els 60Hz sense tearing). Si cap dels dos, ens quedem amb VSYNC i avisem.
|
||||
auto try_set = [&](SDL_GPUPresentMode mode, const char* name) -> bool {
|
||||
if (!SDL_WindowSupportsGPUPresentMode(dev, win, mode)) {
|
||||
return false;
|
||||
}
|
||||
if (!SDL_SetGPUSwapchainParameters(dev, win, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, mode)) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_SetGPUSwapchainParameters("
|
||||
<< name << "): " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
std::cout << "[GpuFrameRenderer] Present mode: " << name << '\n';
|
||||
return true;
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
try_set(SDL_GPU_PRESENTMODE_VSYNC, "VSYNC");
|
||||
return;
|
||||
}
|
||||
|
||||
if (try_set(SDL_GPU_PRESENTMODE_IMMEDIATE, "IMMEDIATE")) {
|
||||
return;
|
||||
}
|
||||
if (try_set(SDL_GPU_PRESENTMODE_MAILBOX, "MAILBOX (fallback)")) {
|
||||
return;
|
||||
}
|
||||
// Tots dos rebutjats: el driver/compositor força VSync. Hi tornem
|
||||
// explícitament perquè la swapchain quedi en estat conegut.
|
||||
std::cerr << "[GpuFrameRenderer] VSync OFF no disponible (driver/compositor "
|
||||
"força VSYNC). Mantenim VSYNC.\n";
|
||||
try_set(SDL_GPU_PRESENTMODE_VSYNC, "VSYNC (forçat)");
|
||||
}
|
||||
|
||||
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;
|
||||
void GpuFrameRenderer::applyFinalViewport() {
|
||||
if (render_pass_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (viewport_w_ <= 0.0F || viewport_h_ <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (swapchain_texture_ == nullptr) {
|
||||
// Ventana minimizada o swapchain no disponible: solo submit y salir.
|
||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||
cmd_buffer_ = nullptr;
|
||||
return false;
|
||||
void GpuFrameRenderer::pushLine(float x1, float y1, float x2, float y2, float thickness, float r, float g, float b, float a) {
|
||||
const float DX = x2 - x1;
|
||||
const float DY = y2 - y1;
|
||||
const float LEN = std::sqrt((DX * DX) + (DY * DY));
|
||||
if (LEN < 1e-6F) {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
vertices_.push_back({x1 + NX, y1 + NY, r, g, b, a});
|
||||
vertices_.push_back({x1 - NX, y1 - NY, r, g, b, a});
|
||||
vertices_.push_back({x2 + NX, y2 + NY, r, g, b, a});
|
||||
vertices_.push_back({x2 - NX, y2 - NY, r, g, b, a});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Abrir render pass sobre OFFSCREEN con clear a negro.
|
||||
SDL_GPUColorTargetInfo color_target{};
|
||||
color_target.texture = offscreen_texture_;
|
||||
color_target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
color_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
color_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
color_target.cycle = false;
|
||||
void GpuFrameRenderer::flushBatch() {
|
||||
if (vertices_.empty() || indices_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &color_target, 1, nullptr);
|
||||
if (render_pass_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_BeginGPURenderPass (offscreen): "
|
||||
<< SDL_GetError() << '\n';
|
||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||
cmd_buffer_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
// Sin SetGPUViewport: el offscreen se llena entero a tamaño lógico.
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
return true;
|
||||
}
|
||||
const auto VBO_SIZE = static_cast<uint32_t>(vertices_.size() * sizeof(LineVertex));
|
||||
const auto IBO_SIZE = static_cast<uint32_t>(indices_.size() * sizeof(uint16_t));
|
||||
|
||||
void GpuFrameRenderer::setViewport(float x, float y, float w, float h) {
|
||||
viewport_x_ = x;
|
||||
viewport_y_ = y;
|
||||
viewport_w_ = w;
|
||||
viewport_h_ = h;
|
||||
// El viewport solo se aplica en el pase final (composite). Si estamos
|
||||
// ya dentro del composite, lo aplicaríamos inmediatamente, pero la API
|
||||
// está pensada para llamarse antes de endFrame/al cambiar de ventana.
|
||||
}
|
||||
SDL_GPUBufferCreateInfo vbo_info{};
|
||||
vbo_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
vbo_info.size = VBO_SIZE;
|
||||
SDL_GPUBuffer* vbo = SDL_CreateGPUBuffer(dev, &vbo_info);
|
||||
|
||||
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;
|
||||
if (!SDL_SetGPUSwapchainParameters(dev, device_.window(),
|
||||
SDL_GPU_SWAPCHAINCOMPOSITION_SDR, MODE)) {
|
||||
std::cerr << "[GpuFrameRenderer] SDL_SetGPUSwapchainParameters: " << SDL_GetError() << '\n';
|
||||
}
|
||||
}
|
||||
SDL_GPUBufferCreateInfo ibo_info{};
|
||||
ibo_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
||||
ibo_info.size = IBO_SIZE;
|
||||
SDL_GPUBuffer* ibo = SDL_CreateGPUBuffer(dev, &ibo_info);
|
||||
|
||||
void GpuFrameRenderer::applyFinalViewport() {
|
||||
if (render_pass_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (viewport_w_ <= 0.0F || viewport_h_ <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
||||
void GpuFrameRenderer::pushLine(float x1, float y1, float x2, float y2, float thickness,
|
||||
float r, float g, float b, float a) {
|
||||
const float DX = x2 - x1;
|
||||
const float DY = y2 - y1;
|
||||
const float LEN = std::sqrt((DX * DX) + (DY * DY));
|
||||
if (LEN < 1e-6F) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
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());
|
||||
|
||||
vertices_.push_back({x1 + NX, y1 + NY, r, g, b, a});
|
||||
vertices_.push_back({x1 - NX, y1 - NY, r, g, b, a});
|
||||
vertices_.push_back({x2 + NX, y2 + NY, r, g, b, a});
|
||||
vertices_.push_back({x2 - NX, y2 - NY, r, g, b, a});
|
||||
|
||||
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();
|
||||
|
||||
const auto VBO_SIZE = static_cast<uint32_t>(vertices_.size() * sizeof(LineVertex));
|
||||
const auto 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);
|
||||
|
||||
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 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 sobre OFFSCREEN (load_op=LOAD para preservar el clear).
|
||||
SDL_GPUColorTargetInfo color_target{};
|
||||
color_target.texture = offscreen_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);
|
||||
|
||||
SDL_BindGPUGraphicsPipeline(render_pass_, line_pipeline_.get());
|
||||
|
||||
// UBO de líneas usa el tamaño lógico (también del offscreen).
|
||||
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, 0, 0, 0);
|
||||
|
||||
SDL_ReleaseGPUBuffer(dev, vbo);
|
||||
SDL_ReleaseGPUBuffer(dev, ibo);
|
||||
SDL_ReleaseGPUTransferBuffer(dev, tbo);
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::compositePass() {
|
||||
// Cierra el render pass actual (sobre offscreen).
|
||||
if (render_pass_ != nullptr) {
|
||||
// Copy pass 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 sobre OFFSCREEN (load_op=LOAD para preservar el clear).
|
||||
SDL_GPUColorTargetInfo color_target{};
|
||||
color_target.texture = offscreen_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);
|
||||
|
||||
SDL_BindGPUGraphicsPipeline(render_pass_, line_pipeline_.get());
|
||||
|
||||
// UBO de líneas usa el tamaño lógico (también del offscreen).
|
||||
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,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
SDL_ReleaseGPUBuffer(dev, vbo);
|
||||
SDL_ReleaseGPUBuffer(dev, ibo);
|
||||
SDL_ReleaseGPUTransferBuffer(dev, tbo);
|
||||
}
|
||||
|
||||
// Pase final: render pass sobre SWAPCHAIN con clear a negro (cubre el
|
||||
// letterbox del viewport físico).
|
||||
SDL_GPUColorTargetInfo target{};
|
||||
target.texture = swapchain_texture_;
|
||||
target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
target.cycle = false;
|
||||
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &target, 1, nullptr);
|
||||
if (render_pass_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] BeginRenderPass (composite): "
|
||||
<< SDL_GetError() << '\n';
|
||||
return;
|
||||
void GpuFrameRenderer::compositePass() {
|
||||
// Cierra el render pass actual (sobre offscreen).
|
||||
if (render_pass_ != nullptr) {
|
||||
SDL_EndGPURenderPass(render_pass_);
|
||||
render_pass_ = nullptr;
|
||||
}
|
||||
|
||||
// Pase final: render pass sobre SWAPCHAIN con clear a negro (cubre el
|
||||
// letterbox del viewport físico).
|
||||
SDL_GPUColorTargetInfo target{};
|
||||
target.texture = swapchain_texture_;
|
||||
target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
target.cycle = false;
|
||||
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &target, 1, nullptr);
|
||||
if (render_pass_ == nullptr) {
|
||||
std::cerr << "[GpuFrameRenderer] BeginRenderPass (composite): "
|
||||
<< SDL_GetError() << '\n';
|
||||
return;
|
||||
}
|
||||
applyFinalViewport();
|
||||
|
||||
SDL_BindGPUGraphicsPipeline(render_pass_, postfx_pipeline_.get());
|
||||
|
||||
// Bind del sampler (escena offscreen) en slot 0 del fragment shader.
|
||||
SDL_GPUTextureSamplerBinding sampler_binding{};
|
||||
sampler_binding.texture = offscreen_texture_;
|
||||
sampler_binding.sampler = linear_sampler_;
|
||||
SDL_BindGPUFragmentSamplers(render_pass_, 0, &sampler_binding, 1);
|
||||
|
||||
// Uniforms del postpro. Si una sección está desactivada, anulamos sus
|
||||
// contribuciones (intensidad / amplitud / max=min) en lugar de tener
|
||||
// un branch en el shader.
|
||||
const float BLOOM_INTENSITY = postfx_params_.bloom_enabled
|
||||
? postfx_params_.bloom_intensity
|
||||
: 0.0F;
|
||||
const float FLICKER_AMPLITUDE = postfx_params_.flicker_enabled
|
||||
? postfx_params_.flicker_amplitude
|
||||
: 0.0F;
|
||||
const float BG_MIN_R = postfx_params_.background_enabled ? postfx_params_.background_min_r : 0.0F;
|
||||
const float BG_MIN_G = postfx_params_.background_enabled ? postfx_params_.background_min_g : 0.0F;
|
||||
const float BG_MIN_B = postfx_params_.background_enabled ? postfx_params_.background_min_b : 0.0F;
|
||||
const float BG_MAX_R = postfx_params_.background_enabled ? postfx_params_.background_max_r : 0.0F;
|
||||
const float BG_MAX_G = postfx_params_.background_enabled ? postfx_params_.background_max_g : 0.0F;
|
||||
const float BG_MAX_B = postfx_params_.background_enabled ? postfx_params_.background_max_b : 0.0F;
|
||||
|
||||
// Tiempo en segundos desde el inicio de SDL (wall-clock real, robusto a FPS variables).
|
||||
const float TIME_SECONDS = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||
|
||||
PostFxUniforms ubo{};
|
||||
ubo.time = TIME_SECONDS;
|
||||
ubo.bloom_intensity = BLOOM_INTENSITY;
|
||||
ubo.bloom_threshold = postfx_params_.bloom_threshold;
|
||||
ubo.bloom_radius_px = postfx_params_.bloom_radius_px;
|
||||
ubo.flicker_amplitude = FLICKER_AMPLITUDE;
|
||||
ubo.flicker_frequency_hz = postfx_params_.flicker_frequency_hz;
|
||||
ubo.background_pulse_freq_hz = postfx_params_.background_pulse_freq_hz;
|
||||
ubo.pad_a = 0.0F;
|
||||
ubo.background_min_r = BG_MIN_R;
|
||||
ubo.background_min_g = BG_MIN_G;
|
||||
ubo.background_min_b = BG_MIN_B;
|
||||
ubo.background_min_a = 1.0F;
|
||||
ubo.background_max_r = BG_MAX_R;
|
||||
ubo.background_max_g = BG_MAX_G;
|
||||
ubo.background_max_b = BG_MAX_B;
|
||||
ubo.background_max_a = 1.0F;
|
||||
ubo.texel_size_x = 1.0F / logical_w_;
|
||||
ubo.texel_size_y = 1.0F / logical_h_;
|
||||
ubo.pad_b = 0.0F;
|
||||
ubo.pad_c = 0.0F;
|
||||
|
||||
SDL_PushGPUFragmentUniformData(cmd_buffer_, 0, &ubo, sizeof(ubo));
|
||||
|
||||
// Fullscreen triangle: 3 vértices generados en el shader, sin VBO.
|
||||
SDL_DrawGPUPrimitives(render_pass_, 3, 1, 0, 0);
|
||||
}
|
||||
applyFinalViewport();
|
||||
|
||||
SDL_BindGPUGraphicsPipeline(render_pass_, postfx_pipeline_.get());
|
||||
|
||||
// Bind del sampler (escena offscreen) en slot 0 del fragment shader.
|
||||
SDL_GPUTextureSamplerBinding sampler_binding{};
|
||||
sampler_binding.texture = offscreen_texture_;
|
||||
sampler_binding.sampler = linear_sampler_;
|
||||
SDL_BindGPUFragmentSamplers(render_pass_, 0, &sampler_binding, 1);
|
||||
|
||||
// Uniforms del postpro. Si una sección está desactivada, anulamos sus
|
||||
// contribuciones (intensidad / amplitud / max=min) en lugar de tener
|
||||
// un branch en el shader.
|
||||
const float BLOOM_INTENSITY = postfx_params_.bloom_enabled
|
||||
? postfx_params_.bloom_intensity : 0.0F;
|
||||
const float FLICKER_AMPLITUDE = postfx_params_.flicker_enabled
|
||||
? postfx_params_.flicker_amplitude : 0.0F;
|
||||
const float BG_MIN_R = postfx_params_.background_enabled ? postfx_params_.background_min_r : 0.0F;
|
||||
const float BG_MIN_G = postfx_params_.background_enabled ? postfx_params_.background_min_g : 0.0F;
|
||||
const float BG_MIN_B = postfx_params_.background_enabled ? postfx_params_.background_min_b : 0.0F;
|
||||
const float BG_MAX_R = postfx_params_.background_enabled ? postfx_params_.background_max_r : 0.0F;
|
||||
const float BG_MAX_G = postfx_params_.background_enabled ? postfx_params_.background_max_g : 0.0F;
|
||||
const float BG_MAX_B = postfx_params_.background_enabled ? postfx_params_.background_max_b : 0.0F;
|
||||
|
||||
// Tiempo en segundos desde el inicio de SDL (wall-clock real, robusto a FPS variables).
|
||||
const float TIME_SECONDS = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||
|
||||
PostFxUniforms ubo{};
|
||||
ubo.time = TIME_SECONDS;
|
||||
ubo.bloom_intensity = BLOOM_INTENSITY;
|
||||
ubo.bloom_threshold = postfx_params_.bloom_threshold;
|
||||
ubo.bloom_radius_px = postfx_params_.bloom_radius_px;
|
||||
ubo.flicker_amplitude = FLICKER_AMPLITUDE;
|
||||
ubo.flicker_frequency_hz = postfx_params_.flicker_frequency_hz;
|
||||
ubo.background_pulse_freq_hz = postfx_params_.background_pulse_freq_hz;
|
||||
ubo.pad_a = 0.0F;
|
||||
ubo.background_min_r = BG_MIN_R;
|
||||
ubo.background_min_g = BG_MIN_G;
|
||||
ubo.background_min_b = BG_MIN_B;
|
||||
ubo.background_min_a = 1.0F;
|
||||
ubo.background_max_r = BG_MAX_R;
|
||||
ubo.background_max_g = BG_MAX_G;
|
||||
ubo.background_max_b = BG_MAX_B;
|
||||
ubo.background_max_a = 1.0F;
|
||||
ubo.texel_size_x = 1.0F / logical_w_;
|
||||
ubo.texel_size_y = 1.0F / logical_h_;
|
||||
ubo.pad_b = 0.0F;
|
||||
ubo.pad_c = 0.0F;
|
||||
|
||||
SDL_PushGPUFragmentUniformData(cmd_buffer_, 0, &ubo, sizeof(ubo));
|
||||
|
||||
// Fullscreen triangle: 3 vértices generados en el shader, sin VBO.
|
||||
SDL_DrawGPUPrimitives(render_pass_, 3, 1, 0, 0);
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::endFrame() {
|
||||
if (cmd_buffer_ == nullptr) {
|
||||
return;
|
||||
void GpuFrameRenderer::endFrame() {
|
||||
if (cmd_buffer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
flushBatch();
|
||||
compositePass();
|
||||
if (render_pass_ != nullptr) {
|
||||
SDL_EndGPURenderPass(render_pass_);
|
||||
render_pass_ = nullptr;
|
||||
}
|
||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||
cmd_buffer_ = nullptr;
|
||||
swapchain_texture_ = nullptr;
|
||||
}
|
||||
flushBatch();
|
||||
compositePass();
|
||||
if (render_pass_ != nullptr) {
|
||||
SDL_EndGPURenderPass(render_pass_);
|
||||
render_pass_ = nullptr;
|
||||
}
|
||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||
cmd_buffer_ = nullptr;
|
||||
swapchain_texture_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
Reference in New Issue
Block a user