02b4141cc1
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)
99 lines
4.8 KiB
Markdown
99 lines
4.8 KiB
Markdown
---
|
|
name: graph_renderer
|
|
kind: function
|
|
lang: cpp
|
|
domain: viz
|
|
version: "1.2.0"
|
|
purity: impure
|
|
signature: "GraphRenderer* graph_renderer_create(int width, int height, const GraphRendererConfig& config)"
|
|
description: "Renderer GPU de grafos con instanced rendering a FBO, compatible con ImGui::Image para visualizacion de grafos grandes"
|
|
tags: [graph, renderer, opengl, gpu, instanced, fbo, visualization, frustum-cull, rgba8]
|
|
uses_functions: ["gl_loader_cpp_gfx"]
|
|
uses_types: ["GraphData_cpp_viz"]
|
|
returns: []
|
|
returns_optional: false
|
|
error_type: "error_go_core"
|
|
imports: [imgui]
|
|
tested: false
|
|
tests: []
|
|
test_file_path: ""
|
|
file_path: "cpp/functions/viz/graph_renderer.cpp"
|
|
framework: imgui
|
|
params:
|
|
- name: width
|
|
desc: "Ancho del framebuffer en pixels"
|
|
- name: height
|
|
desc: "Alto del framebuffer en pixels"
|
|
- name: config
|
|
desc: "Configuracion visual: outline width, edge width, edge alpha, color de fondo, fade de aristas por distancia a camara"
|
|
output: "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
|
|
|
|
```cpp
|
|
// 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
|
|
|
|
```cpp
|
|
// 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.
|