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)
4.4 KiB
4.4 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, notes
| 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 | notes | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| graph_force_layout | function | cpp | viz | 1.1.0 | pure | float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config) | Layout force-directed con aproximacion Barnes-Hut para grafos grandes, ejecuta un paso de simulacion por llamada |
|
|
false | true |
|
cpp/tests/test_graph_should_pause.cpp | cpp/functions/viz/graph_force_layout.cpp | imgui |
|
Energia cinetica total (suma de |v|^2). Cuando cae por debajo de un umbral elegido por el caller, el layout ha convergido y se puede dejar de llamar. | scaffolding/demo en primitives_gallery |
graph_force_layout
Implementa el algoritmo de layout force-directed clasico (Fruchterman-Reingold / Eades) con aproximacion Barnes-Hut O(n log n) para escalar a grafos de miles de nodos.
Algoritmo
Cada llamada a graph_force_layout_step ejecuta config.iterations pasos. Un paso:
- Construccion del quadtree (Barnes-Hut): se calcula el bounding box de las posiciones actuales, se construye un quadtree flat en
quad_pool(sin allocaciones por nodo). Cada celda acumula centro de masa y masa total. - Repulsion: para cada nodo se recorre el quadtree. Si el cociente
cell_size / distance < theta, la celda se trata como una sola masa puntual (multipolo de orden 0). Si no, se desciende a los hijos. Contheta=0es O(n²) exacto; contheta=0.5es O(n log n). - Atraccion: para cada arista
(s, t), fuerza de HookeF = k * dist * weighten la direccion del arco. - Gravedad: fuerza proporcional a la distancia al origen, evita que el grafo derive fuera de pantalla.
- Integracion:
v = v * damping + F,pos += v, con clamping de velocidad. - Nodos con
pinned = trueno se mueven en ningun paso.
Funciones auxiliares
// Randomizar posiciones para empezar la simulacion
graph_force_layout_reset(graph, 200.0f);
// Layout circular instantaneo (sin iteracion)
graph_layout_circular(graph, 150.0f);
// Layout en grid instantaneo
graph_layout_grid(graph, 25.0f);
// Auto-pause: parar la simulacion cuando la energia se ha estabilizado.
// Pure: el caller mantiene el contador, la funcion solo decide.
// bool graph_force_layout_should_pause(int low_frames, int min_consecutive);
Ejemplo de uso tipico (loop ImGui)
static ForceLayoutConfig cfg;
static bool running = true;
static int low_frames = 0;
const int k_min_consecutive = 30;
const float k_threshold_per_node = 0.001f;
if (running) {
float energy = graph_force_layout_step(my_graph, cfg);
float per_node = my_graph.node_count > 0
? energy / my_graph.node_count : 0.0f;
if (per_node < k_threshold_per_node) ++low_frames;
else low_frames = 0;
if (graph_force_layout_should_pause(low_frames, k_min_consecutive)) {
running = false;
low_frames = 0;
}
}
Notas de implementacion
- El quadtree usa un pool dinamico (
std::vector<QuadNode>) que se redimensiona una vez por step a5*N + 1024celdas. La pila de traversal enquad_forcees local en pila (256 entradas) — thread-safe bajo OpenMP. graph_force_layout_resetusarand(). Para reproducibilidad llamasrand(seed)antes.- Los buffers de fuerza (
fx_buf,fy_buf) se realocan una sola vez cuando el conteo de nodos supera la capacidad previa; en el uso normal (tamano fijo) no hay allocaciones por frame.
Notas de version
- v1.1 (2026-04-29, issue 0049c): añade el helper puro
graph_force_layout_should_pause(low_frames, min_consecutive)para que las apps detecten convergencia sin replicar el contador por todas partes. Sin cambios engraph_force_layout_stepni en la API existente. Test:cpp/tests/test_graph_should_pause.cpp.