Files
fn_registry/dev/issues/completed/0049c-graph-renderer-tier1.md
T
egutierrez 427262b892 perf(viz): graph_renderer Tier 1 (RGBA8 + orphan + frustum cull) + force_layout auto-pause helper
Issue 0049c. Tres optimizaciones internas en graph_renderer.cpp + un
helper puro en graph_force_layout para detectar convergencia. API publica
intacta — solo cambian el layout interno de los buffers, el shader y
los costes por frame.

1. RGBA8 color packing
   - El instance buffer de nodos pasa de (x,y,size,r,g,b,a) 28B a
     (x,y,size,color_u32) 16B (-43%). Aristas: 24B → 12B/vertex (-50%).
   - Shaders desempaquetan con bit shifts (compatible GL 3.30+, no
     necesita unpackUnorm4x8 que es 4.20+).
   - Helpers expuestos: pack_rgba8 / unpack_rgba8 / modulate_alpha_rgba8
     en graph_renderer.h. Los GraphNode.color y la paleta ya tenian el
     layout correcto (R en LSB), asi que CPU ahora pasa el uint32 directo
     sin convertir a 4 floats por nodo y por frame.

2. Capacity-tracked streaming buffers
   - Sustituye el doble glBufferData de antes por:
       glBufferData(NULL, capacity, STREAM_DRAW)   // orphan + reserva
       glBufferSubData(0, used_bytes, data)        // solo lo usado
   - capacity crece x2 cuando hace falta (inicial 4096 nodos /
     8192 vertices de aristas) → reallocaciones en O(log N).
   - Staging CPU (NodeInstance* / EdgeVertex*) reusado entre frames con
     realloc, no malloc/free per frame.

3. Frustum cull (CPU-side)
   - AABB del viewport en world coords con margen 10%.
   - Aristas: skip si AABB del segmento no intersecta el viewport.
   - Nodos: solo los visibles entran al instance buffer; visible_count
     es el N que pasa a glDrawArraysInstanced. Pop-in de borde mitigado
     por el margen.

4. graph_force_layout_should_pause(low_frames, min_consecutive)
   - Helper puro: el caller mantiene el contador, la funcion solo
     decide si parar. Reemplaza la rama inline en demos_graph.cpp.
   - Test Catch2 con secuencias artificiales.

Tests: test_graph_pack_rgba8 (16401 asserts, 4 cases — roundtrip exhaustivo
+ alpha modulation + clamp). test_graph_should_pause (3 cases, 14 asserts).
Los 29 tests del cpp/tests/ siguen verdes (incluido test_visual con goldens).

Bump versiones:
- graph_renderer 1.1.0 → 1.2.0
- graph_force_layout 1.0.0 → 1.1.0  (tested: true via should_pause test)
2026-04-29 22:17:13 +02:00

106 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0049c — `graph_renderer` Tier 1: RGBA8, orphan buffers, frustum cull, auto-pause
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0049c |
| **Estado** | pendiente |
| **Prioridad** | alta |
| **Tipo** | mejora rendimiento — parte de [#0049](0049-osint-graph-viewer.md) |
## Dependencias
**Bloqueada por:** [0049b](0049b-cpp-bump-gl-43.md) (recomendado pero no estricto — cambios funcionan en 3.3 tambien).
**Desbloquea:** [0049d](0049d-graph-edges-vertex-pulling.md), demos perf-realistas para issues posteriores.
---
## Objetivo
Optimizaciones baratas y de gran impacto sobre `graph_renderer.cpp` y `graph_force_layout` para subir de ~5k nodos a ~20k nodos a 60fps en GPU integrada **sin cambiar la API publica**.
## Contexto
Hoy el renderer:
- Empaqueta colores como 4 floats × N (16 bytes/nodo) en el instance buffer.
- Llama `glBufferData` cada frame → driver realloca el VBO.
- Sube todas las aristas siempre, aunque esten fuera del viewport.
- Force layout corre cada frame aunque la energia sea minima (estado convergido).
## Arquitectura
```
cpp/functions/viz/
├── graph_renderer.{h,cpp} # MOD
├── graph_renderer.md # MOD: bump version (1.x)
├── graph_force_layout.{h,cpp} # MOD: helper auto_pause
└── graph_force_layout.md # MOD
```
Sin cambios en la API publica — son optimizaciones internas.
## Tareas
### Fase 1 — Color packing RGBA8
- [ ] **1.1** En el instance buffer, cambiar layout de `(x, y, size, r, g, b, a)` floats a `(x, y, size, color_rgba8)` donde `color_rgba8` es uint32 packed.
- [ ] **1.2** Ajustar shader vertex de nodos: `layout(location=3) in uint a_color; vec4 col = unpackUnorm4x8(a_color);`.
- [ ] **1.3** Ajustar el packing en CPU: helper `pack_rgba8(r,g,b,a) = (a<<24)|(b<<16)|(g<<8)|r`.
- [ ] **1.4** Idem para el buffer de aristas (color por vertex → uint32 por vertex).
### Fase 2 — Orphan buffer pattern
- [ ] **2.1** Reemplazar `glBufferData(GL_ARRAY_BUFFER, sz, data, GL_DYNAMIC_DRAW)` por:
```cpp
glBufferData(GL_ARRAY_BUFFER, capacity_bytes, nullptr, GL_STREAM_DRAW); // orphan
glBufferSubData(GL_ARRAY_BUFFER, 0, used_bytes, data);
```
- [ ] **2.2** Mantener `capacity_bytes` interno en el `GraphRenderer` y crecer al doble si `used_bytes > capacity`.
### Fase 3 — Frustum cull aristas
- [ ] **3.1** Calcular AABB visible en world coords:
```cpp
float wx0 = cam_x - (width/2)/zoom; float wx1 = cam_x + (width/2)/zoom;
float wy0 = cam_y - (height/2)/zoom; float wy1 = cam_y + (height/2)/zoom;
```
- [ ] **3.2** En el bucle de aristas, skip si AABB de la arista (segmento source→target con margen) no intersecta el viewport AABB.
- [ ] **3.3** Nodos: similar — skip nodos cuyo AABB (centro ± size) cae fuera. Como son draws instanced, el cull se hace empaquetando solo los visibles en el instance buffer (mantener un counter `visible_count`).
### Fase 4 — Auto-pause force layout
- [ ] **4.1** En `graph_force_layout.h`, anadir helper:
```cpp
// Devuelve true si la energia ha caido bajo el umbral durante N frames consecutivos.
bool graph_force_layout_should_pause(float energy, float threshold, int min_consecutive);
```
- [ ] **4.2** Documentar uso en el `.md`. El consumer guarda un contador interno; el helper es puro.
- [ ] **4.3** Migrar `demos_graph.cpp` para usarlo y para no invocar `_step` cuando `paused == true`. Boton "Resume" ya existe.
### Fase 5 — Tests + benchmark
- [ ] **5.1** Test Catch2 sobre `pack_rgba8`/`unpack_rgba8`: roundtrip exacto.
- [ ] **5.2** Test Catch2 sobre `graph_force_layout_should_pause`: secuencias artificiales.
- [ ] **5.3** Benchmark manual en `demos_graph` con N=20000: anotar fps antes/despues en el .md de la funcion (`notes:`).
### Fase 6 — Cleanup
- [ ] Bump version del .md de `graph_renderer` a 1.1.0 y de `graph_force_layout` a 1.1.0.
- [ ] `fn index` y verificar.
- [ ] Commit `perf(viz): graph_renderer Tier 1 (RGBA8, orphan, cull) + force_layout auto-pause`.
## Criterio de done
- [ ] `demos_graph` con 20k nodos a 60fps en GPU integrada de pruebas.
- [ ] Tests verdes.
- [ ] `nvidia-smi` o `radeontop` muestran que la CPU baja respecto al baseline (perfilar con Tracy si TRACY_ENABLE).
## Riesgos
| Riesgo | Mitigacion |
|---|---|
| `unpackUnorm4x8` no esta en GL 3.3 sin extension | Esta en core 4.0+; con bump 0049b ya disponible. Si 0049b no se mergea antes, fallback a `(color>>0)&0xff)/255.0` manual |
| Frustum cull provoca pop-in en bordes | Anadir margen del 10% del viewport AABB |
| Crecimiento de capacity buffer en streaming | Crecer al doble; documentar capacity inicial razonable (4096 nodos) |