diff --git a/source/blender/editors/include/ED_node.hh b/source/blender/editors/include/ED_node.hh index 686ca24277f..360ac8f543e 100644 --- a/source/blender/editors/include/ED_node.hh +++ b/source/blender/editors/include/ED_node.hh @@ -37,8 +37,6 @@ void node_insert_on_link_flags_clear(bNodeTree &node_tree); /** * Draw a single node socket at default size. - * \note this is only called from external code, internally #node_socket_draw_nested() is used for - * optimized drawing of multiple/all sockets of a node. */ void node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[4], float scale); diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 0d9937de184..a5dd1722f16 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -1820,6 +1820,150 @@ void node_link_bezier_points_evaluated(const bNodeLink &link, sizeof(float2)); } +/* -------------------------------------------------------------------- */ +/** \name Node Socket Drawing + * \{ */ + +/* Node Socket shader parameters, must match the shader layout. */ +struct NodeSocketShaderParameters { + rctf rect; + float color_inner[4]; + float color_outline[4]; + float outline_thickness; + float outline_offset; + float dot_radius; + float shape; +}; + +/* Keep in sync with shader. */ +#define MAX_SOCKET_PARAMETERS 4 +#define MAX_SOCKET_INSTANCE 32 + +static struct { + gpu::Batch *batch; + Vector params; + bool enabled; +} g_batch_nodesocket; + +static gpu::Batch *nodesocket_batch_init(void) +{ + if (g_batch_nodesocket.batch == NULL) { + GPUVertFormat format = {0}; + gpu::VertBuf *vbo = GPU_vertbuf_create_with_format_ex(&format, GPU_USAGE_STATIC); + + GPU_vertbuf_data_alloc(vbo, 6); + + GPUIndexBufBuilder ibuf; + GPU_indexbuf_init(&ibuf, GPU_PRIM_TRIS, 2, 4); + /* Quad to draw the node socket in. */ + GPU_indexbuf_add_tri_verts(&ibuf, 0, 1, 2); + GPU_indexbuf_add_tri_verts(&ibuf, 2, 1, 3); + + g_batch_nodesocket.batch = GPU_batch_create_ex( + GPU_PRIM_TRIS, vbo, GPU_indexbuf_build(&ibuf), GPU_BATCH_OWNS_INDEX | GPU_BATCH_OWNS_VBO); + gpu_batch_presets_register(g_batch_nodesocket.batch); + } + return g_batch_nodesocket.batch; +} + +static void nodesocket_cache_flush() +{ + if (g_batch_nodesocket.params.is_empty()) { + return; + } + + gpu::Batch *batch = nodesocket_batch_init(); + if (g_batch_nodesocket.params.size() == 1) { + /* draw single */ + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODE_SOCKET); + GPU_batch_uniform_4fv_array( + batch, "parameters", 4, (const float(*)[4])g_batch_nodesocket.params.data()); + GPU_batch_draw(batch); + } + else { + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODE_SOCKET_INST); + GPU_batch_uniform_4fv_array(batch, + "parameters", + MAX_SOCKET_PARAMETERS * MAX_SOCKET_INSTANCE, + (float(*)[4])g_batch_nodesocket.params.data()); + GPU_batch_draw_instance_range(batch, 0, g_batch_nodesocket.params.size()); + } + g_batch_nodesocket.params.clear(); +} + +void nodesocket_batch_start() +{ + BLI_assert(g_batch_nodesocket.enabled == false); + g_batch_nodesocket.enabled = true; +} + +void nodesocket_batch_end() +{ + BLI_assert(g_batch_nodesocket.enabled == true); + g_batch_nodesocket.enabled = false; + + GPU_blend(GPU_BLEND_ALPHA); + nodesocket_cache_flush(); + GPU_blend(GPU_BLEND_NONE); +} + +static void draw_node_socket_batch(const NodeSocketShaderParameters &socket_params) +{ + if (g_batch_nodesocket.enabled) { + g_batch_nodesocket.params.append(socket_params); + + if (g_batch_nodesocket.params.size() >= MAX_SOCKET_INSTANCE) { + nodesocket_cache_flush(); + } + } + else { + /* Draw single instead of batch. */ + gpu::Batch *batch = nodesocket_batch_init(); + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODE_SOCKET); + GPU_batch_uniform_4fv_array( + batch, "parameters", MAX_SOCKET_PARAMETERS, (const float(*)[4])(&socket_params)); + GPU_batch_draw(batch); + } +} + +void node_draw_nodesocket(const rctf *rect, + const float color_inner[4], + const float color_outline[4], + const float outline_thickness, + const float outline_offset, + const float dot_radius, + int shape) +{ + /* WATCH: This is assuming the ModelViewProjectionMatrix is area pixel space. + * If it has been scaled, then it's no longer valid. */ + BLI_assert((color_inner != nullptr) && (color_outline != nullptr)); + + NodeSocketShaderParameters socket_params = {}; + socket_params.rect = *rect; + socket_params.color_inner[0] = color_inner[0]; + socket_params.color_inner[1] = color_inner[1]; + socket_params.color_inner[2] = color_inner[2]; + socket_params.color_inner[3] = color_inner[3]; + socket_params.color_outline[0] = color_outline[0]; + socket_params.color_outline[1] = color_outline[1]; + socket_params.color_outline[2] = color_outline[2]; + socket_params.color_outline[3] = color_outline[3]; + socket_params.outline_thickness = outline_thickness; + socket_params.dot_radius = dot_radius; + socket_params.outline_offset = outline_offset; + socket_params.shape = float(shape) + 0.1f; + + GPU_blend(GPU_BLEND_ALPHA); + draw_node_socket_batch(socket_params); + GPU_blend(GPU_BLEND_NONE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Node Link Drawing + * \{ */ + #define NODELINK_GROUP_SIZE 256 #define LINK_RESOL 24 #define LINK_WIDTH 2.5f @@ -2419,3 +2563,5 @@ void ED_node_draw_snap(View2D *v2d, const float cent[2], float size, NodeBorder immEnd(); } + +/** \} */ diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 3999abbfd4f..fbb99d78600 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -1135,125 +1135,6 @@ static void node_draw_mute_line(const bContext &C, GPU_blend(GPU_BLEND_NONE); } -static void node_socket_draw(const bNodeSocket &sock, - const float color[4], - const float color_outline[4], - const float size, - const float locx, - const float locy, - uint pos_id, - uint col_id, - uint shape_id, - uint size_id, - uint outline_col_id) -{ - int flags; - - /* Set shape flags. */ - switch (sock.display_shape) { - case SOCK_DISPLAY_SHAPE_DIAMOND: - case SOCK_DISPLAY_SHAPE_DIAMOND_DOT: - flags = GPU_KEYFRAME_SHAPE_DIAMOND; - break; - case SOCK_DISPLAY_SHAPE_SQUARE: - case SOCK_DISPLAY_SHAPE_SQUARE_DOT: - flags = GPU_KEYFRAME_SHAPE_SQUARE; - break; - default: - case SOCK_DISPLAY_SHAPE_CIRCLE: - case SOCK_DISPLAY_SHAPE_CIRCLE_DOT: - flags = GPU_KEYFRAME_SHAPE_CIRCLE; - break; - } - - if (ELEM(sock.display_shape, - SOCK_DISPLAY_SHAPE_DIAMOND_DOT, - SOCK_DISPLAY_SHAPE_SQUARE_DOT, - SOCK_DISPLAY_SHAPE_CIRCLE_DOT)) - { - flags |= GPU_KEYFRAME_SHAPE_INNER_DOT; - } - - immAttr4fv(col_id, color); - immAttr1u(shape_id, flags); - immAttr1f(size_id, size); - immAttr4fv(outline_col_id, color_outline); - immVertex2f(pos_id, locx, locy); -} - -static void node_socket_tooltip_set(uiBlock &block, - const int socket_index_in_tree, - const float2 location, - const float2 size) -{ - /* Ideally sockets themselves should be buttons, but they aren't currently. So add an invisible - * button on top of them for the tooltip. */ - const eUIEmbossType old_emboss = UI_block_emboss_get(&block); - UI_block_emboss_set(&block, UI_EMBOSS_NONE); - uiBut *but = uiDefIconBut(&block, - UI_BTYPE_BUT, - 0, - ICON_NONE, - location.x - size.x / 2.0f, - location.y - size.y / 2.0f, - size.x, - size.y, - nullptr, - 0, - 0, - nullptr); - - UI_but_func_tooltip_set( - but, - [](bContext *C, void *argN, const char * /*tip*/) { - const SpaceNode &snode = *CTX_wm_space_node(C); - const bNodeTree &ntree = *snode.edittree; - const int index_in_tree = POINTER_AS_INT(argN); - ntree.ensure_topology_cache(); - return node_socket_get_tooltip(&snode, ntree, *ntree.all_sockets()[index_in_tree]); - }, - POINTER_FROM_INT(socket_index_in_tree), - nullptr); - /* Disable the button so that clicks on it are ignored the link operator still works. */ - UI_but_flag_enable(but, UI_BUT_DISABLED); - UI_block_emboss_set(&block, old_emboss); -} - -static void node_socket_draw_multi_input(uiBlock &block, - const int index_in_tree, - const float2 location, - const float2 draw_size, - const float color[4], - const float color_outline[4], - const float2 tooltip_size) -{ - /* The other sockets are drawn with the keyframe shader. There, the outline has a base - * thickness that can be varied but always scales with the size the socket is drawn at. Using - * `UI_SCALE_FAC` has the same effect here. It scales the outline correctly across different - * screen DPI's and UI scales without being affected by the 'line-width'. */ - const float half_outline_width = NODE_SOCK_OUTLINE_SCALE * UI_SCALE_FAC * 0.5f; - - /* UI_draw_roundbox draws the outline on the outer side, so compensate for the outline width. - */ - const rctf rect = { - location.x - draw_size.x + half_outline_width, - location.x + draw_size.x + half_outline_width, - location.y - draw_size.y + half_outline_width, - location.y + draw_size.y + half_outline_width, - }; - - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv_ex(&rect, - color, - nullptr, - 1.0f, - color_outline, - half_outline_width * 2.0f, - draw_size.x - half_outline_width); - - node_socket_tooltip_set(block, index_in_tree, location, tooltip_size); -} - static const float virtual_node_socket_outline_color[4] = {0.5, 0.5, 0.5, 1.0}; static void node_socket_outline_color_get(const bool selected, @@ -1977,87 +1858,28 @@ void node_socket_add_tooltip(const bNodeTree &ntree, const bNodeSocket &sock, ui MEM_freeN); } -static void node_socket_draw_nested(const bContext &C, - const bNodeTree &ntree, - PointerRNA &node_ptr, - uiBlock &block, - const bNodeSocket &sock, - const uint pos_id, - const uint col_id, - const uint shape_id, - const uint size_id, - const uint outline_col_id, - const float size, - const bool selected) -{ - const float2 location = sock.runtime->location; - - float color[4]; - float outline_color[4]; - node_socket_color_get(C, ntree, node_ptr, sock, color); - node_socket_outline_color_get(selected, sock.type, outline_color); - - node_socket_draw(sock, - color, - outline_color, - size, - location.x, - location.y, - pos_id, - col_id, - shape_id, - size_id, - outline_col_id); - - node_socket_tooltip_set(block, sock.index_in_tree(), location, float2(size, size)); -} +#define NODE_SOCKET_OUTLINE U.pixelsize +#define NODE_SOCKET_DOT U.scale_factor void node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[4], float scale) { - const float size = NODE_SOCKSIZE_DRAW_MULIPLIER * NODE_SOCKSIZE * scale; - rcti draw_rect = *rect; + const float radius = NODE_SOCKSIZE * scale; + const float2 center = {BLI_rcti_cent_x_fl(rect), BLI_rcti_cent_y_fl(rect)}; + const rctf draw_rect = { + center.x - radius, + center.x + radius, + center.y - radius, + center.y + radius, + }; float outline_color[4] = {0}; - node_socket_outline_color_get(sock->flag & SELECT, sock->type, outline_color); - - BLI_rcti_resize(&draw_rect, size, size); - - GPUVertFormat *format = immVertexFormat(); - uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - uint col_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - uint shape_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); - uint size_id = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); - uint outline_col_id = GPU_vertformat_attr_add( - format, "outlineColor", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - - eGPUBlend state = GPU_blend_get(); - GPU_blend(GPU_BLEND_ALPHA); - GPU_program_point_size(true); - - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); - immUniform1f("outline_scale", NODE_SOCK_OUTLINE_SCALE); - immUniform2f("ViewportSize", -1.0f, -1.0f); - - /* Single point. */ - immBegin(GPU_PRIM_POINTS, 1); - node_socket_draw(*sock, - color, - outline_color, - BLI_rcti_size_y(&draw_rect), - BLI_rcti_cent_x(&draw_rect), - BLI_rcti_cent_y(&draw_rect), - pos_id, - col_id, - shape_id, - size_id, - outline_col_id); - immEnd(); - - immUnbindProgram(); - GPU_program_point_size(false); - - /* Restore. */ - GPU_blend(state); + node_draw_nodesocket(&draw_rect, + color, + outline_color, + NODE_SOCKET_OUTLINE * scale, + 0.0f, + NODE_SOCKET_DOT * scale, + sock->display_shape); } static void node_draw_preview_background(rctf *rect) @@ -2165,212 +1987,93 @@ static void node_draw_shadow(const SpaceNode &snode, UI_draw_roundbox_4fv(&rect, false, radius + 0.5f, color); } -static void node_draw_sockets(const View2D &v2d, - const bContext &C, - const bNodeTree &ntree, - const bNode &node, - uiBlock &block, - const bool draw_outputs, - const bool select_all) +static void node_draw_socket(const bContext &C, + const bNodeTree &ntree, + const bNode &node, + PointerRNA &node_ptr, + const bNodeSocket &sock, + const float outline_thickness, + const float outline_offset, + const float dot_radius, + const bool selected) { + const float half_width = NODE_SOCKSIZE; + + const bool multi_socket = (sock.flag & SOCK_MULTI_INPUT) && !(node.flag & NODE_HIDDEN); + float half_height = multi_socket ? node_socket_calculate_height(sock) : half_width; + + ColorTheme4f socket_color; + ColorTheme4f outline_color; + node_socket_color_get(C, ntree, node_ptr, sock, socket_color); + node_socket_outline_color_get(selected, sock.type, outline_color); + + const float2 socket_location = sock.runtime->location; + + const rctf rect = { + socket_location.x - half_width, + socket_location.x + half_width, + socket_location.y - half_height, + socket_location.y + half_height, + }; + + node_draw_nodesocket(&rect, + socket_color, + outline_color, + outline_thickness, + outline_offset, + dot_radius, + sock.display_shape); +} + +/* Some elements of the node tree like labels or node sockets are hardly visible when zoomed + * out and can slow down the drawing quite a bit. + * This function can be used to check if it's worth to draw those details and return + * early. */ +static bool draw_node_details(const View2D &v2d) +{ + float scale; + UI_view2d_scale_get(&v2d, &scale, nullptr); + return scale > 0.2f * UI_INV_SCALE_FAC; +} + +void node_draw_sockets(const View2D &v2d, const bContext &C, bNodeTree &ntree, const bNode &node) +{ + if (!draw_node_details(v2d)) { + return; + } + if (node.input_sockets().is_empty() && node.output_sockets().is_empty()) { return; } - bool selected = false; + PointerRNA nodeptr = RNA_pointer_create( + const_cast(&ntree.id), &RNA_Node, const_cast(&node)); - GPUVertFormat *format = immVertexFormat(); - uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - uint col_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - uint shape_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); - uint size_id = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); - uint outline_col_id = GPU_vertformat_attr_add( - format, "outlineColor", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + const float outline_thickness = NODE_SOCKET_OUTLINE; + const float border_offset = 0.0f; + const float dot_radius = NODE_SOCKET_DOT; - GPU_blend(GPU_BLEND_ALPHA); - GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); - immUniform1f("outline_scale", NODE_SOCK_OUTLINE_SCALE); - immUniform2f("ViewportSize", -1.0f, -1.0f); - - /* Set handle size. */ - const float socket_draw_size = NODE_SOCKSIZE * NODE_SOCKSIZE_DRAW_MULIPLIER; - float scale; - UI_view2d_scale_get(&v2d, &scale, nullptr); - scale *= socket_draw_size; - - if (!select_all) { - immBeginAtMost(GPU_PRIM_POINTS, node.input_sockets().size() + node.output_sockets().size()); - } - - PointerRNA node_ptr = RNA_pointer_create( - &const_cast(ntree.id), &RNA_Node, &const_cast(node)); - - /* Socket inputs. */ - int selected_input_len = 0; + nodesocket_batch_start(); + /* Input sockets. */ for (const bNodeSocket *sock : node.input_sockets()) { - /* In "hidden" nodes: draw sockets even when panels are collapsed. */ if (!node.is_socket_icon_drawn(*sock)) { continue; } - if (select_all || (sock->flag & SELECT)) { - if (!(sock->flag & SOCK_MULTI_INPUT)) { - /* Don't add multi-input sockets here since they are drawn in a different batch. */ - selected_input_len++; - } + const bool selected = (sock->flag & SELECT); + node_draw_socket( + C, ntree, node, nodeptr, *sock, outline_thickness, border_offset, dot_radius, selected); + } + + /* Output sockets. */ + for (const bNodeSocket *sock : node.output_sockets()) { + if (!node.is_socket_icon_drawn(*sock)) { continue; } - /* Don't draw multi-input sockets here since they are drawn in a different batch. */ - if (sock->flag & SOCK_MULTI_INPUT) { - continue; - } - - node_socket_draw_nested(C, - ntree, - node_ptr, - block, - *sock, - pos_id, - col_id, - shape_id, - size_id, - outline_col_id, - scale, - selected); - } - - /* Socket outputs. */ - int selected_output_len = 0; - if (draw_outputs) { - for (const bNodeSocket *sock : node.output_sockets()) { - /* In "hidden" nodes: draw sockets even when panels are collapsed. */ - if (!node.is_socket_icon_drawn(*sock)) { - continue; - } - if (select_all || (sock->flag & SELECT)) { - selected_output_len++; - continue; - } - - node_socket_draw_nested(C, - ntree, - node_ptr, - block, - *sock, - pos_id, - col_id, - shape_id, - size_id, - outline_col_id, - scale, - selected); - } - } - - if (!select_all) { - immEnd(); - } - - /* Go back and draw selected sockets. */ - if (selected_input_len + selected_output_len > 0) { - /* Outline for selected sockets. */ - - selected = true; - - immBegin(GPU_PRIM_POINTS, selected_input_len + selected_output_len); - - if (selected_input_len) { - /* Socket inputs. */ - for (const bNodeSocket *sock : node.input_sockets()) { - if (!node.is_socket_icon_drawn(*sock)) { - continue; - } - /* Don't draw multi-input sockets here since they are drawn in a different batch. */ - if (sock->flag & SOCK_MULTI_INPUT) { - continue; - } - if (select_all || (sock->flag & SELECT)) { - node_socket_draw_nested(C, - ntree, - node_ptr, - block, - *sock, - pos_id, - col_id, - shape_id, - size_id, - outline_col_id, - scale, - selected); - if (--selected_input_len == 0) { - /* Stop as soon as last one is drawn. */ - break; - } - } - } - } - - if (selected_output_len) { - /* Socket outputs. */ - for (const bNodeSocket *sock : node.output_sockets()) { - if (!node.is_socket_icon_drawn(*sock)) { - continue; - } - if (select_all || (sock->flag & SELECT)) { - node_socket_draw_nested(C, - ntree, - node_ptr, - block, - *sock, - pos_id, - col_id, - shape_id, - size_id, - outline_col_id, - scale, - selected); - if (--selected_output_len == 0) { - /* Stop as soon as last one is drawn. */ - break; - } - } - } - } - - immEnd(); - } - - immUnbindProgram(); - - GPU_program_point_size(false); - GPU_blend(GPU_BLEND_NONE); - - /* Draw multi-input sockets after the others because they are drawn with `UI_draw_roundbox` - * rather than with `GL_POINT`. */ - for (const bNodeSocket *socket : node.input_sockets()) { - if (!node.is_socket_icon_drawn(*socket)) { - continue; - } - if (!(socket->flag & SOCK_MULTI_INPUT)) { - continue; - } - - const bool is_node_hidden = (node.flag & NODE_HIDDEN); - const float width = 0.5f * socket_draw_size; - float height = is_node_hidden ? width : node_socket_calculate_height(*socket) - width; - - float color[4]; - float outline_color[4]; - node_socket_color_get(C, ntree, node_ptr, *socket, color); - node_socket_outline_color_get(socket->flag & SELECT, socket->type, outline_color); - - const int index_in_tree = socket->index_in_tree(); - const float2 location = socket->runtime->location; - const float2 draw_size(width, height); - const float2 tooltip_size(scale, height * 2.0f - socket_draw_size + scale); - node_socket_draw_multi_input( - block, index_in_tree, location, draw_size, color, outline_color, tooltip_size); + const bool selected = (sock->flag & SELECT); + node_draw_socket( + C, ntree, node, nodeptr, *sock, outline_thickness, border_offset, dot_radius, selected); } + nodesocket_batch_end(); } static void node_panel_toggle_button_cb(bContext *C, void *panel_state_argv, void *ntree_argv) @@ -3625,13 +3328,7 @@ static void node_draw_basis(const bContext &C, UI_draw_roundbox_4fv(&rect, false, BASIS_RAD + outline_width, color_outline); } - float scale; - UI_view2d_scale_get(&v2d, &scale, nullptr); - - /* Skip slow socket drawing if zoom is small. */ - if (scale > 0.2f) { - node_draw_sockets(v2d, C, ntree, node, block, true, false); - } + node_draw_sockets(v2d, C, ntree, node); if (is_node_panels_supported(node)) { node_draw_panels(ntree, node, block); @@ -3820,7 +3517,7 @@ static void node_draw_hidden(const bContext &C, immUnbindProgram(); GPU_blend(GPU_BLEND_NONE); - node_draw_sockets(v2d, C, ntree, node, block, true, false); + node_draw_sockets(v2d, C, ntree, node); UI_block_end(&C, &block); UI_block_draw(&C, &block); @@ -3969,16 +3666,16 @@ static void reroute_node_prepare_for_draw(bNode &node) { const float2 loc = node_to_view(node, float2(0)); - /* Reroute node has exactly one input and one output, both in the same place. */ + /* When the node is hidden, the input and output socket are both in the same place. */ node.input_socket(0).runtime->location = loc; node.output_socket(0).runtime->location = loc; - const float size = 8.0f; - node.width = size * 2; - node.runtime->totr.xmin = loc.x - size; - node.runtime->totr.xmax = loc.x + size; - node.runtime->totr.ymax = loc.y + size; - node.runtime->totr.ymin = loc.y - size; + const float radius = NODE_SOCKSIZE; + node.width = radius * 2; + node.runtime->totr.xmin = loc.x - radius; + node.runtime->totr.xmax = loc.x + radius; + node.runtime->totr.ymax = loc.y + radius; + node.runtime->totr.ymin = loc.y - radius; } static void node_update_nodetree(const bContext &C, @@ -4151,35 +3848,71 @@ static void frame_node_draw(const bContext &C, UI_block_draw(&C, &block); } -static void reroute_node_draw( - const bContext &C, ARegion ®ion, bNodeTree &ntree, const bNode &node, uiBlock &block) +void reroute_node_draw_body(const bContext &C, + const bNodeTree &ntree, + const bNode &node, + const bool selected) +{ + BLI_assert(node.is_reroute()); + + bNodeSocket &sock = *static_cast(node.inputs.first); + + PointerRNA nodeptr = RNA_pointer_create( + const_cast(&ntree.id), &RNA_Node, const_cast(&node)); + + ColorTheme4f socket_color; + ColorTheme4f outline_color; + + node_socket_color_get(C, ntree, nodeptr, sock, socket_color); + node_socket_outline_color_get(selected, sock.type, outline_color); + + node_draw_nodesocket(&node.runtime->totr, + socket_color, + outline_color, + NODE_SOCKET_OUTLINE, + 0.0f, + NODE_SOCKET_DOT, + sock.display_shape); +} + +static void reroute_node_draw_label(const bNode &node, uiBlock &block) +{ + /* Draw title (node label). */ + char showname[128]; /* 128 used below */ + STRNCPY(showname, node.label); + const short width = 512; + const int x = BLI_rctf_cent_x(&node.runtime->totr) - (width / 2); + const int y = node.runtime->totr.ymax; + + uiBut *label_but = uiDefBut( + &block, UI_BTYPE_LABEL, 0, showname, x, y, width, short(NODE_DY), nullptr, 0, 0, nullptr); + + UI_but_drawflag_disable(label_but, UI_BUT_TEXT_LEFT); +} + +static void reroute_node_draw( + const bContext &C, ARegion ®ion, const bNodeTree &ntree, const bNode &node, uiBlock &block) { - /* Skip if out of view. */ const rctf &rct = node.runtime->totr; - if (rct.xmax < region.v2d.cur.xmin || rct.xmin > region.v2d.cur.xmax || - rct.ymax < region.v2d.cur.ymin || node.runtime->totr.ymin > region.v2d.cur.ymax) + const View2D &v2d = region.v2d; + + /* Skip if out of view. */ + if (rct.xmax < v2d.cur.xmin || rct.xmin > v2d.cur.xmax || rct.ymax < v2d.cur.ymin || + node.runtime->totr.ymin > v2d.cur.ymax) { UI_block_end(&C, &block); return; } - if (node.label[0] != '\0') { - /* Draw title (node label). */ - char showname[128]; /* 128 used below */ - STRNCPY(showname, node.label); - const short width = 512; - const int x = BLI_rctf_cent_x(&node.runtime->totr) - (width / 2); - const int y = node.runtime->totr.ymax; - - uiBut *label_but = uiDefBut( - &block, UI_BTYPE_LABEL, 0, showname, x, y, width, short(NODE_DY), nullptr, 0, 0, nullptr); - - UI_but_drawflag_disable(label_but, UI_BUT_TEXT_LEFT); + if (draw_node_details(v2d)) { + if (node.label[0] != '\n') { + reroute_node_draw_label(node, block); + } } - /* Only draw input socket as they all are placed on the same position highlight - * if node itself is selected, since we don't display the node body separately. */ - node_draw_sockets(region.v2d, C, ntree, node, block, false, node.flag & SELECT); + /* Only draw the input socket, since all sockets are at the same location. */ + const bool selected = node.flag & NODE_SELECT; + reroute_node_draw_body(C, ntree, node, selected); UI_block_end(&C, &block); UI_block_draw(&C, &block); diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 2f947089d39..47205d205d6 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -107,7 +107,7 @@ struct CompoJob { float node_socket_calculate_height(const bNodeSocket &socket) { - float sock_height = NODE_SOCKSIZE * NODE_SOCKSIZE_DRAW_MULIPLIER; + float sock_height = NODE_SOCKSIZE; if (socket.flag & SOCK_MULTI_INPUT) { sock_height += max_ii(NODE_MULTI_INPUT_LINK_GAP * 0.5f * socket.runtime->total_inputs, NODE_SOCKSIZE); diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index ea26196bc8f..dd41f90100a 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -144,7 +144,6 @@ ENUM_OPERATORS(NodeResizeDirection, NODE_RESIZE_LEFT); #define NODE_HEIGHT(node) (node.height * UI_SCALE_FAC) #define NODE_MARGIN_X (1.2f * U.widget_unit) #define NODE_SOCKSIZE (0.25f * U.widget_unit) -#define NODE_SOCKSIZE_DRAW_MULIPLIER 2.25f #define NODE_SOCK_OUTLINE_SCALE 1.0f #define NODE_MULTI_INPUT_LINK_GAP (0.25f * U.widget_unit) #define NODE_RESIZE_MARGIN (0.20f * U.widget_unit) @@ -245,6 +244,18 @@ NodeResizeDirection node_get_resize_direction(const SpaceNode &snode, int x, int y); +/* node socket batched drawing */ +void UI_node_socket_draw_cache_flush(); +void nodesocket_batch_start(); +void nodesocket_batch_end(); +void node_draw_nodesocket(const rctf *rect, + const float color_inner[4], + const float color_outline[4], + const float outline_thickness, + const float outline_offset, + const float dot_radius, + int shape); + void nodelink_batch_start(SpaceNode &snode); void nodelink_batch_end(SpaceNode &snode); diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index b452a1dcfa0..7d2eb4971d9 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -150,13 +150,6 @@ static bool node_under_mouse_tweak(const SpaceNode &snode, const float2 &mouse) { for (bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) { switch (node->type) { - case NODE_REROUTE: { - const float2 location = node_to_view(*node, {node->locx, node->locy}); - if (math::distance_squared(mouse, location) < square_f(24.0f)) { - return true; - } - break; - } case NODE_FRAME: { if (node_frame_select_isect_mouse(snode, *node, mouse)) { return true; diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index fbbcef64ef1..776917d7abb 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -458,6 +458,8 @@ set(GLSL_SRC shaders/gpu_shader_2D_widget_base_frag.glsl shaders/gpu_shader_2D_widget_shadow_vert.glsl shaders/gpu_shader_2D_widget_shadow_frag.glsl + shaders/gpu_shader_2D_node_socket_frag.glsl + shaders/gpu_shader_2D_node_socket_vert.glsl shaders/gpu_shader_2D_nodelink_frag.glsl shaders/gpu_shader_2D_nodelink_vert.glsl shaders/gpu_shader_2D_line_dashed_frag.glsl @@ -770,6 +772,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/gpu_shader_2D_image_overlays_stereo_merge_info.hh shaders/infos/gpu_shader_2D_image_rect_color_info.hh shaders/infos/gpu_shader_2D_image_shuffle_color_info.hh + shaders/infos/gpu_shader_2D_node_socket_info.hh shaders/infos/gpu_shader_2D_nodelink_info.hh shaders/infos/gpu_shader_2D_point_uniform_size_uniform_color_aa_info.hh shaders/infos/gpu_shader_2D_point_uniform_size_uniform_color_outline_aa_info.hh diff --git a/source/blender/gpu/GPU_shader_builtin.hh b/source/blender/gpu/GPU_shader_builtin.hh index 43935ec4ce8..6e55cc0c967 100644 --- a/source/blender/gpu/GPU_shader_builtin.hh +++ b/source/blender/gpu/GPU_shader_builtin.hh @@ -58,6 +58,10 @@ enum eGPUBuiltinShader { GPU_SHADER_2D_WIDGET_BASE, GPU_SHADER_2D_WIDGET_BASE_INST, GPU_SHADER_2D_WIDGET_SHADOW, + /** Draw a node socket given it's bounding rectangle. All socket shapes are supported through + * a single shader. */ + GPU_SHADER_2D_NODE_SOCKET, + GPU_SHADER_2D_NODE_SOCKET_INST, /** Draw a node link given an input quadratic Bezier curve. */ GPU_SHADER_2D_NODELINK, GPU_SHADER_2D_NODELINK_INST, diff --git a/source/blender/gpu/intern/gpu_shader_builtin.cc b/source/blender/gpu/intern/gpu_shader_builtin.cc index 1b40caff978..2eacbbd9421 100644 --- a/source/blender/gpu/intern/gpu_shader_builtin.cc +++ b/source/blender/gpu/intern/gpu_shader_builtin.cc @@ -81,6 +81,10 @@ static const char *builtin_shader_create_info_name(eGPUBuiltinShader shader) return "gpu_shader_2D_widget_base_inst"; case GPU_SHADER_2D_WIDGET_SHADOW: return "gpu_shader_2D_widget_shadow"; + case GPU_SHADER_2D_NODE_SOCKET: + return "gpu_shader_2D_node_socket"; + case GPU_SHADER_2D_NODE_SOCKET_INST: + return "gpu_shader_2D_node_socket_inst"; case GPU_SHADER_2D_NODELINK: return "gpu_shader_2D_nodelink"; case GPU_SHADER_2D_NODELINK_INST: diff --git a/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl new file mode 100644 index 00000000000..90881499475 --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl @@ -0,0 +1,43 @@ +/* SPDX-FileCopyrightText: 2018-2022 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#define COS45 (0.70710678118) +#define SIN45 (0.70710678118) + +float square_sdf(vec2 absCo, float half_width) +{ + vec2 extruded = vec2(max(0.0, absCo.x - half_width), max(0.0, absCo.y - half_width)); + return dot(extruded, extruded); +} + +vec2 rotate_45(vec2 co) +{ + return vec2(COS45 * co.x - SIN45 * co.y, SIN45 * co.x + COS45 * co.y); +} + +void main() +{ + vec2 absUV = abs(uv); + vec2 co = vec2(max(absUV.x - extrusion.x, 0.0), max(absUV.y - extrusion.y, 0.0)); + + co = (is_diamond == 1) ? abs(rotate_45(co)) : co; + float distSquared = square_sdf(co, sdf_shape_radius); + + /* Needed to draw two dots for the wide reroute nodes. */ + vec2 biCenteredUV = abs(absUV - extrusion); + + /* Black mask with a white dot */ + float mask_dot = smoothstep(dotThresholds[1], dotThresholds[0], dot(biCenteredUV, biCenteredUV)); + + /* Alpha for the socket: White where the socket is, black outside of it. */ + float mask_all = smoothstep(thresholds[3], thresholds[2], distSquared); + + /* Mask for the outline. The inner part of the socket is masked with black. */ + bool noOutline = thresholds[2] - thresholds[0] < 0.0001; + float mask_outline = noOutline ? 0.0 : smoothstep(thresholds[0], thresholds[1], distSquared); + mask_outline += mask_dot; + + fragColor = mix(finalColor, finalOutlineColor, mask_outline); + fragColor.a *= mask_all; +} diff --git a/source/blender/gpu/shaders/gpu_shader_2D_node_socket_vert.glsl b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_vert.glsl new file mode 100644 index 00000000000..d8d333376b5 --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_vert.glsl @@ -0,0 +1,117 @@ +/* SPDX-FileCopyrightText: 2018-2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Values in `eNodeSocketDisplayShape` in DNA_node_types.h. Keep in sync. */ +#define SOCK_DISPLAY_SHAPE_CIRCLE 0 +#define SOCK_DISPLAY_SHAPE_SQUARE 1 +#define SOCK_DISPLAY_SHAPE_DIAMOND 2 +#define SOCK_DISPLAY_SHAPE_CIRCLE_DOT 3 +#define SOCK_DISPLAY_SHAPE_SQUARE_DOT 4 +#define SOCK_DISPLAY_SHAPE_DIAMOND_DOT 5 + +#define rect parameters[widgetID * MAX_PARAM + 0] +#define colorInner parameters[widgetID * MAX_PARAM + 1] +#define colorOutline parameters[widgetID * MAX_PARAM + 2] +#define outlineThickness parameters[widgetID * MAX_PARAM + 3].x +#define outlineOffset parameters[widgetID * MAX_PARAM + 3].y +#define dotRadius parameters[widgetID * MAX_PARAM + 3].z +#define shape parameters[widgetID * MAX_PARAM + 3].w + +#define AA_SIZE 0.75 +#define IS_DIAMOND \ + (shapeFlags == SOCK_DISPLAY_SHAPE_DIAMOND || shapeFlags == SOCK_DISPLAY_SHAPE_DIAMOND_DOT) +#define HAS_DOT \ + (shapeFlags > 2) + +/* Offsetting by a pixel further to avoid losing pixels. */ +vec2 ofs = vec2(outlineOffset + 1.0, -outlineOffset - 1.0); + +/* Calculate size of the original rectangle before expanding it based on the offset for the outline + */ +vec2 rectSize = rect.yw - rect.xz; +float minSize = min(rectSize.x, rectSize.y); + +/* Set the parameters for the sdf function that is used to draw the socket. */ +#define CIRCLE_RADIUS 0.5 +#define SQUARE_RADIUS 0.5 +#define DIAMOND_RADIUS 0.4 + +vec3 shapeRadii = vec3(CIRCLE_RADIUS, SQUARE_RADIUS, DIAMOND_RADIUS); +vec3 cornerRoundness = vec3(1.0, 0.4, 0.4); + +int shapeFlags = int(shape); +int shapeIndex = shapeFlags % 3; +float shapeRadius = shapeRadii[shapeIndex]; +float cornerRadius = shapeRadius * cornerRoundness[shapeIndex]; + +float pow2(float x) +{ + return x * x; +} + +void main() +{ + vec2 pos; + switch (gl_VertexID) { + default: + case 0: { + pos = rect.xz + ofs.yy; + break; + } + case 1: { + pos = rect.xw + ofs.yx; + break; + } + case 2: { + pos = rect.yz + ofs.xy; + break; + } + case 3: { + pos = rect.yw + ofs.xx; + break; + } + } + + gl_Position = ModelViewProjectionMatrix * vec4(pos, 0.0, 1.0); + + vec2 centeredCoordinates = pos - ((rect.xz + rect.yw) / 2.0); + uv = centeredCoordinates / minSize; + + /* Calculate the necessary "extrusion" of the coordinates to draw the middle part of + * multi sockets. */ + float aspect = rectSize.x / rectSize.y; + extrusion = (aspect > 1.0) ? vec2((aspect - 1.0) / 2.0, 0.0) : + vec2(0.0, ((1.0 / aspect) - 1.0) / 2.0); + + /* Thresholds for the masks in UV Space. Use squared values, so we can use the squared length in + * the fragment shader. */ + float InnerOutlineUVSquared1 = pow2( + ((outlineOffset - 0.5 * outlineThickness - AA_SIZE) / minSize) + cornerRadius); + float InnerOutlineUVSquared2 = pow2(((outlineOffset - 0.5 * outlineThickness) / minSize) + + cornerRadius); + float OuterOutlineUVSquared1 = pow2( + ((outlineOffset + 0.5 * outlineThickness - AA_SIZE) / minSize) + cornerRadius); + float OuterOutlineUVSquared2 = pow2(((outlineOffset + 0.5 * outlineThickness) / minSize) + + cornerRadius); + + thresholds = vec4(InnerOutlineUVSquared1, + InnerOutlineUVSquared2, + OuterOutlineUVSquared1, + OuterOutlineUVSquared2); + + /* Thresholds for the mask of the dot. */ + bool has_dot = HAS_DOT; + float dotRadiusSquared1 = has_dot ? pow2((dotRadius - AA_SIZE) / minSize) : (-1.0f); + float dotRadiusSquared2 = has_dot ? pow2(dotRadius / minSize) : 0.0f; + + dotThresholds = vec2(dotRadiusSquared1, dotRadiusSquared2); + + /* Shape parameters. */ + sdf_shape_radius = shapeRadius - cornerRadius; + is_diamond = IS_DIAMOND ? 1 : 0; + + /* Pass through parameters. */ + finalColor = colorInner; + finalOutlineColor = colorOutline; +} diff --git a/source/blender/gpu/shaders/infos/gpu_shader_2D_node_socket_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_2D_node_socket_info.hh new file mode 100644 index 00000000000..e9042420a71 --- /dev/null +++ b/source/blender/gpu/shaders/infos/gpu_shader_2D_node_socket_info.hh @@ -0,0 +1,47 @@ +/* SPDX-FileCopyrightText: 2022 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup gpu + */ + +#include "gpu_interface_info.hh" +#include "gpu_shader_create_info.hh" +GPU_SHADER_INTERFACE_INFO(gpu_node_socket_iface, "") + .flat(Type::FLOAT, "sdf_shape_radius") + .flat(Type::VEC4, "finalColor") + .flat(Type::VEC4, "finalOutlineColor") + .flat(Type::VEC4, "thresholds") + .flat(Type::VEC2, "dotThresholds") + .flat(Type::VEC2, "extrusion") + .flat(Type::INT, "is_diamond") + .smooth(Type::VEC2, "uv"); + +/* TODO(Leon): Share with C code. */ +/* TODO(Leon): Tweak the instance count to test if there's a noticable sweet spot. */ +#define MAX_SOCKET_PARAMETERS 4 +#define MAX_SOCKET_INSTANCE 32 + +GPU_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_shared) + .define("MAX_PARAM", STRINGIFY(MAX_SOCKET_PARAMETERS)) + .push_constant(Type::MAT4, "ModelViewProjectionMatrix") + .vertex_out(gpu_node_socket_iface) + .fragment_out(0, Type::VEC4, "fragColor") + .vertex_source("gpu_shader_2D_node_socket_vert.glsl") + .fragment_source("gpu_shader_2D_node_socket_frag.glsl"); + +GPU_SHADER_CREATE_INFO(gpu_shader_2D_node_socket) + .do_static_compilation(true) + /* gl_InstanceID is supposed to be 0 if not drawing instances, but this seems + * to be violated in some drivers. For example, macOS 10.15.4 and Intel Iris + * causes #78307 when using gl_InstanceID outside of instance. */ + .define("widgetID", "0") + .push_constant(Type::VEC4, "parameters", MAX_SOCKET_PARAMETERS) + .additional_info("gpu_shader_2D_node_socket_shared"); + +GPU_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_inst) + .do_static_compilation(true) + .define("widgetID", "gl_InstanceID") + .push_constant(Type::VEC4, "parameters", (MAX_SOCKET_PARAMETERS * MAX_SOCKET_INSTANCE)) + .additional_info("gpu_shader_2D_node_socket_shared");