Files
fn_registry/cpp/functions/viz/graph_renderer.cpp
T
egutierrez 53402d84d5 docs(issues): marcar 0025 y 0026 como completados + WIP master
Wave 1 de parallel-fix-issues integrada a master:
- 0025: text_editor_cpp_core + file_watcher_cpp_core
- 0026: gl_texture_load_cpp_gfx (vendor: stb_image v2.30)

Ademas se commitea WIP previo de master que estaba sin commitear (cambios
en shaders_lab, dag_*, framework, tokens, kpi_card, gl_loader.md, etc.)
para dejar HEAD buildable.

Notas:
- Algunos deps del gallery (button.cpp, toolbar.cpp, modal_dialog.cpp...)
  siguen UNTRACKED — gating con FN_BUILD_GALLERY=ON (default OFF) para
  que master build (sin flag) no los necesite.
- Build OK con y sin flag. fn index registra 904 functions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:14:15 +02:00

447 lines
16 KiB
C++

#include "viz/graph_renderer.h"
#include "viz/graph_types.h"
// gl_loader: en Linux es no-op (incluye GL headers con GL_GLEXT_PROTOTYPES);
// en Windows expone los punteros via #define gl* fn_gl* tras gl_loader_init().
#include "gfx/gl_loader.h"
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
// ---------------------------------------------------------------------------
// Community palette (ABGR packed, 10 colors)
// ---------------------------------------------------------------------------
static const uint32_t k_palette[10] = {
0xFF4CAF50, // green
0xFFF44336, // red
0xFF2196F3, // blue
0xFFFF9800, // orange
0xFF9C27B0, // purple
0xFF00BCD4, // cyan
0xFFFFEB3B, // yellow
0xFFE91E63, // pink
0xFF795548, // brown
0xFF607D8B // blue-grey
};
// ---------------------------------------------------------------------------
// Internal struct
// ---------------------------------------------------------------------------
struct GraphRenderer {
unsigned int fbo;
unsigned int texture;
unsigned int rbo; // depth/stencil renderbuffer
int width, height;
// Node rendering (instanced quads)
unsigned int node_vao, node_quad_vbo, node_instance_vbo;
unsigned int node_shader;
// Edge rendering (lines)
unsigned int edge_vao, edge_vbo;
unsigned int edge_shader;
GraphRendererConfig config;
};
// ---------------------------------------------------------------------------
// Shader sources
// ---------------------------------------------------------------------------
// Node vertex shader — instanced unit quad
static const char* k_node_vert = R"(
#version 330 core
// Quad corners [-0.5, 0.5]
layout(location = 0) in vec2 a_quad;
// Per-instance: world position, size, RGBA color
layout(location = 1) in vec2 a_pos;
layout(location = 2) in float a_size;
layout(location = 3) in vec4 a_color;
out vec2 v_uv;
out vec4 v_color;
uniform vec2 u_viewport; // (width, height) in pixels
uniform float u_scale; // cam_zoom
uniform vec2 u_translate; // (tx, ty) in pixels
void main() {
// World -> screen (pixels)
vec2 screen = a_pos * u_scale + u_translate;
// Expand quad by node radius (size = diameter)
screen += a_quad * a_size * u_scale;
// Screen -> NDC
vec2 ndc = (screen / u_viewport) * 2.0 - 1.0;
ndc.y = -ndc.y; // flip Y (screen Y grows downward)
gl_Position = vec4(ndc, 0.0, 1.0);
v_uv = a_quad + 0.5; // [0,1]
v_color = a_color;
}
)";
// Node fragment shader — SDF circle with outline
static const char* k_node_frag = R"(
#version 330 core
in vec2 v_uv;
in vec4 v_color;
out vec4 frag_color;
uniform float u_outline_px; // outline width in uv units
uniform float u_node_px; // node diameter in pixels (= size * zoom)
void main() {
// SDF circle centered at (0.5, 0.5) in uv space
float dist = length(v_uv - 0.5);
float r = 0.5;
// Anti-alias edge (in uv units; 1px ~ 1/u_node_px in uv space)
float fwidth_uv = 1.5 / max(u_node_px, 1.0);
float alpha = 1.0 - smoothstep(r - fwidth_uv, r, dist);
if (alpha < 0.001) discard;
// Outline ring
float outline_uv = u_outline_px / max(u_node_px, 1.0);
float outline = smoothstep(r - outline_uv - fwidth_uv, r - outline_uv, dist);
vec3 fill = v_color.rgb;
vec3 outline_col = mix(fill, vec3(1.0), 0.6); // lighter outline
vec3 color = mix(fill, outline_col, outline);
frag_color = vec4(color, v_color.a * alpha);
}
)";
// Edge vertex shader
static const char* k_edge_vert = R"(
#version 330 core
layout(location = 0) in vec2 a_pos;
layout(location = 1) in vec4 a_color;
out vec4 v_color;
uniform vec2 u_viewport;
uniform float u_scale;
uniform vec2 u_translate;
void main() {
vec2 screen = a_pos * u_scale + u_translate;
vec2 ndc = (screen / u_viewport) * 2.0 - 1.0;
ndc.y = -ndc.y;
gl_Position = vec4(ndc, 0.0, 1.0);
v_color = a_color;
}
)";
// Edge fragment shader
static const char* k_edge_frag = R"(
#version 330 core
in vec4 v_color;
out vec4 frag_color;
void main() {
frag_color = v_color;
}
)";
// ---------------------------------------------------------------------------
// Shader helpers
// ---------------------------------------------------------------------------
static unsigned int compile_shader(GLenum type, const char* src) {
unsigned int s = glCreateShader(type);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
int ok;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char buf[512];
glGetShaderInfoLog(s, sizeof(buf), nullptr, buf);
fprintf(stderr, "[graph_renderer] shader compile error: %s\n", buf);
}
return s;
}
static unsigned int link_program(const char* vert_src, const char* frag_src) {
unsigned int vs = compile_shader(GL_VERTEX_SHADER, vert_src);
unsigned int fs = compile_shader(GL_FRAGMENT_SHADER, frag_src);
unsigned int prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
int ok;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
char buf[512];
glGetProgramInfoLog(prog, sizeof(buf), nullptr, buf);
fprintf(stderr, "[graph_renderer] program link error: %s\n", buf);
}
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
// ---------------------------------------------------------------------------
// FBO helpers
// ---------------------------------------------------------------------------
static void create_fbo(GraphRenderer* r) {
// Texture
glGenTextures(1, &r->texture);
glBindTexture(GL_TEXTURE_2D, r->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, r->width, r->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
// Depth renderbuffer
glGenRenderbuffers(1, &r->rbo);
glBindRenderbuffer(GL_RENDERBUFFER, r->rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, r->width, r->height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// FBO
glGenFramebuffers(1, &r->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, r->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, r->texture, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, r->rbo);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
static void destroy_fbo(GraphRenderer* r) {
glDeleteFramebuffers(1, &r->fbo);
glDeleteTextures(1, &r->texture);
glDeleteRenderbuffers(1, &r->rbo);
r->fbo = r->texture = r->rbo = 0;
}
// ---------------------------------------------------------------------------
// Helper: unpack ABGR uint32 to float RGBA
// ---------------------------------------------------------------------------
static inline void abgr_to_rgba(uint32_t abgr, float& r, float& g, float& b, float& a) {
// ABGR layout: bits 31-24 = A, 23-16 = B, 15-8 = G, 7-0 = R
a = ((abgr >> 24) & 0xFF) / 255.0f;
b = ((abgr >> 16) & 0xFF) / 255.0f;
g = ((abgr >> 8) & 0xFF) / 255.0f;
r = ((abgr ) & 0xFF) / 255.0f;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
GraphRenderer* graph_renderer_create(int width, int height, const GraphRendererConfig& config) {
GraphRenderer* r = new GraphRenderer();
r->width = width;
r->height = height;
r->config = config;
// --- FBO ---
create_fbo(r);
// --- Node VAO ---
// Unit quad: 4 vertices, each (x, y) in [-0.5, 0.5]
static const float quad_verts[8] = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
0.5f, 0.5f,
};
glGenVertexArrays(1, &r->node_vao);
glBindVertexArray(r->node_vao);
// Quad VBO (location 0)
glGenBuffers(1, &r->node_quad_vbo);
glBindBuffer(GL_ARRAY_BUFFER, r->node_quad_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_verts), quad_verts, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
// Instance VBO (location 1,2,3 — position, size, color)
glGenBuffers(1, &r->node_instance_vbo);
glBindBuffer(GL_ARRAY_BUFFER, r->node_instance_vbo);
// layout: x, y, size, r, g, b, a — 7 floats per instance
glEnableVertexAttribArray(1); // pos
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)0);
glVertexAttribDivisor(1, 1);
glEnableVertexAttribArray(2); // size
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(2 * sizeof(float)));
glVertexAttribDivisor(2, 1);
glEnableVertexAttribArray(3); // color
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(3 * sizeof(float)));
glVertexAttribDivisor(3, 1);
glBindVertexArray(0);
// --- Edge VAO ---
// Each edge: 2 vertices x (x, y, r, g, b, a) = 2 * 6 floats
glGenVertexArrays(1, &r->edge_vao);
glBindVertexArray(r->edge_vao);
glGenBuffers(1, &r->edge_vbo);
glBindBuffer(GL_ARRAY_BUFFER, r->edge_vbo);
glEnableVertexAttribArray(0); // pos
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1); // color
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(2 * sizeof(float)));
glBindVertexArray(0);
// --- Shaders ---
r->node_shader = link_program(k_node_vert, k_node_frag);
r->edge_shader = link_program(k_edge_vert, k_edge_frag);
return r;
}
void graph_renderer_destroy(GraphRenderer* r) {
if (!r) return;
destroy_fbo(r);
glDeleteVertexArrays(1, &r->node_vao);
glDeleteBuffers(1, &r->node_quad_vbo);
glDeleteBuffers(1, &r->node_instance_vbo);
glDeleteVertexArrays(1, &r->edge_vao);
glDeleteBuffers(1, &r->edge_vbo);
glDeleteProgram(r->node_shader);
glDeleteProgram(r->edge_shader);
delete r;
}
void graph_renderer_resize(GraphRenderer* r, int width, int height) {
if (!r) return;
if (r->width == width && r->height == height) return;
r->width = width;
r->height = height;
destroy_fbo(r);
create_fbo(r);
}
unsigned int graph_renderer_draw(GraphRenderer* r, const GraphData& graph,
float cam_x, float cam_y, float cam_zoom) {
if (!r) return 0;
// --- Save GL state ---
GLint prev_fbo;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
GLint prev_viewport[4];
glGetIntegerv(GL_VIEWPORT, prev_viewport);
// --- Bind FBO ---
glBindFramebuffer(GL_FRAMEBUFFER, r->fbo);
glViewport(0, 0, r->width, r->height);
// Clear with bg_color (ABGR)
float bg_a, bg_b, bg_g, bg_cr;
abgr_to_rgba(r->config.bg_color, bg_cr, bg_g, bg_b, bg_a);
glClearColor(bg_cr, bg_g, bg_b, bg_a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Enable blending for anti-aliasing and transparency
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// View transform: world -> screen pixels
// tx = -cam_x * scale + width/2
// ty = -cam_y * scale + height/2
float scale = cam_zoom;
float tx = -cam_x * scale + (float)r->width * 0.5f;
float ty = -cam_y * scale + (float)r->height * 0.5f;
// ----------------------------------------------------------------
// Draw edges
// ----------------------------------------------------------------
if (graph.edge_count > 0 && graph.edges && graph.nodes) {
// Pack: 2 vertices per edge, each vertex = (x, y, r, g, b, a) = 6 floats
const int floats_per_edge = 2 * 6;
float* edge_buf = (float*)malloc((size_t)graph.edge_count * floats_per_edge * sizeof(float));
int vi = 0;
for (int i = 0; i < graph.edge_count; ++i) {
const GraphEdge& e = graph.edges[i];
uint32_t ecol = e.color != 0 ? e.color : 0xFF888888u; // default gray
float er, eg, eb, ea;
abgr_to_rgba(ecol, er, eg, eb, ea);
ea *= r->config.edge_alpha;
if (e.source < (uint32_t)graph.node_count && e.target < (uint32_t)graph.node_count) {
const GraphNode& ns = graph.nodes[e.source];
const GraphNode& nt = graph.nodes[e.target];
// Source vertex
edge_buf[vi++] = ns.x; edge_buf[vi++] = ns.y;
edge_buf[vi++] = er; edge_buf[vi++] = eg;
edge_buf[vi++] = eb; edge_buf[vi++] = ea;
// Target vertex
edge_buf[vi++] = nt.x; edge_buf[vi++] = nt.y;
edge_buf[vi++] = er; edge_buf[vi++] = eg;
edge_buf[vi++] = eb; edge_buf[vi++] = ea;
}
}
glUseProgram(r->edge_shader);
glUniform2f(glGetUniformLocation(r->edge_shader, "u_viewport"), (float)r->width, (float)r->height);
glUniform1f(glGetUniformLocation(r->edge_shader, "u_scale"), scale);
glUniform2f(glGetUniformLocation(r->edge_shader, "u_translate"), tx, ty);
glLineWidth(r->config.edge_width);
glBindVertexArray(r->edge_vao);
glBindBuffer(GL_ARRAY_BUFFER, r->edge_vbo);
glBufferData(GL_ARRAY_BUFFER, vi * (int)sizeof(float), edge_buf, GL_DYNAMIC_DRAW);
glDrawArrays(GL_LINES, 0, vi / 6);
glBindVertexArray(0);
free(edge_buf);
}
// ----------------------------------------------------------------
// Draw nodes (instanced quads)
// ----------------------------------------------------------------
if (graph.node_count > 0 && graph.nodes) {
// Pack: 7 floats per node: x, y, size, r, g, b, a
float* node_buf = (float*)malloc((size_t)graph.node_count * 7 * sizeof(float));
for (int i = 0; i < graph.node_count; ++i) {
const GraphNode& n = graph.nodes[i];
uint32_t ncol = n.color != 0 ? n.color : k_palette[n.community % 10];
float nr, ng, nb, na;
abgr_to_rgba(ncol, nr, ng, nb, na);
float sz = n.size > 0.0f ? n.size : 4.0f;
float* p = node_buf + i * 7;
p[0] = n.x; p[1] = n.y; p[2] = sz;
p[3] = nr; p[4] = ng; p[5] = nb; p[6] = na;
}
glUseProgram(r->node_shader);
glUniform2f(glGetUniformLocation(r->node_shader, "u_viewport"), (float)r->width, (float)r->height);
glUniform1f(glGetUniformLocation(r->node_shader, "u_scale"), scale);
glUniform2f(glGetUniformLocation(r->node_shader, "u_translate"), tx, ty);
glUniform1f(glGetUniformLocation(r->node_shader, "u_outline_px"), r->config.node_outline);
glBindVertexArray(r->node_vao);
glBindBuffer(GL_ARRAY_BUFFER, r->node_instance_vbo);
glBufferData(GL_ARRAY_BUFFER, graph.node_count * 7 * (int)sizeof(float), node_buf, GL_DYNAMIC_DRAW);
// Draw 4 vertices (triangle strip quad) x node_count instances
// Pass per-instance node_px uniform via the average size (approximation)
// For exact per-node pixel size we'd need a texture or another approach;
// use a uniform average for AA quality — good enough for most graphs.
float avg_px = 8.0f * scale; // rough estimate
glUniform1f(glGetUniformLocation(r->node_shader, "u_node_px"), avg_px);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, graph.node_count);
glBindVertexArray(0);
free(node_buf);
}
// --- Restore GL state ---
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, (GLuint)prev_fbo);
glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]);
return r->texture;
}