fad4006f60
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
120 lines
4.7 KiB
Markdown
120 lines
4.7 KiB
Markdown
---
|
||
id: "0049d"
|
||
title: "Aristas via vertex pulling con TBO"
|
||
status: completado
|
||
type: feature
|
||
domain: []
|
||
scope: multi-app
|
||
priority: alta
|
||
depends: []
|
||
blocks: []
|
||
related: []
|
||
created: 2026-05-17
|
||
updated: 2026-05-17
|
||
tags: []
|
||
---
|
||
# 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 |
|