Files
fn_registry/cpp/functions/viz/graph_renderer.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

4.8 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, framework, params, output
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path framework params output
graph_renderer function cpp viz 1.2.0 impure GraphRenderer* graph_renderer_create(int width, int height, const GraphRendererConfig& config) Renderer GPU de grafos con instanced rendering a FBO, compatible con ImGui::Image para visualizacion de grafos grandes
graph
renderer
opengl
gpu
instanced
fbo
visualization
frustum-cull
rgba8
gl_loader_cpp_gfx
GraphData_cpp_viz
false error_go_core
imgui
false
cpp/functions/viz/graph_renderer.cpp imgui
name desc
width Ancho del framebuffer en pixels
name desc
height Alto del framebuffer en pixels
name desc
config Configuracion visual: outline width, edge width, edge alpha, color de fondo, fade de aristas por distancia a camara
Handle opaco al renderer. Usar graph_renderer_draw() para obtener texture ID de OpenGL, pasable directamente a ImGui::Image()

graph_renderer

Renderer GPU de grafos basado en OpenGL 3.3 core profile con instanced rendering. Renderiza nodos y aristas de un GraphData a un FBO interno y retorna el texture ID para integracion directa con ImGui::Image().

Funciones del API

// Ciclo de vida
GraphRenderer* graph_renderer_create(int width, int height, const GraphRendererConfig& config = {});
void graph_renderer_destroy(GraphRenderer* r);
void graph_renderer_resize(GraphRenderer* r, int width, int height);

// Renderizado
unsigned int graph_renderer_draw(GraphRenderer* r, const GraphData& graph,
                                  float cam_x, float cam_y, float cam_zoom);

Ejemplo de uso con ImGui

// Inicializacion (una vez)
GraphRenderer* renderer = graph_renderer_create(800, 600);

// En el render loop
ImVec2 panel_size = ImGui::GetContentRegionAvail();
graph_renderer_resize(renderer, (int)panel_size.x, (int)panel_size.y);

unsigned int tex = graph_renderer_draw(renderer, graph_data,
                                        cam_x, cam_y, cam_zoom);

ImGui::Image((ImTextureID)(uintptr_t)tex,
             panel_size,
             ImVec2(0, 1), ImVec2(1, 0)); // flip Y para OpenGL

// Destruccion
graph_renderer_destroy(renderer);

Notas de implementacion

Renderizado de nodos: Instanced rendering con un quad unitario [-0.5, 0.5] expandido por el tamano del nodo. El fragment shader aplica un SDF circular con anti-aliasing via smoothstep y un anillo de outline.

Renderizado de aristas: GL_LINES con datos de posicion y color empaquetados por arista. El ancho se controla con GraphRendererConfig::edge_width.

Transformacion de camara:

tx = -cam_x * zoom + width/2
ty = -cam_y * zoom + height/2
ndc = (screen / viewport) * 2 - 1

Paleta de comunidades: 10 colores ABGR usados cuando node.color == 0, seleccionados por node.community % 10.

Estado GL: Guarda y restaura GL_FRAMEBUFFER_BINDING y GL_VIEWPORT para ser compatible con el render loop de ImGui sin efectos secundarios.

Includes GL: Usa gfx/gl_loader.h (v1.1+). En Linux es no-op (incluye headers con GL_GLEXT_PROTOTYPES). En Windows expone los simbolos modernos via wglGetProcAddress con macros #define gl* fn_gl*. Cualquier app que use graph_renderer debe linkear gl_loader.cpp y llamar fn::gfx::gl_loader_init() una vez tras crear el contexto GL.

Notas

  • v1.2 (2026-04-29, issue 0049c): tres optimizaciones internas, API publica intacta.

    1. RGBA8 packing: el buffer de instancia/vertice usa uint32 por color en lugar de 4 floats. Nodo: 28 → 16 bytes/instance (-43%). Edge: 24 → 12 bytes/vertex (-50%). Los shaders desempaquetan con bit shifts (compatible GL 3.30+, sin necesidad de unpackUnorm4x8 que es 4.20+). Helpers expuestos en el .h: pack_rgba8, unpack_rgba8, modulate_alpha_rgba8 (testeados en test_graph_pack_rgba8.cpp).
    2. Capacity-tracked streaming buffers: el VBO se mantiene orphaned con glBufferData(NULL, capacity) y se actualiza con glBufferSubData solo los bytes usados. La capacidad crece x2 cuando hace falta (inicial: 4096 nodos / 8192 vertices de aristas) → reallocaciones en O(log N). Staging CPU reutilizado entre frames.
    3. Frustum cull: nodos y aristas fuera del viewport AABB (con margen 10%) se saltan en CPU antes del upload. Para nodos, solo los visibles entran en el instance buffer (glDrawArraysInstanced con visible_count). Para aristas, AABB del segmento contra viewport. Pop-in al borde mitigado por el margen.

    Resultado esperado: ~20k nodos a 60fps en GPU integrada cuando cam_zoom mantiene la mayoria fuera del viewport.

  • v1.1 (2026-04-25): cambia de raw <GL/glext.h> a gfx/gl_loader.h para que compile en cross-compile MinGW. Sin cambios funcionales — el binario Linux es bit-equivalente.