Tres mejoras de UX/escala en el demo de grafos:
1. **Wheel zoom dentro del canvas no scrollea la pagina**
En graph_viewport.cpp tras procesar MouseWheel para zoom hacemos
io.MouseWheel = 0 — consume el evento para que el BeginChild padre
(la galeria) no scrollee a la vez que el grafo se acerca. Antes
sentia "doble accion" al rodar la rueda sobre el canvas.
2. **graph_force_layout: pool dinamico (soporta 1M nodos)**
El array static QuadNode[1<<20] (~48MB siempre reservados, tope
rigido en ~250k nodos por la fan-out) se reemplaza por
std::vector<QuadNode>. graph_force_layout_step llama a
quad_pool_reserve(5*N + 1024) ANTES de construir el arbol — asi las
referencias QuadNode& que mantenemos vivas durante quad_subdivide
no se invalidan por reallocaciones a mitad del build (resize solo
ocurre en el reserve inicial). Memoria escala lineal con N: 1M
nodos ≈ 240MB de pool, una vez por programa.
3. **Demo de grafo: sliders extendidos + cluster_r escala con sqrt(N)**
- "Nodes" pasa de 100..20k a 100..1M con escala logaritmica
(ImGuiSliderFlags_Logarithmic) para que el rango medio sea util.
- Nuevos sliders "Edges/node" (1..10) e "Inter %" (0..30%) — antes
hardcoded a 3 y 5%.
- cluster_radius y scatter ahora escalan con sqrt(N): a 1k nodos
~370 px de radio, a 1M ~12000 px. Antes era constante a 200/40
y los nodos quedaban empaquetados al subir N — visualmente "sin
limite cuadrado", esparcidos sobre un area proporcional al grafo.
- Golden de graph_viewport regenerado por la nueva fila de sliders.
Notas:
- A 1M nodos sin GPU compute esta limitado por el upload de aristas
(vertex pulling con TBO llega en 0049d). Render mantenible hasta
~200-300k.
- En Linux/Windows ambos builds limpios. 27/27 tests verde.
Tres atajos de rendimiento sin GPU compute (eso llega en 0049h). Probados
en Linux y cross-compile Windows, todos los tests pasan, OpenMP 4.5
detectado.
1. **OpenMP en graph_force_layout_step** (cpp/functions/viz/...)
- find_package(OpenMP) en cpp/CMakeLists.txt; fn_framework lo enlaza
PUBLIC para que cualquier app/funcion lo herede transparentemente.
Si no esta disponible, los pragmas se ignoran (single-thread).
- #pragma omp parallel for con guard if(N>=1024) en los 4 bucles
embarazosamente paralelos: zero forces, repulsion Barnes-Hut (con
schedule dynamic), gravity, integration (con reduction sobre energy).
La attraction-along-edges se queda secuencial: edges multiples
escriben en el mismo nodo y meterle atomic mata el speedup.
- quad_force usaba un static int stack[1<<20] (4MB compartidos entre
threads — race). Lo reemplazo por int stack[256] en pila: el
quadtree crece como log4(N) ~= 10 niveles para N <= 1M, asi que 256
es holgado y thread-safe sin coste.
- Esperable: ~4-8x menos tiempo CPU/step en 20k nodos en CPU multicore.
2. **Buffer orphan en graph_renderer** (edges + nodes)
- Antes del glBufferData(.., data, DYNAMIC_DRAW), un primer
glBufferData(.., NULL, DYNAMIC_DRAW) que descarta el buffer previo.
El driver da uno fresco sin esperar al frame anterior — evita los
sync stalls clasicos del DYNAMIC_DRAW reuploadeado cada frame.
- Esperable: 2-3x throughput de upload (Mesa/NVIDIA/AMD respetan el
hint).
3. **Auto-pause en demo_graph cuando converge**
- Si energy_per_node < 0.001 durante 30 frames consecutivos, paramos
la simulacion automaticamente. CPU/GPU a 0% cuando el grafo ya
esta estable. Resume con "Resume layout" o "Regenerate".
Lo de OpenMP se sustituye cuando entre 0049h (force layout en compute
shader): cuando llegue, los #pragma omp se borran. Orphan y auto-pause
son keepers definitivos.