WIP: Experiment: Nodes: Add new shader for node sockets #119243

Draft
Leon Schittek wants to merge 9 commits from lone_noel/blender:node-socket-shader into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
12 changed files with 534 additions and 416 deletions

View File

@ -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);

View File

@ -1799,6 +1799,152 @@ 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 {
blender::gpu::Batch *batch;
NodeSocketShaderParameters params[MAX_SOCKET_INSTANCE];
int count;
bool enabled;
} g_batch_nodesocket;
blender::gpu::Batch *nodesocket_batch_init(void)
{
if (g_batch_nodesocket.batch == NULL) {
GPUVertFormat format = {0};
blender::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.count == 0) {
return;
}
blender::gpu::Batch *batch = nodesocket_batch_init();
if (g_batch_nodesocket.count == 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);
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);
GPU_batch_draw_instance_range(batch, 0, g_batch_nodesocket.count);
}
g_batch_nodesocket.count = 0;
}
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(NodeSocketShaderParameters *socket_params)
{
if (g_batch_nodesocket.enabled) {
g_batch_nodesocket.params[g_batch_nodesocket.count] = *socket_params;
g_batch_nodesocket.count++;
if (g_batch_nodesocket.count >= MAX_SOCKET_INSTANCE) {
nodesocket_cache_flush();
}
}
else {
/* draw single */
blender::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, (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
@ -2407,3 +2553,5 @@ void ED_node_draw_snap(View2D *v2d, const float cent[2], float size, NodeBorder
immEnd();
}
/** \} */

View File

@ -1134,78 +1134,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_draw_multi_input(const float color[4],
const float color_outline[4],
const float width,
const float height,
const float2 location)
{
/* 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 outline_width = NODE_SOCK_OUTLINE_SCALE * UI_SCALE_FAC;
/* UI_draw_roundbox draws the outline on the outer side, so compensate for the outline width.
*/
const rctf rect = {
location.x - width + outline_width * 0.5f,
location.x + width - outline_width * 0.5f,
location.y - height + outline_width * 0.5f,
location.y + height - outline_width * 0.5f,
};
UI_draw_roundbox_corner_set(UI_CNR_ALL);
UI_draw_roundbox_4fv_ex(
&rect, color, nullptr, 1.0f, color_outline, outline_width, width - outline_width * 0.5f);
}
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,
@ -1695,121 +1623,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);
if (!node_socket_has_tooltip(ntree, sock)) {
return;
}
/* 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 / 2.0f,
location.y - size / 2.0f,
size,
size,
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(sock.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);
}
#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)
@ -1917,208 +1752,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<ID *>(&ntree.id), &RNA_Node, const_cast<bNode *>(&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<ID &>(ntree.id), &RNA_Node, &const_cast<bNode &>(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 float2 location = socket->runtime->location;
node_socket_draw_multi_input(color, outline_color, width, height, location);
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)
@ -3316,13 +3036,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);
@ -3511,7 +3225,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);
@ -3660,16 +3374,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,
@ -3842,35 +3556,71 @@ static void frame_node_draw(const bContext &C,
UI_block_draw(&C, &block);
}
static void reroute_node_draw(
const bContext &C, ARegion &region, 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<bNodeSocket *>(node.inputs.first);
PointerRNA nodeptr = RNA_pointer_create(
const_cast<ID *>(&ntree.id), &RNA_Node, const_cast<bNode *>(&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 &region, 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);

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -425,6 +425,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
@ -745,6 +747,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

View File

@ -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,

View File

@ -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:

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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");