From 13e0077c5c848b667a4b725f6e5c91129464b87a Mon Sep 17 00:00:00 2001 From: Leon Schittek Date: Sat, 23 Nov 2024 16:42:38 +0100 Subject: [PATCH] Nodes: Add new shader for node sockets Add a new shader specifically for node sockets rather than using the keyframe shader. Motivation: 1. Allow easier addition of new socket shapes 2. Simplify socket drawing by avoiding special handling of multi-inputs 3. Support multi-inputs for all socket types (diamond, square, etc.) The new shader is tweaked to look the same to the old ones. **Comparison** The biggest difference is that the multi socket is now more consistent with the other sockets. For single sockets there can be small size differences depending on zoom level because the old socket shader always aligned the sockets to the pixel grid. This could cause a bit of jiggling compared to the rest of the node when slowly zooming. Therefore I left it out of the new shader and it now scales strictly linear with the view. **Multi Socket Types** While there currently is no need for (.) internally, there are a few obvious use-cases for multi-input field (diamond) sockets like generalized math nodes with an arbitrary number of inputs (Add, Multiply, Minimum etc.). Co-authored-by: Jacques Lucke Pull Request: https://projects.blender.org/blender/blender/pulls/119243 --- source/blender/editors/include/ED_node.hh | 2 - source/blender/editors/space_node/drawnode.cc | 142 +++++ .../blender/editors/space_node/node_draw.cc | 506 +++++------------- .../blender/editors/space_node/node_edit.cc | 2 +- .../blender/editors/space_node/node_intern.hh | 13 +- source/blender/gpu/CMakeLists.txt | 2 + source/blender/gpu/GPU_shader_builtin.hh | 4 + source/blender/gpu/GPU_shader_shared.hh | 15 + .../blender/gpu/intern/gpu_shader_builtin.cc | 4 + .../gpu/intern/gpu_shader_create_info_list.hh | 1 + source/blender/gpu/shaders/CMakeLists.txt | 2 + .../gpu_shader_2D_node_socket_frag.glsl | 113 ++++ .../gpu_shader_2D_node_socket_vert.glsl | 70 +++ .../infos/gpu_shader_2D_node_socket_info.hh | 57 ++ 14 files changed, 561 insertions(+), 372 deletions(-) create mode 100644 source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl create mode 100644 source/blender/gpu/shaders/gpu_shader_2D_node_socket_vert.glsl create mode 100644 source/blender/gpu/shaders/infos/gpu_shader_2D_node_socket_info.hh 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()