diff --git a/source/blender/editors/include/ED_node.hh b/source/blender/editors/include/ED_node.hh index 549fe492c7d..bc960096ee8 100644 --- a/source/blender/editors/include/ED_node.hh +++ b/source/blender/editors/include/ED_node.hh @@ -47,8 +47,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 7fe86a8be06..929ff4e6b61 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -1810,6 +1810,146 @@ void node_link_bezier_points_evaluated(const bNodeLink &link, sizeof(float2)); } +/* -------------------------------------------------------------------- */ +/** \name Node Socket Drawing + * \{ */ + +/* Keep in sync with node socket shader. */ +#define MAX_SOCKET_PARAMETERS 4 +#define MAX_SOCKET_INSTANCE 32 + +struct GBatchNodesocket { + gpu::Batch *batch; + Vector params; + bool enabled; +}; + +static GBatchNodesocket &g_batch_nodesocket() +{ + static GBatchNodesocket nodesocket_batch; + return nodesocket_batch; +} + +static gpu::Batch *nodesocket_batch_init() +{ + if (g_batch_nodesocket().batch == nullptr) { + 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, nullptr, GPU_indexbuf_build(&ibuf), GPU_BATCH_OWNS_INDEX); + 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, + reinterpret_cast(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, + reinterpret_cast(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 int shape, + const float aspect) +{ + /* 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[0] = rect->xmin; + socket_params.rect[1] = rect->xmax; + socket_params.rect[2] = rect->ymin; + socket_params.rect[3] = rect->ymax; + 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.outline_offset = 0.0; + socket_params.shape = float(shape) + 0.1f; + socket_params.aspect = aspect; + + 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 @@ -2433,3 +2573,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 513d1971e4f..668c6ea6f71 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -1386,52 +1386,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, @@ -1470,41 +1424,6 @@ static void node_socket_tooltip_set(uiBlock &block, 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, @@ -2242,87 +2161,26 @@ 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 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, + sock->display_shape, + 1.0 / scale); } /** Some elements of the node UI are hidden, when they get too small. */ @@ -2334,6 +2192,15 @@ static float node_tree_view_scale(const SpaceNode &snode) return (1.0f / snode.runtime->aspect) * UI_SCALE_FAC; } +/* 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 SpaceNode &snode) +{ + return node_tree_view_scale(snode) > NODE_TREE_SCALE_SMALL * UI_INV_SCALE_FAC; +} + static void node_draw_preview_background(rctf *rect) { GPUVertFormat *format = immVertexFormat(); @@ -2439,212 +2306,79 @@ 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, + uiBlock &block, + const bNodeSocket &sock, + const float outline_thickness, + const bool selected, + const float aspect) { + 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, sock.display_shape, aspect); + + node_socket_tooltip_set( + block, sock.index_in_tree(), socket_location, float2(2.0f * half_width, 2.0f * half_height)); +} + +static void node_draw_sockets( + const bContext &C, uiBlock &block, const SpaceNode &snode, bNodeTree &ntree, const bNode &node) +{ + if (!draw_node_details(snode)) { + 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; - 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, block, *sock, outline_thickness, selected, snode.runtime->aspect); + } + + /* 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, block, *sock, outline_thickness, selected, snode.runtime->aspect); } + nodesocket_batch_end(); } static void node_panel_toggle_button_cb(bContext *C, void *panel_state_argv, void *ntree_argv) @@ -3849,8 +3583,8 @@ static void node_draw_basis(const bContext &C, } /* Skip slow socket drawing if zoom is small. */ - if (node_tree_view_scale(snode) > NODE_TREE_SCALE_SMALL) { - node_draw_sockets(v2d, C, ntree, node, block, true, false); + if (draw_node_details(snode)) { + node_draw_sockets(C, block, snode, ntree, node); } if (is_node_panels_supported(node)) { @@ -4046,7 +3780,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(C, block, snode, ntree, node); UI_block_end_ex(&C, tree_draw_ctx.bmain, @@ -4201,16 +3935,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, @@ -4522,6 +4256,40 @@ static StringRefNull reroute_node_get_auto_label(TreeDrawContext &tree_draw_ctx, return label; } +static void reroute_node_draw_body(const bContext &C, + const SpaceNode &snode, + const bNodeTree &ntree, + const bNode &node, + uiBlock &block, + 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, + sock.display_shape, + snode.runtime->aspect); + + const float2 location = float2(BLI_rctf_cent_x(&node.runtime->totr), + BLI_rctf_cent_y(&node.runtime->totr)); + const float2 size = float2(BLI_rctf_size_x(&node.runtime->totr), + BLI_rctf_size_y(&node.runtime->totr)); + node_socket_tooltip_set(block, sock.index_in_tree(), location, size); +} + static void reroute_node_draw_label(TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node, @@ -4536,7 +4304,7 @@ static void reroute_node_draw_label(TreeDrawContext &tree_draw_ctx, } /* Don't show the automatic label, when being zoomed out. */ - if (!has_label && node_tree_view_scale(snode) < NODE_TREE_SCALE_SMALL) { + if (!has_label && !draw_node_details(snode)) { return; } @@ -4566,10 +4334,12 @@ static void reroute_node_draw(const bContext &C, 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_ex(&C, tree_draw_ctx.bmain, @@ -4581,11 +4351,13 @@ static void reroute_node_draw(const bContext &C, return; } - reroute_node_draw_label(tree_draw_ctx, snode, node, block); + if (draw_node_details(snode)) { + reroute_node_draw_label(tree_draw_ctx, snode, 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, snode, ntree, node, block, selected); UI_block_end_ex(&C, tree_draw_ctx.bmain, diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index fb07ed7cc8f..f64bf279ee8 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 188106b87a7..99c0ec6d138 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -138,8 +138,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) #define NODE_LINK_RESOL 12 @@ -239,6 +237,17 @@ 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], + float outline_thickness, + int shape, + float aspect); + void nodelink_batch_start(SpaceNode &snode); void nodelink_batch_end(SpaceNode &snode); diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index e6af6db3812..fc9a761263f 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -482,6 +482,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 diff --git a/source/blender/gpu/GPU_shader_builtin.hh b/source/blender/gpu/GPU_shader_builtin.hh index 14b989d68da..b9b8ff478f1 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/GPU_shader_shared.hh b/source/blender/gpu/GPU_shader_shared.hh index ff136fb29af..9f7d27a1d91 100644 --- a/source/blender/gpu/GPU_shader_shared.hh +++ b/source/blender/gpu/GPU_shader_shared.hh @@ -32,6 +32,21 @@ enum eGPUKeyframeShapes : uint32_t { GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL), }; +#define MAX_SOCKET_PARAMETERS 4 +#define MAX_SOCKET_INSTANCE 32 + +/* Node Socket shader parameters. Must match the shader layout of "gpu_shader_2D_node_socket". */ +struct NodeSocketShaderParameters { + float4 rect; + float4 color_inner; + float4 color_outline; + float outline_thickness; + float outline_offset; + float shape; + float aspect; +}; +BLI_STATIC_ASSERT_ALIGN(NodeSocketShaderParameters, 16) + struct NodeLinkData { float4 colors[3]; /* bezierPts Is actually a float2, but due to std140 each element needs to be aligned to 16 diff --git a/source/blender/gpu/intern/gpu_shader_builtin.cc b/source/blender/gpu/intern/gpu_shader_builtin.cc index 68c65f8dbc5..f047563f66a 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/intern/gpu_shader_create_info_list.hh b/source/blender/gpu/intern/gpu_shader_create_info_list.hh index edff78f97e9..8d0cf7cad79 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info_list.hh +++ b/source/blender/gpu/intern/gpu_shader_create_info_list.hh @@ -14,6 +14,7 @@ #include "gpu_shader_2D_image_overlays_stereo_merge_info.hh" #include "gpu_shader_2D_image_rect_color_info.hh" #include "gpu_shader_2D_image_shuffle_color_info.hh" +#include "gpu_shader_2D_node_socket_info.hh" #include "gpu_shader_2D_nodelink_info.hh" #include "gpu_shader_2D_point_uniform_size_uniform_color_aa_info.hh" #include "gpu_shader_2D_point_uniform_size_uniform_color_outline_aa_info.hh" diff --git a/source/blender/gpu/shaders/CMakeLists.txt b/source/blender/gpu/shaders/CMakeLists.txt index 10c18fea503..7195bb81b75 100644 --- a/source/blender/gpu/shaders/CMakeLists.txt +++ b/source/blender/gpu/shaders/CMakeLists.txt @@ -25,6 +25,7 @@ set(SRC_GLSL_VERT gpu_shader_2D_area_borders_vert.glsl gpu_shader_2D_image_rect_vert.glsl gpu_shader_2D_image_vert.glsl + gpu_shader_2D_node_socket_vert.glsl gpu_shader_2D_nodelink_vert.glsl gpu_shader_2D_point_uniform_size_aa_vert.glsl gpu_shader_2D_point_uniform_size_outline_aa_vert.glsl @@ -53,6 +54,7 @@ set(SRC_GLSL_VERT set(SRC_GLSL_FRAG gpu_shader_2D_area_borders_frag.glsl gpu_shader_2D_line_dashed_frag.glsl + gpu_shader_2D_node_socket_frag.glsl gpu_shader_2D_nodelink_frag.glsl gpu_shader_2D_widget_base_frag.glsl gpu_shader_2D_widget_shadow_frag.glsl 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..d24e125e11d --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl @@ -0,0 +1,113 @@ +/* SPDX-FileCopyrightText: 2018-2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "infos/gpu_shader_2D_node_socket_info.hh" + +#include "gpu_shader_math_matrix_lib.glsl" + +FRAGMENT_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_inst) + +/* 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 + +/* Calculates a squared distance field of a square. */ +float square_sdf(vec2 absCo, float half_width_x, float half_width_y) +{ + vec2 extruded_co = absCo - vec2(half_width_x, half_width_y); + vec2 clamped_extruded_co = vec2(max(0.0, extruded_co.x), max(0.0, extruded_co.y)); + + float exterior_distance_squared = dot(clamped_extruded_co, clamped_extruded_co); + + float interior_distance = min(max(extruded_co.x, extruded_co.y), 0.0); + float interior_distance_squared = interior_distance * interior_distance; + + return exterior_distance_squared - interior_distance_squared; +} + +vec2 rotate_45(vec2 co) +{ + return from_rotation(Angle(M_PI * 0.25)) * co; +} + +/* Calculates an upper and lower limit for an antialiased cutoff of the squared distance. */ +vec2 calculate_thresholds(float threshold) +{ + /* Use the absolute on one of the factors to preserve the sign. */ + float inner_threshold = (threshold - 0.5 * AAsize) * abs(threshold - 0.5 * AAsize); + float outer_threshold = (threshold + 0.5 * AAsize) * abs(threshold + 0.5 * AAsize); + return vec2(inner_threshold, outer_threshold); +} + +void main() +{ + vec2 absUV = abs(uv); + vec2 co = vec2(max(absUV.x - extrusion.x, 0.0), max(absUV.y - extrusion.y, 0.0)); + + float distance_squared = 0.0; + float alpha_threshold = 0.0; + float dot_threshold = -1.0; + + const float circle_radius = 0.5; + const float square_radius = 0.5 / sqrt(2.0 / M_PI) * M_SQRT1_2; + const float diamond_radius = 0.5 / sqrt(2.0 / M_PI) * M_SQRT1_2; + const float corner_rounding = 0.0; + + switch (finalShape) { + default: + case SOCK_DISPLAY_SHAPE_CIRCLE: { + distance_squared = dot(co, co); + alpha_threshold = circle_radius; + break; + } + case SOCK_DISPLAY_SHAPE_CIRCLE_DOT: { + distance_squared = dot(co, co); + alpha_threshold = circle_radius; + dot_threshold = finalDotRadius; + break; + } + case SOCK_DISPLAY_SHAPE_SQUARE: { + float square_radius = square_radius - corner_rounding; + distance_squared = square_sdf(co, square_radius, square_radius); + alpha_threshold = corner_rounding; + break; + } + case SOCK_DISPLAY_SHAPE_SQUARE_DOT: { + float square_radius = square_radius - corner_rounding; + distance_squared = square_sdf(co, square_radius, square_radius); + alpha_threshold = corner_rounding; + dot_threshold = finalDotRadius; + break; + } + case SOCK_DISPLAY_SHAPE_DIAMOND: { + float diamond_radius = diamond_radius - corner_rounding; + distance_squared = square_sdf(abs(rotate_45(co)), diamond_radius, diamond_radius); + alpha_threshold = corner_rounding; + break; + } + case SOCK_DISPLAY_SHAPE_DIAMOND_DOT: { + float diamond_radius = diamond_radius - corner_rounding; + distance_squared = square_sdf(abs(rotate_45(co)), diamond_radius, diamond_radius); + alpha_threshold = corner_rounding; + dot_threshold = finalDotRadius; + break; + } + } + + vec2 alpha_thresholds = calculate_thresholds(alpha_threshold); + vec2 outline_thresholds = calculate_thresholds(alpha_threshold - finalOutlineThickness); + vec2 dot_thresholds = calculate_thresholds(dot_threshold); + + float alpha_mask = smoothstep(alpha_thresholds[1], alpha_thresholds[0], distance_squared); + float dot_mask = smoothstep(dot_thresholds[1], dot_thresholds[0], dot(co, co)); + float outline_mask = smoothstep(outline_thresholds[0], outline_thresholds[1], distance_squared) + + dot_mask; + + fragColor = mix(finalColor, finalOutlineColor, outline_mask); + fragColor.a *= alpha_mask; +} 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..9295e727d9a --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_vert.glsl @@ -0,0 +1,70 @@ +/* SPDX-FileCopyrightText: 2018-2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "infos/gpu_shader_2D_node_socket_info.hh" + +#include "gpu_shader_math_base_lib.glsl" + +VERTEX_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_inst) + +#define rect parameters[widgetID * MAX_SOCKET_PARAMETERS + 0] +#define colorInner parameters[widgetID * MAX_SOCKET_PARAMETERS + 1] +#define colorOutline parameters[widgetID * MAX_SOCKET_PARAMETERS + 2] +#define outlineThickness parameters[widgetID * MAX_SOCKET_PARAMETERS + 3].x +#define outlineOffset parameters[widgetID * MAX_SOCKET_PARAMETERS + 3].y +#define shape parameters[widgetID * MAX_SOCKET_PARAMETERS + 3].z +#define aspect parameters[widgetID * MAX_SOCKET_PARAMETERS + 3].w + +void main() +{ + /* Scale the original rectangle to accomodate the diagonal of the diamond shape. */ + vec2 originalRectSize = rect.yw - rect.xz; + float offset = 0.125 * min(originalRectSize.x, originalRectSize.y) + + outlineOffset * outlineThickness; + vec2 ofs = vec2(offset, -offset); + 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 rectSize = rect.yw - rect.xz + 2.0 * vec2(outlineOffset, outlineOffset); + float minSize = min(rectSize.x, rectSize.y); + + 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 ratio = rectSize.x / rectSize.y; + extrusion = (ratio > 1.0) ? vec2((ratio - 1.0) / 2.0, 0.0) : + vec2(0.0, ((1.0 / ratio) - 1.0) / 2.0); + + /* Shape parameters. */ + finalShape = int(shape); + finalOutlineThickness = outlineThickness / minSize; + finalDotRadius = outlineThickness / minSize; + AAsize = 1.0 * aspect / minSize; + + /* 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..ae3cde72394 --- /dev/null +++ b/source/blender/gpu/shaders/infos/gpu_shader_2D_node_socket_info.hh @@ -0,0 +1,57 @@ +/* SPDX-FileCopyrightText: 2022 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup gpu + */ + +#ifdef GPU_SHADER +# pragma once +# include "gpu_glsl_cpp_stubs.hh" + +# define widgetID 0 +#endif + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_INTERFACE_INFO(gpu_node_socket_iface) +FLAT(VEC4, finalColor) +FLAT(VEC4, finalOutlineColor) +FLAT(FLOAT, finalDotRadius) +FLAT(FLOAT, finalOutlineThickness) +FLAT(FLOAT, AAsize) +FLAT(VEC2, extrusion) +FLAT(INT, finalShape) +SMOOTH(VEC2, uv) +GPU_SHADER_INTERFACE_END() + +/* TODO(lone_noel): Share with C code. */ +#define MAX_SOCKET_PARAMETERS 4 +#define MAX_SOCKET_INSTANCE 32 + +GPU_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_shared) +DEFINE_VALUE("MAX_SOCKET_PARAMETERS", STRINGIFY(MAX_SOCKET_PARAMETERS)) +PUSH_CONSTANT(MAT4, ModelViewProjectionMatrix) +VERTEX_OUT(gpu_node_socket_iface) +FRAGMENT_OUT(0, VEC4, fragColor) +VERTEX_SOURCE("gpu_shader_2D_node_socket_vert.glsl") +FRAGMENT_SOURCE("gpu_shader_2D_node_socket_frag.glsl") +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(gpu_shader_2D_node_socket) +DO_STATIC_COMPILATION() +/* 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_VALUE("widgetID", "0") +PUSH_CONSTANT_ARRAY(VEC4, parameters, MAX_SOCKET_PARAMETERS) +ADDITIONAL_INFO(gpu_shader_2D_node_socket_shared) +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_inst) +DO_STATIC_COMPILATION() +DEFINE_VALUE("widgetID", "gl_InstanceID") +PUSH_CONSTANT_ARRAY(VEC4, parameters, (MAX_SOCKET_PARAMETERS * MAX_SOCKET_INSTANCE)) +ADDITIONAL_INFO(gpu_shader_2D_node_socket_shared) +GPU_SHADER_CREATE_END()