# 0049d — Aristas via vertex pulling con TBO ## Metadata | Campo | Valor | |-------|-------| | **ID** | 0049d | | **Estado** | pendiente | | **Prioridad** | alta | | **Tipo** | mejora rendimiento — parte de [#0049](0049-osint-graph-viewer.md) | ## Dependencias **Bloqueada por:** [0049c](0049c-graph-renderer-tier1.md) (orphan + RGBA8 ya en sitio). --- ## Objetivo Eliminar la reconstruccion del buffer de aristas en CPU cada frame. Las posiciones de nodos viven en un Texture Buffer Object (TBO); el vertex shader de aristas hace fetch de source/target con `gl_VertexID`. El buffer de aristas es estatico (`source_idx`, `target_idx`, `type_id`), solo cambian las posiciones — y eso ya estaba para los nodos. ## Contexto Tras 0049c, el bottleneck principal restante es el upload de 12 floats × E aristas cada frame. Para 100k aristas: 4.8 MB/frame. Vertex pulling lo elimina por completo. ## Arquitectura ``` cpp/functions/viz/ ├── graph_renderer.{h,cpp} # MOD: anadir TBO + buffer estatico de aristas └── graph_renderer.md # MOD: bump 1.1 → 1.2 ``` Cambios: 1. `GraphRenderer` gana `unsigned int node_pos_tbo, node_pos_tex` (texture buffer y su sampler view). 2. `GraphRenderer` gana `unsigned int edge_static_vbo` con `(uint source, uint target, uint color, uint flags)` por arista — subido una vez (o cuando cambia el grafo, no cada frame). 3. Buffer de posiciones de nodos (`vec2[]`) se sube como TBO vinculado al `node_pos_tex`. 4. Vertex shader de aristas: ```glsl #version 430 core layout(location=0) in uint a_source; layout(location=1) in uint a_target; layout(location=2) in uint a_color; layout(location=3) in uint a_flags; uniform samplerBuffer u_node_pos; // vec2[] como TBO out vec4 v_color; void main() { int idx = (gl_VertexID & 1) == 0 ? int(a_source) : int(a_target); vec2 p = texelFetch(u_node_pos, idx).xy; // mismo MVP que ya estaba gl_Position = u_mvp * vec4(p, 0.0, 1.0); v_color = unpackUnorm4x8(a_color); } ``` Cada arista renderiza con `glDrawArrays(GL_LINES, 0, edge_count*2)`. El fragment shader ya existia. ## Tareas ### Fase 1 — TBO de posiciones - [ ] **1.1** En `graph_renderer_create`: crear `node_pos_buf` (VBO) + `node_pos_tex` (texture buffer view) con `glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, node_pos_buf)`. - [ ] **1.2** En `graph_renderer_draw`: empaquetar posiciones de nodos en un buffer flotante `(x,y) × N` y `glBufferSubData` al `node_pos_buf` (orphan + sub). - [ ] **1.3** Antes del draw de aristas: `glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_BUFFER, node_pos_tex); glUniform1i(u_node_pos_loc, 0);`. ### Fase 2 — Buffer estatico de aristas - [ ] **2.1** Anadir API interna `mark_edges_dirty()` que regenera el `edge_static_vbo`. - [ ] **2.2** Si el caller pasa `graph.edges_revision != cached_revision`, regenerar. Para el primer paso, usar siempre `cached==false → regenerar` y optimizar luego con un campo `revision` en `GraphData`. - [ ] **2.3** Layout: `struct EdgeStatic { uint source; uint target; uint color_rgba8; uint flags; }` (16 bytes por arista). ### Fase 3 — Shaders de aristas - [ ] **3.1** Reescribir vertex shader como en la arquitectura (4.3 core). - [ ] **3.2** Verificar fragment shader no necesita cambios. - [ ] **3.3** Reverificar `glLineWidth`/edge_alpha siguen funcionando. ### Fase 4 — Bench + tests - [ ] **4.1** `demos_graph` con 20k nodos + 100k aristas a 60fps en GPU integrada. - [ ] **4.2** Profile con Tracy: el bucle de aristas en CPU debe desaparecer. - [ ] **4.3** Test Catch2 minimo: render a FBO + readback, verificar que un grafo conocido produce un frame no-vacio (smoke test, no golden). ### Fase 5 — Cleanup - [ ] Bump version `graph_renderer` 1.1.0 → 1.2.0. - [ ] `fn index`. - [ ] Commit `perf(viz): graph_renderer edges via TBO + vertex pulling`. ## Criterio de done - [ ] CPU ms del frame para 100k aristas baja a < 0.5 ms (medible con Tracy o reloj manual). - [ ] Render visualmente identico al pre-cambio. - [ ] Demos de la galeria afectados (`demos_graph`) sin regresiones. ## Riesgos | Riesgo | Mitigacion | |---|---| | `samplerBuffer` no funciona en alguna driver Linux | GL 4.3 core lo exige; si falla en WSL software, marcar test como SKIP igual que `test_visual` | | Mantener `edges_revision` complica la API | Empezar con regenerar siempre y optimizar despues — no premature optimization | | 4 bytes desperdiciados por arista (`flags`) | Justificado por alineacion + futuras flechas/styles |