diff --git a/CMakeLists.txt b/CMakeLists.txt index 8777d7c..bdb14ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,46 @@ add_custom_command( add_custom_target(resource_pack ALL DEPENDS ${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 --- find_program(CLANG_TIDY_EXE NAMES clang-tidy) find_program(CLANG_FORMAT_EXE NAMES clang-format) diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md new file mode 100644 index 0000000..2f92025 --- /dev/null +++ b/MIGRATION_PLAN.md @@ -0,0 +1,140 @@ +# Plan de migración Orni Attack → SDL3 GPU + física vectorial + +Documento maestro de la rama `rewrite/physics-gpu`. Resumen del progreso y +qué queda. Si pierdes el contexto de la sesión, **léeme primero**. + +## Visión + +Modernizar la base técnica del juego antes de tunear jugabilidad: +1. Limpiar legacy (polares, mezcla catalán/inglés, header guards). +2. Adoptar formato 16:9 (1280×720). +3. Importar subsistemas robustos de AEEA (audio, *input deferido*). +4. Implementar física vectorial 2D con rigid bodies + colisiones elásticas. +5. **Migrar renderizado a SDL3 GPU sin fallback** ([memoria: project-no-sdl-fallback](./.claude/projects/-home-sergio-gitea-orni-attack/memory/project_no_sdl_fallback.md)). +6. Postprocesado y color. + +El tuning de feeling se hace al final, post-GPU. + +## Estado actual + +Rama de trabajo: **`rewrite/physics-gpu`** (pusheada a Gitea). +Tag de seguridad: **`beta-3.0`** (snapshot de `main` antes de empezar). + +| Fase | Estado | Commits | +|---|---|---| +| 0 — Limpieza de código muerto (primitives, polígonos, Bresenham, polares) | ✅ | `6cf990b` | +| 1a — `Punt → Vec2` con operadores modernos | ✅ | `cd38101` | +| 1b — Renames de entidades + métodos virtuales a camelBack | ✅ | `ae5cc1c` | +| 1c — Renames de escenas | ✅ | `5871d29` | +| 1d — Renames effects/stage_system/locales | ✅ | `7ee359b` | +| 1e — Pragma once + locales restantes + comentarios castellano | ✅ | `bf83f16` | +| (fix) — clave YAML `quadrat → cuadrado` post-sweep | ✅ | `56533ca` | +| 2 — Cambio a 1280×720 (16:9) | ✅ | `a4f6a55` | +| 3 — Import del subsistema de Audio desde AEEA | ✅ | `ed98ef6` | +| **4** — Input parcial AEEA | ⏭️ **saltado** (decisión usuario) | — | +| 5 — Infraestructura física (`RigidBody`, `PhysicsWorld`) | ✅ | `0fd9360` | +| 6a+b — `body_` en Entity + `world_` en GameScene | ✅ | `0574077` | +| 6c — Migrar Ship | ✅ | `2fe22ff` | +| 6d — Migrar Enemy | ✅ | `27242f5` | +| 6e — Migrar Bullet | ✅ | `9993b2d` | +| 7a — Infra GPU (shaders + wrappers, runtime dormido) | ✅ | `ba6fd00` | +| 7b+c — Swap atómico a SDL3 GPU + line_renderer al pipeline | ✅ | `fa7da4c` | +| 7d — Validación visual del usuario en hardware Vulkan | ✅ | — | +| 9a — Extraer `Systems::Collision` | ✅ | `896a899` | +| 9b — Extraer `Systems::ContinueScreen` | ✅ | `816bc02` | +| 9c — Extraer `Systems::InitHud` | ✅ | `a4942fc` | +| 9d — Descomponer `GameScene::update` (339→18 LOC) | ✅ | `808abb2` | +| **8 — Postprocesado + paleta de colores por entidad** | 🔲 **siguiente** | — | +| 10 — Tuning final de masa/restitución/damping | 🔲 | — | +| Mac/Metal — shaders MSL en build | 🔲 | — | +| 8 — Postprocesado, color, paleta por tipo | 🔲 | — | +| 9 — Refactor de GameScene (2.877 LOC → módulos) | 🔲 | — | +| 10 — Tuning final de masa/restitución/damping | 🔲 | — | + +## Lo que queda inmediato (Fase 8 — postprocesado + paleta de colores) + +Fase 7 y Fase 9 cerradas. Validación visual SDL3 GPU OK (`Backend GPU: vulkan`). +`GameScene.cpp` 1429 → 1015 LOC; `update()` 339 → 18 LOC, complexity baja a +< 10 por sub-paso. Tres sistemas extraídos a `source/game/systems/`: +- `Systems::Collision` (~210 LOC propios) +- `Systems::ContinueScreen` (~160 LOC propios) +- `Systems::InitHud` (~155 LOC propios) + +**Fase 8 — siguientes pasos**: +1. Color por entidad (paleta semántica): naves blancas, balas verdes, + pentagons azules, quadrats rojos, molinillos magenta, debris hereda + color del padre. Hoy todo se pinta con el color global del oscilador + (`g_current_line_color`). Hay que pasar el color al `pushLine` desde + las entidades (cada entity expone un `getColor()`). +2. Postprocesado básico: + - Bloom/glow: render-to-texture al swapchain real con fragment shader + que aplique kernel separable de blur sobre los píxeles iluminados. + Requiere un pipeline extra y un par de texturas off-screen. + - Opcional: scanlines o leve aberración cromática para feel CRT. + +**Fase 10** (tras 8): tuning de mass/restitution/damping con feedback +visual de los colores. + +**Mac/Metal**: cuando vayamos a release de macOS, añadir compilación +de los shaders también a MSL (con `spirv-cross` o `glslangValidator`). + +## Memoria del proyecto + +Las preferencias y decisiones persistentes están en: +`~/.claude/projects/-home-sergio-gitea-orni-attack/memory/MEMORY.md` y los +archivos enlazados desde ahí. Léelos al empezar sesión nueva. + +Resumen: +- **No fallback a SDL_Renderer** en Fase 7 — solo GPU o falla. +- **Naming**: `.clang-tidy` exige `CamelCase` tipos, `camelBack` métodos, + `lower_case_` miembros privados, `UPPER_CASE` constantes. +- **Costuras del título tras 16:9** son tuning artístico, no bug, post-Fase 10. +- **Push automático** tras cada commit en `rewrite/physics-gpu`. +- **Smoke test xvfb** (`timeout 5 xvfb-run -a ./build/orni 2>&1 | head -40`) + tras builds importantes para validar arranque. + +## Convenciones técnicas + +- **Lenguaje código**: inglés. **Lenguaje comentarios**: castellano. **Strings + de UI**: valenciano (preservados). +- **Coordenadas**: cartesianas (`Vec2`). Polares prohibidas (legacy). +- **Resolución lógica**: 1280×720. Constantes en `core/defaults.hpp`. +- **Audio**: import de AEEA. API: `Audio::get()->playSound/playMusic` con + crossfade y efectos. +- **Física**: `Physics::RigidBody` en `core/physics/rigid_body.hpp`, + `Physics::PhysicsWorld` en `core/physics/physics_world.hpp/.cpp`. + Entity tiene `body_` como member. Flujo tri-fase por frame en + `GameScene::update`: + 1. `physics_world_.update(dt)` — integrar y resolver + 2. `entity.postUpdate(dt)` — sincronizar mirror (center_, angle_) + 3. Lógica del juego (input, AI, etc.) — aplica fuerzas/cambia velocity + +## Cómo reanudar tras compactación + +1. Lee este archivo. +2. Lee `MEMORY.md` (memorias persistentes). +3. `git log --oneline -20` para ver últimos commits. +4. `git status` para ver si hay trabajo en curso sin commitear. +5. Si la fase actual estaba a medias: continúa donde se quedó (este archivo + indica en qué fase estamos). +6. Tras cada commit, push automático: `git push origin rewrite/physics-gpu`. + +## Configuración física actual (Fase 6e) + +| Entidad | mass | radius (world) | restitution | linear_damping | angular_damping | +|---|---|---|---|---|---| +| Ship | 10.0 | 12 | 0.6 | 1.5 | 0.0 | +| Enemy Pentagon | 5.0 | 20 | 1.0 | 0.0 | 0.0 | +| Enemy Quadrat | 8.0 | 20 | 1.0 | 0.0 | 0.0 | +| Enemy Molinillo | 4.0 | 20 | 1.0 | 0.0 | 0.0 | +| Bullet | 0.5 | **0** (cinemática) | 0 | 0 | 0 | +| Wall (PLAYAREA bounds) | ∞ (estático) | — | — | — | — | + +Nota: Bullet usa `radius=0` en el body físico para que no participe en las +colisiones del world (ni body-body ni bounds). El radio de gameplay +(`Defaults::Entities::BULLET_RADIUS = 3`) lo expone vía `getCollisionRadius()` +y lo consumen `check_collision` y la detección de salida del PLAYAREA en +`Bullet::update`. + +Las paredes son implícitas: `physics_world_.setBounds(PLAYAREA)` en +`GameScene::init`. No son `RigidBody` separados, sino un AABB que rebota. diff --git a/data/config/postfx.yaml b/data/config/postfx.yaml new file mode 100644 index 0000000..812cf01 --- /dev/null +++ b/data/config/postfx.yaml @@ -0,0 +1,34 @@ +# postfx.yaml - Parámetros del shader de postprocesado +# +# Este archivo configura el pase final del renderer (bloom + flicker + +# background pulse). Se carga al iniciar el juego desde resources.pack. +# Si falta o tiene errores, se usan los valores por defecto de +# Defaults::PostFx (defaults.hpp). +# +# Tip de tuning: +# - Para más "neón vector", sube bloom.intensity y bloom.radius_px. +# - Para más "CRT viejo", sube flicker.amplitude (riesgo de mareo si >0.3). +# - Background es muy sutil; pasa los componentes G a 0.15-0.20 para +# un fondo verde-tenue más marcado. + +# Bloom / glow: desenfoque gaussiano de las regiones brillantes. +bloom: + enabled: true + intensity: 0.6 # 0..2 — cuanto del bloom se suma a la imagen + threshold: 0.30 # 0..1 — luminancia mínima que aporta al bloom + radius_px: 2.0 # radio del kernel en píxeles lógicos (1..8 razonable) + +# Flicker: modulación global de brillo (efecto fósforo CRT). +# Sustituye a la antigua oscilación CPU del ColorOscillator. +flicker: + enabled: true + amplitude: 0.10 # 0..1 — profundidad del flicker + frequency_hz: 6.0 # Hz — velocidad de la pulsación + +# Background pulse: color de fondo oscilante (suma aditiva). +# RGB en [0..255]; el shader normaliza a [0..1]. +background: + enabled: true + color_min: [0, 5, 0] # negro casi puro + color_max: [0, 15, 0] # verde muy tenue + pulse_frequency_hz: 6.0 # Hz — sincronizado con flicker por defecto diff --git a/data/shapes/bullet.shp b/data/shapes/bullet.shp index 1b3a353..ad9d4e2 100644 --- a/data/shapes/bullet.shp +++ b/data/shapes/bullet.shp @@ -1,23 +1,7 @@ -# bullet.shp - Projectil (petit pentàgon) -# © 1999 Visente i Sergi (versió Pascal) -# © 2025 Port a C++20 amb SDL3 +# bullet.shp - Projectil (octàgon, radi=3) name: bullet scale: 1.0 center: 0, 0 -# Cercle (octàgon regular radi=3) -# 8 punts equidistants (45° entre ells) per aproximar un cercle -# Començant a angle=-90° (amunt), rotant sentit horari -# -# Conversió polar→cartesià (radi=3, SDL: Y creix cap avall): -# angle=-90°: (0.00, -3.00) -# angle=-45°: (2.12, -2.12) -# angle=0°: (3.00, 0.00) -# angle=45°: (2.12, 2.12) -# angle=90°: (0.00, 3.00) -# angle=135°: (-2.12, 2.12) -# angle=180°: (-3.00, 0.00) -# angle=225°: (-2.12, -2.12) - polyline: 0,-3 2.12,-2.12 3,0 2.12,2.12 0,3 -2.12,2.12 -3,0 -2.12,-2.12 0,-3 diff --git a/data/shapes/enemy_pentagon.shp b/data/shapes/enemy_pentagon.shp index 8771dc2..23c84c9 100644 --- a/data/shapes/enemy_pentagon.shp +++ b/data/shapes/enemy_pentagon.shp @@ -1,21 +1,7 @@ -# enemy_pentagon.shp - ORNI enemic (pentàgon regular) -# © 1999 Visente i Sergi (versió Pascal) -# © 2025 Port a C++20 amb SDL3 +# enemy_pentagon.shp - ORNI enemic (pentàgon regular, radi=20) name: enemy_pentagon scale: 1.0 center: 0, 0 -# Pentàgon regular radi=20 -# 5 punts equidistants al voltant d'un cercle (72° entre ells) -# Començant a angle=-90° (amunt), rotant sentit antihorari -# -# Angles: -90°, -18°, 54°, 126°, 198° -# Conversió polar→cartesià (SDL: Y creix cap avall): -# angle=-90°: (0.00, -20.00) -# angle=-18°: (19.02, -6.18) -# angle=54°: (11.76, 16.18) -# angle=126°: (-11.76, 16.18) -# angle=198°: (-19.02, -6.18) - polyline: 0,-20 19.02,-6.18 11.76,16.18 -11.76,16.18 -19.02,-6.18 0,-20 diff --git a/data/shapes/enemy_square.shp b/data/shapes/enemy_square.shp index 2d6f16c..3c17405 100644 --- a/data/shapes/enemy_square.shp +++ b/data/shapes/enemy_square.shp @@ -1,19 +1,7 @@ -# enemy_square.shp - ORNI enemic (quadrat regular) -# © 2025 Port a C++20 amb SDL3 +# enemy_square.shp - ORNI enemic (quadrat regular, radi=20) name: enemy_square scale: 1.0 center: 0, 0 -# Quadrat regular radi=20 (circumscrit) -# 4 punts equidistants al voltant d'un cercle (90° entre ells) -# Començant a angle=-90° (amunt), rotant sentit horari -# -# Angles: -90°, 0°, 90°, 180° -# Conversió polar→cartesià (SDL: Y creix cap avall): -# angle=-90°: (0.00, -20.00) -# angle=0°: (20.00, 0.00) -# angle=90°: (0.00, 20.00) -# angle=180°: (-20.00, 0.00) - polyline: 0,-20 20,0 0,20 -20,0 0,-20 diff --git a/data/shapes/ship.shp b/data/shapes/ship.shp index f4f683e..aebcbe7 100644 --- a/data/shapes/ship.shp +++ b/data/shapes/ship.shp @@ -1,24 +1,8 @@ -# ship.shp - Nau del jugador 1 (triangle amb base còncava - punta de fletxa) -# © 1999 Visente i Sergi (versió Pascal) -# © 2025 Port a C++20 amb SDL3 +# ship.shp - Nau del jugador 1 +# Triangle amb base còncava (punta de fletxa) name: ship scale: 1.0 center: 0, 0 -# Triangle amb base còncava tipus "punta de fletxa" -# Punts originals (polar): -# p1: r=12, angle=270° (3π/2) → punta amunt -# p2: r=12, angle=45° (π/4) → base dreta-darrere -# p3: r=12, angle=135° (3π/4) → base esquerra-darrere -# -# MODIFICACIÓ: afegit p4 al mig de la base, desplaçat cap al centre -# p4: (0, 4) → punt central de la base, cap endins -# -# Conversió polar→cartesià (angle-90° perquè origen visual és amunt): -# p1: (0, -12) → punta -# p2: (8.49, 8.49) → base dreta -# p4: (0, 4) → base centre (cap endins) -# p3: (-8.49, 8.49) → base esquerra - polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12 diff --git a/data/shapes/ship2.shp b/data/shapes/ship2.shp index 9d6280c..4bf35d3 100644 --- a/data/shapes/ship2.shp +++ b/data/shapes/ship2.shp @@ -1,30 +1,11 @@ -# ship2.shp - Nau del jugador 2 (triangle amb circulito central) -# © 1999 Visente i Sergi (versió Pascal) -# © 2025 Port a C++20 amb SDL3 +# ship2.shp - Nau del jugador 2 +# Triangle amb cercle central (distintiu visual) name: ship2 scale: 1.0 center: 0, 0 -# Triangle amb base còncava tipus "punta de fletxa" -# (Mateix que ship.shp) -# Punts originals (polar): -# p1: r=12, angle=270° (3π/2) → punta amunt -# p2: r=12, angle=45° (π/4) → base dreta-darrere -# p3: r=12, angle=135° (3π/4) → base esquerra-darrere -# -# MODIFICACIÓ: afegit p4 al mig de la base, desplaçat cap al centre -# p4: (0, 4) → punt central de la base, cap endins -# -# Conversió polar→cartesià (angle-90° perquè origen visual és amunt): -# p1: (0, -12) → punta -# p2: (8.49, 8.49) → base dreta -# p4: (0, 4) → base centre (cap endins) -# p3: (-8.49, 8.49) → base esquerra - -#polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12 polyline: 0,-12 8.49,8.49 -8.49,8.49 0,-12 -# Circulito central (octàgon r=2.5) -# Distintiu visual del jugador 2 +# Octàgon central (radi=2.5) polyline: 0,-2.5 1.77,-1.77 2.5,0 1.77,1.77 0,2.5 -1.77,1.77 -2.5,0 -1.77,-1.77 0,-2.5 diff --git a/data/stages/stages.yaml b/data/stages/stages.yaml index e6a24ac..b1bb66c 100644 --- a/data/stages/stages.yaml +++ b/data/stages/stages.yaml @@ -16,7 +16,7 @@ stages: spawn_interval: 3.0 enemy_distribution: pentagon: 100 - quadrat: 0 + cuadrado: 0 molinillo: 0 difficulty_multipliers: speed_multiplier: 0.7 @@ -32,7 +32,7 @@ stages: spawn_interval: 2.5 enemy_distribution: pentagon: 70 - quadrat: 30 + cuadrado: 30 molinillo: 0 difficulty_multipliers: speed_multiplier: 0.85 @@ -48,7 +48,7 @@ stages: spawn_interval: 2.0 enemy_distribution: pentagon: 50 - quadrat: 30 + cuadrado: 30 molinillo: 20 difficulty_multipliers: speed_multiplier: 1.0 @@ -64,7 +64,7 @@ stages: spawn_interval: 1.8 enemy_distribution: pentagon: 40 - quadrat: 35 + cuadrado: 35 molinillo: 25 difficulty_multipliers: speed_multiplier: 1.1 @@ -80,7 +80,7 @@ stages: spawn_interval: 1.5 enemy_distribution: pentagon: 35 - quadrat: 35 + cuadrado: 35 molinillo: 30 difficulty_multipliers: speed_multiplier: 1.2 @@ -96,7 +96,7 @@ stages: spawn_interval: 1.3 enemy_distribution: pentagon: 30 - quadrat: 30 + cuadrado: 30 molinillo: 40 difficulty_multipliers: speed_multiplier: 1.3 @@ -112,7 +112,7 @@ stages: spawn_interval: 1.0 enemy_distribution: pentagon: 25 - quadrat: 30 + cuadrado: 30 molinillo: 45 difficulty_multipliers: speed_multiplier: 1.4 @@ -128,7 +128,7 @@ stages: spawn_interval: 0.8 enemy_distribution: pentagon: 20 - quadrat: 30 + cuadrado: 30 molinillo: 50 difficulty_multipliers: speed_multiplier: 1.5 @@ -144,7 +144,7 @@ stages: spawn_interval: 0.6 enemy_distribution: pentagon: 15 - quadrat: 25 + cuadrado: 25 molinillo: 60 difficulty_multipliers: speed_multiplier: 1.6 @@ -160,7 +160,7 @@ stages: spawn_interval: 0.5 enemy_distribution: pentagon: 10 - quadrat: 20 + cuadrado: 20 molinillo: 70 difficulty_multipliers: speed_multiplier: 1.8 diff --git a/shaders/line.frag.glsl b/shaders/line.frag.glsl new file mode 100644 index 0000000..086e216 --- /dev/null +++ b/shaders/line.frag.glsl @@ -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; +} diff --git a/shaders/line.vert.glsl b/shaders/line.vert.glsl new file mode 100644 index 0000000..247d3ff --- /dev/null +++ b/shaders/line.vert.glsl @@ -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; +} diff --git a/shaders/postfx.frag.glsl b/shaders/postfx.frag.glsl new file mode 100644 index 0000000..eccd236 --- /dev/null +++ b/shaders/postfx.frag.glsl @@ -0,0 +1,82 @@ +#version 450 + +// Fragment shader del pase de postprocesado. +// Lee la textura offscreen (escena vectorial sobre fondo negro) y produce +// el fragmento final aplicando: +// 1. Bloom kernel 5×5 con high-pass (solo los brillos por encima de +// threshold contribuyen). +// 2. Flicker: multiplicador global de brillo modulado por tiempo +// (sustituye al oscilador CPU del legacy). +// 3. Background pulse: color de fondo que oscila entre min y max y se +// suma a la imagen (las líneas brillan por encima). +// +// Resource sets (SDL_gpu): +// set=2, binding=0 → sampler2D (escena offscreen) +// set=3, binding=0 → uniform buffer (parámetros del postpro) + +layout(set = 2, binding = 0) uniform sampler2D scene; + +layout(set = 3, binding = 0) uniform PostFxUBO { + float time; + float bloom_intensity; + float bloom_threshold; + float bloom_radius_px; + + float flicker_amplitude; + float flicker_frequency_hz; + float background_pulse_freq_hz; + float _pad_a; + + vec4 background_min; // RGB en [0..1], A=1 + vec4 background_max; // RGB en [0..1], A=1 + + vec2 texel_size; // 1.0 / texture_size + vec2 _pad_b; +} ubo; + +layout(location = 0) in vec2 v_uv; +layout(location = 0) out vec4 frag; + +const float TAU = 6.28318530718; + +void main() { + // === BLOOM === + // Kernel 5×5 con muestreo radial y high-pass por luminancia (max RGB). + // Pesos gaussianos: w = exp(-(dx²+dy²) / 4). + vec3 src = texture(scene, v_uv).rgb; + vec3 bloom = vec3(0.0); + float total_weight = 0.0; + for (int dy = -2; dy <= 2; ++dy) { + for (int dx = -2; dx <= 2; ++dx) { + vec2 offset = vec2(float(dx), float(dy)) * ubo.texel_size * ubo.bloom_radius_px; + vec3 c = texture(scene, v_uv + offset).rgb; + float luma = max(c.r, max(c.g, c.b)); + float high_pass = max(0.0, luma - ubo.bloom_threshold); + float w = exp(-(float(dx * dx + dy * dy)) / 4.0); + bloom += c * high_pass * w; + total_weight += w; + } + } + if (total_weight > 0.0) { + bloom /= total_weight; + } + bloom *= ubo.bloom_intensity; + + // === FLICKER === + // Multiplicador global de brillo. Oscila entre (1.0 - amplitude) y 1.0. + // amplitude=0 → sin flicker; amplitude=1 → pulsa entre apagado y máximo. + float pulse = (sin(ubo.time * ubo.flicker_frequency_hz * TAU) * 0.5) + 0.5; + float flicker = 1.0 - (ubo.flicker_amplitude * (1.0 - pulse)); + + // === BACKGROUND PULSE === + // Suma de color de fondo oscilante. min..max se interpolan con sin(t). + float bg_pulse = (sin(ubo.time * ubo.background_pulse_freq_hz * TAU) * 0.5) + 0.5; + vec3 background = mix(ubo.background_min.rgb, ubo.background_max.rgb, bg_pulse); + + // === COMPOSICIÓN === + // El offscreen viene con clear=black, por lo que solo las líneas y el + // bloom aportan luz. Sumamos el fondo y luego multiplicamos por flicker + // para que el pulso afecte a todo (líneas + bloom + bg). + vec3 lines_and_glow = (src + bloom) * flicker; + frag = vec4(background + lines_and_glow, 1.0); +} diff --git a/shaders/postfx.vert.glsl b/shaders/postfx.vert.glsl new file mode 100644 index 0000000..70ac360 --- /dev/null +++ b/shaders/postfx.vert.glsl @@ -0,0 +1,28 @@ +#version 450 + +// Vertex shader del pase de postprocesado. +// Emite un solo triángulo que cubre toda la pantalla (técnica del "fullscreen +// triangle"): tres vértices en (-1,-1), (3,-1), (-1,3) → la región visible +// [-1..1]² queda completamente cubierta y el clip recorta el resto. No hace +// falta vertex buffer; el draw es DrawPrimitives(vertex_count=3). + +layout(location = 0) out vec2 v_uv; + +void main() { + vec2 positions[3] = vec2[3]( + vec2(-1.0, -1.0), + vec2( 3.0, -1.0), + vec2(-1.0, 3.0) + ); + // UV.y invertida para compensar la diferencia entre la convención de + // clip-space del line shader (ndc.y flipeado, GL-style) y la convención + // de muestreo de SDL_gpu/Vulkan (origen de textura en top-left). Sin esta + // inversión, el offscreen se ve cabeza-abajo en el composite. + vec2 uvs[3] = vec2[3]( + vec2(0.0, 1.0), + vec2(2.0, 1.0), + vec2(0.0, -1.0) + ); + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + v_uv = uvs[gl_VertexIndex]; +} diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp index be1eac7..8bb79d0 100644 --- a/source/core/audio/audio.cpp +++ b/source/core/audio/audio.cpp @@ -1,183 +1,291 @@ -#include "audio.hpp" +#include "core/audio/audio.hpp" -#include // Para SDL_LogInfo, SDL_LogCategory, SDL_G... +#include // Para SDL_GetError, SDL_Init #include // Para clamp -#include // Para std::cout +#include // Para std::fprintf -// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp) -// clang-format off -#undef STB_VORBIS_HEADER_ONLY -#include "external/stb_vorbis.h" -// clang-format on +#include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound +#include "core/audio/jail_audio.hpp" // Para Ja::* (motor jailgames) +#include "core/audio/sound_effects_config.hpp" // Para SoundEffectsConfig +#include "core/defaults.hpp" // Para Defaults::Audio::FREQUENCY -#include "core/audio/audio_cache.hpp" // Para AudioCache -#include "core/audio/jail_audio.hpp" // Para JA_FadeOutMusic, JA_Init, JA_PauseM... -#include "game/options.hpp" // Para AudioOptions, audio, MusicOptions +// Invariant compile-time: tots los valors d'Audio::Group han de cabre als slots +// de volum per grup que manté l'engine. Si s'afegeix una nueva entrada a Group +// y no s'incrementa Ja::MAX_GROUPS, este assert falla antes de compilar. +static_assert(static_cast(Audio::Group::INTERFACE) < Ja::MAX_GROUPS, + "Audio::Group té més entrades que slots té Ja::MAX_GROUPS"); // Singleton -Audio* Audio::instance = nullptr; +std::unique_ptr