#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 #include #include #include // --------------------------------------------------------------------------- // 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; }