From 582bd0ee305547a058edc1d65a8adb8426cdcbae Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 29 May 2026 11:56:14 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20detalla=20el=20pipeline=20de=20shaders?= =?UTF-8?q?=20i=20la=20f=C3=ADsica=20al=20document=20d'arquitectura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DOCS/ARQUITECTURA.md | 104 +++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/DOCS/ARQUITECTURA.md b/DOCS/ARQUITECTURA.md index 0eeb94f..30acb61 100644 --- a/DOCS/ARQUITECTURA.md +++ b/DOCS/ARQUITECTURA.md @@ -378,19 +378,41 @@ OFF, la escena offscreen sale tal cual (passthrough), útil para A/B testing. - **[Curtain](source/core/graphics/curtain.hpp)** — cortinilla negra para transiciones; se pinta siempre la última. -### 5.5 Shaders +### 5.5 Shaders: fuentes, compilación y selección -Las fuentes GLSL viven en [shaders/](shaders/) (`line.vert/frag`, `bloom.frag`, -`postfx.vert/frag`). Se compilan a SPIR-V (Linux/Windows) y MSL (macOS) y se -**embeben** como cabeceras en el binario: ver -[gpu/spv/](source/core/rendering/gpu/spv/) y [gpu/msl/](source/core/rendering/gpu/msl/). -La selección por plataforma la hace el `GpuDevice`. No se cargan shaders de disco -en runtime. +Las fuentes GLSL viven en [shaders/](shaders/): `line.vert.glsl`, `line.frag.glsl`, +`postfx.vert.glsl`, `postfx.frag.glsl`, `bloom.frag.glsl`. **No se cargan de disco en +runtime**: se embeben como arrays/strings en el binario. -> No verificado en detalle: los pormenores del pipeline de compilación de shaders -> (qué target de CMake genera los `.spv.h`/`.msl.h`). Los headers embebidos existen -> y se referencian desde las pipelines; el mecanismo exacto de generación habría que -> confirmarlo en [CMakeLists.txt](CMakeLists.txt) y [tools/shaders/](tools/shaders/). +**Pipeline de compilación (SPIR-V, Linux/Windows).** Lo orquesta +[CMakeLists.txt:139-187](CMakeLists.txt#L139). La lógica clave: + +- Para cada `.glsl` hay un header destino en + [gpu/spv/](source/core/rendering/gpu/spv/) (p. ej. `line_vert_spv.h`). +- CMake busca `glslc` (`find_program(GLSLC_EXE ...)`). Hay **tres caminos**: + 1. `glslc` presente → un `add_custom_command` regenera los headers SPV cuando + cambian los `.glsl`, vía el target `shaders` del que depende el ejecutable. + 2. `glslc` ausente pero **los headers ya están commiteados** → se usan tal cual + (los `.spv.h` están versionados en el repo). + 3. `glslc` ausente **y** faltan headers → `FATAL_ERROR` pidiendo instalar + `shaderc`/`vulkan-sdk`. +- La conversión binario→header la hace el script + [tools/shaders/compile_spirv.cmake](tools/shaders/compile_spirv.cmake): invoca + `glslc -O -fshader-stage=` para producir el `.spv`, lee el binario como + hex (`file(READ ... HEX)`) y escribe un header con + `static const uint8_t LINE_VERT_SPV[] = { 0x.., ... };` y su `_SIZE`. Es + multiplataforma puro CMake (no necesita `bash` ni `xxd`). + +**MSL (macOS).** Los headers Metal en [gpu/msl/](source/core/rendering/gpu/msl/) +(`line_vert.msl.h`, etc.) están **escritos a mano** (no los genera CMake), como +strings literales C++. + +**Selección SPV vs MSL: es _compile-time_, no runtime.** La hace +[shader_factory.hpp](source/core/rendering/gpu/shader_factory.hpp) con `#ifdef __APPLE__`: +en Apple expone `createShaderMSL(...)` (`SDL_GPU_SHADERFORMAT_MSL`), y en el resto +`createShaderSPIRV(...)` (`SDL_GPU_SHADERFORMAT_SPIRV`). Cada pipeline llama al helper +disponible con el header embebido correspondiente. (Es decir: no es `GpuDevice` quien +elige el backend de shader, sino el preprocesador al compilar.) --- @@ -637,6 +659,43 @@ primer toque y muere al segundo durante ese estado. [StageLoader](source/game/stage_system/stage_loader.hpp) — modelo y carga del YAML de stages. +### 10.6 Dos capas de colisión: física vs gameplay + +Conviene no confundirlas, porque conviven: + +**1. Física** — [PhysicsWorld](source/core/physics/physics_world.hpp) / +[physics_world.cpp](source/core/physics/physics_world.cpp). Es un mundo 2D +minimalista de arcade. Cada frame, `update(dt)` hace tres pasos: + +1. **Integración** semi-implícita de Euler con damping exponencial + (`v += (F·invMass)·dt; v *= exp(-damping·dt); x += v·dt`) sobre cada + [RigidBody](source/core/physics/rigid_body.hpp) no estático. Un cuerpo con + `mass=0` (`inverse_mass=0`) es estático (masa infinita). +2. **Rebote contra los bordes** del `PLAYAREA` (`resolveBoundsCollisions`): reposiciona + el cuerpo dentro del rect y refleja la componente normal de la velocidad por su + `restitution`. Antes de reflejar, invoca un `BoundsHitCallback` opcional con la + velocidad de impacto entrante (lo usa GameScene para los efectos de borde). +3. **Colisiones cuerpo-cuerpo** (`resolveBodyCollisions`): broadphase trivial + **O(n²)** (suficiente para ~23 cuerpos), círculo-círculo, con corrección posicional + de penetración + **impulso elástico** `j = -(1+e)(v_rel·n) / (1/mₐ + 1/m_b)` + (referencia Box2D / Chris Hecker, en `resolveBodyPair`). Los cuerpos con `radius=0` + (las balas, cinemáticas puras) **no** participan aquí. + +Los `RigidBody` los poseen las entidades; el mundo solo guarda punteros no-owning +(`addBody`/`removeBody`). + +**2. Gameplay** — [collision_system.cpp](source/game/systems/collision_system.cpp) +(ver [§10.4](#104-colisiones)), que decide *qué pasa* (daño, score, muerte). Usa los +helpers de [collision.hpp](source/core/physics/collision.hpp): `checkCollision` +(círculo-círculo discreto, distancia al cuadrado sin `sqrt`) y `checkCollisionSwept` +(segment-círculo, para que una bala rápida no atraviese un enemigo entre frames — +*anti-tunneling*). Estos checks usan el `collision_radius` de la **entidad** +(con amplificador opcional de hitbox), no el `radius` del body. + +En resumen: la **física** mueve y rebota los cuerpos; el **gameplay** detecta los +contactos relevantes para las reglas. Una bala no rebota físicamente (radius 0) pero sí +provoca daño vía el check *swept*. + --- ## 11. IA del modo demo (attract) @@ -765,18 +824,15 @@ Viven en [game/effects/](source/game/effects/) y son managers con pools: ### Notas de honestidad sobre la cobertura -- Las secciones de **render** ([§5](#5-renderizado-de-la-lógica-al-píxel)), **bucle** - ([§3](#3-bucle-principal)), **escenas** ([§4](#4-sistema-de-escenas)), **eventos - globales** ([§9](#9-comunicación-entre-módulos)), **GameScene/IA demo** - ([§10](#10-lógica-del-juego)–[§11](#11-ia-del-modo-demo-attract)) se verificaron - leyendo directamente los ficheros y firmas citados. -- El **pipeline de compilación de shaders** (cómo se generan los `.spv.h`/`.msl.h`) - no se ha trazado al detalle; los headers embebidos existen y se usan, pero el - paso de build exacto queda por confirmar en [CMakeLists.txt](CMakeLists.txt) / - [tools/shaders/](tools/shaders/). -- El detalle interno de la **PhysicsWorld** (algoritmo de resolución de colisiones - físicas) se ha descrito a alto nivel; para el detalle, ver - [physics_world.cpp](source/core/physics/physics_world.cpp) y - [collision.hpp](source/core/physics/collision.hpp). +- Todas las secciones se verificaron leyendo directamente los ficheros y firmas + citados, incluyendo el **pipeline de compilación de shaders** + ([§5.5](#55-shaders-fuentes-compilación-y-selección): `CMakeLists.txt` + + `tools/shaders/compile_spirv.cmake` + `shader_factory.hpp`) y el interior de la + **física** ([§10.6](#106-dos-capas-de-colisión-física-vs-gameplay): + `physics_world.cpp` + `collision.hpp` + `rigid_body.hpp`). +- Lo que **no** se ha trazado a fondo y queda como lectura directa del código si hace + falta: los detalles finos de animación de cada overlay (curvas de easing del + `Notifier`/`ServiceMenu`) y la coreografía interna completa de `LogoScene` y + `TitleScene` (más allá de sus estados). Son descriptivos, no estructurales.