--- id: "0049c" title: "`graph_renderer` Tier 1: RGBA8, orphan buffers, frustum cull, auto-pause" status: completado type: feature domain: [] scope: multi-app priority: alta depends: [] blocks: [] related: [] created: 2026-05-17 updated: 2026-05-17 tags: [] --- # 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) |