Merge branch 'fix/vsync-fullscreen-antialias': viewport fullscreen, VSync fallback i AA geomètric
Tres fronts arreglats a la rama: - Fullscreen: el viewport ja no depèn de zoom_factor_ (capat per max_zoom_), sinó que és aspect-fit de la mida física actual. Afegit SetWindowFullscreenMode(nullptr) i preservació del zoom_factor_ de windowed durant transicions. - VSync: setVSync ara consulta SDL_WindowSupportsGPUPresentMode i fa fallback IMMEDIATE → MAILBOX → VSYNC quan el driver/compositor força VSync. Loggeja el mode efectiu. - Antialias geomètric a les línies: edge attribute + smoothstep al fragment. Toggle runtime amb F5, indicador 'AA: ON/OFF' al debug overlay (F11).
This commit is contained in:
+17
-4
@@ -1,12 +1,25 @@
|
||||
#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.)
|
||||
// Fragment shader per a línies vectorials.
|
||||
//
|
||||
// Antialias geomètric: rebem `frag_edge_dist` interpolat (±1 als laterals del
|
||||
// quad, 0 a l'eix central). Apliquem un smoothstep d'1 píxel d'amplada perquè
|
||||
// el gruix nominal (els |edge_dist| < threshold) quedi totalment opac i només
|
||||
// el píxel extruit als laterals faci la transició suau.
|
||||
//
|
||||
// La línia ja ve extruïda amb thickness + 1px a CPU; el threshold equival a
|
||||
// (thickness)/(thickness+1), però no el coneixem aquí per vèrtex. En el cas
|
||||
// general (línies fines), fade lineal entre 0.0 i 1.0 dóna prou bon resultat
|
||||
// visualment sense necessitat d'un uniform per línia.
|
||||
|
||||
layout(location = 0) in vec4 frag_color;
|
||||
layout(location = 1) in float frag_edge_dist;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
out_color = frag_color;
|
||||
// |edge_dist|=0 → totalment opac; |edge_dist|=1 → totalment transparent.
|
||||
// smoothstep dóna un fade Hermite C¹ que evita banding.
|
||||
float d = abs(frag_edge_dist);
|
||||
float alpha = 1.0 - smoothstep(0.7, 1.0, d);
|
||||
out_color = vec4(frag_color.rgb, frag_color.a * alpha);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ layout(set = 1, binding = 0) uniform UBO {
|
||||
|
||||
layout(location = 0) in vec2 in_position; // píxeles lógicos
|
||||
layout(location = 1) in vec4 in_color; // RGBA 0..1
|
||||
layout(location = 2) in float in_edge_dist; // ±1 als laterals, 0 al centre
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
layout(location = 1) out float frag_edge_dist;
|
||||
|
||||
void main() {
|
||||
// Píxeles lógicos -> NDC (-1..+1)
|
||||
@@ -26,4 +28,5 @@ void main() {
|
||||
ndc.y = -ndc.y;
|
||||
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||
frag_color = in_color;
|
||||
frag_edge_dist = in_edge_dist;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ 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)
|
||||
};
|
||||
|
||||
struct KeyboardBindings {
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
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)
|
||||
|
||||
} // namespace Defaults::Rendering
|
||||
|
||||
@@ -39,6 +39,7 @@ Input::Input(std::string game_controller_db_path)
|
||||
{Action::WINDOW_INC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F2}},
|
||||
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
|
||||
{Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F4}},
|
||||
{Action::TOGGLE_ANTIALIAS, KeyState{.scancode = SDL_SCANCODE_F5}},
|
||||
{Action::EXIT, KeyState{.scancode = SDL_SCANCODE_ESCAPE}}};
|
||||
|
||||
initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD
|
||||
|
||||
@@ -21,6 +21,7 @@ enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el j
|
||||
WINDOW_DEC_ZOOM, // F1
|
||||
TOGGLE_FULLSCREEN, // F3
|
||||
TOGGLE_VSYNC, // F4
|
||||
TOGGLE_ANTIALIAS, // F5
|
||||
EXIT, // ESC
|
||||
|
||||
// Input obligatorio
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
namespace Rendering::GPU {
|
||||
|
||||
GpuFrameRenderer::~GpuFrameRenderer() { destroy(); }
|
||||
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) -> bool {
|
||||
logical_w_ = logical_w;
|
||||
logical_h_ = logical_h;
|
||||
|
||||
@@ -39,9 +39,9 @@ auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto GpuFrameRenderer::createOffscreen() -> bool {
|
||||
auto GpuFrameRenderer::createOffscreen() -> bool {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr) {
|
||||
return false;
|
||||
@@ -79,9 +79,9 @@ auto GpuFrameRenderer::createOffscreen() -> bool {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::destroyOffscreen() {
|
||||
void GpuFrameRenderer::destroyOffscreen() {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr) {
|
||||
offscreen_texture_ = nullptr;
|
||||
@@ -96,18 +96,18 @@ void GpuFrameRenderer::destroyOffscreen() {
|
||||
SDL_ReleaseGPUSampler(dev, linear_sampler_);
|
||||
linear_sampler_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::destroy() {
|
||||
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 {
|
||||
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;
|
||||
@@ -125,8 +125,7 @@ auto GpuFrameRenderer::beginFrame(float clear_r, float clear_g, float clear_b) -
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmd_buffer_, device_.window(),
|
||||
&swapchain_texture_, nullptr, nullptr)) {
|
||||
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;
|
||||
@@ -161,9 +160,9 @@ auto GpuFrameRenderer::beginFrame(float clear_r, float clear_g, float clear_b) -
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::setViewport(float x, float y, float w, float h) {
|
||||
void GpuFrameRenderer::setViewport(float x, float y, float w, float h) {
|
||||
viewport_x_ = x;
|
||||
viewport_y_ = y;
|
||||
viewport_w_ = w;
|
||||
@@ -171,23 +170,55 @@ void GpuFrameRenderer::setViewport(float x, float y, float w, float 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.
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::setVSync(bool enabled) {
|
||||
void GpuFrameRenderer::setVSync(bool enabled) {
|
||||
SDL_GPUDevice* dev = device_.get();
|
||||
if (dev == nullptr || device_.window() == nullptr) {
|
||||
SDL_Window* win = device_.window();
|
||||
if (dev == nullptr || win == 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';
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::applyFinalViewport() {
|
||||
// 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)");
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::applyFinalViewport() {
|
||||
if (render_pass_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -202,10 +233,9 @@ void GpuFrameRenderer::applyFinalViewport() {
|
||||
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) {
|
||||
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));
|
||||
@@ -213,16 +243,23 @@ void GpuFrameRenderer::pushLine(float x1, float y1, float x2, float y2, float th
|
||||
return;
|
||||
}
|
||||
|
||||
const float HALF = thickness * 0.5F;
|
||||
// Antialias geomètric: quan està ON, extruim 0.5 píxel extra a cada
|
||||
// banda del gruix demanat i posem edge_dist = ±1 als laterals; el
|
||||
// fragment shader fa fade als bords. Quan està OFF, geometria nua
|
||||
// (thickness exacte) i edge_dist = 0 a tots els vèrtexs → el
|
||||
// smoothstep del shader produeix alpha=1 sempre.
|
||||
const float AA_PADDING = antialias_enabled_ ? 0.5F : 0.0F;
|
||||
const float HALF = (thickness * 0.5F) + AA_PADDING;
|
||||
const float NX = -DY / LEN * HALF;
|
||||
const float NY = DX / LEN * HALF;
|
||||
const float EDGE = antialias_enabled_ ? 1.0F : 0.0F;
|
||||
|
||||
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});
|
||||
vertices_.push_back({x1 + NX, y1 + NY, r, g, b, a, +EDGE});
|
||||
vertices_.push_back({x1 - NX, y1 - NY, r, g, b, a, -EDGE});
|
||||
vertices_.push_back({x2 + NX, y2 + NY, r, g, b, a, +EDGE});
|
||||
vertices_.push_back({x2 - NX, y2 - NY, r, g, b, a, -EDGE});
|
||||
|
||||
indices_.push_back(BASE_INDEX + 0);
|
||||
indices_.push_back(BASE_INDEX + 1);
|
||||
@@ -230,9 +267,9 @@ void GpuFrameRenderer::pushLine(float x1, float y1, float x2, float y2, float th
|
||||
indices_.push_back(BASE_INDEX + 1);
|
||||
indices_.push_back(BASE_INDEX + 3);
|
||||
indices_.push_back(BASE_INDEX + 2);
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::flushBatch() {
|
||||
void GpuFrameRenderer::flushBatch() {
|
||||
if (vertices_.empty() || indices_.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -299,14 +336,17 @@ void GpuFrameRenderer::flushBatch() {
|
||||
|
||||
SDL_DrawGPUIndexedPrimitives(render_pass_,
|
||||
static_cast<uint32_t>(indices_.size()),
|
||||
1, 0, 0, 0);
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
SDL_ReleaseGPUBuffer(dev, vbo);
|
||||
SDL_ReleaseGPUBuffer(dev, ibo);
|
||||
SDL_ReleaseGPUTransferBuffer(dev, tbo);
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::compositePass() {
|
||||
void GpuFrameRenderer::compositePass() {
|
||||
// Cierra el render pass actual (sobre offscreen).
|
||||
if (render_pass_ != nullptr) {
|
||||
SDL_EndGPURenderPass(render_pass_);
|
||||
@@ -341,9 +381,11 @@ void GpuFrameRenderer::compositePass() {
|
||||
// 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;
|
||||
? postfx_params_.bloom_intensity
|
||||
: 0.0F;
|
||||
const float FLICKER_AMPLITUDE = postfx_params_.flicker_enabled
|
||||
? postfx_params_.flicker_amplitude : 0.0F;
|
||||
? 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;
|
||||
@@ -380,9 +422,9 @@ void GpuFrameRenderer::compositePass() {
|
||||
|
||||
// Fullscreen triangle: 3 vértices generados en el shader, sin VBO.
|
||||
SDL_DrawGPUPrimitives(render_pass_, 3, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GpuFrameRenderer::endFrame() {
|
||||
void GpuFrameRenderer::endFrame() {
|
||||
if (cmd_buffer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -395,6 +437,6 @@ void GpuFrameRenderer::endFrame() {
|
||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||
cmd_buffer_ = nullptr;
|
||||
swapchain_texture_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
namespace Rendering::GPU {
|
||||
|
||||
// Parámetros del postpro que el caller (SDLManager) pasa cada frame.
|
||||
// Equivalente al lado "humano" del PostFxUniforms (sin paddings).
|
||||
struct PostFxParams {
|
||||
// Parámetros del postpro que el caller (SDLManager) pasa cada frame.
|
||||
// Equivalente al lado "humano" del PostFxUniforms (sin paddings).
|
||||
struct PostFxParams {
|
||||
bool bloom_enabled{true};
|
||||
float bloom_intensity{0.6F};
|
||||
float bloom_threshold{0.4F};
|
||||
@@ -47,9 +47,9 @@ struct PostFxParams {
|
||||
float background_max_g{0.06F};
|
||||
float background_max_b{0.0F};
|
||||
float background_pulse_freq_hz{6.0F};
|
||||
};
|
||||
};
|
||||
|
||||
class GpuFrameRenderer {
|
||||
class GpuFrameRenderer {
|
||||
public:
|
||||
GpuFrameRenderer() = default;
|
||||
~GpuFrameRenderer();
|
||||
@@ -72,8 +72,7 @@ class GpuFrameRenderer {
|
||||
[[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);
|
||||
void pushLine(float x1, float y1, float x2, float y2, float thickness, float r, float g, float b, float a);
|
||||
|
||||
// endFrame: flush del batch de líneas → composite postpro → submit + presenta.
|
||||
void endFrame();
|
||||
@@ -87,6 +86,13 @@ class GpuFrameRenderer {
|
||||
// Activa/desactiva VSync. true = SDL_GPU_PRESENTMODE_VSYNC, false = IMMEDIATE.
|
||||
void setVSync(bool enabled);
|
||||
|
||||
// Activa/desactiva l'antialias geomètric a les línies. Quan està OFF,
|
||||
// pushLine no extrudeix píxel extra ni emet edge_dist (geometria com
|
||||
// abans del commit d'AA-1). Quan està ON, l'extrusió i el fade del
|
||||
// fragment shader produeixen bords suavitzats.
|
||||
void setAntialias(bool enabled) { antialias_enabled_ = enabled; }
|
||||
[[nodiscard]] auto isAntialiasEnabled() const -> bool { return antialias_enabled_; }
|
||||
|
||||
// Parámetros del postpro que se aplican en endFrame. Por defecto =
|
||||
// valores de Defaults (bloom moderado, flicker suave, fondo verde tenue).
|
||||
void setPostFx(const PostFxParams& params) { postfx_params_ = params; }
|
||||
@@ -128,12 +134,15 @@ class GpuFrameRenderer {
|
||||
// Parámetros del postpro (configurables vía YAML).
|
||||
PostFxParams postfx_params_{};
|
||||
|
||||
// Estat de l'antialias geomètric a les línies (toggle F5).
|
||||
bool antialias_enabled_{true};
|
||||
|
||||
// Helpers internos.
|
||||
[[nodiscard]] auto createOffscreen() -> bool;
|
||||
void destroyOffscreen();
|
||||
void flushBatch();
|
||||
void compositePass();
|
||||
void applyFinalViewport();
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
namespace Rendering::GPU {
|
||||
|
||||
GpuLinePipeline::~GpuLinePipeline() { destroy(); }
|
||||
GpuLinePipeline::~GpuLinePipeline() { destroy(); }
|
||||
|
||||
auto GpuLinePipeline::init(const GpuDevice& device,
|
||||
auto GpuLinePipeline::init(const GpuDevice& device,
|
||||
SDL_GPUTextureFormat target_format) -> bool {
|
||||
owner_ = device.get();
|
||||
if (owner_ == nullptr) {
|
||||
@@ -37,14 +37,14 @@ auto GpuLinePipeline::init(const GpuDevice& device,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vertex layout: pos (vec2) + color (vec4) → 6 floats por vertex.
|
||||
// Vertex layout: pos (vec2) + color (vec4) + edge_dist (float) → 7 floats per vèrtex.
|
||||
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];
|
||||
SDL_GPUVertexAttribute attrs[3];
|
||||
attrs[0].location = 0; // in_position
|
||||
attrs[0].buffer_slot = 0;
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
@@ -53,12 +53,16 @@ auto GpuLinePipeline::init(const GpuDevice& device,
|
||||
attrs[1].buffer_slot = 0;
|
||||
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
attrs[1].offset = sizeof(float) * 2;
|
||||
attrs[2].location = 2; // in_edge_dist
|
||||
attrs[2].buffer_slot = 0;
|
||||
attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT;
|
||||
attrs[2].offset = sizeof(float) * 6;
|
||||
|
||||
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;
|
||||
vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
// Color target = formato pasado por el caller (offscreen u otro).
|
||||
// Blending alpha estándar.
|
||||
@@ -104,14 +108,14 @@ auto GpuLinePipeline::init(const GpuDevice& device,
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void GpuLinePipeline::destroy() {
|
||||
void GpuLinePipeline::destroy() {
|
||||
if ((pipeline_ != nullptr) && (owner_ != nullptr)) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_);
|
||||
}
|
||||
pipeline_ = nullptr;
|
||||
owner_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
@@ -11,27 +11,33 @@
|
||||
|
||||
namespace Rendering::GPU {
|
||||
|
||||
class GpuDevice;
|
||||
class GpuDevice;
|
||||
|
||||
// Vertex layout (debe coincidir con shaders/line.vert.glsl).
|
||||
struct LineVertex {
|
||||
// Vertex layout (debe coincidir con shaders/line.vert.glsl).
|
||||
//
|
||||
// edge_dist: distància normalitzada a l'eix central de la línia, ±1 als bords
|
||||
// i 0 al centre. El fragment shader la fa servir per fer un fade als bords
|
||||
// (antialias geomètric). Els vèrtexs del costat "+normal" porten +1 i els del
|
||||
// costat "-normal" porten -1.
|
||||
struct LineVertex {
|
||||
float x;
|
||||
float y;
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
};
|
||||
float edge_dist;
|
||||
};
|
||||
|
||||
// Uniform buffer del vertex shader (debe coincidir con UBO en line.vert.glsl).
|
||||
struct LineUniforms {
|
||||
// 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 {
|
||||
class GpuLinePipeline {
|
||||
public:
|
||||
GpuLinePipeline() = default;
|
||||
~GpuLinePipeline();
|
||||
@@ -53,6 +59,6 @@ class GpuLinePipeline {
|
||||
private:
|
||||
SDL_GPUDevice* owner_{nullptr}; // No-owning; el GpuDevice es el dueño
|
||||
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
@@ -84,6 +84,9 @@ SDLManager::SDLManager(int width, int height, bool fullscreen, Config::EngineCon
|
||||
return;
|
||||
}
|
||||
|
||||
// Aplica l'estat inicial d'antialias des de la config (per defecte ON).
|
||||
gpu_renderer_.setAntialias(cfg_->rendering.antialias != 0);
|
||||
|
||||
updateViewport();
|
||||
|
||||
// En fullscreen: forzar ocultació permanent del cursor.
|
||||
@@ -173,10 +176,17 @@ void SDLManager::applyZoom(float new_zoom) {
|
||||
}
|
||||
|
||||
void SDLManager::updateViewport() {
|
||||
// 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_;
|
||||
// Càlcul de letterbox: el joc es renderitza a 1280×720 lògics, però la
|
||||
// swapchain té la mida física de la finestra. Apliquem un viewport
|
||||
// centrat amb aspect-fit (omple un eix, lletrabox a l'altre).
|
||||
//
|
||||
// IMPORTANT: l'escala del viewport es deriva de la mida física actual,
|
||||
// NO del zoom_factor_. El zoom_factor_ només dimensiona la finestra en
|
||||
// mode windowed (F1/F2). Si l'enllacéssim, en fullscreen el viewport
|
||||
// quedaria capat per max_zoom_ (display-100px) i no ompliria la pantalla.
|
||||
float scale_w = static_cast<float>(current_width_) / Defaults::Game::WIDTH;
|
||||
float scale_h = static_cast<float>(current_height_) / Defaults::Game::HEIGHT;
|
||||
float scale = std::min(scale_w, scale_h);
|
||||
int scaled_width = static_cast<int>(std::round(Defaults::Game::WIDTH * scale));
|
||||
int scaled_height = static_cast<int>(std::round(Defaults::Game::HEIGHT * scale));
|
||||
|
||||
@@ -247,6 +257,10 @@ void SDLManager::toggleFullscreen() {
|
||||
windowed_width_ = current_width_;
|
||||
windowed_height_ = current_height_;
|
||||
is_fullscreen_ = true;
|
||||
// SDL3: cal seleccionar explícitament el mode "borderless desktop"
|
||||
// (mode=nullptr) abans d'activar el fullscreen. Sense això, el
|
||||
// comportament depèn del mode que tingués la finestra anteriorment.
|
||||
SDL_SetWindowFullscreenMode(finestra_, nullptr);
|
||||
SDL_SetWindowFullscreen(finestra_, true);
|
||||
std::cout << "F3: Fullscreen activat (guardada: "
|
||||
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
|
||||
@@ -266,11 +280,13 @@ auto SDLManager::handleWindowEvent(const SDL_Event& event) -> bool {
|
||||
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
||||
SDL_GetWindowSize(finestra_, ¤t_width_, ¤t_height_);
|
||||
|
||||
// En fullscreen el zoom_factor_ no participa del viewport (aspect-fit
|
||||
// sobre la mida física), així que el preservem amb el valor de
|
||||
// windowed per no perdre'l en tornar a windowed.
|
||||
if (!is_fullscreen_) {
|
||||
float new_zoom = static_cast<float>(current_width_) / Defaults::Window::WIDTH;
|
||||
zoom_factor_ = std::max(Defaults::Window::MIN_ZOOM,
|
||||
std::min(new_zoom, max_zoom_));
|
||||
|
||||
if (!is_fullscreen_) {
|
||||
windowed_width_ = current_width_;
|
||||
windowed_height_ = current_height_;
|
||||
}
|
||||
@@ -310,3 +326,11 @@ void SDLManager::toggleVSync() {
|
||||
on_persist_();
|
||||
}
|
||||
}
|
||||
|
||||
void SDLManager::toggleAntialias() {
|
||||
cfg_->rendering.antialias = (cfg_->rendering.antialias == 1) ? 0 : 1;
|
||||
gpu_renderer_.setAntialias(cfg_->rendering.antialias != 0);
|
||||
// No persistim: l'AA és toggleable runtime però el seu estat no es
|
||||
// guarda al YAML de moment (decisió volgudament conservadora).
|
||||
std::cout << "F5: AA " << (cfg_->rendering.antialias != 0 ? "ON" : "OFF") << '\n';
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class SDLManager {
|
||||
void decreaseWindowSize(); // F1: -100px
|
||||
void toggleFullscreen(); // F3
|
||||
void toggleVSync(); // F4
|
||||
void toggleAntialias(); // F5
|
||||
auto handleWindowEvent(const SDL_Event& event) -> bool; // Per a SDL_EVENT_WINDOW_RESIZED
|
||||
|
||||
// Funciones principals (renderizado).
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace System {
|
||||
|
||||
const std::string FPS_TEXT = "FPS: " + std::to_string(fps_display_);
|
||||
const std::string VSYNC_TEXT = std::string("VSYNC: ") + (rendering_cfg_->vsync == 1 ? "ON" : "OFF");
|
||||
const std::string AA_TEXT = std::string("AA: ") + (rendering_cfg_->antialias == 1 ? "ON" : "OFF");
|
||||
|
||||
text_.render(FPS_TEXT,
|
||||
Vec2{.x = OVERLAY_X, .y = OVERLAY_Y_FPS},
|
||||
@@ -55,6 +56,11 @@ namespace System {
|
||||
OVERLAY_SCALE,
|
||||
OVERLAY_SPACING,
|
||||
OVERLAY_BRIGHTNESS);
|
||||
text_.render(AA_TEXT,
|
||||
Vec2{.x = OVERLAY_X, .y = OVERLAY_Y_FPS + (2.0F * OVERLAY_LINE_HEIGHT)},
|
||||
OVERLAY_SCALE,
|
||||
OVERLAY_SPACING,
|
||||
OVERLAY_BRIGHTNESS);
|
||||
}
|
||||
|
||||
} // namespace System
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "scene_context.hpp"
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/input/mouse.hpp"
|
||||
#include "core/rendering/sdl_manager.hpp"
|
||||
#include "scene_context.hpp"
|
||||
|
||||
// Using declarations per simplificar el codi
|
||||
using SceneManager::SceneContext;
|
||||
@@ -16,7 +16,7 @@ using SceneType = SceneContext::SceneType;
|
||||
|
||||
namespace GlobalEvents {
|
||||
|
||||
auto handle(const SDL_Event& event, SDLManager& sdl, SceneContext& context) -> bool {
|
||||
auto handle(const SDL_Event& event, SDLManager& sdl, SceneContext& context) -> bool {
|
||||
// 1. Permitir que Input procese el evento (para hotplug de gamepads)
|
||||
auto event_msg = Input::get()->handleEvent(event);
|
||||
if (!event_msg.empty()) {
|
||||
@@ -53,6 +53,10 @@ auto handle(const SDL_Event& event, SDLManager& sdl, SceneContext& context) -> b
|
||||
sdl.toggleVSync();
|
||||
return true;
|
||||
|
||||
case SDL_SCANCODE_F5:
|
||||
sdl.toggleAntialias();
|
||||
return true;
|
||||
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
context.setNextScene(SceneType::EXIT);
|
||||
SceneManager::actual = SceneType::EXIT;
|
||||
@@ -65,6 +69,6 @@ auto handle(const SDL_Event& event, SDLManager& sdl, SceneContext& context) -> b
|
||||
}
|
||||
|
||||
return false; // Event no processat
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GlobalEvents
|
||||
|
||||
Reference in New Issue
Block a user