Files
fn_registry/dev/issues/completed/0049d-graph-edges-vertex-pulling.md
T
egutierrez daf491cd99 perf(viz): graph_renderer edges via TBO + vertex pulling (issue 0049d)
El buffer de aristas pasa a estatico (16B/arista: source, target, color,
flags) y solo se reupload cuando cambia el grafo. Las posiciones de los
nodos viven en un Texture Buffer Object (RG32F) actualizado por frame; el
vertex shader hace texelFetch con gl_VertexID & 1 para elegir endpoint.
Draw call: glDrawArraysInstanced(GL_LINES, 0, 2, edge_count) con divisor=1.

Para 100k aristas: el upload de 4.8 MB/frame baja a 0 en regimen estable.
edge_alpha pasa a uniform; la pre-multiplicacion en CPU desaparece. GLSL
sigue en 330 core (samplerBuffer/texelFetch estan en 1.40+).

gl_loader gana glBufferSubData, glVertexAttribIPointer y glTexBuffer (en
Linux ya estaban via GL_GLEXT_PROTOTYPES; ahora estan disponibles tambien
en MinGW/Windows).

Tests: nuevo test_graph_edge_static valida el layout de 16B y el packing
RGBA8 del fallback. test_visual sigue verde — render visualmente identico.

Bump graph_renderer 1.2.0 -> 1.3.0.
2026-04-29 22:32:38 +02:00

4.5 KiB
Raw Blame History

0049d — Aristas via vertex pulling con TBO

Metadata

Campo Valor
ID 0049d
Estado pendiente
Prioridad alta
Tipo mejora rendimiento — parte de #0049

Dependencias

Bloqueada por: 0049c (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:
    #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