feat(render): resolució d'offscreen configurable via YAML
Separa el tamany lògic (1280×720) del render target offscreen. Llista
tancada de 5 presets 16:9 (720p/900p/1080p/1440p/2160p) llegida de
rendering.render_{width,height} amb fallback a 1280×720 si invàlida.
Inclou API resizeRenderTarget() preparada per al menú de servei futur.
This commit is contained in:
@@ -27,6 +27,13 @@ namespace Config {
|
||||
struct RenderingConfig {
|
||||
int vsync{1}; // 0=disabled, 1=enabled
|
||||
int antialias{1}; // 0=disabled, 1=enabled (AA geomètric a les línies, toggle F5)
|
||||
// Resolució del render target offscreen (independent del tamany lògic
|
||||
// 1280×720 del joc). Aquesta és la resolució real on rasteritzen les
|
||||
// línies abans de l'escala final a la swapchain; pujar-la millora
|
||||
// la nitidesa en finestres grans i fullscreen. Llista tancada de
|
||||
// presets 16:9 — veure Defaults::Rendering::RESOLUTION_PRESETS.
|
||||
int render_width{1280};
|
||||
int render_height{720};
|
||||
};
|
||||
|
||||
struct KeyboardBindings {
|
||||
|
||||
@@ -3,9 +3,36 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace Defaults::Rendering {
|
||||
|
||||
constexpr int VSYNC_DEFAULT = 1; // 0=disabled, 1=enabled
|
||||
constexpr int ANTIALIAS_DEFAULT = 1; // 0=disabled, 1=enabled (AA geomètric a les línies)
|
||||
|
||||
// Resolució del render target offscreen. El tamany lògic del joc roman a
|
||||
// 1280×720 (coordenades dels objectes); aquesta és la resolució física a
|
||||
// la qual es rasteritzen les línies abans de la composició final.
|
||||
struct ResolutionPreset {
|
||||
int w;
|
||||
int h;
|
||||
};
|
||||
|
||||
constexpr std::array<ResolutionPreset, 5> RESOLUTION_PRESETS{{
|
||||
{.w = 1280, .h = 720}, // HD 720p (default)
|
||||
{.w = 1600, .h = 900}, // HD+ 900p
|
||||
{.w = 1920, .h = 1080}, // Full HD 1080p
|
||||
{.w = 2560, .h = 1440}, // QHD 1440p
|
||||
{.w = 3840, .h = 2160} // 4K UHD 2160p
|
||||
}};
|
||||
|
||||
constexpr int RENDER_WIDTH_DEFAULT = 1280;
|
||||
constexpr int RENDER_HEIGHT_DEFAULT = 720;
|
||||
|
||||
constexpr auto isValidRenderResolution(int w, int h) -> bool {
|
||||
return std::ranges::any_of(RESOLUTION_PRESETS,
|
||||
[w, h](const ResolutionPreset& preset) { return preset.w == w && preset.h == h; });
|
||||
}
|
||||
|
||||
} // namespace Defaults::Rendering
|
||||
|
||||
@@ -14,9 +14,11 @@ namespace Rendering::GPU {
|
||||
|
||||
GpuFrameRenderer::~GpuFrameRenderer() { destroy(); }
|
||||
|
||||
auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h) -> bool {
|
||||
auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h, float render_w, float render_h) -> bool {
|
||||
logical_w_ = logical_w;
|
||||
logical_h_ = logical_h;
|
||||
render_w_ = render_w;
|
||||
render_h_ = render_h;
|
||||
|
||||
if (!device_.init(window)) {
|
||||
return false;
|
||||
@@ -47,13 +49,15 @@ namespace Rendering::GPU {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Textura offscreen del tamaño lógico del juego, COLOR_TARGET + SAMPLER.
|
||||
// Textura offscreen del tamaño físico de render, COLOR_TARGET + SAMPLER.
|
||||
// El tamaño lógico se aplica a los vértices vía UBO; el offscreen puede
|
||||
// ser de mayor resolución para ganar nitidez tras el upscale a la swapchain.
|
||||
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.width = static_cast<uint32_t>(render_w_);
|
||||
tex_info.height = static_cast<uint32_t>(render_h_);
|
||||
tex_info.layer_count_or_depth = 1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
@@ -107,6 +111,19 @@ namespace Rendering::GPU {
|
||||
indices_.clear();
|
||||
}
|
||||
|
||||
auto GpuFrameRenderer::resizeRenderTarget(float render_w, float render_h) -> bool {
|
||||
// Solo seguro fuera de un frame: si el cmd buffer está vivo y referencia
|
||||
// la textura antigua, recrearla provocaría un cuelgue/UB.
|
||||
if (isInsideFrame()) {
|
||||
std::cerr << "[GpuFrameRenderer] resizeRenderTarget llamado dentro de frame, ignorado\n";
|
||||
return false;
|
||||
}
|
||||
destroyOffscreen();
|
||||
render_w_ = render_w;
|
||||
render_h_ = render_h;
|
||||
return createOffscreen();
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -438,8 +455,9 @@ namespace Rendering::GPU {
|
||||
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_;
|
||||
// El sampling del bloom muestrea el offscreen → texel size del tamaño físico.
|
||||
ubo.texel_size_x = 1.0F / render_w_;
|
||||
ubo.texel_size_y = 1.0F / render_h_;
|
||||
ubo.pad_b = 0.0F;
|
||||
ubo.pad_c = 0.0F;
|
||||
|
||||
|
||||
@@ -59,12 +59,24 @@ namespace Rendering::GPU {
|
||||
GpuFrameRenderer(GpuFrameRenderer&&) = delete;
|
||||
auto operator=(GpuFrameRenderer&&) -> GpuFrameRenderer& = delete;
|
||||
|
||||
// Crea device + pipeline + offscreen + sampler. logical_w/h = tamaño
|
||||
// en píxeles lógicos del juego (1280×720), usado como base del
|
||||
// offscreen y de la transformación a NDC del shader de líneas.
|
||||
[[nodiscard]] auto init(SDL_Window* window, float logical_w, float logical_h) -> bool;
|
||||
// Crea device + pipeline + offscreen + sampler.
|
||||
// logical_w/h: tamaño en píxeles lógicos del juego (1280×720). Lo
|
||||
// consume el shader de líneas para transformar a NDC.
|
||||
// render_w/h: tamaño físico del offscreen donde se rasterizan las
|
||||
// líneas. Puede ser > logical para ganar nitidez al
|
||||
// escalar a la swapchain (configurable vía YAML).
|
||||
[[nodiscard]] auto init(SDL_Window* window,
|
||||
float logical_w,
|
||||
float logical_h,
|
||||
float render_w,
|
||||
float render_h) -> bool;
|
||||
void destroy();
|
||||
|
||||
// Recrea el offscreen con un nuevo tamaño físico de render. Solo es
|
||||
// seguro fuera de un frame (isInsideFrame() == false). Devuelve false
|
||||
// si está dentro de frame o si la creación de la textura falla.
|
||||
[[nodiscard]] auto resizeRenderTarget(float render_w, float render_h) -> bool;
|
||||
|
||||
// beginFrame: adquiere swapchain, abre render pass sobre offscreen
|
||||
// con clear a negro. Devuelve false si no hay textura disponible.
|
||||
// Los argumentos clear_r/g/b se ignoran (compatibilidad de API: el
|
||||
@@ -114,10 +126,18 @@ namespace Rendering::GPU {
|
||||
GpuLinePipeline line_pipeline_;
|
||||
GpuPostFxPipeline postfx_pipeline_;
|
||||
|
||||
// Tamaño lógico del juego (= tamaño del offscreen).
|
||||
// Tamaño lógico del juego: espacio de coordenadas de las primitivas
|
||||
// (vértices, UBO del line shader). Fijo a 1280×720.
|
||||
float logical_w_{1280.0F};
|
||||
float logical_h_{720.0F};
|
||||
|
||||
// Tamaño físico del offscreen (configurable). Independiente del lógico:
|
||||
// las coordenadas en NDC son agnósticas a la resolución de salida, así
|
||||
// que rasterizar a mayor render_w_/h_ da líneas más nítidas tras el
|
||||
// upscale lineal a la swapchain.
|
||||
float render_w_{1280.0F};
|
||||
float render_h_{720.0F};
|
||||
|
||||
// Viewport del pase final en píxeles físicos. <0 = full window.
|
||||
float viewport_x_{0.0F};
|
||||
float viewport_y_{0.0F};
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "core/config/postfx_config.hpp"
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/defaults/game.hpp"
|
||||
#include "core/defaults/rendering.hpp"
|
||||
#include "core/defaults/window.hpp"
|
||||
#include "core/input/mouse.hpp"
|
||||
#include "core/rendering/coordinate_transform.hpp"
|
||||
#include "core/system/notifier.hpp"
|
||||
@@ -22,7 +24,9 @@ namespace {
|
||||
int width,
|
||||
int height,
|
||||
bool fullscreen,
|
||||
int initial_vsync) -> bool {
|
||||
int initial_vsync,
|
||||
int render_width,
|
||||
int render_height) -> bool {
|
||||
// Título estático estilo CCAE. El FPS y el estado de VSync los muestra
|
||||
// el DebugOverlay (toggle F11), no la barra de título.
|
||||
const std::string TITLE = std::format("© 2026 {} — JailDesigner",
|
||||
@@ -44,9 +48,13 @@ namespace {
|
||||
}
|
||||
|
||||
// Inicializar el FrameRenderer (claim del window + pipeline de líneas).
|
||||
// logical_*: espacio de coordenadas del juego (fijo 1280×720).
|
||||
// render_*: resolución física del offscreen (configurable vía YAML).
|
||||
if (!gpu_renderer.init(window,
|
||||
static_cast<float>(Defaults::Game::WIDTH),
|
||||
static_cast<float>(Defaults::Game::HEIGHT))) {
|
||||
static_cast<float>(Defaults::Game::HEIGHT),
|
||||
static_cast<float>(render_width),
|
||||
static_cast<float>(render_height))) {
|
||||
std::cerr << "Error inicialitzant GpuFrameRenderer\n";
|
||||
SDL_DestroyWindow(window);
|
||||
return false;
|
||||
@@ -80,7 +88,30 @@ SDLManager::SDLManager(int width, int height, bool fullscreen, Config::EngineCon
|
||||
|
||||
calculateMaxWindowSize();
|
||||
|
||||
if (!initWindowAndGpu(&finestra_, gpu_renderer_, current_width_, current_height_, is_fullscreen_, cfg_->rendering.vsync)) {
|
||||
// Validar la resolució de render del config: si no és un preset 16:9
|
||||
// conegut, fer fallback a 1280×720 i avisar. Això protegeix d'edicions
|
||||
// manuals invàlides al YAML.
|
||||
int effective_render_w = cfg_->rendering.render_width;
|
||||
int effective_render_h = cfg_->rendering.render_height;
|
||||
if (!Defaults::Rendering::isValidRenderResolution(effective_render_w, effective_render_h)) {
|
||||
std::cerr << "Resolució de render invàlida (" << effective_render_w << "x"
|
||||
<< effective_render_h << "), fent fallback a "
|
||||
<< Defaults::Rendering::RENDER_WIDTH_DEFAULT << "x"
|
||||
<< Defaults::Rendering::RENDER_HEIGHT_DEFAULT << '\n';
|
||||
effective_render_w = Defaults::Rendering::RENDER_WIDTH_DEFAULT;
|
||||
effective_render_h = Defaults::Rendering::RENDER_HEIGHT_DEFAULT;
|
||||
cfg_->rendering.render_width = effective_render_w;
|
||||
cfg_->rendering.render_height = effective_render_h;
|
||||
}
|
||||
|
||||
if (!initWindowAndGpu(&finestra_,
|
||||
gpu_renderer_,
|
||||
current_width_,
|
||||
current_height_,
|
||||
is_fullscreen_,
|
||||
cfg_->rendering.vsync,
|
||||
effective_render_w,
|
||||
effective_render_h)) {
|
||||
SDL_Quit();
|
||||
return;
|
||||
}
|
||||
@@ -97,7 +128,9 @@ SDLManager::SDLManager(int width, int height, bool fullscreen, Config::EngineCon
|
||||
|
||||
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
|
||||
<< " (logic: " << Defaults::Game::WIDTH << "x"
|
||||
<< Defaults::Game::HEIGHT << ")";
|
||||
<< Defaults::Game::HEIGHT
|
||||
<< ", render: " << effective_render_w << "x" << effective_render_h
|
||||
<< ")";
|
||||
if (is_fullscreen_) {
|
||||
std::cout << " [FULLSCREEN]";
|
||||
}
|
||||
|
||||
@@ -204,6 +204,8 @@ namespace ConfigYaml {
|
||||
|
||||
// Rendering
|
||||
rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT;
|
||||
rendering.render_width = Defaults::Rendering::RENDER_WIDTH_DEFAULT;
|
||||
rendering.render_height = Defaults::Rendering::RENDER_HEIGHT_DEFAULT;
|
||||
|
||||
// Version
|
||||
version = std::string(Project::VERSION);
|
||||
@@ -275,6 +277,28 @@ namespace ConfigYaml {
|
||||
rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolució de render: validem el parell (w, h) contra la llista
|
||||
// tancada de presets 16:9. Si falla l'una o l'altra, fem fallback
|
||||
// dels dos camps al default per mantenir un parell vàlid.
|
||||
int candidate_w = rendering.render_width;
|
||||
int candidate_h = rendering.render_height;
|
||||
readField(rend, "render_width", candidate_w, Defaults::Rendering::RENDER_WIDTH_DEFAULT);
|
||||
readField(rend, "render_height", candidate_h, Defaults::Rendering::RENDER_HEIGHT_DEFAULT);
|
||||
if (Defaults::Rendering::isValidRenderResolution(candidate_w, candidate_h)) {
|
||||
rendering.render_width = candidate_w;
|
||||
rendering.render_height = candidate_h;
|
||||
} else {
|
||||
if (console) {
|
||||
std::cerr << "Resolució de render invàlida al YAML ("
|
||||
<< candidate_w << "x" << candidate_h
|
||||
<< "), fallback a "
|
||||
<< Defaults::Rendering::RENDER_WIDTH_DEFAULT << "x"
|
||||
<< Defaults::Rendering::RENDER_HEIGHT_DEFAULT << '\n';
|
||||
}
|
||||
rendering.render_width = Defaults::Rendering::RENDER_WIDTH_DEFAULT;
|
||||
rendering.render_height = Defaults::Rendering::RENDER_HEIGHT_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +525,11 @@ namespace ConfigYaml {
|
||||
|
||||
file << "# RENDERITZACIÓ\n";
|
||||
file << "rendering:\n";
|
||||
file << " vsync: " << rendering.vsync << " # 0=disabled, 1=enabled\n\n";
|
||||
file << " vsync: " << rendering.vsync << " # 0=disabled, 1=enabled\n";
|
||||
file << " render_width: " << rendering.render_width
|
||||
<< " # Presets 16:9: 1280, 1600, 1920, 2560, 3840 (fallback 1280)\n";
|
||||
file << " render_height: " << rendering.render_height
|
||||
<< " # Parell amb render_width (720, 900, 1080, 1440, 2160)\n\n";
|
||||
|
||||
// Guardar controls de jugadors
|
||||
savePlayer1ControlsToYaml(file);
|
||||
|
||||
Reference in New Issue
Block a user