#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 #include #include // --------------------------------------------------------------------------- // Fallback palette (RGBA8 con R en LSB) — usada solo cuando GraphData::types // esta vacio. En el modelo extendido (issue 0049e) la apariencia de cada // nodo viene resuelta por `resolve_node_color()`, que mira primero el // override del nodo, luego el EntityType de la tabla, y finalmente este // fallback. Mantener 10 colores para nodos sin tipos sigue siendo util en // demos que aun no construyen tablas EntityType. // --------------------------------------------------------------------------- static const uint32_t k_fallback_palette[10] = { 0xFF4CAF50u, 0xFFF44336u, 0xFF2196F3u, 0xFFFF9800u, 0xFF9C27B0u, 0xFF00BCD4u, 0xFFFFEB3Bu, 0xFFE91E63u, 0xFF795548u, 0xFF607D8Bu, }; // Maximo de iconos que cabe en el uniform array del shader. 256 es lo que // genera `graph_icons` (grid 16×16 en 512×512). Subirlo requiere mas budget // de uniforms (vec4×N → 4 floats por entrada) y aun cabe holgado en el // limite GL 3.30 de 1024 vec4 por bloque. static constexpr int k_max_icons = 256; // --------------------------------------------------------------------------- // Per-instance / per-vertex data layouts // --------------------------------------------------------------------------- // 0049f: NodeInstance crece de 16 a 24 bytes para llevar shape + icon_id. // `shape_icon` empaqueta shape (8 bits bajos) + icon_id (16 bits siguientes); // los UVs del icono no viajan por instancia — el shader los busca en un // `uniform vec4 u_icon_uvs[256]` indexado por icon_id-1. Asi conservamos // bandwidth aunque haya muchos nodos con el mismo icono. struct NodeInstance { // 24 bytes (alineado a 4) float x, y; // 8 float size; // 4 (= diametro en pixels world-space) uint32_t color; // 4 uint32_t shape_icon; // 4 — (shape & 0xFF) | (icon_id << 8) uint32_t pad_; // 4 — relleno explicito; reservado para flags futuros }; // 0049f: EdgeStatic crece de 16 a 20 bytes para llevar style + flags reales. // `style_flags`: flags (low 8 bits) | style (next 8 bits). El resto sigue // siendo source/target/color como en 0049d. struct EdgeStatic { // 20 bytes uint32_t source; // index into nodes uint32_t target; // index into nodes uint32_t color; // packed RGBA8 uint32_t style_flags; // (flags & 0xFF) | (style << 8) uint32_t pad_; // pad a multiplo de 4 — actualmente sin uso }; // --------------------------------------------------------------------------- // Internal struct // --------------------------------------------------------------------------- struct GraphRenderer { unsigned int fbo; unsigned int texture; unsigned int rbo; int width, height; // Node rendering (instanced quads) unsigned int node_vao, node_quad_vbo, node_instance_vbo; unsigned int node_shader; int node_u_viewport_loc; int node_u_scale_loc; int node_u_translate_loc; int node_u_outline_loc; int node_u_node_px_loc; int node_u_icon_atlas_loc; int node_u_has_icons_loc; int node_u_icon_uvs_loc; // Edge rendering (vertex pulling con 6 vertices/instancia para flecha) unsigned int edge_vao, edge_vbo; unsigned int edge_shader; unsigned int node_pos_buf; unsigned int node_pos_tex; int edge_u_viewport_loc; int edge_u_scale_loc; int edge_u_translate_loc; int edge_u_alpha_loc; int edge_u_node_pos_loc; // Streaming buffer capacities (in bytes). size_t node_vbo_capacity; size_t node_pos_capacity; size_t edge_static_capacity; // CPU staging NodeInstance* node_staging; size_t node_staging_cap; float* node_pos_staging; size_t node_pos_staging_cap; EdgeStatic* edge_static_staging; size_t edge_static_staging_cap; // Edge cache (reupload solo cuando cambia el grafo) const void* cached_edges_ptr; int cached_edge_count; int cached_edges_drawn; bool edges_uploaded; // Icon atlas binding (0 = sin iconos) unsigned int icon_atlas_tex; float icon_uvs[k_max_icons * 4]; int icon_uv_count; GraphRendererConfig config; }; // --------------------------------------------------------------------------- // Shader sources // --------------------------------------------------------------------------- // Node vertex shader — instanced unit quad, ahora con shape + icon_id. // Pasamos el texto del shape al fragment para que despache el SDF correcto; // los UVs del icono se buscan en `u_icon_uvs[icon_id-1]`. static const char* k_node_vert = R"( #version 330 core layout(location = 0) in vec2 a_quad; layout(location = 1) in vec2 a_pos; layout(location = 2) in float a_size; layout(location = 3) in uint a_color; layout(location = 4) in uint a_shape_icon; out vec2 v_uv; out vec4 v_color; flat out uint v_shape; flat out uint v_icon_id; flat out vec4 v_icon_uv; uniform vec2 u_viewport; uniform float u_scale; uniform vec2 u_translate; uniform vec4 u_icon_uvs[256]; vec4 unpack_rgba8(uint c) { return vec4( float( c & 0xFFu), float((c >> 8) & 0xFFu), float((c >> 16) & 0xFFu), float((c >> 24) & 0xFFu) ) * (1.0 / 255.0); } void main() { vec2 screen = a_pos * u_scale + u_translate; screen += a_quad * a_size * u_scale; vec2 ndc = (screen / u_viewport) * 2.0 - 1.0; ndc.y = -ndc.y; gl_Position = vec4(ndc, 0.0, 1.0); v_uv = a_quad + 0.5; v_color = unpack_rgba8(a_color); v_shape = a_shape_icon & 0xFFu; v_icon_id = (a_shape_icon >> 8) & 0xFFFFu; if (v_icon_id != 0u) { v_icon_uv = u_icon_uvs[int(v_icon_id) - 1]; } else { v_icon_uv = vec4(0.0); } } )"; // Node fragment shader — SDF dispatch + opcional icon overlay. // Para mantener la calidad del AA usamos `fwidth(d)` en lugar del // `1.5/u_node_px` viejo: sirve igual a cualquier zoom y se queda nitido en // los bordes complejos (hexagono, triangulo). static const char* k_node_frag = R"( #version 330 core in vec2 v_uv; in vec4 v_color; flat in uint v_shape; flat in uint v_icon_id; flat in vec4 v_icon_uv; out vec4 frag_color; uniform float u_outline_px; uniform float u_node_px; uniform sampler2D u_icon_atlas; uniform int u_has_icons; float sdf_circle(vec2 uv) { return length(uv - 0.5) - 0.5; } float sdf_square(vec2 uv) { vec2 d = abs(uv - 0.5) - 0.5; return max(d.x, d.y); } float sdf_diamond(vec2 uv) { vec2 d = abs(uv - 0.5); return d.x + d.y - 0.5; } // Hexagono regular alineado horizontalmente; SDF derivado del clasico de // Inigo Quilez adaptado al cuadrado [0,1]^2. Inscribimos el hex dentro del // circulo de radio 0.5 para que sus vertices toquen los bordes — asi // area visual ~ a la del circulo del mismo `size`. float sdf_hex(vec2 uv) { vec2 p = abs(uv - 0.5); const vec2 k = vec2(0.866025404, 0.5); p -= 2.0 * min(dot(k, p), 0.0) * k; p -= vec2(clamp(p.x, -k.y * 0.5, k.y * 0.5), 0.5); return length(p) * sign(p.y) - (0.5 * 0.866025404); } // Triangulo equilatero apuntando hacia arriba dentro de [0,1]^2. float sdf_triangle(vec2 uv) { const float k = 1.732050808; // sqrt(3) vec2 p = uv - vec2(0.5, 0.5); p.x = abs(p.x) - 0.5; p.y = p.y + 0.5 / k; if (p.x + k * p.y > 0.0) p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0; p.x -= clamp(p.x, -1.0, 0.0); return -length(p) * sign(p.y); } float sdf_rrect(vec2 uv) { float r = 0.18; vec2 d = abs(uv - 0.5) - (0.5 - r); return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r; } float pick_sdf(uint shape, vec2 uv) { // shape 0 = SHAPE_USE_TYPE: el CPU resuelve antes; aqui no debe llegar. // 1=circle 2=square 3=diamond 4=hex 5=triangle 6=rounded_square if (shape == 1u) return sdf_circle(uv); else if (shape == 2u) return sdf_square(uv); else if (shape == 3u) return sdf_diamond(uv); else if (shape == 4u) return sdf_hex(uv); else if (shape == 5u) return sdf_triangle(uv); else if (shape == 6u) return sdf_rrect(uv); return sdf_circle(uv); // default robusto si shape mal codificado } void main() { float d = pick_sdf(v_shape, v_uv); float aa = max(fwidth(d), 0.001); float fill_alpha = 1.0 - smoothstep(-aa, 0.0, d); if (fill_alpha < 0.001) discard; // Outline: anillo exterior — la anchura en uv viene de outline_px / node_px. float outline_uv = u_outline_px / max(u_node_px, 1.0); float outline = smoothstep(-outline_uv - aa, -outline_uv, d); vec3 fill = v_color.rgb; vec3 outline_col = mix(fill, vec3(1.0), 0.6); vec3 col = mix(fill, outline_col, outline); // Overlay del icono (solo si hay atlas + icon_id != 0). El icono se // tintamos sumando blanco modulado por su alpha — el resultado sigue // siendo legible sobre cualquier color de fondo del nodo. if (u_has_icons != 0 && v_icon_id != 0u) { vec2 atlas_uv = mix(v_icon_uv.xy, v_icon_uv.zw, v_uv); vec4 ic = texture(u_icon_atlas, atlas_uv); col = mix(col, vec3(1.0), ic.a * 0.85); } frag_color = vec4(col, v_color.a * fill_alpha); } )"; // Edge vertex shader — vertex pulling, ahora 6 vertices por arista para // soportar flecha en aristas EF_DIRECTED: // gl_VertexID 0 (line src→tip), 1 (tip) // gl_VertexID 2 (tip), 3 (back_left) // gl_VertexID 4 (tip), 5 (back_right) // Si no esta directed, los vertices 2..5 se colapsan al tip (lineas // degeneradas no visibles). // // Para dashed/dotted: pasamos `arc_length` interpolado (en pixels) al // fragment shader; este descarta segun style. static const char* k_edge_vert = R"( #version 330 core layout(location = 0) in uint a_source; layout(location = 1) in uint a_target; layout(location = 2) in uint a_color; layout(location = 3) in uint a_style_flags; uniform samplerBuffer u_node_pos; uniform vec2 u_viewport; uniform float u_scale; uniform vec2 u_translate; uniform float u_alpha; out vec4 v_color; flat out uint v_style; flat out uint v_segment; // 0=line, 1=arrow out float v_arc; vec4 unpack_rgba8(uint c) { return vec4( float( c & 0xFFu), float((c >> 8) & 0xFFu), float((c >> 16) & 0xFFu), float((c >> 24) & 0xFFu) ) * (1.0 / 255.0); } vec2 to_screen(vec2 wpos) { return wpos * u_scale + u_translate; } vec2 to_ndc(vec2 screen) { vec2 ndc = (screen / u_viewport) * 2.0 - 1.0; return vec2(ndc.x, -ndc.y); } void main() { int vid = gl_VertexID; uint flags = a_style_flags & 0xFFu; uint style = (a_style_flags >> 8) & 0xFFu; bool directed = (flags & 1u) != 0u; // EF_DIRECTED == 1 vec2 wsrc = texelFetch(u_node_pos, int(a_source)).xy; vec2 wtgt = texelFetch(u_node_pos, int(a_target)).xy; vec2 ssrc = to_screen(wsrc); vec2 stgt = to_screen(wtgt); vec2 dir = stgt - ssrc; float seg_len = length(dir); vec2 dir_n = (seg_len > 0.0001) ? dir / seg_len : vec2(1.0, 0.0); vec2 perp = vec2(-dir_n.y, dir_n.x); // Acortamos el segmento principal si la arista es directed para que la // flecha no se incruste en el nodo target. Tamano fijo en pixels = 10. float arrow_px = 10.0; vec2 tip = stgt; vec2 line_end = directed ? (stgt - dir_n * arrow_px * 0.5) : stgt; vec2 spos; float arc; uint segment; if (vid <= 1) { // Linea principal source→line_end. segment = 0u; if (vid == 0) { spos = ssrc; arc = 0.0; } else { spos = line_end; arc = length(line_end - ssrc); } } else { segment = 1u; arc = 0.0; if (!directed) { // Sin flecha: degenerado en el tip — sin pintar. spos = tip; } else { // Triangulo de la flecha en 2 lineas (chevron): // (tip, back_left) y (tip, back_right) vec2 back = tip - dir_n * arrow_px; vec2 left_p = back + perp * arrow_px * 0.5; vec2 right_p = back - perp * arrow_px * 0.5; if (vid == 2) spos = tip; else if (vid == 3) spos = left_p; else if (vid == 4) spos = tip; else spos = right_p; } } gl_Position = vec4(to_ndc(spos), 0.0, 1.0); vec4 c = unpack_rgba8(a_color); c.a *= u_alpha; v_color = c; v_style = style; v_segment = segment; v_arc = arc; } )"; // Edge fragment shader — descarta segun style + arc_length para producir // dashed (period 8 px, duty 0.5) o dotted (period 4 px, duty 0.25). Las // lineas de la flecha (segment==1) se renderizan siempre solidas. static const char* k_edge_frag = R"( #version 330 core in vec4 v_color; flat in uint v_style; flat in uint v_segment; in float v_arc; out vec4 frag_color; void main() { if (v_segment == 0u) { // EDGE_USE_TYPE=0, EDGE_SOLID=1, EDGE_DASHED=2, EDGE_DOTTED=3 if (v_style == 2u) { if (mod(v_arc, 8.0) > 4.0) discard; } else if (v_style == 3u) { if (mod(v_arc, 4.0) > 1.0) discard; } } 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[1024]; 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[1024]; 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) { 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); glGenRenderbuffers(1, &r->rbo); glBindRenderbuffer(GL_RENDERBUFFER, r->rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, r->width, r->height); glBindRenderbuffer(GL_RENDERBUFFER, 0); 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; } // --------------------------------------------------------------------------- // Capacity-tracked streaming helpers // --------------------------------------------------------------------------- static size_t grow_capacity(size_t current, size_t needed, size_t initial) { size_t cap = current > 0 ? current : initial; while (cap < needed) cap *= 2; return cap; } // --------------------------------------------------------------------------- // 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; r->node_vbo_capacity = 0; r->node_pos_capacity = 0; r->edge_static_capacity = 0; r->node_staging = nullptr; r->node_staging_cap = 0; r->node_pos_staging = nullptr; r->node_pos_staging_cap = 0; r->edge_static_staging = nullptr; r->edge_static_staging_cap = 0; r->cached_edges_ptr = nullptr; r->cached_edge_count = 0; r->cached_edges_drawn = 0; r->edges_uploaded = false; r->icon_atlas_tex = 0; r->icon_uv_count = 0; std::memset(r->icon_uvs, 0, sizeof(r->icon_uvs)); create_fbo(r); 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); 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); glGenBuffers(1, &r->node_instance_vbo); glBindBuffer(GL_ARRAY_BUFFER, r->node_instance_vbo); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(NodeInstance), (void*)offsetof(NodeInstance, x)); glVertexAttribDivisor(1, 1); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(NodeInstance), (void*)offsetof(NodeInstance, size)); glVertexAttribDivisor(2, 1); glEnableVertexAttribArray(3); glVertexAttribIPointer(3, 1, GL_UNSIGNED_INT, sizeof(NodeInstance), (void*)offsetof(NodeInstance, color)); glVertexAttribDivisor(3, 1); glEnableVertexAttribArray(4); glVertexAttribIPointer(4, 1, GL_UNSIGNED_INT, sizeof(NodeInstance), (void*)offsetof(NodeInstance, shape_icon)); glVertexAttribDivisor(4, 1); glBindVertexArray(0); glGenVertexArrays(1, &r->edge_vao); glBindVertexArray(r->edge_vao); glGenBuffers(1, &r->edge_vbo); glBindBuffer(GL_ARRAY_BUFFER, r->edge_vbo); glEnableVertexAttribArray(0); glVertexAttribIPointer(0, 1, GL_UNSIGNED_INT, sizeof(EdgeStatic), (void*)offsetof(EdgeStatic, source)); glVertexAttribDivisor(0, 1); glEnableVertexAttribArray(1); glVertexAttribIPointer(1, 1, GL_UNSIGNED_INT, sizeof(EdgeStatic), (void*)offsetof(EdgeStatic, target)); glVertexAttribDivisor(1, 1); glEnableVertexAttribArray(2); glVertexAttribIPointer(2, 1, GL_UNSIGNED_INT, sizeof(EdgeStatic), (void*)offsetof(EdgeStatic, color)); glVertexAttribDivisor(2, 1); glEnableVertexAttribArray(3); glVertexAttribIPointer(3, 1, GL_UNSIGNED_INT, sizeof(EdgeStatic), (void*)offsetof(EdgeStatic, style_flags)); glVertexAttribDivisor(3, 1); glBindVertexArray(0); glGenBuffers(1, &r->node_pos_buf); glBindBuffer(GL_TEXTURE_BUFFER, r->node_pos_buf); glBufferData(GL_TEXTURE_BUFFER, 4096 * 2 * sizeof(float), nullptr, GL_STREAM_DRAW); r->node_pos_capacity = 4096 * 2 * sizeof(float); glGenTextures(1, &r->node_pos_tex); glBindTexture(GL_TEXTURE_BUFFER, r->node_pos_tex); glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, r->node_pos_buf); glBindTexture(GL_TEXTURE_BUFFER, 0); glBindBuffer(GL_TEXTURE_BUFFER, 0); r->node_shader = link_program(k_node_vert, k_node_frag); r->edge_shader = link_program(k_edge_vert, k_edge_frag); r->node_u_viewport_loc = glGetUniformLocation(r->node_shader, "u_viewport"); r->node_u_scale_loc = glGetUniformLocation(r->node_shader, "u_scale"); r->node_u_translate_loc = glGetUniformLocation(r->node_shader, "u_translate"); r->node_u_outline_loc = glGetUniformLocation(r->node_shader, "u_outline_px"); r->node_u_node_px_loc = glGetUniformLocation(r->node_shader, "u_node_px"); r->node_u_icon_atlas_loc = glGetUniformLocation(r->node_shader, "u_icon_atlas"); r->node_u_has_icons_loc = glGetUniformLocation(r->node_shader, "u_has_icons"); r->node_u_icon_uvs_loc = glGetUniformLocation(r->node_shader, "u_icon_uvs"); r->edge_u_viewport_loc = glGetUniformLocation(r->edge_shader, "u_viewport"); r->edge_u_scale_loc = glGetUniformLocation(r->edge_shader, "u_scale"); r->edge_u_translate_loc = glGetUniformLocation(r->edge_shader, "u_translate"); r->edge_u_alpha_loc = glGetUniformLocation(r->edge_shader, "u_alpha"); r->edge_u_node_pos_loc = glGetUniformLocation(r->edge_shader, "u_node_pos"); 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); glDeleteBuffers(1, &r->node_pos_buf); glDeleteTextures(1, &r->node_pos_tex); glDeleteProgram(r->node_shader); glDeleteProgram(r->edge_shader); free(r->node_staging); free(r->node_pos_staging); free(r->edge_static_staging); 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); } void graph_renderer_set_icon_atlas(GraphRenderer* r, unsigned int texture_id, const float* uv_table, int count) { if (!r) return; r->icon_atlas_tex = texture_id; r->icon_uv_count = (count > k_max_icons) ? k_max_icons : (count < 0 ? 0 : count); if (r->icon_uv_count > 0 && uv_table) { std::memcpy(r->icon_uvs, uv_table, (size_t)r->icon_uv_count * 4 * sizeof(float)); } // Limpia las entradas no usadas para evitar UVs basura si el shader las // sample por error. Costo: O(k_max_icons) — irrelevante. if (r->icon_uv_count < k_max_icons) { std::memset(r->icon_uvs + r->icon_uv_count * 4, 0, (size_t)(k_max_icons - r->icon_uv_count) * 4 * sizeof(float)); } } unsigned int graph_renderer_draw(GraphRenderer* r, const GraphData& graph, float cam_x, float cam_y, float cam_zoom) { if (!r) return 0; GLint prev_fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo); GLint prev_viewport[4]; glGetIntegerv(GL_VIEWPORT, prev_viewport); glBindFramebuffer(GL_FRAMEBUFFER, r->fbo); glViewport(0, 0, r->width, r->height); uint8_t br, bg, bb, ba; unpack_rgba8(r->config.bg_color, br, bg, bb, ba); glClearColor(br / 255.0f, bg / 255.0f, bb / 255.0f, ba / 255.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); float scale = cam_zoom; float tx = -cam_x * scale + (float)r->width * 0.5f; float ty = -cam_y * scale + (float)r->height * 0.5f; float half_w = ((float)r->width * 0.5f) / std::max(scale, 0.0001f); float half_h = ((float)r->height * 0.5f) / std::max(scale, 0.0001f); const float margin = 0.10f; float vx0 = cam_x - half_w * (1.0f + margin); float vx1 = cam_x + half_w * (1.0f + margin); float vy0 = cam_y - half_h * (1.0f + margin); float vy1 = cam_y + half_h * (1.0f + margin); // ---------------------------------------------------------------- // Subir posiciones de nodos al TBO. // ---------------------------------------------------------------- bool tbo_ready = false; if (graph.node_count > 0 && graph.nodes) { size_t need_floats = (size_t)graph.node_count * 2; if (need_floats > r->node_pos_staging_cap) { size_t new_cap = grow_capacity(r->node_pos_staging_cap, need_floats, 8192); r->node_pos_staging = (float*)realloc(r->node_pos_staging, new_cap * sizeof(float)); r->node_pos_staging_cap = new_cap; } for (int i = 0; i < graph.node_count; ++i) { r->node_pos_staging[i * 2 + 0] = graph.nodes[i].x; r->node_pos_staging[i * 2 + 1] = graph.nodes[i].y; } const size_t used_bytes = need_floats * sizeof(float); if (used_bytes > r->node_pos_capacity) { r->node_pos_capacity = grow_capacity(r->node_pos_capacity, used_bytes, 4096 * 2 * sizeof(float)); } glBindBuffer(GL_TEXTURE_BUFFER, r->node_pos_buf); glBufferData(GL_TEXTURE_BUFFER, (GLsizeiptr)r->node_pos_capacity, nullptr, GL_STREAM_DRAW); glBufferSubData(GL_TEXTURE_BUFFER, 0, (GLsizeiptr)used_bytes, r->node_pos_staging); glBindBuffer(GL_TEXTURE_BUFFER, 0); tbo_ready = true; } // ---------------------------------------------------------------- // Aristas via vertex pulling. 6 vertices por arista (line + arrow). // El buffer estatico se reupload solo cuando cambia el grafo. // ---------------------------------------------------------------- if (tbo_ready && graph.edge_count > 0 && graph.edges) { const bool graph_changed = !r->edges_uploaded || r->cached_edges_ptr != (const void*)graph.edges || r->cached_edge_count != graph.edge_count; if (graph_changed) { if ((size_t)graph.edge_count > r->edge_static_staging_cap) { size_t new_cap = grow_capacity(r->edge_static_staging_cap, (size_t)graph.edge_count, 8192); r->edge_static_staging = (EdgeStatic*)realloc(r->edge_static_staging, new_cap * sizeof(EdgeStatic)); r->edge_static_staging_cap = new_cap; } size_t out = 0; for (int i = 0; i < graph.edge_count; ++i) { const GraphEdge& e = graph.edges[i]; if (e.source >= (uint32_t)graph.node_count) continue; if (e.target >= (uint32_t)graph.node_count) continue; if (!(e.flags & EF_VISIBLE)) continue; if (!(graph.nodes[e.source].flags & NF_VISIBLE)) continue; if (!(graph.nodes[e.target].flags & NF_VISIBLE)) continue; uint32_t col = resolve_edge_color(e, graph.rel_types, graph.rel_type_count); if (col == 0u) col = pack_rgba8(0x88, 0x88, 0x88, 0xFF); uint8_t style = resolve_edge_style(e, graph.rel_types, graph.rel_type_count); uint32_t style_flags = ((uint32_t)e.flags & 0xFFu) | ((uint32_t)style << 8); r->edge_static_staging[out++] = { e.source, e.target, col, style_flags, 0u }; } if (out > 0) { const size_t used_bytes = out * sizeof(EdgeStatic); if (used_bytes > r->edge_static_capacity) { r->edge_static_capacity = grow_capacity(r->edge_static_capacity, used_bytes, 8192 * sizeof(EdgeStatic)); } glBindBuffer(GL_ARRAY_BUFFER, r->edge_vbo); glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)r->edge_static_capacity, nullptr, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, (GLsizeiptr)used_bytes, r->edge_static_staging); } r->cached_edges_ptr = (const void*)graph.edges; r->cached_edge_count = graph.edge_count; r->cached_edges_drawn = (int)out; r->edges_uploaded = (out > 0); } if (r->edges_uploaded) { glUseProgram(r->edge_shader); glUniform2f(r->edge_u_viewport_loc, (float)r->width, (float)r->height); glUniform1f(r->edge_u_scale_loc, scale); glUniform2f(r->edge_u_translate_loc, tx, ty); glUniform1f(r->edge_u_alpha_loc, r->config.edge_alpha); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_BUFFER, r->node_pos_tex); glUniform1i(r->edge_u_node_pos_loc, 0); glLineWidth(r->config.edge_width); glBindVertexArray(r->edge_vao); // 6 vertices por instancia: 2 linea + 4 chevron de la flecha. glDrawArraysInstanced(GL_LINES, 0, 6, (GLsizei)r->cached_edges_drawn); glBindVertexArray(0); glBindTexture(GL_TEXTURE_BUFFER, 0); } } else if (graph.edge_count == 0) { r->edges_uploaded = false; } // ---------------------------------------------------------------- // Draw nodes (instanced quads, frustum-culled). Empaqueta shape e // icon_id por instancia; el shader despacha el SDF y aplica overlay. // ---------------------------------------------------------------- if (graph.node_count > 0 && graph.nodes) { if ((size_t)graph.node_count > r->node_staging_cap) { size_t new_cap = grow_capacity(r->node_staging_cap, (size_t)graph.node_count, 4096); r->node_staging = (NodeInstance*)realloc(r->node_staging, new_cap * sizeof(NodeInstance)); r->node_staging_cap = new_cap; } size_t visible = 0; for (int i = 0; i < graph.node_count; ++i) { const GraphNode& n = graph.nodes[i]; if (!(n.flags & NF_VISIBLE)) continue; float sz = resolve_node_size(n, graph.types, graph.type_count); if (sz <= 0.0f) sz = 4.0f; float half = sz * 0.5f; if (n.x + half < vx0 || n.x - half > vx1) continue; if (n.y + half < vy0 || n.y - half > vy1) continue; uint32_t ncol; if (n.color_override != 0u) { ncol = n.color_override; } else if (graph.types && n.type_id < (uint16_t)graph.type_count) { ncol = graph.types[n.type_id].color; } else { ncol = k_fallback_palette[n.type_id % 10]; } uint8_t shape = resolve_node_shape(n, graph.types, graph.type_count); if (shape == SHAPE_USE_TYPE) shape = SHAPE_CIRCLE; // icon_id solo viene del EntityType (los nodos no tienen override // de icono en el modelo actual). 0 = sin overlay. uint16_t icon_id = 0; if (graph.types && n.type_id < (uint16_t)graph.type_count) { icon_id = graph.types[n.type_id].icon_id; } if (icon_id > r->icon_uv_count) icon_id = 0; // fuera de tabla uint32_t shape_icon = ((uint32_t)shape & 0xFFu) | ((uint32_t)icon_id << 8); r->node_staging[visible++] = { n.x, n.y, sz, ncol, shape_icon, 0u }; } if (visible > 0) { const size_t used_bytes = visible * sizeof(NodeInstance); if (used_bytes > r->node_vbo_capacity) { r->node_vbo_capacity = grow_capacity(r->node_vbo_capacity, used_bytes, 4096 * sizeof(NodeInstance)); } glUseProgram(r->node_shader); glUniform2f(r->node_u_viewport_loc, (float)r->width, (float)r->height); glUniform1f(r->node_u_scale_loc, scale); glUniform2f(r->node_u_translate_loc, tx, ty); glUniform1f(r->node_u_outline_loc, r->config.node_outline); float avg_px = 8.0f * scale; glUniform1f(r->node_u_node_px_loc, avg_px); // Subimos siempre la tabla de UVs — son 256 vec4 = 4KB, peanuts. glUniform4fv(r->node_u_icon_uvs_loc, k_max_icons, r->icon_uvs); const int has_icons = (r->icon_atlas_tex != 0 && r->icon_uv_count > 0) ? 1 : 0; glUniform1i(r->node_u_has_icons_loc, has_icons); if (has_icons) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, r->icon_atlas_tex); glUniform1i(r->node_u_icon_atlas_loc, 1); } glBindVertexArray(r->node_vao); glBindBuffer(GL_ARRAY_BUFFER, r->node_instance_vbo); glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)r->node_vbo_capacity, nullptr, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, (GLsizeiptr)used_bytes, r->node_staging); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)visible); glBindVertexArray(0); if (has_icons) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); } } } 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; }