graph_labels_draw pinta etiquetas de nodos sobre el FBO del graph_renderer
via ImDrawList. Politica configurable: always-on para selected/hovered/
pinned, top-N por size*(degree+1), culling por viewport AABB y
min_node_pixel_size. Cap duro = max_visible + |always_*|.
API:
- graph_labels_draw(graph, viewport_state, policy, cb, user)
- graph_labels_draw_at(...) — variante con rect explicito
- graph_labels_select(...) — helper puro testeable
- graph_compute_degrees(...) — O(E)
Splitting en dos TUs:
- graph_labels.cpp — funciones draw (depende de ImGui)
- graph_labels_select.cpp — helpers puros para tests sin ImGui
12 tests en test_graph_labels (culling, max_visible cap, min_pixel_size,
always_* gating por viewport, top-N por score, edge cases). Todos verdes.
Integrado en demos_graph con UI: toggle Labels, sliders Max visible /
Font / Min px, checkboxes Selected/Hovered/Pinned. Golden de
graph_viewport regenerado.
Cierra issue 0049j.
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.
Cierra 0049b. El context de fn::run_app pide ahora GL 4.3 core con
forward-compat global, habilitando compute shaders, SSBOs, image
load/store y atomic counters — bloques esenciales del graph_renderer GPU
del proyecto osint_graph (issues 0049f y 0049h).
Cambios:
- cpp/framework/app_base.cpp: 4.3 core + forward-compat. Comentario
marcando que es backward-compatible con shaders #version 330.
- cpp/apps/primitives_gallery/capture.cpp: deja explicitamente 3.3 core
porque WSL Mesa no entrega 4.3 offscreen (GLXBadFBConfig); ImGui +
ImPlot funcionan igual en 3.3 para los goldens.
- primitives_gallery: nuevo demo Gfx > gl_info que muestra
Vendor/Renderer/Version/GLSL en runtime + status 4.3 (verde) +
limites (MAX_TEXTURE_SIZE, MAX_VERTEX_ATTRIBS, MAX_UNIFORM_BLOCK_SIZE
y, si 4.3+, MAX_SHADER_STORAGE_BUFFER_BINDINGS y compute shared mem).
Solo glGetString/glGetIntegerv — sin loader extra.
- About bumped a 0.4.0 con la nota del nuevo demo y de GL 4.3.
- cpp/tests/test_visual.cpp: usa LIBGL_ALWAYS_SOFTWARE=1 al lanzar el
capture para alinear el driver con update_goldens.sh; sin esto las
diferencias de strings (llvmpipe vs d3d12) hacen que gl_info supere
el 1% de tolerancia.
- cpp/tests/golden/gl_info.png: nuevo golden.
Build verificado en Linux (cmake build OK) + Windows cross-compile
(cmake build OK). Las 27 pruebas pasan (incluida test_visual con 42
demos comparadas).
- png_diff.{h,cpp}: pixel_diff_ratio(path_a, path_b, channel_threshold) con
stb_image. Devuelve PngDiffResult con pixels_total, pixels_different y
diff_ratio. Si dimensiones difieren, diff_ratio=1.0.
- test_visual.cpp: invoca primitives_gallery --capture sobre tmpdir, compara
cada PNG vs cpp/tests/golden/<demo>.png con tolerancia 1% pixels distintos
(threshold 5/255 por canal). SKIPea con WARN si:
* golden dir vacio (no hay goldens todavia)
* binario primitives_gallery no construido
* el binario falla al capturar (entorno sin GL)
- CMakeLists: registra test_visual con FN_TEST_GOLDEN_DIR, FN_TEST_GALLERY_BIN,
FN_TEST_TMP_DIR y FN_TEST_REPO_ROOT (para que la captura corra desde la
raiz del repo y resuelva paths relativos como sql_workbench's registry.db).
- golden/: 41 PNGs iniciales generados en este entorno (WSL +
LIBGL_ALWAYS_SOFTWARE=1). Pueden regenerarse con cpp/scripts/update_goldens.sh.
Issue 0048.