Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHabib Gahbiche <zazizizou>2021-03-29 08:44:27 +0300
committerJeroen Bakker <jeroen@blender.org>2021-03-29 08:56:58 +0300
commit805d9478109e76ca221f202ff152bae685f77ff4 (patch)
tree61b5b35de39fcdae1f31da98be39155f6d215386 /source/blender
parent6af4163a3f190ed8b33a622ac038d9df88757a20 (diff)
Compositor: Add Anti-Aliasing node
This is an implementation of Enhanced Subpixel Morphological Antialiasing (SMAA) The algorithm was proposed by: Jorge Jimenez, Jose I. Echevarria, Tiago Sousa, Diego Gutierrez This node provides only SMAA 1x mode, so the operation will be done with no spatial multisampling nor temporal supersampling. See Patch for comparisons. The existing AA operation seems to be used only for binary images by some other nodes. Using SMAA for binary images needs no important parameter such as "threshold", so we perhaps can switch the operation to SMAA, though that changes existing behavior. Notes: 1. The program code assumes the screen coordinates are DirectX style that the vertical direction is upside-down, so "top" and "bottom" actually represent bottom and top, respectively. Thanks for Habib Gahbiche (zazizizou) to polish and finalize this patch. Reviewed By: jbakker Differential Revision: https://developer.blender.org/D2411
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_node.h1
-rw-r--r--source/blender/blenkernel/intern/node.c3969
-rw-r--r--source/blender/blenkernel/intern/node.cc1
-rw-r--r--source/blender/blenlib/intern/math_base_inline.c7
-rw-r--r--source/blender/compositor/CMakeLists.txt24
-rw-r--r--source/blender/compositor/intern/COM_Converter.cc4
-rw-r--r--source/blender/compositor/nodes/COM_AntiAliasingNode.cc56
-rw-r--r--source/blender/compositor/nodes/COM_AntiAliasingNode.h38
-rw-r--r--source/blender/compositor/operations/COM_SMAAOperation.cc865
-rw-r--r--source/blender/compositor/operations/COM_SMAAOperation.h147
-rw-r--r--source/blender/editors/space_node/drawnode.c14
-rw-r--r--source/blender/makesdna/DNA_node_types.h6
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c35
-rw-r--r--source/blender/nodes/CMakeLists.txt1
-rw-r--r--source/blender/nodes/NOD_composite.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_antialiasing.c65
17 files changed, 5235 insertions, 0 deletions
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 8dae1da69cb..42ed026cb01 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1190,6 +1190,7 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree,
#define CMP_NODE_TRACKPOS 271
#define CMP_NODE_INPAINT 272
#define CMP_NODE_DESPECKLE 273
+#define CMP_NODE_ANTIALIASING 274
#define CMP_NODE_GLARE 301
#define CMP_NODE_TONEMAP 302
diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c
new file mode 100644
index 00000000000..95013264026
--- /dev/null
+++ b/source/blender/blenkernel/intern/node.c
@@ -0,0 +1,3969 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2005 Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Bob Holcomb.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/node.c
+ * \ingroup bke
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "DNA_action_types.h"
+#include "DNA_anim_types.h"
+#include "DNA_lamp_types.h"
+#include "DNA_linestyle_types.h"
+#include "DNA_material_types.h"
+#include "DNA_node_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_texture_types.h"
+#include "DNA_world_types.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+#include "BLI_string_utils.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_animsys.h"
+#include "BKE_global.h"
+#include "BKE_idprop.h"
+#include "BKE_library.h"
+#include "BKE_library_query.h"
+#include "BKE_library_remap.h"
+#include "BKE_main.h"
+#include "BKE_node.h"
+
+#include "BLI_ghash.h"
+#include "BLI_threads.h"
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "NOD_common.h"
+#include "NOD_composite.h"
+#include "NOD_shader.h"
+#include "NOD_socket.h"
+#include "NOD_texture.h"
+
+#define NODE_DEFAULT_MAX_WIDTH 700
+
+/* Fallback types for undefined tree, nodes, sockets */
+bNodeTreeType NodeTreeTypeUndefined;
+bNodeType NodeTypeUndefined;
+bNodeSocketType NodeSocketTypeUndefined;
+
+static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType *ntype)
+{
+ bNodeSocketTemplate *sockdef;
+ /* bNodeSocket *sock; */ /* UNUSED */
+
+ if (ntype->inputs) {
+ sockdef = ntype->inputs;
+ while (sockdef->type != -1) {
+ /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_IN);
+
+ sockdef++;
+ }
+ }
+ if (ntype->outputs) {
+ sockdef = ntype->outputs;
+ while (sockdef->type != -1) {
+ /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_OUT);
+
+ sockdef++;
+ }
+ }
+}
+
+/* Note: This function is called to initialize node data based on the type.
+ * The bNodeType may not be registered at creation time of the node,
+ * so this can be delayed until the node type gets registered.
+ */
+static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node)
+{
+ bNodeType *ntype = node->typeinfo;
+ if (ntype == &NodeTypeUndefined)
+ return;
+
+ /* only do this once */
+ if (node->flag & NODE_INIT)
+ return;
+
+ node->flag = NODE_SELECT | NODE_OPTIONS | ntype->flag;
+ node->width = ntype->width;
+ node->miniwidth = 42.0f;
+ node->height = ntype->height;
+ node->color[0] = node->color[1] = node->color[2] = 0.608; /* default theme color */
+ /* initialize the node name with the node label.
+ * note: do this after the initfunc so nodes get their data set which may be used in naming
+ * (node groups for example) */
+ /* XXX Do not use nodeLabel() here, it returns translated content for UI, which should *only* be
+ * used in UI, *never* in data... Data have their own translation option! This solution may be a
+ * bit rougher than nodeLabel()'s returned string, but it's simpler than adding "do_translate"
+ * flags to this func (and labelfunc() as well). */
+ BLI_strncpy(node->name, DATA_(ntype->ui_name), NODE_MAXSTR);
+ nodeUniqueName(ntree, node);
+
+ node_add_sockets_from_type(ntree, node, ntype);
+
+ if (ntype->initfunc != NULL)
+ ntype->initfunc(ntree, node);
+
+ if (ntree->typeinfo->node_add_init != NULL)
+ ntree->typeinfo->node_add_init(ntree, node);
+
+ /* extra init callback */
+ if (ntype->initfunc_api) {
+ PointerRNA ptr;
+ RNA_pointer_create((ID *)ntree, &RNA_Node, node, &ptr);
+
+ /* XXX Warning: context can be NULL in case nodes are added in do_versions.
+ * Delayed init is not supported for nodes with context-based initfunc_api atm.
+ */
+ BLI_assert(C != NULL);
+ ntype->initfunc_api(C, &ptr);
+ }
+
+ if (node->id)
+ id_us_plus(node->id);
+
+ node->flag |= NODE_INIT;
+}
+
+static void ntree_set_typeinfo(bNodeTree *ntree, bNodeTreeType *typeinfo)
+{
+ if (typeinfo) {
+ ntree->typeinfo = typeinfo;
+
+ /* deprecated integer type */
+ ntree->type = typeinfo->type;
+ }
+ else {
+ ntree->typeinfo = &NodeTreeTypeUndefined;
+
+ ntree->init &= ~NTREE_TYPE_INIT;
+ }
+}
+
+static void node_set_typeinfo(const struct bContext *C,
+ bNodeTree *ntree,
+ bNode *node,
+ bNodeType *typeinfo)
+{
+ /* for nodes saved in older versions storage can get lost, make undefined then */
+ if (node->flag & NODE_INIT) {
+ if (typeinfo && typeinfo->storagename[0] && !node->storage)
+ typeinfo = NULL;
+ }
+
+ if (typeinfo) {
+ node->typeinfo = typeinfo;
+
+ /* deprecated integer type */
+ node->type = typeinfo->type;
+
+ /* initialize the node if necessary */
+ node_init(C, ntree, node);
+ }
+ else {
+ node->typeinfo = &NodeTypeUndefined;
+
+ ntree->init &= ~NTREE_TYPE_INIT;
+ }
+}
+
+static void node_socket_set_typeinfo(bNodeTree *ntree,
+ bNodeSocket *sock,
+ bNodeSocketType *typeinfo)
+{
+ if (typeinfo) {
+ sock->typeinfo = typeinfo;
+
+ /* deprecated integer type */
+ sock->type = typeinfo->type;
+
+ if (sock->default_value == NULL) {
+ /* initialize the default_value pointer used by standard socket types */
+ node_socket_init_default_value(sock);
+ }
+ }
+ else {
+ sock->typeinfo = &NodeSocketTypeUndefined;
+
+ ntree->init &= ~NTREE_TYPE_INIT;
+ }
+}
+
+/* Set specific typeinfo pointers in all node trees on register/unregister */
+static void update_typeinfo(Main *bmain,
+ const struct bContext *C,
+ bNodeTreeType *treetype,
+ bNodeType *nodetype,
+ bNodeSocketType *socktype,
+ bool unregister)
+{
+ if (!bmain)
+ return;
+
+ FOREACH_NODETREE(bmain, ntree, id)
+ {
+ bNode *node;
+ bNodeSocket *sock;
+
+ ntree->init |= NTREE_TYPE_INIT;
+
+ if (treetype && STREQ(ntree->idname, treetype->idname))
+ ntree_set_typeinfo(ntree, unregister ? NULL : treetype);
+
+ /* initialize nodes */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (nodetype && STREQ(node->idname, nodetype->idname))
+ node_set_typeinfo(C, ntree, node, unregister ? NULL : nodetype);
+
+ /* initialize node sockets */
+ for (sock = node->inputs.first; sock; sock = sock->next)
+ if (socktype && STREQ(sock->idname, socktype->idname))
+ node_socket_set_typeinfo(ntree, sock, unregister ? NULL : socktype);
+ for (sock = node->outputs.first; sock; sock = sock->next)
+ if (socktype && STREQ(sock->idname, socktype->idname))
+ node_socket_set_typeinfo(ntree, sock, unregister ? NULL : socktype);
+ }
+
+ /* initialize tree sockets */
+ for (sock = ntree->inputs.first; sock; sock = sock->next)
+ if (socktype && STREQ(sock->idname, socktype->idname))
+ node_socket_set_typeinfo(ntree, sock, unregister ? NULL : socktype);
+ for (sock = ntree->outputs.first; sock; sock = sock->next)
+ if (socktype && STREQ(sock->idname, socktype->idname))
+ node_socket_set_typeinfo(ntree, sock, unregister ? NULL : socktype);
+ }
+ FOREACH_NODETREE_END
+}
+
+/* Try to initialize all typeinfo in a node tree.
+ * NB: In general undefined typeinfo is a perfectly valid case, the type may just be registered
+ * later. In that case the update_typeinfo function will set typeinfo on registration and do
+ * necessary updates.
+ */
+void ntreeSetTypes(const struct bContext *C, bNodeTree *ntree)
+{
+ bNode *node;
+ bNodeSocket *sock;
+
+ ntree->init |= NTREE_TYPE_INIT;
+
+ ntree_set_typeinfo(ntree, ntreeTypeFind(ntree->idname));
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ node_set_typeinfo(C, ntree, node, nodeTypeFind(node->idname));
+
+ for (sock = node->inputs.first; sock; sock = sock->next)
+ node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(sock->idname));
+ for (sock = node->outputs.first; sock; sock = sock->next)
+ node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(sock->idname));
+ }
+
+ for (sock = ntree->inputs.first; sock; sock = sock->next)
+ node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(sock->idname));
+ for (sock = ntree->outputs.first; sock; sock = sock->next)
+ node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(sock->idname));
+}
+
+static GHash *nodetreetypes_hash = NULL;
+static GHash *nodetypes_hash = NULL;
+static GHash *nodesockettypes_hash = NULL;
+static SpinLock spin;
+
+bNodeTreeType *ntreeTypeFind(const char *idname)
+{
+ bNodeTreeType *nt;
+
+ if (idname[0]) {
+ nt = BLI_ghash_lookup(nodetreetypes_hash, idname);
+ if (nt)
+ return nt;
+ }
+
+ return NULL;
+}
+
+void ntreeTypeAdd(bNodeTreeType *nt)
+{
+ BLI_ghash_insert(nodetreetypes_hash, nt->idname, nt);
+ /* XXX pass Main to register function? */
+ /* Probably not. It is pretty much expected we want to update G_MAIN her I think - or we'd want
+ * to update *all* active Mains, which we cannot do anyway currently. */
+ update_typeinfo(G_MAIN, NULL, nt, NULL, NULL, false);
+}
+
+/* callback for hash value free function */
+static void ntree_free_type(void *treetype_v)
+{
+ bNodeTreeType *treetype = treetype_v;
+ /* XXX pass Main to unregister function? */
+ /* Probably not. It is pretty much expected we want to update G_MAIN her I think - or we'd want
+ * to update *all* active Mains, which we cannot do anyway currently. */
+ update_typeinfo(G_MAIN, NULL, treetype, NULL, NULL, true);
+ MEM_freeN(treetype);
+}
+
+void ntreeTypeFreeLink(const bNodeTreeType *nt)
+{
+ BLI_ghash_remove(nodetreetypes_hash, nt->idname, NULL, ntree_free_type);
+}
+
+bool ntreeIsRegistered(bNodeTree *ntree)
+{
+ return (ntree->typeinfo != &NodeTreeTypeUndefined);
+}
+
+GHashIterator *ntreeTypeGetIterator(void)
+{
+ return BLI_ghashIterator_new(nodetreetypes_hash);
+}
+
+bNodeType *nodeTypeFind(const char *idname)
+{
+ bNodeType *nt;
+
+ if (idname[0]) {
+ nt = BLI_ghash_lookup(nodetypes_hash, idname);
+ if (nt)
+ return nt;
+ }
+
+ return NULL;
+}
+
+static void free_dynamic_typeinfo(bNodeType *ntype)
+{
+ if (ntype->type == NODE_DYNAMIC) {
+ if (ntype->inputs) {
+ MEM_freeN(ntype->inputs);
+ }
+ if (ntype->outputs) {
+ MEM_freeN(ntype->outputs);
+ }
+ }
+}
+
+/* callback for hash value free function */
+static void node_free_type(void *nodetype_v)
+{
+ bNodeType *nodetype = nodetype_v;
+ /* XXX pass Main to unregister function? */
+ /* Probably not. It is pretty much expected we want to update G_MAIN her I think - or we'd want
+ * to update *all* active Mains, which we cannot do anyway currently. */
+ update_typeinfo(G_MAIN, NULL, NULL, nodetype, NULL, true);
+
+ /* XXX deprecated */
+ if (nodetype->type == NODE_DYNAMIC)
+ free_dynamic_typeinfo(nodetype);
+
+ if (nodetype->needs_free)
+ MEM_freeN(nodetype);
+}
+
+void nodeRegisterType(bNodeType *nt)
+{
+ /* debug only: basic verification of registered types */
+ BLI_assert(nt->idname[0] != '\0');
+ BLI_assert(nt->poll != NULL);
+
+ BLI_ghash_insert(nodetypes_hash, nt->idname, nt);
+ /* XXX pass Main to register function? */
+ /* Probably not. It is pretty much expected we want to update G_MAIN her I think - or we'd want
+ * to update *all* active Mains, which we cannot do anyway currently. */
+ update_typeinfo(G_MAIN, NULL, NULL, nt, NULL, false);
+}
+
+void nodeUnregisterType(bNodeType *nt)
+{
+ BLI_ghash_remove(nodetypes_hash, nt->idname, NULL, node_free_type);
+}
+
+bool nodeIsRegistered(bNode *node)
+{
+ return (node->typeinfo != &NodeTypeUndefined);
+}
+
+GHashIterator *nodeTypeGetIterator(void)
+{
+ return BLI_ghashIterator_new(nodetypes_hash);
+}
+
+bNodeSocketType *nodeSocketTypeFind(const char *idname)
+{
+ bNodeSocketType *st;
+
+ if (idname[0]) {
+ st = BLI_ghash_lookup(nodesockettypes_hash, idname);
+ if (st)
+ return st;
+ }
+
+ return NULL;
+}
+
+/* callback for hash value free function */
+static void node_free_socket_type(void *socktype_v)
+{
+ bNodeSocketType *socktype = socktype_v;
+ /* XXX pass Main to unregister function? */
+ /* Probably not. It is pretty much expected we want to update G_MAIN her I think - or we'd want
+ * to update *all* active Mains, which we cannot do anyway currently. */
+ update_typeinfo(G_MAIN, NULL, NULL, NULL, socktype, true);
+
+ MEM_freeN(socktype);
+}
+
+void nodeRegisterSocketType(bNodeSocketType *st)
+{
+ BLI_ghash_insert(nodesockettypes_hash, (void *)st->idname, st);
+ /* XXX pass Main to register function? */
+ /* Probably not. It is pretty much expected we want to update G_MAIN her I think - or we'd want
+ * to update *all* active Mains, which we cannot do anyway currently. */
+ update_typeinfo(G_MAIN, NULL, NULL, NULL, st, false);
+}
+
+void nodeUnregisterSocketType(bNodeSocketType *st)
+{
+ BLI_ghash_remove(nodesockettypes_hash, st->idname, NULL, node_free_socket_type);
+}
+
+bool nodeSocketIsRegistered(bNodeSocket *sock)
+{
+ return (sock->typeinfo != &NodeSocketTypeUndefined);
+}
+
+GHashIterator *nodeSocketTypeGetIterator(void)
+{
+ return BLI_ghashIterator_new(nodesockettypes_hash);
+}
+
+struct bNodeSocket *nodeFindSocket(bNode *node, int in_out, const char *identifier)
+{
+ bNodeSocket *sock = (in_out == SOCK_IN ? node->inputs.first : node->outputs.first);
+ for (; sock; sock = sock->next) {
+ if (STREQ(sock->identifier, identifier))
+ return sock;
+ }
+ return NULL;
+}
+
+/* find unique socket identifier */
+static bool unique_identifier_check(void *arg, const char *identifier)
+{
+ struct ListBase *lb = arg;
+ bNodeSocket *sock;
+ for (sock = lb->first; sock; sock = sock->next) {
+ if (STREQ(sock->identifier, identifier))
+ return true;
+ }
+ return false;
+}
+
+static bNodeSocket *make_socket(bNodeTree *ntree,
+ bNode *UNUSED(node),
+ int in_out,
+ ListBase *lb,
+ const char *idname,
+ const char *identifier,
+ const char *name)
+{
+ bNodeSocket *sock;
+ char auto_identifier[MAX_NAME];
+
+ if (identifier && identifier[0] != '\0') {
+ /* use explicit identifier */
+ BLI_strncpy(auto_identifier, identifier, sizeof(auto_identifier));
+ }
+ else {
+ /* if no explicit identifier is given, assign a unique identifier based on the name */
+ BLI_strncpy(auto_identifier, name, sizeof(auto_identifier));
+ }
+ /* make the identifier unique */
+ BLI_uniquename_cb(
+ unique_identifier_check, lb, "socket", '.', auto_identifier, sizeof(auto_identifier));
+
+ sock = MEM_callocN(sizeof(bNodeSocket), "sock");
+ sock->in_out = in_out;
+
+ BLI_strncpy(sock->identifier, auto_identifier, NODE_MAXSTR);
+ sock->limit = (in_out == SOCK_IN ? 1 : 0xFFF);
+
+ BLI_strncpy(sock->name, name, NODE_MAXSTR);
+ sock->storage = NULL;
+ sock->flag |= SOCK_COLLAPSED;
+ sock->type = SOCK_CUSTOM; /* int type undefined by default */
+
+ BLI_strncpy(sock->idname, idname, sizeof(sock->idname));
+ node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(idname));
+
+ return sock;
+}
+
+void nodeModifySocketType(
+ bNodeTree *ntree, bNode *UNUSED(node), bNodeSocket *sock, int type, int subtype)
+{
+ const char *idname = nodeStaticSocketType(type, subtype);
+
+ if (!idname) {
+ printf("Error: static node socket type %d undefined\n", type);
+ return;
+ }
+
+ if (sock->default_value) {
+ MEM_freeN(sock->default_value);
+ sock->default_value = NULL;
+ }
+
+ sock->type = type;
+ BLI_strncpy(sock->idname, idname, sizeof(sock->idname));
+ node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(idname));
+}
+
+bNodeSocket *nodeAddSocket(bNodeTree *ntree,
+ bNode *node,
+ int in_out,
+ const char *idname,
+ const char *identifier,
+ const char *name)
+{
+ ListBase *lb = (in_out == SOCK_IN ? &node->inputs : &node->outputs);
+ bNodeSocket *sock = make_socket(ntree, node, in_out, lb, idname, identifier, name);
+
+ BLI_remlink(lb, sock); /* does nothing for new socket */
+ BLI_addtail(lb, sock);
+
+ node->update |= NODE_UPDATE;
+
+ return sock;
+}
+
+bNodeSocket *nodeInsertSocket(bNodeTree *ntree,
+ bNode *node,
+ int in_out,
+ const char *idname,
+ bNodeSocket *next_sock,
+ const char *identifier,
+ const char *name)
+{
+ ListBase *lb = (in_out == SOCK_IN ? &node->inputs : &node->outputs);
+ bNodeSocket *sock = make_socket(ntree, node, in_out, lb, idname, identifier, name);
+
+ BLI_remlink(lb, sock); /* does nothing for new socket */
+ BLI_insertlinkbefore(lb, next_sock, sock);
+
+ node->update |= NODE_UPDATE;
+
+ return sock;
+}
+
+const char *nodeStaticSocketType(int type, int subtype)
+{
+ switch (type) {
+ case SOCK_FLOAT:
+ switch (subtype) {
+ case PROP_UNSIGNED:
+ return "NodeSocketFloatUnsigned";
+ case PROP_PERCENTAGE:
+ return "NodeSocketFloatPercentage";
+ case PROP_FACTOR:
+ return "NodeSocketFloatFactor";
+ case PROP_ANGLE:
+ return "NodeSocketFloatAngle";
+ case PROP_TIME:
+ return "NodeSocketFloatTime";
+ case PROP_NONE:
+ default:
+ return "NodeSocketFloat";
+ }
+ case SOCK_INT:
+ switch (subtype) {
+ case PROP_UNSIGNED:
+ return "NodeSocketIntUnsigned";
+ case PROP_PERCENTAGE:
+ return "NodeSocketIntPercentage";
+ case PROP_FACTOR:
+ return "NodeSocketIntFactor";
+ case PROP_NONE:
+ default:
+ return "NodeSocketInt";
+ }
+ case SOCK_BOOLEAN:
+ return "NodeSocketBool";
+ case SOCK_VECTOR:
+ switch (subtype) {
+ case PROP_TRANSLATION:
+ return "NodeSocketVectorTranslation";
+ case PROP_DIRECTION:
+ return "NodeSocketVectorDirection";
+ case PROP_VELOCITY:
+ return "NodeSocketVectorVelocity";
+ case PROP_ACCELERATION:
+ return "NodeSocketVectorAcceleration";
+ case PROP_EULER:
+ return "NodeSocketVectorEuler";
+ case PROP_XYZ:
+ return "NodeSocketVectorXYZ";
+ case PROP_NONE:
+ default:
+ return "NodeSocketVector";
+ }
+ case SOCK_RGBA:
+ return "NodeSocketColor";
+ case SOCK_STRING:
+ return "NodeSocketString";
+ case SOCK_SHADER:
+ return "NodeSocketShader";
+ }
+ return NULL;
+}
+
+const char *nodeStaticSocketInterfaceType(int type, int subtype)
+{
+ switch (type) {
+ case SOCK_FLOAT:
+ switch (subtype) {
+ case PROP_UNSIGNED:
+ return "NodeSocketInterfaceFloatUnsigned";
+ case PROP_PERCENTAGE:
+ return "NodeSocketInterfaceFloatPercentage";
+ case PROP_FACTOR:
+ return "NodeSocketInterfaceFloatFactor";
+ case PROP_ANGLE:
+ return "NodeSocketInterfaceFloatAngle";
+ case PROP_TIME:
+ return "NodeSocketInterfaceFloatTime";
+ case PROP_NONE:
+ default:
+ return "NodeSocketInterfaceFloat";
+ }
+ case SOCK_INT:
+ switch (subtype) {
+ case PROP_UNSIGNED:
+ return "NodeSocketInterfaceIntUnsigned";
+ case PROP_PERCENTAGE:
+ return "NodeSocketInterfaceIntPercentage";
+ case PROP_FACTOR:
+ return "NodeSocketInterfaceIntFactor";
+ case PROP_NONE:
+ default:
+ return "NodeSocketInterfaceInt";
+ }
+ case SOCK_BOOLEAN:
+ return "NodeSocketInterfaceBool";
+ case SOCK_VECTOR:
+ switch (subtype) {
+ case PROP_TRANSLATION:
+ return "NodeSocketInterfaceVectorTranslation";
+ case PROP_DIRECTION:
+ return "NodeSocketInterfaceVectorDirection";
+ case PROP_VELOCITY:
+ return "NodeSocketInterfaceVectorVelocity";
+ case PROP_ACCELERATION:
+ return "NodeSocketInterfaceVectorAcceleration";
+ case PROP_EULER:
+ return "NodeSocketInterfaceVectorEuler";
+ case PROP_XYZ:
+ return "NodeSocketInterfaceVectorXYZ";
+ case PROP_NONE:
+ default:
+ return "NodeSocketInterfaceVector";
+ }
+ case SOCK_RGBA:
+ return "NodeSocketInterfaceColor";
+ case SOCK_STRING:
+ return "NodeSocketInterfaceString";
+ case SOCK_SHADER:
+ return "NodeSocketInterfaceShader";
+ }
+ return NULL;
+}
+
+bNodeSocket *nodeAddStaticSocket(bNodeTree *ntree,
+ bNode *node,
+ int in_out,
+ int type,
+ int subtype,
+ const char *identifier,
+ const char *name)
+{
+ const char *idname = nodeStaticSocketType(type, subtype);
+ bNodeSocket *sock;
+
+ if (!idname) {
+ printf("Error: static node socket type %d undefined\n", type);
+ return NULL;
+ }
+
+ sock = nodeAddSocket(ntree, node, in_out, idname, identifier, name);
+ sock->type = type;
+ return sock;
+}
+
+bNodeSocket *nodeInsertStaticSocket(bNodeTree *ntree,
+ bNode *node,
+ int in_out,
+ int type,
+ int subtype,
+ bNodeSocket *next_sock,
+ const char *identifier,
+ const char *name)
+{
+ const char *idname = nodeStaticSocketType(type, subtype);
+ bNodeSocket *sock;
+
+ if (!idname) {
+ printf("Error: static node socket type %d undefined\n", type);
+ return NULL;
+ }
+
+ sock = nodeInsertSocket(ntree, node, in_out, idname, next_sock, identifier, name);
+ sock->type = type;
+ return sock;
+}
+
+static void node_socket_free(bNodeTree *UNUSED(ntree), bNodeSocket *sock, bNode *UNUSED(node))
+{
+ if (sock->prop) {
+ IDP_FreeProperty(sock->prop);
+ MEM_freeN(sock->prop);
+ }
+
+ if (sock->default_value)
+ MEM_freeN(sock->default_value);
+}
+
+void nodeRemoveSocket(bNodeTree *ntree, bNode *node, bNodeSocket *sock)
+{
+ bNodeLink *link, *next;
+
+ for (link = ntree->links.first; link; link = next) {
+ next = link->next;
+ if (link->fromsock == sock || link->tosock == sock) {
+ nodeRemLink(ntree, link);
+ }
+ }
+
+ /* this is fast, this way we don't need an in_out argument */
+ BLI_remlink(&node->inputs, sock);
+ BLI_remlink(&node->outputs, sock);
+
+ node_socket_free(ntree, sock, node);
+ MEM_freeN(sock);
+
+ node->update |= NODE_UPDATE;
+}
+
+void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node)
+{
+ bNodeSocket *sock, *sock_next;
+ bNodeLink *link, *next;
+
+ for (link = ntree->links.first; link; link = next) {
+ next = link->next;
+ if (link->fromnode == node || link->tonode == node) {
+ nodeRemLink(ntree, link);
+ }
+ }
+
+ for (sock = node->inputs.first; sock; sock = sock_next) {
+ sock_next = sock->next;
+ node_socket_free(ntree, sock, node);
+ MEM_freeN(sock);
+ }
+ BLI_listbase_clear(&node->inputs);
+
+ for (sock = node->outputs.first; sock; sock = sock_next) {
+ sock_next = sock->next;
+ node_socket_free(ntree, sock, node);
+ MEM_freeN(sock);
+ }
+ BLI_listbase_clear(&node->outputs);
+
+ node->update |= NODE_UPDATE;
+}
+
+/* finds a node based on its name */
+bNode *nodeFindNodebyName(bNodeTree *ntree, const char *name)
+{
+ return BLI_findstring(&ntree->nodes, name, offsetof(bNode, name));
+}
+
+/* finds a node based on given socket */
+int nodeFindNode(bNodeTree *ntree, bNodeSocket *sock, bNode **nodep, int *sockindex)
+{
+ int in_out = sock->in_out;
+ bNode *node;
+ bNodeSocket *tsock;
+ int index = 0;
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ tsock = (in_out == SOCK_IN ? node->inputs.first : node->outputs.first);
+ for (index = 0; tsock; tsock = tsock->next, index++) {
+ if (tsock == sock)
+ break;
+ }
+ if (tsock)
+ break;
+ }
+
+ if (node) {
+ *nodep = node;
+ if (sockindex)
+ *sockindex = index;
+ return 1;
+ }
+
+ *nodep = NULL;
+ return 0;
+}
+
+/**
+ * \note Recursive
+ */
+bNode *nodeFindRootParent(bNode *node)
+{
+ if (node->parent) {
+ return nodeFindRootParent(node->parent);
+ }
+ else {
+ return node->type == NODE_FRAME ? node : NULL;
+ }
+}
+
+/**
+ * \returns true if \a child has \a parent as a parent/grandparent/...
+ * \note Recursive
+ */
+bool nodeIsChildOf(const bNode *parent, const bNode *child)
+{
+ if (parent == child) {
+ return true;
+ }
+ else if (child->parent) {
+ return nodeIsChildOf(parent, child->parent);
+ }
+ return false;
+}
+
+/**
+ * Iterate over a chain of nodes, starting with \a node_start, executing
+ * \a callback for each node (which can return false to end iterator).
+ *
+ * \param reversed for backwards iteration
+ * \note Recursive
+ */
+void nodeChainIter(const bNodeTree *ntree,
+ const bNode *node_start,
+ bool (*callback)(bNode *, bNode *, void *, const bool),
+ void *userdata,
+ const bool reversed)
+{
+ bNodeLink *link;
+
+ for (link = ntree->links.first; link; link = link->next) {
+ if ((link->flag & NODE_LINK_VALID) == 0) {
+ /* Skip links marked as cyclic. */
+ continue;
+ }
+ if (link->tonode && link->fromnode) {
+ /* is the link part of the chain meaning node_start == fromnode (or tonode for reversed
+ * case)? */
+ if ((reversed && (link->tonode == node_start)) ||
+ (!reversed && link->fromnode == node_start)) {
+ if (!callback(link->fromnode, link->tonode, userdata, reversed)) {
+ return;
+ }
+ nodeChainIter(
+ ntree, reversed ? link->fromnode : link->tonode, callback, userdata, reversed);
+ }
+ }
+ }
+}
+
+/**
+ * Iterate over all parents of \a node, executing \a callback for each parent (which can return
+ * false to end iterator)
+ *
+ * \note Recursive
+ */
+void nodeParentsIter(bNode *node, bool (*callback)(bNode *, void *), void *userdata)
+{
+ if (node->parent) {
+ if (!callback(node->parent, userdata)) {
+ return;
+ }
+ nodeParentsIter(node->parent, callback, userdata);
+ }
+}
+
+/* ************** Add stuff ********** */
+
+/* Find the first available, non-duplicate name for a given node */
+void nodeUniqueName(bNodeTree *ntree, bNode *node)
+{
+ BLI_uniquename(
+ &ntree->nodes, node, DATA_("Node"), '.', offsetof(bNode, name), sizeof(node->name));
+}
+
+bNode *nodeAddNode(const struct bContext *C, bNodeTree *ntree, const char *idname)
+{
+ bNode *node;
+
+ node = MEM_callocN(sizeof(bNode), "new node");
+ BLI_addtail(&ntree->nodes, node);
+
+ BLI_strncpy(node->idname, idname, sizeof(node->idname));
+ node_set_typeinfo(C, ntree, node, nodeTypeFind(idname));
+
+ ntree->update |= NTREE_UPDATE_NODES;
+
+ return node;
+}
+
+bNode *nodeAddStaticNode(const struct bContext *C, bNodeTree *ntree, int type)
+{
+ const char *idname = NULL;
+
+ NODE_TYPES_BEGIN (ntype)
+ /* do an extra poll here, because some int types are used
+ * for multiple node types, this helps find the desired type
+ */
+ if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree))) {
+ idname = ntype->idname;
+ break;
+ }
+ NODE_TYPES_END
+ if (!idname) {
+ printf("Error: static node type %d undefined\n", type);
+ return NULL;
+ }
+ return nodeAddNode(C, ntree, idname);
+}
+
+static void node_socket_copy(bNodeSocket *sock_dst, bNodeSocket *sock_src, const int flag)
+{
+ sock_src->new_sock = sock_dst;
+
+ if (sock_src->prop) {
+ sock_dst->prop = IDP_CopyProperty_ex(sock_src->prop, flag);
+ }
+
+ if (sock_src->default_value) {
+ sock_dst->default_value = MEM_dupallocN(sock_src->default_value);
+ }
+
+ sock_dst->stack_index = 0;
+ /* XXX some compositor node (e.g. image, render layers) still store
+ * some persistent buffer data here, need to clear this to avoid dangling pointers.
+ */
+ sock_dst->cache = NULL;
+}
+
+/* keep socket listorder identical, for copying links */
+/* ntree is the target tree */
+bNode *BKE_node_copy_ex(bNodeTree *ntree, bNode *node_src, const int flag)
+{
+ bNode *node_dst = MEM_callocN(sizeof(bNode), "dupli node");
+ bNodeSocket *sock_dst, *sock_src;
+ bNodeLink *link_dst, *link_src;
+
+ *node_dst = *node_src;
+ /* can be called for nodes outside a node tree (e.g. clipboard) */
+ if (ntree) {
+ nodeUniqueName(ntree, node_dst);
+
+ BLI_addtail(&ntree->nodes, node_dst);
+ }
+
+ BLI_duplicatelist(&node_dst->inputs, &node_src->inputs);
+ for (sock_dst = node_dst->inputs.first, sock_src = node_src->inputs.first; sock_dst != NULL;
+ sock_dst = sock_dst->next, sock_src = sock_src->next) {
+ node_socket_copy(sock_dst, sock_src, flag);
+ }
+
+ BLI_duplicatelist(&node_dst->outputs, &node_src->outputs);
+ for (sock_dst = node_dst->outputs.first, sock_src = node_src->outputs.first; sock_dst != NULL;
+ sock_dst = sock_dst->next, sock_src = sock_src->next) {
+ node_socket_copy(sock_dst, sock_src, flag);
+ }
+
+ if (node_src->prop) {
+ node_dst->prop = IDP_CopyProperty_ex(node_src->prop, flag);
+ }
+
+ BLI_duplicatelist(&node_dst->internal_links, &node_src->internal_links);
+ for (link_dst = node_dst->internal_links.first, link_src = node_src->internal_links.first;
+ link_dst != NULL;
+ link_dst = link_dst->next, link_src = link_src->next) {
+ link_dst->fromnode = node_dst;
+ link_dst->tonode = node_dst;
+ link_dst->fromsock = link_dst->fromsock->new_sock;
+ link_dst->tosock = link_dst->tosock->new_sock;
+ }
+
+ if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
+ id_us_plus(node_dst->id);
+ }
+
+ if (node_src->typeinfo->copyfunc) {
+ node_src->typeinfo->copyfunc(ntree, node_dst, node_src);
+ }
+
+ node_src->new_node = node_dst;
+ node_dst->new_node = NULL;
+
+ if (node_dst->typeinfo->copyfunc_api) {
+ PointerRNA ptr;
+ RNA_pointer_create((ID *)ntree, &RNA_Node, node_dst, &ptr);
+
+ node_dst->typeinfo->copyfunc_api(&ptr, node_src);
+ }
+
+ if (ntree) {
+ ntree->update |= NTREE_UPDATE_NODES;
+ }
+
+ return node_dst;
+}
+
+bNode *nodeCopyNode(bNodeTree *ntree, bNode *node)
+{
+ return BKE_node_copy_ex(ntree, node, LIB_ID_CREATE_NO_USER_REFCOUNT);
+}
+
+/* also used via rna api, so we check for proper input output direction */
+bNodeLink *nodeAddLink(
+ bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock)
+{
+ bNodeLink *link = NULL;
+
+ /* test valid input */
+ BLI_assert(fromnode);
+ BLI_assert(tonode);
+
+ if (fromsock->in_out == SOCK_OUT && tosock->in_out == SOCK_IN) {
+ link = MEM_callocN(sizeof(bNodeLink), "link");
+ if (ntree)
+ BLI_addtail(&ntree->links, link);
+ link->fromnode = fromnode;
+ link->fromsock = fromsock;
+ link->tonode = tonode;
+ link->tosock = tosock;
+ }
+ else if (fromsock->in_out == SOCK_IN && tosock->in_out == SOCK_OUT) {
+ /* OK but flip */
+ link = MEM_callocN(sizeof(bNodeLink), "link");
+ if (ntree)
+ BLI_addtail(&ntree->links, link);
+ link->fromnode = tonode;
+ link->fromsock = tosock;
+ link->tonode = fromnode;
+ link->tosock = fromsock;
+ }
+
+ if (ntree)
+ ntree->update |= NTREE_UPDATE_LINKS;
+
+ return link;
+}
+
+void nodeRemLink(bNodeTree *ntree, bNodeLink *link)
+{
+ /* can be called for links outside a node tree (e.g. clipboard) */
+ if (ntree)
+ BLI_remlink(&ntree->links, link);
+
+ if (link->tosock)
+ link->tosock->link = NULL;
+ MEM_freeN(link);
+
+ if (ntree)
+ ntree->update |= NTREE_UPDATE_LINKS;
+}
+
+void nodeRemSocketLinks(bNodeTree *ntree, bNodeSocket *sock)
+{
+ bNodeLink *link, *next;
+
+ for (link = ntree->links.first; link; link = next) {
+ next = link->next;
+ if (link->fromsock == sock || link->tosock == sock) {
+ nodeRemLink(ntree, link);
+ }
+ }
+
+ ntree->update |= NTREE_UPDATE_LINKS;
+}
+
+bool nodeLinkIsHidden(bNodeLink *link)
+{
+ return nodeSocketIsHidden(link->fromsock) || nodeSocketIsHidden(link->tosock);
+}
+
+void nodeInternalRelink(bNodeTree *ntree, bNode *node)
+{
+ bNodeLink *link, *link_next;
+
+ /* store link pointers in output sockets, for efficient lookup */
+ for (link = node->internal_links.first; link; link = link->next)
+ link->tosock->link = link;
+
+ /* redirect downstream links */
+ for (link = ntree->links.first; link; link = link_next) {
+ link_next = link->next;
+
+ /* do we have internal link? */
+ if (link->fromnode == node) {
+ if (link->fromsock->link) {
+ /* get the upstream input link */
+ bNodeLink *fromlink = link->fromsock->link->fromsock->link;
+ /* skip the node */
+ if (fromlink) {
+ link->fromnode = fromlink->fromnode;
+ link->fromsock = fromlink->fromsock;
+
+ /* if the up- or downstream link is invalid,
+ * the replacement link will be invalid too.
+ */
+ if (!(fromlink->flag & NODE_LINK_VALID))
+ link->flag &= ~NODE_LINK_VALID;
+
+ ntree->update |= NTREE_UPDATE_LINKS;
+ }
+ else
+ nodeRemLink(ntree, link);
+ }
+ else
+ nodeRemLink(ntree, link);
+ }
+ }
+
+ /* remove remaining upstream links */
+ for (link = ntree->links.first; link; link = link_next) {
+ link_next = link->next;
+
+ if (link->tonode == node)
+ nodeRemLink(ntree, link);
+ }
+}
+
+void nodeToView(bNode *node, float x, float y, float *rx, float *ry)
+{
+ if (node->parent) {
+ nodeToView(node->parent, x + node->locx, y + node->locy, rx, ry);
+ }
+ else {
+ *rx = x + node->locx;
+ *ry = y + node->locy;
+ }
+}
+
+void nodeFromView(bNode *node, float x, float y, float *rx, float *ry)
+{
+ if (node->parent) {
+ nodeFromView(node->parent, x, y, rx, ry);
+ *rx -= node->locx;
+ *ry -= node->locy;
+ }
+ else {
+ *rx = x - node->locx;
+ *ry = y - node->locy;
+ }
+}
+
+bool nodeAttachNodeCheck(bNode *node, bNode *parent)
+{
+ bNode *parent_recurse;
+ for (parent_recurse = node; parent_recurse; parent_recurse = parent_recurse->parent) {
+ if (parent_recurse == parent) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nodeAttachNode(bNode *node, bNode *parent)
+{
+ float locx, locy;
+
+ BLI_assert(parent->type == NODE_FRAME);
+ BLI_assert(nodeAttachNodeCheck(parent, node) == false);
+
+ nodeToView(node, 0.0f, 0.0f, &locx, &locy);
+
+ node->parent = parent;
+ /* transform to parent space */
+ nodeFromView(parent, locx, locy, &node->locx, &node->locy);
+}
+
+void nodeDetachNode(struct bNode *node)
+{
+ float locx, locy;
+
+ if (node->parent) {
+
+ BLI_assert(node->parent->type == NODE_FRAME);
+
+ /* transform to view space */
+ nodeToView(node, 0.0f, 0.0f, &locx, &locy);
+ node->locx = locx;
+ node->locy = locy;
+ node->parent = NULL;
+ }
+}
+
+void ntreeInitDefault(bNodeTree *ntree)
+{
+ ntree_set_typeinfo(ntree, NULL);
+}
+
+bNodeTree *ntreeAddTree(Main *bmain, const char *name, const char *idname)
+{
+ bNodeTree *ntree;
+
+ /* trees are created as local trees for compositor, material or texture nodes,
+ * node groups and other tree types are created as library data.
+ */
+ if (bmain) {
+ ntree = BKE_libblock_alloc(bmain, ID_NT, name, 0);
+ }
+ else {
+ ntree = MEM_callocN(sizeof(bNodeTree), "new node tree");
+ *((short *)ntree->id.name) = ID_NT;
+ BLI_strncpy(ntree->id.name + 2, name, sizeof(ntree->id.name));
+ }
+
+ /* Types are fully initialized at this point,
+ * if an undefined node is added later this will be reset.
+ */
+ ntree->init |= NTREE_TYPE_INIT;
+
+ BLI_strncpy(ntree->idname, idname, sizeof(ntree->idname));
+ ntree_set_typeinfo(ntree, ntreeTypeFind(idname));
+
+ return ntree;
+}
+
+/**
+ * Only copy internal data of NodeTree ID from source to already allocated/initialized destination.
+ * You probably nerver want to use that directly, use id_copy or BKE_id_copy_ex for typical needs.
+ *
+ * WARNING! This function will not handle ID user count!
+ *
+ * \param flag Copying options (see BKE_library.h's LIB_ID_COPY_... flags for more).
+ */
+void BKE_node_tree_copy_data(Main *UNUSED(bmain),
+ bNodeTree *ntree_dst,
+ const bNodeTree *ntree_src,
+ const int flag)
+{
+ bNodeSocket *sock_dst, *sock_src;
+ bNodeLink *link_dst;
+
+ /* We never handle usercount here for own data. */
+ const int flag_subdata = flag | LIB_ID_CREATE_NO_USER_REFCOUNT;
+
+ /* in case a running nodetree is copied */
+ ntree_dst->execdata = NULL;
+
+ ntree_dst->duplilock = NULL;
+
+ BLI_listbase_clear(&ntree_dst->nodes);
+ BLI_listbase_clear(&ntree_dst->links);
+
+ for (bNode *node_src = ntree_src->nodes.first; node_src; node_src = node_src->next) {
+ BKE_node_copy_ex(ntree_dst, node_src, flag_subdata);
+ }
+
+ /* copy links */
+ BLI_duplicatelist(&ntree_dst->links, &ntree_src->links);
+ for (link_dst = ntree_dst->links.first; link_dst; link_dst = link_dst->next) {
+ link_dst->fromnode = (link_dst->fromnode ? link_dst->fromnode->new_node : NULL);
+ link_dst->fromsock = (link_dst->fromsock ? link_dst->fromsock->new_sock : NULL);
+ link_dst->tonode = (link_dst->tonode ? link_dst->tonode->new_node : NULL);
+ link_dst->tosock = (link_dst->tosock ? link_dst->tosock->new_sock : NULL);
+ /* update the link socket's pointer */
+ if (link_dst->tosock) {
+ link_dst->tosock->link = link_dst;
+ }
+ }
+
+ /* copy interface sockets */
+ BLI_duplicatelist(&ntree_dst->inputs, &ntree_src->inputs);
+ for (sock_dst = ntree_dst->inputs.first, sock_src = ntree_src->inputs.first; sock_dst != NULL;
+ sock_dst = sock_dst->next, sock_src = sock_src->next) {
+ node_socket_copy(sock_dst, sock_src, flag_subdata);
+ }
+
+ BLI_duplicatelist(&ntree_dst->outputs, &ntree_src->outputs);
+ for (sock_dst = ntree_dst->outputs.first, sock_src = ntree_src->outputs.first; sock_dst != NULL;
+ sock_dst = sock_dst->next, sock_src = sock_src->next) {
+ node_socket_copy(sock_dst, sock_src, flag_subdata);
+ }
+
+ /* copy preview hash */
+ if (ntree_src->previews && (flag & LIB_ID_COPY_NO_PREVIEW) == 0) {
+ bNodeInstanceHashIterator iter;
+
+ ntree_dst->previews = BKE_node_instance_hash_new("node previews");
+
+ NODE_INSTANCE_HASH_ITER (iter, ntree_src->previews) {
+ bNodeInstanceKey key = BKE_node_instance_hash_iterator_get_key(&iter);
+ bNodePreview *preview = BKE_node_instance_hash_iterator_get_value(&iter);
+ BKE_node_instance_hash_insert(ntree_dst->previews, key, BKE_node_preview_copy(preview));
+ }
+ }
+ else {
+ ntree_dst->previews = NULL;
+ }
+
+ /* update node->parent pointers */
+ for (bNode *node_dst = ntree_dst->nodes.first, *node_src = ntree_src->nodes.first; node_dst;
+ node_dst = node_dst->next, node_src = node_src->next) {
+ if (node_dst->parent) {
+ node_dst->parent = node_dst->parent->new_node;
+ }
+ }
+
+ /* node tree will generate its own interface type */
+ ntree_dst->interface_type = NULL;
+}
+
+bNodeTree *ntreeCopyTree_ex(const bNodeTree *ntree, Main *bmain, const bool do_id_user)
+{
+ bNodeTree *ntree_copy;
+ BKE_id_copy_ex(bmain,
+ (ID *)ntree,
+ (ID **)&ntree_copy,
+ do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT,
+ false);
+ return ntree_copy;
+}
+bNodeTree *ntreeCopyTree(Main *bmain, const bNodeTree *ntree)
+{
+ return ntreeCopyTree_ex(ntree, bmain, true);
+}
+
+void ntreeUserIncrefID(bNodeTree *ntree)
+{
+ bNode *node;
+ for (node = ntree->nodes.first; node; node = node->next) {
+ id_us_plus(node->id);
+ }
+}
+void ntreeUserDecrefID(bNodeTree *ntree)
+{
+ bNode *node;
+ for (node = ntree->nodes.first; node; node = node->next) {
+ id_us_min(node->id);
+ }
+}
+
+/* *************** Node Preview *********** */
+
+/* XXX this should be removed eventually ...
+ * Currently BKE functions are modeled closely on previous code,
+ * using BKE_node_preview_init_tree to set up previews for a whole node tree in advance.
+ * This should be left more to the individual node tree implementations.
+ */
+int BKE_node_preview_used(bNode *node)
+{
+ /* XXX check for closed nodes? */
+ return (node->typeinfo->flag & NODE_PREVIEW) != 0;
+}
+
+bNodePreview *BKE_node_preview_verify(
+ bNodeInstanceHash *previews, bNodeInstanceKey key, int xsize, int ysize, bool create)
+{
+ bNodePreview *preview;
+
+ preview = BKE_node_instance_hash_lookup(previews, key);
+ if (!preview) {
+ if (create) {
+ preview = MEM_callocN(sizeof(bNodePreview), "node preview");
+ BKE_node_instance_hash_insert(previews, key, preview);
+ }
+ else
+ return NULL;
+ }
+
+ /* node previews can get added with variable size this way */
+ if (xsize == 0 || ysize == 0)
+ return preview;
+
+ /* sanity checks & initialize */
+ if (preview->rect) {
+ if (preview->xsize != xsize || preview->ysize != ysize) {
+ MEM_freeN(preview->rect);
+ preview->rect = NULL;
+ }
+ }
+
+ if (preview->rect == NULL) {
+ preview->rect = MEM_callocN(4 * xsize + xsize * ysize * sizeof(char) * 4, "node preview rect");
+ preview->xsize = xsize;
+ preview->ysize = ysize;
+ }
+ /* no clear, makes nicer previews */
+
+ return preview;
+}
+
+bNodePreview *BKE_node_preview_copy(bNodePreview *preview)
+{
+ bNodePreview *new_preview = MEM_dupallocN(preview);
+ if (preview->rect)
+ new_preview->rect = MEM_dupallocN(preview->rect);
+ return new_preview;
+}
+
+void BKE_node_preview_free(bNodePreview *preview)
+{
+ if (preview->rect)
+ MEM_freeN(preview->rect);
+ MEM_freeN(preview);
+}
+
+static void node_preview_init_tree_recursive(bNodeInstanceHash *previews,
+ bNodeTree *ntree,
+ bNodeInstanceKey parent_key,
+ int xsize,
+ int ysize,
+ int create)
+{
+ bNode *node;
+ for (node = ntree->nodes.first; node; node = node->next) {
+ bNodeInstanceKey key = BKE_node_instance_key(parent_key, ntree, node);
+
+ if (BKE_node_preview_used(node)) {
+ node->preview_xsize = xsize;
+ node->preview_ysize = ysize;
+
+ BKE_node_preview_verify(previews, key, xsize, ysize, create);
+ }
+
+ if (node->type == NODE_GROUP && node->id)
+ node_preview_init_tree_recursive(previews, (bNodeTree *)node->id, key, xsize, ysize, create);
+ }
+}
+
+void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize, int create_previews)
+{
+ if (!ntree)
+ return;
+
+ if (!ntree->previews)
+ ntree->previews = BKE_node_instance_hash_new("node previews");
+
+ node_preview_init_tree_recursive(
+ ntree->previews, ntree, NODE_INSTANCE_KEY_BASE, xsize, ysize, create_previews);
+}
+
+static void node_preview_tag_used_recursive(bNodeInstanceHash *previews,
+ bNodeTree *ntree,
+ bNodeInstanceKey parent_key)
+{
+ bNode *node;
+ for (node = ntree->nodes.first; node; node = node->next) {
+ bNodeInstanceKey key = BKE_node_instance_key(parent_key, ntree, node);
+
+ if (BKE_node_preview_used(node))
+ BKE_node_instance_hash_tag_key(previews, key);
+
+ if (node->type == NODE_GROUP && node->id)
+ node_preview_tag_used_recursive(previews, (bNodeTree *)node->id, key);
+ }
+}
+
+void BKE_node_preview_remove_unused(bNodeTree *ntree)
+{
+ if (!ntree || !ntree->previews)
+ return;
+
+ /* use the instance hash functions for tagging and removing unused previews */
+ BKE_node_instance_hash_clear_tags(ntree->previews);
+ node_preview_tag_used_recursive(ntree->previews, ntree, NODE_INSTANCE_KEY_BASE);
+
+ BKE_node_instance_hash_remove_untagged(ntree->previews,
+ (bNodeInstanceValueFP)BKE_node_preview_free);
+}
+
+void BKE_node_preview_free_tree(bNodeTree *ntree)
+{
+ if (!ntree)
+ return;
+
+ if (ntree->previews) {
+ BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free);
+ ntree->previews = NULL;
+ }
+}
+
+void BKE_node_preview_clear(bNodePreview *preview)
+{
+ if (preview && preview->rect)
+ memset(preview->rect, 0, MEM_allocN_len(preview->rect));
+}
+
+void BKE_node_preview_clear_tree(bNodeTree *ntree)
+{
+ bNodeInstanceHashIterator iter;
+
+ if (!ntree || !ntree->previews)
+ return;
+
+ NODE_INSTANCE_HASH_ITER (iter, ntree->previews) {
+ bNodePreview *preview = BKE_node_instance_hash_iterator_get_value(&iter);
+ BKE_node_preview_clear(preview);
+ }
+}
+
+static void node_preview_sync(bNodePreview *to, bNodePreview *from)
+{
+ /* sizes should have been initialized by BKE_node_preview_init_tree */
+ BLI_assert(to->xsize == from->xsize && to->ysize == from->ysize);
+
+ /* copy over contents of previews */
+ if (to->rect && from->rect) {
+ int xsize = to->xsize;
+ int ysize = to->ysize;
+ memcpy(to->rect, from->rect, xsize * ysize * sizeof(char) * 4);
+ }
+}
+
+void BKE_node_preview_sync_tree(bNodeTree *to_ntree, bNodeTree *from_ntree)
+{
+ bNodeInstanceHash *from_previews = from_ntree->previews;
+ bNodeInstanceHash *to_previews = to_ntree->previews;
+ bNodeInstanceHashIterator iter;
+
+ if (!from_previews || !to_previews)
+ return;
+
+ NODE_INSTANCE_HASH_ITER (iter, from_previews) {
+ bNodeInstanceKey key = BKE_node_instance_hash_iterator_get_key(&iter);
+ bNodePreview *from = BKE_node_instance_hash_iterator_get_value(&iter);
+ bNodePreview *to = BKE_node_instance_hash_lookup(to_previews, key);
+
+ if (from && to)
+ node_preview_sync(to, from);
+ }
+}
+
+void BKE_node_preview_merge_tree(bNodeTree *to_ntree, bNodeTree *from_ntree, bool remove_old)
+{
+ if (remove_old || !to_ntree->previews) {
+ /* free old previews */
+ if (to_ntree->previews)
+ BKE_node_instance_hash_free(to_ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free);
+
+ /* transfer previews */
+ to_ntree->previews = from_ntree->previews;
+ from_ntree->previews = NULL;
+
+ /* clean up, in case any to_ntree nodes have been removed */
+ BKE_node_preview_remove_unused(to_ntree);
+ }
+ else {
+ bNodeInstanceHashIterator iter;
+
+ if (from_ntree->previews) {
+ NODE_INSTANCE_HASH_ITER (iter, from_ntree->previews) {
+ bNodeInstanceKey key = BKE_node_instance_hash_iterator_get_key(&iter);
+ bNodePreview *preview = BKE_node_instance_hash_iterator_get_value(&iter);
+
+ /* replace existing previews */
+ BKE_node_instance_hash_remove(
+ to_ntree->previews, key, (bNodeInstanceValueFP)BKE_node_preview_free);
+ BKE_node_instance_hash_insert(to_ntree->previews, key, preview);
+ }
+
+ /* Note: NULL free function here, because pointers have already been moved over to
+ * to_ntree->previews! */
+ BKE_node_instance_hash_free(from_ntree->previews, NULL);
+ from_ntree->previews = NULL;
+ }
+ }
+}
+
+/* hack warning! this function is only used for shader previews, and
+ * since it gets called multiple times per pixel for Ztransp we only
+ * add the color once. Preview gets cleared before it starts render though */
+void BKE_node_preview_set_pixel(
+ bNodePreview *preview, const float col[4], int x, int y, bool do_manage)
+{
+ if (preview) {
+ if (x >= 0 && y >= 0) {
+ if (x < preview->xsize && y < preview->ysize) {
+ unsigned char *tar = preview->rect + 4 * ((preview->xsize * y) + x);
+
+ if (do_manage) {
+ linearrgb_to_srgb_uchar4(tar, col);
+ }
+ else {
+ rgba_float_to_uchar(tar, col);
+ }
+ }
+ // else printf("prv out bound x y %d %d\n", x, y);
+ }
+ // else printf("prv out bound x y %d %d\n", x, y);
+ }
+}
+
+#if 0
+static void nodeClearPreview(bNode *node)
+{
+ if (node->preview && node->preview->rect)
+ memset(node->preview->rect, 0, MEM_allocN_len(node->preview->rect));
+}
+
+/* use it to enforce clear */
+void ntreeClearPreview(bNodeTree *ntree)
+{
+ bNode *node;
+
+ if (ntree == NULL)
+ return;
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->typeinfo->flag & NODE_PREVIEW)
+ nodeClearPreview(node);
+ if (node->type == NODE_GROUP)
+ ntreeClearPreview((bNodeTree *)node->id);
+ }
+}
+
+/* hack warning! this function is only used for shader previews, and
+ * since it gets called multiple times per pixel for Ztransp we only
+ * add the color once. Preview gets cleared before it starts render though */
+void nodeAddToPreview(bNode *node, const float col[4], int x, int y, int do_manage)
+{
+ bNodePreview *preview = node->preview;
+ if (preview) {
+ if (x >= 0 && y >= 0) {
+ if (x < preview->xsize && y < preview->ysize) {
+ unsigned char *tar = preview->rect + 4 * ((preview->xsize * y) + x);
+
+ if (do_manage) {
+ linearrgb_to_srgb_uchar4(tar, col);
+ }
+ else {
+ rgba_float_to_uchar(tar, col);
+ }
+ }
+ //else printf("prv out bound x y %d %d\n", x, y);
+ }
+ //else printf("prv out bound x y %d %d\n", x, y);
+ }
+}
+#endif
+
+/* ************** Free stuff ********** */
+
+/* goes over entire tree */
+void nodeUnlinkNode(bNodeTree *ntree, bNode *node)
+{
+ bNodeLink *link, *next;
+ bNodeSocket *sock;
+ ListBase *lb;
+
+ for (link = ntree->links.first; link; link = next) {
+ next = link->next;
+
+ if (link->fromnode == node) {
+ lb = &node->outputs;
+ if (link->tonode)
+ link->tonode->update |= NODE_UPDATE;
+ }
+ else if (link->tonode == node)
+ lb = &node->inputs;
+ else
+ lb = NULL;
+
+ if (lb) {
+ for (sock = lb->first; sock; sock = sock->next) {
+ if (link->fromsock == sock || link->tosock == sock)
+ break;
+ }
+ if (sock) {
+ nodeRemLink(ntree, link);
+ }
+ }
+ }
+}
+
+static void node_unlink_attached(bNodeTree *ntree, bNode *parent)
+{
+ bNode *node;
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->parent == parent)
+ nodeDetachNode(node);
+ }
+}
+
+/** \note caller needs to manage node->id user */
+static void node_free_node_ex(bNodeTree *ntree,
+ bNode *node,
+ bool remove_animdata,
+ bool use_api_free_cb)
+{
+ bNodeSocket *sock, *nextsock;
+
+ /* don't remove node animdata if the tree is localized,
+ * Action is shared with the original tree (T38221)
+ */
+ remove_animdata &= ntree && !(ntree->flag & NTREE_IS_LOCALIZED);
+
+ /* extra free callback */
+ if (use_api_free_cb && node->typeinfo->freefunc_api) {
+ PointerRNA ptr;
+ RNA_pointer_create((ID *)ntree, &RNA_Node, node, &ptr);
+
+ node->typeinfo->freefunc_api(&ptr);
+ }
+
+ /* since it is called while free database, node->id is undefined */
+
+ /* can be called for nodes outside a node tree (e.g. clipboard) */
+ if (ntree) {
+ /* remove all references to this node */
+ nodeUnlinkNode(ntree, node);
+ node_unlink_attached(ntree, node);
+
+ BLI_remlink(&ntree->nodes, node);
+
+ if (remove_animdata) {
+ char propname_esc[MAX_IDPROP_NAME * 2];
+ char prefix[MAX_IDPROP_NAME * 2];
+
+ BLI_strescape(propname_esc, node->name, sizeof(propname_esc));
+ BLI_snprintf(prefix, sizeof(prefix), "nodes[\"%s\"]", propname_esc);
+
+ BKE_animdata_fix_paths_remove((ID *)ntree, prefix);
+ }
+
+ if (ntree->typeinfo->free_node_cache)
+ ntree->typeinfo->free_node_cache(ntree, node);
+
+ /* texture node has bad habit of keeping exec data around */
+ if (ntree->type == NTREE_TEXTURE && ntree->execdata) {
+ ntreeTexEndExecTree(ntree->execdata);
+ ntree->execdata = NULL;
+ }
+ }
+
+ if (node->typeinfo->freefunc) {
+ node->typeinfo->freefunc(node);
+ }
+
+ for (sock = node->inputs.first; sock; sock = nextsock) {
+ nextsock = sock->next;
+ node_socket_free(ntree, sock, node);
+ MEM_freeN(sock);
+ }
+ for (sock = node->outputs.first; sock; sock = nextsock) {
+ nextsock = sock->next;
+ node_socket_free(ntree, sock, node);
+ MEM_freeN(sock);
+ }
+
+ BLI_freelistN(&node->internal_links);
+
+ if (node->prop) {
+ IDP_FreeProperty(node->prop);
+ MEM_freeN(node->prop);
+ }
+
+ MEM_freeN(node);
+
+ if (ntree)
+ ntree->update |= NTREE_UPDATE_NODES;
+}
+
+void nodeFreeNode(bNodeTree *ntree, bNode *node)
+{
+ node_free_node_ex(ntree, node, true, true);
+}
+
+static void node_socket_interface_free(bNodeTree *UNUSED(ntree), bNodeSocket *sock)
+{
+ if (sock->prop) {
+ IDP_FreeProperty(sock->prop);
+ MEM_freeN(sock->prop);
+ }
+
+ if (sock->default_value)
+ MEM_freeN(sock->default_value);
+}
+
+static void free_localized_node_groups(bNodeTree *ntree)
+{
+ bNode *node;
+
+ /* Only localized node trees store a copy for each node group tree.
+ * Each node group tree in a localized node tree can be freed,
+ * since it is a localized copy itself (no risk of accessing free'd
+ * data in main, see [#37939]).
+ */
+ if (!(ntree->flag & NTREE_IS_LOCALIZED))
+ return;
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->type == NODE_GROUP && node->id) {
+ bNodeTree *ngroup = (bNodeTree *)node->id;
+ ntreeFreeTree(ngroup);
+ MEM_freeN(ngroup);
+ }
+ }
+}
+
+/** Free (or release) any data used by this nodetree (does not free the nodetree itself). */
+void ntreeFreeTree(bNodeTree *ntree)
+{
+ bNode *node, *next;
+ bNodeSocket *sock, *nextsock;
+
+ BKE_animdata_free((ID *)ntree, false);
+
+ /* XXX hack! node trees should not store execution graphs at all.
+ * This should be removed when old tree types no longer require it.
+ * Currently the execution data for texture nodes remains in the tree
+ * after execution, until the node tree is updated or freed.
+ */
+ if (ntree->execdata) {
+ switch (ntree->type) {
+ case NTREE_SHADER:
+ ntreeShaderEndExecTree(ntree->execdata);
+ break;
+ case NTREE_TEXTURE:
+ ntreeTexEndExecTree(ntree->execdata);
+ ntree->execdata = NULL;
+ break;
+ }
+ }
+
+ /* XXX not nice, but needed to free localized node groups properly */
+ free_localized_node_groups(ntree);
+
+ /* unregister associated RNA types */
+ ntreeInterfaceTypeFree(ntree);
+
+ BLI_freelistN(&ntree->links); /* do first, then unlink_node goes fast */
+
+ for (node = ntree->nodes.first; node; node = next) {
+ next = node->next;
+ node_free_node_ex(ntree, node, false, false);
+ }
+
+ /* free interface sockets */
+ for (sock = ntree->inputs.first; sock; sock = nextsock) {
+ nextsock = sock->next;
+ node_socket_interface_free(ntree, sock);
+ MEM_freeN(sock);
+ }
+ for (sock = ntree->outputs.first; sock; sock = nextsock) {
+ nextsock = sock->next;
+ node_socket_interface_free(ntree, sock);
+ MEM_freeN(sock);
+ }
+
+ /* free preview hash */
+ if (ntree->previews) {
+ BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free);
+ }
+
+ if (ntree->duplilock)
+ BLI_mutex_free(ntree->duplilock);
+
+ /* if ntree is not part of library, free the libblock data explicitly */
+ if (ntree->id.tag & LIB_TAG_NO_MAIN) {
+ BKE_libblock_free_data(&ntree->id, true);
+ }
+}
+
+void ntreeFreeCache(bNodeTree *ntree)
+{
+ if (ntree == NULL)
+ return;
+
+ if (ntree->typeinfo->free_cache)
+ ntree->typeinfo->free_cache(ntree);
+}
+
+void ntreeSetOutput(bNodeTree *ntree)
+{
+ bNode *node;
+
+ /* find the active outputs, might become tree type dependent handler */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->typeinfo->nclass == NODE_CLASS_OUTPUT) {
+ bNode *tnode;
+ int output = 0;
+
+ /* we need a check for which output node should be tagged like this, below an exception */
+ if (node->type == CMP_NODE_OUTPUT_FILE)
+ continue;
+
+ /* there is more types having output class, each one is checked */
+ for (tnode = ntree->nodes.first; tnode; tnode = tnode->next) {
+ if (tnode->typeinfo->nclass == NODE_CLASS_OUTPUT) {
+
+ if (ntree->type == NTREE_COMPOSIT) {
+
+ /* same type, exception for viewer */
+ if (tnode->type == node->type ||
+ (ELEM(tnode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER) &&
+ ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER))) {
+ if (tnode->flag & NODE_DO_OUTPUT) {
+ output++;
+ if (output > 1)
+ tnode->flag &= ~NODE_DO_OUTPUT;
+ }
+ }
+ }
+ else {
+ /* same type */
+ if (tnode->type == node->type) {
+ if (tnode->flag & NODE_DO_OUTPUT) {
+ output++;
+ if (output > 1)
+ tnode->flag &= ~NODE_DO_OUTPUT;
+ }
+ }
+ }
+ }
+ }
+ if (output == 0)
+ node->flag |= NODE_DO_OUTPUT;
+ }
+
+ /* group node outputs use this flag too */
+ if (node->type == NODE_GROUP_OUTPUT) {
+ bNode *tnode;
+ int output = 0;
+
+ for (tnode = ntree->nodes.first; tnode; tnode = tnode->next) {
+ if (tnode->type == NODE_GROUP_OUTPUT) {
+ if (tnode->flag & NODE_DO_OUTPUT) {
+ output++;
+ if (output > 1)
+ tnode->flag &= ~NODE_DO_OUTPUT;
+ }
+ }
+ }
+ if (output == 0)
+ node->flag |= NODE_DO_OUTPUT;
+ }
+ }
+
+ /* here we could recursively set which nodes have to be done,
+ * might be different for editor or for "real" use... */
+}
+
+bNodeTree *ntreeFromID(ID *id)
+{
+ switch (GS(id->name)) {
+ case ID_MA:
+ return ((Material *)id)->nodetree;
+ case ID_LA:
+ return ((Lamp *)id)->nodetree;
+ case ID_WO:
+ return ((World *)id)->nodetree;
+ case ID_TE:
+ return ((Tex *)id)->nodetree;
+ case ID_SCE:
+ return ((Scene *)id)->nodetree;
+ case ID_LS:
+ return ((FreestyleLineStyle *)id)->nodetree;
+ default:
+ return NULL;
+ }
+}
+
+void ntreeMakeLocal(Main *bmain, bNodeTree *ntree, bool id_in_mainlist, const bool lib_local)
+{
+ BKE_id_make_local_generic(bmain, &ntree->id, id_in_mainlist, lib_local);
+}
+
+int ntreeNodeExists(bNodeTree *ntree, bNode *testnode)
+{
+ bNode *node = ntree->nodes.first;
+ for (; node; node = node->next)
+ if (node == testnode)
+ return 1;
+ return 0;
+}
+
+int ntreeOutputExists(bNode *node, bNodeSocket *testsock)
+{
+ bNodeSocket *sock = node->outputs.first;
+ for (; sock; sock = sock->next)
+ if (sock == testsock)
+ return 1;
+ return 0;
+}
+
+void ntreeNodeFlagSet(const bNodeTree *ntree, const int flag, const bool enable)
+{
+ bNode *node = ntree->nodes.first;
+
+ for (; node; node = node->next) {
+ if (enable) {
+ node->flag |= flag;
+ }
+ else {
+ node->flag &= ~flag;
+ }
+ }
+}
+
+/* returns localized tree for execution in threads */
+bNodeTree *ntreeLocalize(bNodeTree *ntree)
+{
+ if (ntree) {
+ bNodeTree *ltree;
+ bNode *node;
+ AnimData *adt;
+
+ bAction *action_backup = NULL, *tmpact_backup = NULL;
+
+ BLI_spin_lock(&spin);
+ if (!ntree->duplilock) {
+ ntree->duplilock = BLI_mutex_alloc();
+ }
+ BLI_spin_unlock(&spin);
+
+ BLI_mutex_lock(ntree->duplilock);
+
+ /* Workaround for copying an action on each render!
+ * set action to NULL so animdata actions don't get copied */
+ adt = BKE_animdata_from_id(&ntree->id);
+
+ if (adt) {
+ action_backup = adt->action;
+ tmpact_backup = adt->tmpact;
+
+ adt->action = NULL;
+ adt->tmpact = NULL;
+ }
+
+ /* Make full copy outside of Main database.
+ * Note: previews are not copied here.
+ */
+ BKE_id_copy_ex(NULL,
+ (ID *)ntree,
+ (ID **)&ltree,
+ LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_COPY_NO_PREVIEW,
+ false);
+ ltree->flag |= NTREE_IS_LOCALIZED;
+
+ for (node = ltree->nodes.first; node; node = node->next) {
+ if (node->type == NODE_GROUP && node->id) {
+ node->id = (ID *)ntreeLocalize((bNodeTree *)node->id);
+ }
+ }
+
+ if (adt) {
+ AnimData *ladt = BKE_animdata_from_id(&ltree->id);
+
+ adt->action = ladt->action = action_backup;
+ adt->tmpact = ladt->tmpact = tmpact_backup;
+
+ if (action_backup)
+ id_us_plus(&action_backup->id);
+ if (tmpact_backup)
+ id_us_plus(&tmpact_backup->id);
+ }
+ /* end animdata uglyness */
+
+ /* ensures only a single output node is enabled */
+ ntreeSetOutput(ntree);
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ /* store new_node pointer to original */
+ node->new_node->new_node = node;
+ }
+
+ if (ntree->typeinfo->localize)
+ ntree->typeinfo->localize(ltree, ntree);
+
+ BLI_mutex_unlock(ntree->duplilock);
+
+ return ltree;
+ }
+ else
+ return NULL;
+}
+
+/* sync local composite with real tree */
+/* local tree is supposed to be running, be careful moving previews! */
+/* is called by jobs manager, outside threads, so it doesn't happen during draw */
+void ntreeLocalSync(bNodeTree *localtree, bNodeTree *ntree)
+{
+ if (localtree && ntree) {
+ if (ntree->typeinfo->local_sync)
+ ntree->typeinfo->local_sync(localtree, ntree);
+ }
+}
+
+/* merge local tree results back, and free local tree */
+/* we have to assume the editor already changed completely */
+void ntreeLocalMerge(Main *bmain, bNodeTree *localtree, bNodeTree *ntree)
+{
+ if (ntree && localtree) {
+ if (ntree->typeinfo->local_merge)
+ ntree->typeinfo->local_merge(bmain, localtree, ntree);
+
+ ntreeFreeTree(localtree);
+ MEM_freeN(localtree);
+ }
+}
+
+/* ************ NODE TREE INTERFACE *************** */
+
+static bNodeSocket *make_socket_interface(bNodeTree *ntree,
+ int in_out,
+ const char *idname,
+ const char *name)
+{
+ bNodeSocketType *stype = nodeSocketTypeFind(idname);
+ bNodeSocket *sock;
+ int own_index = ntree->cur_index++;
+
+ if (stype == NULL) {
+ return NULL;
+ }
+
+ sock = MEM_callocN(sizeof(bNodeSocket), "socket template");
+ BLI_strncpy(sock->idname, stype->idname, sizeof(sock->idname));
+ node_socket_set_typeinfo(ntree, sock, stype);
+ sock->in_out = in_out;
+ sock->type = SOCK_CUSTOM; /* int type undefined by default */
+
+ /* assign new unique index */
+ own_index = ntree->cur_index++;
+ /* use the own_index as socket identifier */
+ if (in_out == SOCK_IN)
+ BLI_snprintf(sock->identifier, MAX_NAME, "Input_%d", own_index);
+ else
+ BLI_snprintf(sock->identifier, MAX_NAME, "Output_%d", own_index);
+#ifdef USE_NODE_COMPAT_CUSTOMNODES
+ /* XXX forward compatibility:
+ * own_index is deprecated, but needs to be set here.
+ * Node sockets generally use the identifier string instead now,
+ * but reconstructing own_index in writefile.c would require parsing the identifier string.
+ */
+
+# if (defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 406)) || defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+# endif
+
+ sock->own_index = own_index;
+
+# if (defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 406)) || defined(__clang__)
+# pragma GCC diagnostic pop
+# endif
+
+#endif /* USE_NODE_COMPAT_CUSTOMNODES */
+
+ sock->limit = (in_out == SOCK_IN ? 1 : 0xFFF);
+
+ BLI_strncpy(sock->name, name, NODE_MAXSTR);
+ sock->storage = NULL;
+ sock->flag |= SOCK_COLLAPSED;
+
+ return sock;
+}
+
+bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree, int in_out, const char *identifier)
+{
+ bNodeSocket *iosock = (in_out == SOCK_IN ? ntree->inputs.first : ntree->outputs.first);
+ for (; iosock; iosock = iosock->next)
+ if (STREQ(iosock->identifier, identifier))
+ return iosock;
+ return NULL;
+}
+
+bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree,
+ int in_out,
+ const char *idname,
+ const char *name)
+{
+ bNodeSocket *iosock;
+
+ iosock = make_socket_interface(ntree, in_out, idname, name);
+ if (in_out == SOCK_IN) {
+ BLI_addtail(&ntree->inputs, iosock);
+ ntree->update |= NTREE_UPDATE_GROUP_IN;
+ }
+ else if (in_out == SOCK_OUT) {
+ BLI_addtail(&ntree->outputs, iosock);
+ ntree->update |= NTREE_UPDATE_GROUP_OUT;
+ }
+
+ return iosock;
+}
+
+bNodeSocket *ntreeInsertSocketInterface(
+ bNodeTree *ntree, int in_out, const char *idname, bNodeSocket *next_sock, const char *name)
+{
+ bNodeSocket *iosock;
+
+ iosock = make_socket_interface(ntree, in_out, idname, name);
+ if (in_out == SOCK_IN) {
+ BLI_insertlinkbefore(&ntree->inputs, next_sock, iosock);
+ ntree->update |= NTREE_UPDATE_GROUP_IN;
+ }
+ else if (in_out == SOCK_OUT) {
+ BLI_insertlinkbefore(&ntree->outputs, next_sock, iosock);
+ ntree->update |= NTREE_UPDATE_GROUP_OUT;
+ }
+
+ return iosock;
+}
+
+struct bNodeSocket *ntreeAddSocketInterfaceFromSocket(bNodeTree *ntree,
+ bNode *from_node,
+ bNodeSocket *from_sock)
+{
+ bNodeSocket *iosock = ntreeAddSocketInterface(
+ ntree, from_sock->in_out, from_sock->idname, from_sock->name);
+ if (iosock) {
+ if (iosock->typeinfo->interface_from_socket)
+ iosock->typeinfo->interface_from_socket(ntree, iosock, from_node, from_sock);
+ }
+ return iosock;
+}
+
+struct bNodeSocket *ntreeInsertSocketInterfaceFromSocket(bNodeTree *ntree,
+ bNodeSocket *next_sock,
+ bNode *from_node,
+ bNodeSocket *from_sock)
+{
+ bNodeSocket *iosock = ntreeInsertSocketInterface(
+ ntree, from_sock->in_out, from_sock->idname, next_sock, from_sock->name);
+ if (iosock) {
+ if (iosock->typeinfo->interface_from_socket)
+ iosock->typeinfo->interface_from_socket(ntree, iosock, from_node, from_sock);
+ }
+ return iosock;
+}
+
+void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock)
+{
+ /* this is fast, this way we don't need an in_out argument */
+ BLI_remlink(&ntree->inputs, sock);
+ BLI_remlink(&ntree->outputs, sock);
+
+ node_socket_interface_free(ntree, sock);
+ MEM_freeN(sock);
+
+ ntree->update |= NTREE_UPDATE_GROUP;
+}
+
+/* generates a valid RNA identifier from the node tree name */
+static void ntree_interface_identifier_base(bNodeTree *ntree, char *base)
+{
+ /* generate a valid RNA identifier */
+ sprintf(base, "NodeTreeInterface_%s", ntree->id.name + 2);
+ RNA_identifier_sanitize(base, false);
+}
+
+/* check if the identifier is already in use */
+static bool ntree_interface_unique_identifier_check(void *UNUSED(data), const char *identifier)
+{
+ return (RNA_struct_find(identifier) != NULL);
+}
+
+/* generates the actual unique identifier and ui name and description */
+static void ntree_interface_identifier(bNodeTree *ntree,
+ const char *base,
+ char *identifier,
+ int maxlen,
+ char *name,
+ char *description)
+{
+ /* There is a possibility that different node tree names get mapped to the same identifier
+ * after sanitization (e.g. "SomeGroup_A", "SomeGroup.A" both get sanitized to "SomeGroup_A").
+ * On top of the sanitized id string add a number suffix if necessary to avoid duplicates.
+ */
+ identifier[0] = '\0';
+ BLI_uniquename_cb(ntree_interface_unique_identifier_check, NULL, base, '_', identifier, maxlen);
+
+ sprintf(name, "Node Tree %s Interface", ntree->id.name + 2);
+ sprintf(description, "Interface properties of node group %s", ntree->id.name + 2);
+}
+
+static void ntree_interface_type_create(bNodeTree *ntree)
+{
+ StructRNA *srna;
+ bNodeSocket *sock;
+ /* strings are generated from base string + ID name, sizes are sufficient */
+ char base[MAX_ID_NAME + 64], identifier[MAX_ID_NAME + 64], name[MAX_ID_NAME + 64],
+ description[MAX_ID_NAME + 64];
+
+ /* generate a valid RNA identifier */
+ ntree_interface_identifier_base(ntree, base);
+ ntree_interface_identifier(ntree, base, identifier, sizeof(identifier), name, description);
+
+ /* register a subtype of PropertyGroup */
+ srna = RNA_def_struct_ptr(&BLENDER_RNA, identifier, &RNA_PropertyGroup);
+ RNA_def_struct_ui_text(srna, name, description);
+ RNA_def_struct_duplicate_pointers(&BLENDER_RNA, srna);
+
+ /* associate the RNA type with the node tree */
+ ntree->interface_type = srna;
+ RNA_struct_blender_type_set(srna, ntree);
+
+ /* add socket properties */
+ for (sock = ntree->inputs.first; sock; sock = sock->next) {
+ bNodeSocketType *stype = sock->typeinfo;
+ if (stype && stype->interface_register_properties)
+ stype->interface_register_properties(ntree, sock, srna);
+ }
+ for (sock = ntree->outputs.first; sock; sock = sock->next) {
+ bNodeSocketType *stype = sock->typeinfo;
+ if (stype && stype->interface_register_properties)
+ stype->interface_register_properties(ntree, sock, srna);
+ }
+}
+
+StructRNA *ntreeInterfaceTypeGet(bNodeTree *ntree, int create)
+{
+ if (ntree->interface_type) {
+ /* strings are generated from base string + ID name, sizes are sufficient */
+ char base[MAX_ID_NAME + 64], identifier[MAX_ID_NAME + 64], name[MAX_ID_NAME + 64],
+ description[MAX_ID_NAME + 64];
+
+ /* A bit of a hack: when changing the ID name, update the RNA type identifier too,
+ * so that the names match. This is not strictly necessary to keep it working,
+ * but better for identifying associated NodeTree blocks and RNA types.
+ */
+ StructRNA *srna = ntree->interface_type;
+
+ ntree_interface_identifier_base(ntree, base);
+
+ /* RNA identifier may have a number suffix, but should start with the idbase string */
+ if (!STREQLEN(RNA_struct_identifier(srna), base, sizeof(base))) {
+ /* generate new unique RNA identifier from the ID name */
+ ntree_interface_identifier(ntree, base, identifier, sizeof(identifier), name, description);
+
+ /* rename the RNA type */
+ RNA_def_struct_free_pointers(&BLENDER_RNA, srna);
+ RNA_def_struct_identifier(&BLENDER_RNA, srna, identifier);
+ RNA_def_struct_ui_text(srna, name, description);
+ RNA_def_struct_duplicate_pointers(&BLENDER_RNA, srna);
+ }
+ }
+ else if (create) {
+ ntree_interface_type_create(ntree);
+ }
+
+ return ntree->interface_type;
+}
+
+void ntreeInterfaceTypeFree(bNodeTree *ntree)
+{
+ if (ntree->interface_type) {
+ RNA_struct_free(&BLENDER_RNA, ntree->interface_type);
+ ntree->interface_type = NULL;
+ }
+}
+
+void ntreeInterfaceTypeUpdate(bNodeTree *ntree)
+{
+ /* XXX it would be sufficient to just recreate all properties
+ * instead of re-registering the whole struct type,
+ * but there is currently no good way to do this in the RNA functions.
+ * Overhead should be negligible.
+ */
+ ntreeInterfaceTypeFree(ntree);
+ ntree_interface_type_create(ntree);
+}
+
+/* ************ find stuff *************** */
+
+bNode *ntreeFindType(const bNodeTree *ntree, int type)
+{
+ if (ntree) {
+ for (bNode *node = ntree->nodes.first; node; node = node->next) {
+ if (node->type == type) {
+ return node;
+ }
+ }
+ }
+ return NULL;
+}
+
+bool ntreeHasType(const bNodeTree *ntree, int type)
+{
+ return ntreeFindType(ntree, type) != NULL;
+}
+
+bool ntreeHasTree(const bNodeTree *ntree, const bNodeTree *lookup)
+{
+ bNode *node;
+
+ if (ntree == lookup)
+ return true;
+
+ for (node = ntree->nodes.first; node; node = node->next)
+ if (node->type == NODE_GROUP && node->id)
+ if (ntreeHasTree((bNodeTree *)node->id, lookup))
+ return true;
+
+ return false;
+}
+
+bNodeLink *nodeFindLink(bNodeTree *ntree, bNodeSocket *from, bNodeSocket *to)
+{
+ bNodeLink *link;
+
+ for (link = ntree->links.first; link; link = link->next) {
+ if (link->fromsock == from && link->tosock == to)
+ return link;
+ if (link->fromsock == to && link->tosock == from) /* hrms? */
+ return link;
+ }
+ return NULL;
+}
+
+int nodeCountSocketLinks(bNodeTree *ntree, bNodeSocket *sock)
+{
+ bNodeLink *link;
+ int tot = 0;
+
+ for (link = ntree->links.first; link; link = link->next) {
+ if (link->fromsock == sock || link->tosock == sock)
+ tot++;
+ }
+ return tot;
+}
+
+bNode *nodeGetActive(bNodeTree *ntree)
+{
+ bNode *node;
+
+ if (ntree == NULL)
+ return NULL;
+
+ for (node = ntree->nodes.first; node; node = node->next)
+ if (node->flag & NODE_ACTIVE)
+ break;
+ return node;
+}
+
+static bNode *node_get_active_id_recursive(bNodeInstanceKey active_key,
+ bNodeInstanceKey parent_key,
+ bNodeTree *ntree,
+ short idtype)
+{
+ if (parent_key.value == active_key.value || active_key.value == 0) {
+ bNode *node;
+ for (node = ntree->nodes.first; node; node = node->next)
+ if (node->id && GS(node->id->name) == idtype)
+ if (node->flag & NODE_ACTIVE_ID)
+ return node;
+ }
+ else {
+ bNode *node, *tnode;
+ /* no node with active ID in this tree, look inside groups */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->type == NODE_GROUP) {
+ bNodeTree *group = (bNodeTree *)node->id;
+ if (group) {
+ bNodeInstanceKey group_key = BKE_node_instance_key(parent_key, ntree, node);
+ tnode = node_get_active_id_recursive(active_key, group_key, group, idtype);
+ if (tnode)
+ return tnode;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* two active flags, ID nodes have special flag for buttons display */
+bNode *nodeGetActiveID(bNodeTree *ntree, short idtype)
+{
+ if (ntree)
+ return node_get_active_id_recursive(
+ ntree->active_viewer_key, NODE_INSTANCE_KEY_BASE, ntree, idtype);
+ else
+ return NULL;
+}
+
+bool nodeSetActiveID(bNodeTree *ntree, short idtype, ID *id)
+{
+ bNode *node;
+ bool ok = false;
+
+ if (ntree == NULL)
+ return ok;
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->id && GS(node->id->name) == idtype) {
+ if (id && ok == false && node->id == id) {
+ node->flag |= NODE_ACTIVE_ID;
+ ok = true;
+ }
+ else {
+ node->flag &= ~NODE_ACTIVE_ID;
+ }
+ }
+ }
+
+ /* update all groups linked from here
+ * if active ID node has been found already,
+ * just pass NULL so other matching nodes are deactivated.
+ */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->type == NODE_GROUP)
+ ok |= nodeSetActiveID((bNodeTree *)node->id, idtype, (ok == false ? id : NULL));
+ }
+
+ return ok;
+}
+
+/* two active flags, ID nodes have special flag for buttons display */
+void nodeClearActiveID(bNodeTree *ntree, short idtype)
+{
+ bNode *node;
+
+ if (ntree == NULL)
+ return;
+
+ for (node = ntree->nodes.first; node; node = node->next)
+ if (node->id && GS(node->id->name) == idtype)
+ node->flag &= ~NODE_ACTIVE_ID;
+}
+
+void nodeSetSelected(bNode *node, bool select)
+{
+ if (select) {
+ node->flag |= NODE_SELECT;
+ }
+ else {
+ bNodeSocket *sock;
+
+ node->flag &= ~NODE_SELECT;
+
+ /* deselect sockets too */
+ for (sock = node->inputs.first; sock; sock = sock->next)
+ sock->flag &= ~NODE_SELECT;
+ for (sock = node->outputs.first; sock; sock = sock->next)
+ sock->flag &= ~NODE_SELECT;
+ }
+}
+
+void nodeClearActive(bNodeTree *ntree)
+{
+ bNode *node;
+
+ if (ntree == NULL)
+ return;
+
+ for (node = ntree->nodes.first; node; node = node->next)
+ node->flag &= ~(NODE_ACTIVE | NODE_ACTIVE_ID);
+}
+
+/* two active flags, ID nodes have special flag for buttons display */
+void nodeSetActive(bNodeTree *ntree, bNode *node)
+{
+ bNode *tnode;
+
+ /* make sure only one node is active, and only one per ID type */
+ for (tnode = ntree->nodes.first; tnode; tnode = tnode->next) {
+ tnode->flag &= ~NODE_ACTIVE;
+
+ if (node->id && tnode->id) {
+ if (GS(node->id->name) == GS(tnode->id->name))
+ tnode->flag &= ~NODE_ACTIVE_ID;
+ }
+ if (node->typeinfo->nclass == NODE_CLASS_TEXTURE)
+ tnode->flag &= ~NODE_ACTIVE_TEXTURE;
+ }
+
+ node->flag |= NODE_ACTIVE;
+ if (node->id)
+ node->flag |= NODE_ACTIVE_ID;
+ if (node->typeinfo->nclass == NODE_CLASS_TEXTURE)
+ node->flag |= NODE_ACTIVE_TEXTURE;
+}
+
+int nodeSocketIsHidden(bNodeSocket *sock)
+{
+ return ((sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) != 0);
+}
+
+/* ************** Node Clipboard *********** */
+
+#define USE_NODE_CB_VALIDATE
+
+#ifdef USE_NODE_CB_VALIDATE
+/**
+ * This data structure is to validate the node on creation,
+ * otherwise we may reference missing data.
+ *
+ * Currently its only used for ID's, but nodes may one day
+ * reference other pointers which need validation.
+ */
+typedef struct bNodeClipboardExtraInfo {
+ struct bNodeClipboardExtraInfo *next, *prev;
+ ID *id;
+ char id_name[MAX_ID_NAME];
+ char library_name[FILE_MAX];
+} bNodeClipboardExtraInfo;
+#endif /* USE_NODE_CB_VALIDATE */
+
+typedef struct bNodeClipboard {
+ ListBase nodes;
+
+#ifdef USE_NODE_CB_VALIDATE
+ ListBase nodes_extra_info;
+#endif
+
+ ListBase links;
+ int type;
+} bNodeClipboard;
+
+static bNodeClipboard node_clipboard = {{NULL}};
+
+void BKE_node_clipboard_init(struct bNodeTree *ntree)
+{
+ node_clipboard.type = ntree->type;
+}
+
+void BKE_node_clipboard_clear(void)
+{
+ bNode *node, *node_next;
+ bNodeLink *link, *link_next;
+
+ for (link = node_clipboard.links.first; link; link = link_next) {
+ link_next = link->next;
+ nodeRemLink(NULL, link);
+ }
+ BLI_listbase_clear(&node_clipboard.links);
+
+ for (node = node_clipboard.nodes.first; node; node = node_next) {
+ node_next = node->next;
+ node_free_node_ex(NULL, node, false, false);
+ }
+ BLI_listbase_clear(&node_clipboard.nodes);
+
+#ifdef USE_NODE_CB_VALIDATE
+ BLI_freelistN(&node_clipboard.nodes_extra_info);
+#endif
+}
+
+/* return false when one or more ID's are lost */
+bool BKE_node_clipboard_validate(void)
+{
+ bool ok = true;
+
+#ifdef USE_NODE_CB_VALIDATE
+ bNodeClipboardExtraInfo *node_info;
+ bNode *node;
+
+ /* lists must be aligned */
+ BLI_assert(BLI_listbase_count(&node_clipboard.nodes) ==
+ BLI_listbase_count(&node_clipboard.nodes_extra_info));
+
+ for (node = node_clipboard.nodes.first, node_info = node_clipboard.nodes_extra_info.first; node;
+ node = node->next, node_info = node_info->next) {
+ /* validate the node against the stored node info */
+
+ /* re-assign each loop since we may clear,
+ * open a new file where the ID is valid, and paste again */
+ node->id = node_info->id;
+
+ /* currently only validate the ID */
+ if (node->id) {
+ /* We want to search into current blend file, so using G_MAIN is valid here too. */
+ ListBase *lb = which_libbase(G_MAIN, GS(node_info->id_name));
+ BLI_assert(lb != NULL);
+
+ if (BLI_findindex(lb, node_info->id) == -1) {
+ /* may assign NULL */
+ node->id = BLI_findstring(lb, node_info->id_name + 2, offsetof(ID, name) + 2);
+
+ if (node->id == NULL) {
+ ok = false;
+ }
+ }
+ }
+ }
+#endif /* USE_NODE_CB_VALIDATE */
+
+ return ok;
+}
+
+void BKE_node_clipboard_add_node(bNode *node)
+{
+#ifdef USE_NODE_CB_VALIDATE
+ /* add extra info */
+ bNodeClipboardExtraInfo *node_info = MEM_mallocN(sizeof(bNodeClipboardExtraInfo),
+ "bNodeClipboardExtraInfo");
+
+ node_info->id = node->id;
+ if (node->id) {
+ BLI_strncpy(node_info->id_name, node->id->name, sizeof(node_info->id_name));
+ if (ID_IS_LINKED(node->id)) {
+ BLI_strncpy(
+ node_info->library_name, node->id->lib->filepath, sizeof(node_info->library_name));
+ }
+ else {
+ node_info->library_name[0] = '\0';
+ }
+ }
+ else {
+ node_info->id_name[0] = '\0';
+ node_info->library_name[0] = '\0';
+ }
+ BLI_addtail(&node_clipboard.nodes_extra_info, node_info);
+ /* end extra info */
+#endif /* USE_NODE_CB_VALIDATE */
+
+ /* add node */
+ BLI_addtail(&node_clipboard.nodes, node);
+}
+
+void BKE_node_clipboard_add_link(bNodeLink *link)
+{
+ BLI_addtail(&node_clipboard.links, link);
+}
+
+const ListBase *BKE_node_clipboard_get_nodes(void)
+{
+ return &node_clipboard.nodes;
+}
+
+const ListBase *BKE_node_clipboard_get_links(void)
+{
+ return &node_clipboard.links;
+}
+
+int BKE_node_clipboard_get_type(void)
+{
+ return node_clipboard.type;
+}
+
+/* Node Instance Hash */
+
+/* magic number for initial hash key */
+const bNodeInstanceKey NODE_INSTANCE_KEY_BASE = {5381};
+const bNodeInstanceKey NODE_INSTANCE_KEY_NONE = {0};
+
+/* Generate a hash key from ntree and node names
+ * Uses the djb2 algorithm with xor by Bernstein:
+ * http://www.cse.yorku.ca/~oz/hash.html
+ */
+static bNodeInstanceKey node_hash_int_str(bNodeInstanceKey hash, const char *str)
+{
+ char c;
+
+ while ((c = *str++))
+ hash.value = ((hash.value << 5) + hash.value) ^ c; /* (hash * 33) ^ c */
+
+ /* separator '\0' character, to avoid ambiguity from concatenated strings */
+ hash.value = (hash.value << 5) + hash.value; /* hash * 33 */
+
+ return hash;
+}
+
+bNodeInstanceKey BKE_node_instance_key(bNodeInstanceKey parent_key, bNodeTree *ntree, bNode *node)
+{
+ bNodeInstanceKey key;
+
+ key = node_hash_int_str(parent_key, ntree->id.name + 2);
+
+ if (node)
+ key = node_hash_int_str(key, node->name);
+
+ return key;
+}
+
+static unsigned int node_instance_hash_key(const void *key)
+{
+ return ((const bNodeInstanceKey *)key)->value;
+}
+
+static bool node_instance_hash_key_cmp(const void *a, const void *b)
+{
+ unsigned int value_a = ((const bNodeInstanceKey *)a)->value;
+ unsigned int value_b = ((const bNodeInstanceKey *)b)->value;
+
+ return (value_a != value_b);
+}
+
+bNodeInstanceHash *BKE_node_instance_hash_new(const char *info)
+{
+ bNodeInstanceHash *hash = MEM_mallocN(sizeof(bNodeInstanceHash), info);
+ hash->ghash = BLI_ghash_new(
+ node_instance_hash_key, node_instance_hash_key_cmp, "node instance hash ghash");
+ return hash;
+}
+
+void BKE_node_instance_hash_free(bNodeInstanceHash *hash, bNodeInstanceValueFP valfreefp)
+{
+ BLI_ghash_free(hash->ghash, NULL, (GHashValFreeFP)valfreefp);
+ MEM_freeN(hash);
+}
+
+void BKE_node_instance_hash_insert(bNodeInstanceHash *hash, bNodeInstanceKey key, void *value)
+{
+ bNodeInstanceHashEntry *entry = value;
+ entry->key = key;
+ entry->tag = 0;
+ BLI_ghash_insert(hash->ghash, &entry->key, value);
+}
+
+void *BKE_node_instance_hash_lookup(bNodeInstanceHash *hash, bNodeInstanceKey key)
+{
+ return BLI_ghash_lookup(hash->ghash, &key);
+}
+
+int BKE_node_instance_hash_remove(bNodeInstanceHash *hash,
+ bNodeInstanceKey key,
+ bNodeInstanceValueFP valfreefp)
+{
+ return BLI_ghash_remove(hash->ghash, &key, NULL, (GHashValFreeFP)valfreefp);
+}
+
+void BKE_node_instance_hash_clear(bNodeInstanceHash *hash, bNodeInstanceValueFP valfreefp)
+{
+ BLI_ghash_clear(hash->ghash, NULL, (GHashValFreeFP)valfreefp);
+}
+
+void *BKE_node_instance_hash_pop(bNodeInstanceHash *hash, bNodeInstanceKey key)
+{
+ return BLI_ghash_popkey(hash->ghash, &key, NULL);
+}
+
+int BKE_node_instance_hash_haskey(bNodeInstanceHash *hash, bNodeInstanceKey key)
+{
+ return BLI_ghash_haskey(hash->ghash, &key);
+}
+
+int BKE_node_instance_hash_size(bNodeInstanceHash *hash)
+{
+ return BLI_ghash_len(hash->ghash);
+}
+
+void BKE_node_instance_hash_clear_tags(bNodeInstanceHash *hash)
+{
+ bNodeInstanceHashIterator iter;
+
+ NODE_INSTANCE_HASH_ITER (iter, hash) {
+ bNodeInstanceHashEntry *value = BKE_node_instance_hash_iterator_get_value(&iter);
+
+ value->tag = 0;
+ }
+}
+
+void BKE_node_instance_hash_tag(bNodeInstanceHash *UNUSED(hash), void *value)
+{
+ bNodeInstanceHashEntry *entry = value;
+ entry->tag = 1;
+}
+
+bool BKE_node_instance_hash_tag_key(bNodeInstanceHash *hash, bNodeInstanceKey key)
+{
+ bNodeInstanceHashEntry *entry = BKE_node_instance_hash_lookup(hash, key);
+
+ if (entry) {
+ entry->tag = 1;
+ return true;
+ }
+ else
+ return false;
+}
+
+void BKE_node_instance_hash_remove_untagged(bNodeInstanceHash *hash,
+ bNodeInstanceValueFP valfreefp)
+{
+ /* NOTE: Hash must not be mutated during iterating!
+ * Store tagged entries in a separate list and remove items afterward.
+ */
+ bNodeInstanceKey *untagged = MEM_mallocN(sizeof(bNodeInstanceKey) *
+ BKE_node_instance_hash_size(hash),
+ "temporary node instance key list");
+ bNodeInstanceHashIterator iter;
+ int num_untagged, i;
+
+ num_untagged = 0;
+ NODE_INSTANCE_HASH_ITER (iter, hash) {
+ bNodeInstanceHashEntry *value = BKE_node_instance_hash_iterator_get_value(&iter);
+
+ if (!value->tag)
+ untagged[num_untagged++] = BKE_node_instance_hash_iterator_get_key(&iter);
+ }
+
+ for (i = 0; i < num_untagged; ++i) {
+ BKE_node_instance_hash_remove(hash, untagged[i], valfreefp);
+ }
+
+ MEM_freeN(untagged);
+}
+
+/* ************** dependency stuff *********** */
+
+/* node is guaranteed to be not checked before */
+static int node_get_deplist_recurs(bNodeTree *ntree, bNode *node, bNode ***nsort)
+{
+ bNode *fromnode;
+ bNodeLink *link;
+ int level = 0xFFF;
+
+ node->done = true;
+
+ /* check linked nodes */
+ for (link = ntree->links.first; link; link = link->next) {
+ if (link->tonode == node) {
+ fromnode = link->fromnode;
+ if (fromnode->done == 0)
+ fromnode->level = node_get_deplist_recurs(ntree, fromnode, nsort);
+ if (fromnode->level <= level)
+ level = fromnode->level - 1;
+ }
+ }
+
+ /* check parent node */
+ if (node->parent) {
+ if (node->parent->done == 0)
+ node->parent->level = node_get_deplist_recurs(ntree, node->parent, nsort);
+ if (node->parent->level <= level)
+ level = node->parent->level - 1;
+ }
+
+ if (nsort) {
+ **nsort = node;
+ (*nsort)++;
+ }
+
+ return level;
+}
+
+void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***deplist, int *totnodes)
+{
+ bNode *node, **nsort;
+
+ *totnodes = 0;
+
+ /* first clear data */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ node->done = false;
+ (*totnodes)++;
+ }
+ if (*totnodes == 0) {
+ *deplist = NULL;
+ return;
+ }
+
+ nsort = *deplist = MEM_callocN((*totnodes) * sizeof(bNode *), "sorted node array");
+
+ /* recursive check */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->done == 0) {
+ node->level = node_get_deplist_recurs(ntree, node, &nsort);
+ }
+ }
+}
+
+/* only updates node->level for detecting cycles links */
+static void ntree_update_node_level(bNodeTree *ntree)
+{
+ bNode *node;
+
+ /* first clear tag */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ node->done = false;
+ }
+
+ /* recursive check */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->done == 0) {
+ node->level = node_get_deplist_recurs(ntree, node, NULL);
+ }
+ }
+}
+
+void ntreeTagUsedSockets(bNodeTree *ntree)
+{
+ bNode *node;
+ bNodeSocket *sock;
+ bNodeLink *link;
+
+ /* first clear data */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ for (sock = node->inputs.first; sock; sock = sock->next) {
+ sock->flag &= ~SOCK_IN_USE;
+ }
+ for (sock = node->outputs.first; sock; sock = sock->next) {
+ sock->flag &= ~SOCK_IN_USE;
+ }
+ }
+
+ for (link = ntree->links.first; link; link = link->next) {
+ /* link is unused if either side is disabled */
+ if ((link->fromsock->flag & SOCK_UNAVAIL) || (link->tosock->flag & SOCK_UNAVAIL))
+ continue;
+
+ link->fromsock->flag |= SOCK_IN_USE;
+ link->tosock->flag |= SOCK_IN_USE;
+ }
+}
+
+static void ntree_update_link_pointers(bNodeTree *ntree)
+{
+ bNode *node;
+ bNodeSocket *sock;
+ bNodeLink *link;
+
+ /* first clear data */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ for (sock = node->inputs.first; sock; sock = sock->next) {
+ sock->link = NULL;
+ }
+ }
+
+ for (link = ntree->links.first; link; link = link->next) {
+ link->tosock->link = link;
+ }
+
+ ntreeTagUsedSockets(ntree);
+}
+
+static void ntree_validate_links(bNodeTree *ntree)
+{
+ bNodeLink *link;
+
+ for (link = ntree->links.first; link; link = link->next) {
+ link->flag |= NODE_LINK_VALID;
+ if (link->fromnode && link->tonode && link->fromnode->level <= link->tonode->level)
+ link->flag &= ~NODE_LINK_VALID;
+ else if (ntree->typeinfo->validate_link) {
+ if (!ntree->typeinfo->validate_link(ntree, link))
+ link->flag &= ~NODE_LINK_VALID;
+ }
+ }
+}
+
+void ntreeVerifyNodes(struct Main *main, struct ID *id)
+{
+ FOREACH_NODETREE(main, ntree, owner_id)
+ {
+ bNode *node;
+
+ for (node = ntree->nodes.first; node; node = node->next)
+ if (node->typeinfo->verifyfunc)
+ node->typeinfo->verifyfunc(ntree, node, id);
+ }
+ FOREACH_NODETREE_END
+}
+
+void ntreeUpdateTree(Main *bmain, bNodeTree *ntree)
+{
+ bNode *node;
+
+ if (!ntree)
+ return;
+
+ /* avoid reentrant updates, can be caused by RNA update callbacks */
+ if (ntree->is_updating)
+ return;
+ ntree->is_updating = true;
+
+ if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) {
+ /* set the bNodeSocket->link pointers */
+ ntree_update_link_pointers(ntree);
+ }
+
+ /* update individual nodes */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ /* node tree update tags override individual node update flags */
+ if ((node->update & NODE_UPDATE) || (ntree->update & NTREE_UPDATE)) {
+ if (node->typeinfo->updatefunc)
+ node->typeinfo->updatefunc(ntree, node);
+
+ nodeUpdateInternalLinks(ntree, node);
+ }
+ }
+
+ /* generic tree update callback */
+ if (ntree->typeinfo->update)
+ ntree->typeinfo->update(ntree);
+ /* XXX this should be moved into the tree type update callback for tree supporting node groups.
+ * Currently the node tree interface is still a generic feature of the base NodeTree type.
+ */
+ if (ntree->update & NTREE_UPDATE_GROUP)
+ ntreeInterfaceTypeUpdate(ntree);
+
+ /* XXX hack, should be done by depsgraph!! */
+ if (bmain)
+ ntreeVerifyNodes(bmain, &ntree->id);
+
+ if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) {
+ /* node updates can change sockets or links, repeat link pointer update afterward */
+ ntree_update_link_pointers(ntree);
+
+ /* update the node level from link dependencies */
+ ntree_update_node_level(ntree);
+
+ /* check link validity */
+ ntree_validate_links(ntree);
+ }
+
+ /* clear update flags */
+ for (node = ntree->nodes.first; node; node = node->next) {
+ node->update = 0;
+ }
+ ntree->update = 0;
+
+ ntree->is_updating = false;
+}
+
+void nodeUpdate(bNodeTree *ntree, bNode *node)
+{
+ /* avoid reentrant updates, can be caused by RNA update callbacks */
+ if (ntree->is_updating)
+ return;
+ ntree->is_updating = true;
+
+ if (node->typeinfo->updatefunc)
+ node->typeinfo->updatefunc(ntree, node);
+
+ nodeUpdateInternalLinks(ntree, node);
+
+ /* clear update flag */
+ node->update = 0;
+
+ ntree->is_updating = false;
+}
+
+bool nodeUpdateID(bNodeTree *ntree, ID *id)
+{
+ bNode *node;
+ bool changed = false;
+
+ if (ELEM(NULL, id, ntree))
+ return changed;
+
+ /* avoid reentrant updates, can be caused by RNA update callbacks */
+ if (ntree->is_updating)
+ return changed;
+ ntree->is_updating = true;
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ if (node->id == id) {
+ changed = true;
+ node->update |= NODE_UPDATE_ID;
+ if (node->typeinfo->updatefunc)
+ node->typeinfo->updatefunc(ntree, node);
+ /* clear update flag */
+ node->update = 0;
+ }
+ }
+
+ for (node = ntree->nodes.first; node; node = node->next) {
+ nodeUpdateInternalLinks(ntree, node);
+ }
+
+ ntree->is_updating = false;
+ return changed;
+}
+
+void nodeUpdateInternalLinks(bNodeTree *ntree, bNode *node)
+{
+ BLI_freelistN(&node->internal_links);
+
+ if (node->typeinfo && node->typeinfo->update_internal_links)
+ node->typeinfo->update_internal_links(ntree, node);
+}
+
+/* nodes that use ID data get synced with local data */
+void nodeSynchronizeID(bNode *node, bool copy_to_id)
+{
+ if (node->id == NULL)
+ return;
+
+ if (ELEM(node->type, SH_NODE_MATERIAL, SH_NODE_MATERIAL_EXT)) {
+ bNodeSocket *sock;
+ Material *ma = (Material *)node->id;
+ int a;
+ short check_flags = SOCK_UNAVAIL;
+
+ if (!copy_to_id)
+ check_flags |= SOCK_HIDDEN;
+
+ /* hrmf, case in loop isn't super fast, but we don't edit 100s of material at same time either!
+ */
+ for (a = 0, sock = node->inputs.first; sock; sock = sock->next, a++) {
+ if (!(sock->flag & check_flags)) {
+ if (copy_to_id) {
+ switch (a) {
+ case MAT_IN_COLOR:
+ copy_v3_v3(&ma->r, ((bNodeSocketValueRGBA *)sock->default_value)->value);
+ break;
+ case MAT_IN_SPEC:
+ copy_v3_v3(&ma->specr, ((bNodeSocketValueRGBA *)sock->default_value)->value);
+ break;
+ case MAT_IN_REFL:
+ ma->ref = ((bNodeSocketValueFloat *)sock->default_value)->value;
+ break;
+ case MAT_IN_MIR:
+ copy_v3_v3(&ma->mirr, ((bNodeSocketValueRGBA *)sock->default_value)->value);
+ break;
+ case MAT_IN_AMB:
+ ma->amb = ((bNodeSocketValueFloat *)sock->default_value)->value;
+ break;
+ case MAT_IN_EMIT:
+ ma->emit = ((bNodeSocketValueFloat *)sock->default_value)->value;
+ break;
+ case MAT_IN_SPECTRA:
+ ma->spectra = ((bNodeSocketValueFloat *)sock->default_value)->value;
+ break;
+ case MAT_IN_RAY_MIRROR:
+ ma->ray_mirror = ((bNodeSocketValueFloat *)sock->default_value)->value;
+ break;
+ case MAT_IN_ALPHA:
+ ma->alpha = ((bNodeSocketValueFloat *)sock->default_value)->value;
+ break;
+ case MAT_IN_TRANSLUCENCY:
+ ma->translucency = ((bNodeSocketValueFloat *)sock->default_value)->value;
+ break;
+ }
+ }
+ else {
+ switch (a) {
+ case MAT_IN_COLOR:
+ copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, &ma->r);
+ break;
+ case MAT_IN_SPEC:
+ copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, &ma->specr);
+ break;
+ case MAT_IN_REFL:
+ ((bNodeSocketValueFloat *)sock->default_value)->value = ma->ref;
+ break;
+ case MAT_IN_MIR:
+ copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, &ma->mirr);
+ break;
+ case MAT_IN_AMB:
+ ((bNodeSocketValueFloat *)sock->default_value)->value = ma->amb;
+ break;
+ case MAT_IN_EMIT:
+ ((bNodeSocketValueFloat *)sock->default_value)->value = ma->emit;
+ break;
+ case MAT_IN_SPECTRA:
+ ((bNodeSocketValueFloat *)sock->default_value)->value = ma->spectra;
+ break;
+ case MAT_IN_RAY_MIRROR:
+ ((bNodeSocketValueFloat *)sock->default_value)->value = ma->ray_mirror;
+ break;
+ case MAT_IN_ALPHA:
+ ((bNodeSocketValueFloat *)sock->default_value)->value = ma->alpha;
+ break;
+ case MAT_IN_TRANSLUCENCY:
+ ((bNodeSocketValueFloat *)sock->default_value)->value = ma->translucency;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+/* ************* node type access ********** */
+
+void nodeLabel(bNodeTree *ntree, bNode *node, char *label, int maxlen)
+{
+ label[0] = '\0';
+
+ if (node->label[0] != '\0') {
+ BLI_strncpy(label, node->label, maxlen);
+ }
+ else if (node->typeinfo->labelfunc) {
+ node->typeinfo->labelfunc(ntree, node, label, maxlen);
+ }
+
+ /* The previous methods (labelfunc) could not provide an adequate label for the node. */
+ if (label[0] == '\0') {
+ /* Kind of hacky and weak... Ideally would be better to use RNA here. :| */
+ const char *tmp = CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, node->typeinfo->ui_name);
+ if (tmp == node->typeinfo->ui_name) {
+ tmp = IFACE_(node->typeinfo->ui_name);
+ }
+ BLI_strncpy(label, tmp, maxlen);
+ }
+}
+
+static void node_type_base_defaults(bNodeType *ntype)
+{
+ /* default size values */
+ node_type_size_preset(ntype, NODE_SIZE_DEFAULT);
+ ntype->height = 100;
+ ntype->minheight = 30;
+ ntype->maxheight = FLT_MAX;
+}
+
+/* allow this node for any tree type */
+static bool node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(ntree))
+{
+ return true;
+}
+
+/* use the basic poll function */
+static bool node_poll_instance_default(bNode *node, bNodeTree *ntree)
+{
+ return node->typeinfo->poll(node->typeinfo, ntree);
+}
+
+void node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag)
+{
+ /* Use static type info header to map static int type to identifier string and RNA struct type.
+ * Associate the RNA struct type with the bNodeType.
+ * Dynamically registered nodes will create an RNA type at runtime
+ * and call RNA_struct_blender_type_set, so this only needs to be done for old RNA types
+ * created in makesrna, which can not be associated to a bNodeType immediately,
+ * since bNodeTypes are registered afterward ...
+ */
+#define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \
+ case ID: \
+ BLI_strncpy(ntype->idname, #Category #StructName, sizeof(ntype->idname)); \
+ ntype->ext.srna = RNA_struct_find(#Category #StructName); \
+ BLI_assert(ntype->ext.srna != NULL); \
+ RNA_struct_blender_type_set(ntype->ext.srna, ntype); \
+ break;
+
+ switch (type) {
+#include "NOD_static_types.h"
+ }
+
+ /* make sure we have a valid type (everything registered) */
+ BLI_assert(ntype->idname[0] != '\0');
+
+ ntype->type = type;
+ BLI_strncpy(ntype->ui_name, name, sizeof(ntype->ui_name));
+ ntype->nclass = nclass;
+ ntype->flag = flag;
+
+ node_type_base_defaults(ntype);
+
+ ntype->poll = node_poll_default;
+ ntype->poll_instance = node_poll_instance_default;
+}
+
+void node_type_base_custom(
+ bNodeType *ntype, const char *idname, const char *name, short nclass, short flag)
+{
+ BLI_strncpy(ntype->idname, idname, sizeof(ntype->idname));
+ ntype->type = NODE_CUSTOM;
+ BLI_strncpy(ntype->ui_name, name, sizeof(ntype->ui_name));
+ ntype->nclass = nclass;
+ ntype->flag = flag;
+
+ node_type_base_defaults(ntype);
+}
+
+static bool unique_socket_template_identifier_check(void *arg, const char *name)
+{
+ bNodeSocketTemplate *ntemp;
+ struct {
+ bNodeSocketTemplate *list;
+ bNodeSocketTemplate *ntemp;
+ } *data = arg;
+
+ for (ntemp = data->list; ntemp->type >= 0; ++ntemp) {
+ if (ntemp != data->ntemp) {
+ if (STREQ(ntemp->identifier, name)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static void unique_socket_template_identifier(bNodeSocketTemplate *list,
+ bNodeSocketTemplate *ntemp,
+ const char defname[],
+ char delim)
+{
+ struct {
+ bNodeSocketTemplate *list;
+ bNodeSocketTemplate *ntemp;
+ } data;
+ data.list = list;
+ data.ntemp = ntemp;
+
+ BLI_uniquename_cb(unique_socket_template_identifier_check,
+ &data,
+ defname,
+ delim,
+ ntemp->identifier,
+ sizeof(ntemp->identifier));
+}
+
+void node_type_socket_templates(struct bNodeType *ntype,
+ struct bNodeSocketTemplate *inputs,
+ struct bNodeSocketTemplate *outputs)
+{
+ bNodeSocketTemplate *ntemp;
+
+ ntype->inputs = inputs;
+ ntype->outputs = outputs;
+
+ /* automatically generate unique identifiers */
+ if (inputs) {
+ /* clear identifier strings (uninitialized memory) */
+ for (ntemp = inputs; ntemp->type >= 0; ++ntemp)
+ ntemp->identifier[0] = '\0';
+
+ for (ntemp = inputs; ntemp->type >= 0; ++ntemp) {
+ BLI_strncpy(ntemp->identifier, ntemp->name, sizeof(ntemp->identifier));
+ unique_socket_template_identifier(inputs, ntemp, ntemp->identifier, '_');
+ }
+ }
+ if (outputs) {
+ /* clear identifier strings (uninitialized memory) */
+ for (ntemp = outputs; ntemp->type >= 0; ++ntemp)
+ ntemp->identifier[0] = '\0';
+
+ for (ntemp = outputs; ntemp->type >= 0; ++ntemp) {
+ BLI_strncpy(ntemp->identifier, ntemp->name, sizeof(ntemp->identifier));
+ unique_socket_template_identifier(outputs, ntemp, ntemp->identifier, '_');
+ }
+ }
+}
+
+void node_type_init(struct bNodeType *ntype,
+ void (*initfunc)(struct bNodeTree *ntree, struct bNode *node))
+{
+ ntype->initfunc = initfunc;
+}
+
+void node_type_size(struct bNodeType *ntype, int width, int minwidth, int maxwidth)
+{
+ ntype->width = width;
+ ntype->minwidth = minwidth;
+ if (maxwidth <= minwidth)
+ ntype->maxwidth = FLT_MAX;
+ else
+ ntype->maxwidth = maxwidth;
+}
+
+void node_type_size_preset(struct bNodeType *ntype, eNodeSizePreset size)
+{
+ switch (size) {
+ case NODE_SIZE_DEFAULT:
+ node_type_size(ntype, 140, 100, NODE_DEFAULT_MAX_WIDTH);
+ break;
+ case NODE_SIZE_SMALL:
+ node_type_size(ntype, 100, 80, NODE_DEFAULT_MAX_WIDTH);
+ break;
+ case NODE_SIZE_MIDDLE:
+ node_type_size(ntype, 150, 120, NODE_DEFAULT_MAX_WIDTH);
+ break;
+ case NODE_SIZE_LARGE:
+ node_type_size(ntype, 240, 140, NODE_DEFAULT_MAX_WIDTH);
+ break;
+ }
+}
+
+/**
+ * \warning Nodes defining a storage type _must_ allocate this for new nodes.
+ * Otherwise nodes will reload as undefined (T46619).
+ */
+void node_type_storage(bNodeType *ntype,
+ const char *storagename,
+ void (*freefunc)(struct bNode *node),
+ void (*copyfunc)(struct bNodeTree *dest_ntree,
+ struct bNode *dest_node,
+ struct bNode *src_node))
+{
+ if (storagename)
+ BLI_strncpy(ntype->storagename, storagename, sizeof(ntype->storagename));
+ else
+ ntype->storagename[0] = '\0';
+ ntype->copyfunc = copyfunc;
+ ntype->freefunc = freefunc;
+}
+
+void node_type_label(
+ struct bNodeType *ntype,
+ void (*labelfunc)(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen))
+{
+ ntype->labelfunc = labelfunc;
+}
+
+void node_type_update(struct bNodeType *ntype,
+ void (*updatefunc)(struct bNodeTree *ntree, struct bNode *node),
+ void (*verifyfunc)(struct bNodeTree *ntree,
+ struct bNode *node,
+ struct ID *id))
+{
+ ntype->updatefunc = updatefunc;
+ ntype->verifyfunc = verifyfunc;
+}
+
+void node_type_exec(struct bNodeType *ntype,
+ NodeInitExecFunction initexecfunc,
+ NodeFreeExecFunction freeexecfunc,
+ NodeExecFunction execfunc)
+{
+ ntype->initexecfunc = initexecfunc;
+ ntype->freeexecfunc = freeexecfunc;
+ ntype->execfunc = execfunc;
+}
+
+void node_type_gpu(struct bNodeType *ntype, NodeGPUExecFunction gpufunc)
+{
+ ntype->gpufunc = gpufunc;
+}
+
+void node_type_internal_links(bNodeType *ntype,
+ void (*update_internal_links)(bNodeTree *, bNode *))
+{
+ ntype->update_internal_links = update_internal_links;
+}
+
+void node_type_compatibility(struct bNodeType *ntype, short compatibility)
+{
+ ntype->compatibility = compatibility;
+}
+
+/* callbacks for undefined types */
+
+static bool node_undefined_poll(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(nodetree))
+{
+ /* this type can not be added deliberately, it's just a placeholder */
+ return false;
+}
+
+/* register fallback types used for undefined tree, nodes, sockets */
+static void register_undefined_types(void)
+{
+ /* Note: these types are not registered in the type hashes,
+ * they are just used as placeholders in case the actual types are not registered.
+ */
+
+ strcpy(NodeTreeTypeUndefined.idname, "NodeTreeUndefined");
+ strcpy(NodeTreeTypeUndefined.ui_name, "Undefined");
+ strcpy(NodeTreeTypeUndefined.ui_description, "Undefined Node Tree Type");
+
+ node_type_base_custom(&NodeTypeUndefined, "NodeUndefined", "Undefined", 0, 0);
+ NodeTypeUndefined.poll = node_undefined_poll;
+
+ BLI_strncpy(NodeSocketTypeUndefined.idname,
+ "NodeSocketUndefined",
+ sizeof(NodeSocketTypeUndefined.idname));
+ /* extra type info for standard socket types */
+ NodeSocketTypeUndefined.type = SOCK_CUSTOM;
+ NodeSocketTypeUndefined.subtype = PROP_NONE;
+}
+
+static void registerCompositNodes(void)
+{
+ register_node_type_cmp_group();
+
+ register_node_type_cmp_rlayers();
+ register_node_type_cmp_image();
+ register_node_type_cmp_texture();
+ register_node_type_cmp_value();
+ register_node_type_cmp_rgb();
+ register_node_type_cmp_curve_time();
+ register_node_type_cmp_movieclip();
+
+ register_node_type_cmp_composite();
+ register_node_type_cmp_viewer();
+ register_node_type_cmp_splitviewer();
+ register_node_type_cmp_output_file();
+ register_node_type_cmp_view_levels();
+
+ register_node_type_cmp_curve_rgb();
+ register_node_type_cmp_mix_rgb();
+ register_node_type_cmp_hue_sat();
+ register_node_type_cmp_brightcontrast();
+ register_node_type_cmp_gamma();
+ register_node_type_cmp_invert();
+ register_node_type_cmp_alphaover();
+ register_node_type_cmp_zcombine();
+ register_node_type_cmp_colorbalance();
+ register_node_type_cmp_huecorrect();
+
+ register_node_type_cmp_normal();
+ register_node_type_cmp_curve_vec();
+ register_node_type_cmp_map_value();
+ register_node_type_cmp_map_range();
+ register_node_type_cmp_normalize();
+
+ register_node_type_cmp_filter();
+ register_node_type_cmp_blur();
+ register_node_type_cmp_dblur();
+ register_node_type_cmp_bilateralblur();
+ register_node_type_cmp_vecblur();
+ register_node_type_cmp_dilateerode();
+ register_node_type_cmp_inpaint();
+ register_node_type_cmp_despeckle();
+ register_node_type_cmp_defocus();
+ register_node_type_cmp_sunbeams();
+ register_node_type_cmp_antialiasing();
+
+ register_node_type_cmp_valtorgb();
+ register_node_type_cmp_rgbtobw();
+ register_node_type_cmp_setalpha();
+ register_node_type_cmp_idmask();
+ register_node_type_cmp_math();
+ register_node_type_cmp_seprgba();
+ register_node_type_cmp_combrgba();
+ register_node_type_cmp_sephsva();
+ register_node_type_cmp_combhsva();
+ register_node_type_cmp_sepyuva();
+ register_node_type_cmp_combyuva();
+ register_node_type_cmp_sepycca();
+ register_node_type_cmp_combycca();
+ register_node_type_cmp_premulkey();
+
+ register_node_type_cmp_diff_matte();
+ register_node_type_cmp_distance_matte();
+ register_node_type_cmp_chroma_matte();
+ register_node_type_cmp_color_matte();
+ register_node_type_cmp_channel_matte();
+ register_node_type_cmp_color_spill();
+ register_node_type_cmp_luma_matte();
+ register_node_type_cmp_doubleedgemask();
+ register_node_type_cmp_keyingscreen();
+ register_node_type_cmp_keying();
+ register_node_type_cmp_cryptomatte();
+
+ register_node_type_cmp_translate();
+ register_node_type_cmp_rotate();
+ register_node_type_cmp_scale();
+ register_node_type_cmp_flip();
+ register_node_type_cmp_crop();
+ register_node_type_cmp_displace();
+ register_node_type_cmp_mapuv();
+ register_node_type_cmp_glare();
+ register_node_type_cmp_tonemap();
+ register_node_type_cmp_lensdist();
+ register_node_type_cmp_transform();
+ register_node_type_cmp_stabilize2d();
+ register_node_type_cmp_moviedistortion();
+
+ register_node_type_cmp_colorcorrection();
+ register_node_type_cmp_boxmask();
+ register_node_type_cmp_ellipsemask();
+ register_node_type_cmp_bokehimage();
+ register_node_type_cmp_bokehblur();
+ register_node_type_cmp_switch();
+ register_node_type_cmp_switch_view();
+ register_node_type_cmp_pixelate();
+
+ register_node_type_cmp_mask();
+ register_node_type_cmp_trackpos();
+ register_node_type_cmp_planetrackdeform();
+ register_node_type_cmp_cornerpin();
+}
+
+static void registerShaderNodes(void)
+{
+ register_node_type_sh_group();
+
+ register_node_type_sh_output();
+ register_node_type_sh_material();
+ register_node_type_sh_camera();
+ register_node_type_sh_lamp();
+ register_node_type_sh_gamma();
+ register_node_type_sh_brightcontrast();
+ register_node_type_sh_value();
+ register_node_type_sh_rgb();
+ register_node_type_sh_wireframe();
+ register_node_type_sh_wavelength();
+ register_node_type_sh_blackbody();
+ register_node_type_sh_mix_rgb();
+ register_node_type_sh_valtorgb();
+ register_node_type_sh_rgbtobw();
+ register_node_type_sh_texture();
+ register_node_type_sh_normal();
+ register_node_type_sh_geom();
+ register_node_type_sh_mapping();
+ register_node_type_sh_curve_vec();
+ register_node_type_sh_curve_rgb();
+ register_node_type_sh_math();
+ register_node_type_sh_vect_math();
+ register_node_type_sh_vect_transform();
+ register_node_type_sh_squeeze();
+ register_node_type_sh_material_ext();
+ register_node_type_sh_invert();
+ register_node_type_sh_seprgb();
+ register_node_type_sh_combrgb();
+ register_node_type_sh_sephsv();
+ register_node_type_sh_combhsv();
+ register_node_type_sh_sepxyz();
+ register_node_type_sh_combxyz();
+ register_node_type_sh_hue_sat();
+
+ register_node_type_sh_attribute();
+ register_node_type_sh_bevel();
+ register_node_type_sh_displacement();
+ register_node_type_sh_vector_displacement();
+ register_node_type_sh_geometry();
+ register_node_type_sh_light_path();
+ register_node_type_sh_light_falloff();
+ register_node_type_sh_object_info();
+ register_node_type_sh_fresnel();
+ register_node_type_sh_layer_weight();
+ register_node_type_sh_tex_coord();
+ register_node_type_sh_particle_info();
+ register_node_type_sh_bump();
+
+ register_node_type_sh_background();
+ register_node_type_sh_bsdf_anisotropic();
+ register_node_type_sh_bsdf_diffuse();
+ register_node_type_sh_bsdf_principled();
+ register_node_type_sh_bsdf_glossy();
+ register_node_type_sh_bsdf_glass();
+ register_node_type_sh_bsdf_translucent();
+ register_node_type_sh_bsdf_transparent();
+ register_node_type_sh_bsdf_velvet();
+ register_node_type_sh_bsdf_toon();
+ register_node_type_sh_bsdf_hair();
+ register_node_type_sh_bsdf_hair_principled();
+ register_node_type_sh_emission();
+ register_node_type_sh_holdout();
+ register_node_type_sh_volume_absorption();
+ register_node_type_sh_volume_scatter();
+ register_node_type_sh_volume_principled();
+ register_node_type_sh_subsurface_scattering();
+ register_node_type_sh_mix_shader();
+ register_node_type_sh_add_shader();
+ register_node_type_sh_uvmap();
+ register_node_type_sh_uvalongstroke();
+
+ register_node_type_sh_output_lamp();
+ register_node_type_sh_output_material();
+ register_node_type_sh_output_world();
+ register_node_type_sh_output_linestyle();
+
+ register_node_type_sh_tex_image();
+ register_node_type_sh_tex_environment();
+ register_node_type_sh_tex_sky();
+ register_node_type_sh_tex_noise();
+ register_node_type_sh_tex_wave();
+ register_node_type_sh_tex_voronoi();
+ register_node_type_sh_tex_musgrave();
+ register_node_type_sh_tex_gradient();
+ register_node_type_sh_tex_magic();
+ register_node_type_sh_tex_checker();
+ register_node_type_sh_tex_brick();
+ register_node_type_sh_tex_pointdensity();
+ register_node_type_sh_tex_ies();
+}
+
+static void registerTextureNodes(void)
+{
+ register_node_type_tex_group();
+
+ register_node_type_tex_math();
+ register_node_type_tex_mix_rgb();
+ register_node_type_tex_valtorgb();
+ register_node_type_tex_rgbtobw();
+ register_node_type_tex_valtonor();
+ register_node_type_tex_curve_rgb();
+ register_node_type_tex_curve_time();
+ register_node_type_tex_invert();
+ register_node_type_tex_hue_sat();
+ register_node_type_tex_coord();
+ register_node_type_tex_distance();
+ register_node_type_tex_compose();
+ register_node_type_tex_decompose();
+
+ register_node_type_tex_output();
+ register_node_type_tex_viewer();
+ register_node_type_sh_script();
+ register_node_type_sh_tangent();
+ register_node_type_sh_normal_map();
+ register_node_type_sh_hair_info();
+
+ register_node_type_tex_checker();
+ register_node_type_tex_texture();
+ register_node_type_tex_bricks();
+ register_node_type_tex_image();
+ register_node_type_sh_bsdf_refraction();
+ register_node_type_sh_ambient_occlusion();
+
+ register_node_type_tex_rotate();
+ register_node_type_tex_translate();
+ register_node_type_tex_scale();
+ register_node_type_tex_at();
+
+ register_node_type_tex_proc_voronoi();
+ register_node_type_tex_proc_blend();
+ register_node_type_tex_proc_magic();
+ register_node_type_tex_proc_marble();
+ register_node_type_tex_proc_clouds();
+ register_node_type_tex_proc_wood();
+ register_node_type_tex_proc_musgrave();
+ register_node_type_tex_proc_noise();
+ register_node_type_tex_proc_stucci();
+ register_node_type_tex_proc_distnoise();
+}
+
+void init_nodesystem(void)
+{
+ nodetreetypes_hash = BLI_ghash_str_new("nodetreetypes_hash gh");
+ nodetypes_hash = BLI_ghash_str_new("nodetypes_hash gh");
+ nodesockettypes_hash = BLI_ghash_str_new("nodesockettypes_hash gh");
+ BLI_spin_init(&spin);
+
+ register_undefined_types();
+
+ register_standard_node_socket_types();
+
+ register_node_tree_type_cmp();
+ register_node_tree_type_sh();
+ register_node_tree_type_tex();
+
+ register_node_type_frame();
+ register_node_type_reroute();
+ register_node_type_group_input();
+ register_node_type_group_output();
+
+ registerCompositNodes();
+ registerShaderNodes();
+ registerTextureNodes();
+}
+
+void free_nodesystem(void)
+{
+ if (nodetypes_hash) {
+ NODE_TYPES_BEGIN (nt)
+ if (nt->ext.free) {
+ nt->ext.free(nt->ext.data);
+ }
+ NODE_TYPES_END
+
+ BLI_ghash_free(nodetypes_hash, NULL, node_free_type);
+ nodetypes_hash = NULL;
+ }
+
+ if (nodesockettypes_hash) {
+ NODE_SOCKET_TYPES_BEGIN (st)
+ if (st->ext_socket.free)
+ st->ext_socket.free(st->ext_socket.data);
+ if (st->ext_interface.free)
+ st->ext_interface.free(st->ext_interface.data);
+ NODE_SOCKET_TYPES_END
+
+ BLI_ghash_free(nodesockettypes_hash, NULL, node_free_socket_type);
+ nodesockettypes_hash = NULL;
+ }
+
+ if (nodetreetypes_hash) {
+ NODE_TREE_TYPES_BEGIN (nt) {
+ if (nt->ext.free) {
+ nt->ext.free(nt->ext.data);
+ }
+ }
+ NODE_TREE_TYPES_END;
+
+ BLI_ghash_free(nodetreetypes_hash, NULL, ntree_free_type);
+ nodetreetypes_hash = NULL;
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/* NodeTree Iterator Helpers (FOREACH_NODETREE) */
+
+void BKE_node_tree_iter_init(struct NodeTreeIterStore *ntreeiter, struct Main *bmain)
+{
+ ntreeiter->ngroup = bmain->nodetree.first;
+ ntreeiter->scene = bmain->scene.first;
+ ntreeiter->mat = bmain->mat.first;
+ ntreeiter->tex = bmain->tex.first;
+ ntreeiter->lamp = bmain->lamp.first;
+ ntreeiter->world = bmain->world.first;
+ ntreeiter->linestyle = bmain->linestyle.first;
+}
+bool BKE_node_tree_iter_step(struct NodeTreeIterStore *ntreeiter,
+ bNodeTree **r_nodetree,
+ struct ID **r_id)
+{
+ if (ntreeiter->ngroup) {
+ *r_nodetree = ntreeiter->ngroup;
+ *r_id = (ID *)ntreeiter->ngroup;
+ ntreeiter->ngroup = ntreeiter->ngroup->id.next;
+ }
+ else if (ntreeiter->scene) {
+ *r_nodetree = ntreeiter->scene->nodetree;
+ *r_id = (ID *)ntreeiter->scene;
+ ntreeiter->scene = ntreeiter->scene->id.next;
+ }
+ else if (ntreeiter->mat) {
+ *r_nodetree = ntreeiter->mat->nodetree;
+ *r_id = (ID *)ntreeiter->mat;
+ ntreeiter->mat = ntreeiter->mat->id.next;
+ }
+ else if (ntreeiter->tex) {
+ *r_nodetree = ntreeiter->tex->nodetree;
+ *r_id = (ID *)ntreeiter->tex;
+ ntreeiter->tex = ntreeiter->tex->id.next;
+ }
+ else if (ntreeiter->lamp) {
+ *r_nodetree = ntreeiter->lamp->nodetree;
+ *r_id = (ID *)ntreeiter->lamp;
+ ntreeiter->lamp = ntreeiter->lamp->id.next;
+ }
+ else if (ntreeiter->world) {
+ *r_nodetree = ntreeiter->world->nodetree;
+ *r_id = (ID *)ntreeiter->world;
+ ntreeiter->world = ntreeiter->world->id.next;
+ }
+ else if (ntreeiter->linestyle) {
+ *r_nodetree = ntreeiter->linestyle->nodetree;
+ *r_id = (ID *)ntreeiter->linestyle;
+ ntreeiter->linestyle = ntreeiter->linestyle->id.next;
+ }
+ else {
+ return false;
+ }
+
+ return true;
+}
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 56f14dcc07d..08a9628a6e6 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4704,6 +4704,7 @@ static void registerCompositNodes()
register_node_type_cmp_defocus();
register_node_type_cmp_sunbeams();
register_node_type_cmp_denoise();
+ register_node_type_cmp_antialiasing();
register_node_type_cmp_valtorgb();
register_node_type_cmp_rgbtobw();
diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c
index 6481fac5a14..2a7c091d1b9 100644
--- a/source/blender/blenlib/intern/math_base_inline.c
+++ b/source/blender/blenlib/intern/math_base_inline.c
@@ -192,6 +192,13 @@ MINLINE double ratiod(double min, double max, double pos)
return range == 0 ? 0 : ((pos - min) / range);
}
+/* Map a normalized value, i.e. from interval [0, 1] to interval [a, b] */
+MINLINE float scalenorm(float a, float b, float x)
+{
+ BLI_assert(x <= 1 && x >= 0);
+ return (x * (b - a)) + a;
+}
+
/* used for zoom values*/
MINLINE float power_of_2(float val)
{
diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt
index 6ae7fc04237..c8ee8af4542 100644
--- a/source/blender/compositor/CMakeLists.txt
+++ b/source/blender/compositor/CMakeLists.txt
@@ -294,6 +294,9 @@ set(SRC
nodes/COM_FilterNode.h
nodes/COM_InpaintNode.cc
nodes/COM_InpaintNode.h
+ nodes/COM_AntiAliasingNode.cc
+ nodes/COM_AntiAliasingNode.h
+
operations/COM_BlurBaseOperation.cc
operations/COM_BlurBaseOperation.h
operations/COM_BokehBlurOperation.cc
@@ -320,6 +323,8 @@ set(SRC
operations/COM_MovieDistortionOperation.h
operations/COM_VariableSizeBokehBlurOperation.cc
operations/COM_VariableSizeBokehBlurOperation.h
+ operations/COM_SMAAOperation.cc
+ operations/COM_SMAAOperation.h
# Matte nodes
nodes/COM_BoxMaskNode.cc
@@ -566,6 +571,23 @@ data_to_c(
add_definitions(-DCL_USE_DEPRECATED_OPENCL_1_1_APIS)
+set(GENSRC_DIR ${CMAKE_CURRENT_BINARY_DIR}/operations)
+set(GENSRC ${GENSRC_DIR}/COM_SMAAAreaTexture.h)
+add_custom_command(
+ OUTPUT ${GENSRC}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${GENSRC_DIR}
+ COMMAND "$<TARGET_FILE:smaa_areatex>" ${GENSRC}
+ DEPENDS smaa_areatex
+)
+add_custom_target(smaa_areatex_header
+ SOURCES ${GENSRC}
+)
+list(APPEND SRC
+ ${GENSRC}
+)
+unset(GENSRC)
+unset(GENSRC_DIR)
+
if(WITH_INTERNATIONAL)
add_definitions(-DWITH_INTERNATIONAL)
endif()
@@ -584,3 +606,5 @@ if(WITH_OPENIMAGEDENOISE)
endif()
blender_add_lib(bf_compositor "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+add_dependencies(bf_compositor smaa_areatex_header)
diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc
index 125d6e5faee..63c95acce3e 100644
--- a/source/blender/compositor/intern/COM_Converter.cc
+++ b/source/blender/compositor/intern/COM_Converter.cc
@@ -26,6 +26,7 @@
#include "COM_NodeOperationBuilder.h"
#include "COM_AlphaOverNode.h"
+#include "COM_AntiAliasingNode.h"
#include "COM_BilateralBlurNode.h"
#include "COM_BlurNode.h"
#include "COM_BokehBlurNode.h"
@@ -418,6 +419,9 @@ Node *COM_convert_bnode(bNode *b_node)
case CMP_NODE_EXPOSURE:
node = new ExposureNode(b_node);
break;
+ case CMP_NODE_ANTIALIASING:
+ node = new AntiAliasingNode(b_node);
+ break;
}
return node;
}
diff --git a/source/blender/compositor/nodes/COM_AntiAliasingNode.cc b/source/blender/compositor/nodes/COM_AntiAliasingNode.cc
new file mode 100644
index 00000000000..8ed627cbab6
--- /dev/null
+++ b/source/blender/compositor/nodes/COM_AntiAliasingNode.cc
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor: IRIE Shinsuke
+ */
+
+#include "COM_AntiAliasingNode.h"
+#include "COM_SMAAOperation.h"
+#include "DNA_node_types.h"
+
+void AntiAliasingNode::convertToOperations(NodeConverter &converter,
+ const CompositorContext & /*context*/) const
+{
+ bNode *node = this->getbNode();
+ NodeAntiAliasingData *data = (NodeAntiAliasingData *)node->storage;
+
+ /* Edge Detection (First Pass) */
+ SMAAEdgeDetectionOperation *operation1 = nullptr;
+
+ operation1 = new SMAAEdgeDetectionOperation();
+ operation1->setThreshold(data->threshold);
+ operation1->setLocalContrastAdaptationFactor(data->contrast_limit);
+ converter.addOperation(operation1);
+
+ converter.mapInputSocket(getInputSocket(0), operation1->getInputSocket(0));
+
+ /* Blending Weight Calculation Pixel Shader (Second Pass) */
+ SMAABlendingWeightCalculationOperation *operation2 =
+ new SMAABlendingWeightCalculationOperation();
+ operation2->setCornerRounding(data->corner_rounding);
+ converter.addOperation(operation2);
+
+ converter.addLink(operation1->getOutputSocket(), operation2->getInputSocket(0));
+
+ /* Neighborhood Blending Pixel Shader (Third Pass) */
+ SMAANeighborhoodBlendingOperation *operation3 = new SMAANeighborhoodBlendingOperation();
+ converter.addOperation(operation3);
+
+ converter.mapInputSocket(getInputSocket(0), operation3->getInputSocket(0));
+ converter.addLink(operation2->getOutputSocket(), operation3->getInputSocket(1));
+ converter.mapOutputSocket(getOutputSocket(0), operation3->getOutputSocket());
+}
diff --git a/source/blender/compositor/nodes/COM_AntiAliasingNode.h b/source/blender/compositor/nodes/COM_AntiAliasingNode.h
new file mode 100644
index 00000000000..c0eafc013cc
--- /dev/null
+++ b/source/blender/compositor/nodes/COM_AntiAliasingNode.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor: IRIE Shinsuke
+ */
+
+#ifndef _COM_AntiAliasingNode_h_
+#define _COM_AntiAliasingNode_h_
+
+#include "COM_Node.h"
+
+/**
+ * @brief AntiAliasingNode
+ * @ingroup Node
+ */
+class AntiAliasingNode : public Node {
+ public:
+ AntiAliasingNode(bNode *editorNode) : Node(editorNode)
+ {
+ }
+ void convertToOperations(NodeConverter &converter, const CompositorContext &context) const;
+};
+
+#endif
diff --git a/source/blender/compositor/operations/COM_SMAAOperation.cc b/source/blender/compositor/operations/COM_SMAAOperation.cc
new file mode 100644
index 00000000000..415a80f1d63
--- /dev/null
+++ b/source/blender/compositor/operations/COM_SMAAOperation.cc
@@ -0,0 +1,865 @@
+/*
+ * Copyright 2017, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor: IRIE Shinsuke
+ */
+
+#include "COM_SMAAOperation.h"
+#include "BLI_math.h"
+#include "COM_SMAAAreaTexture.h"
+
+extern "C" {
+#include "IMB_colormanagement.h"
+}
+
+/*
+ * An implementation of Enhanced Subpixel Morphological Antialiasing (SMAA)
+ *
+ * The algorithm was proposed by:
+ * Jorge Jimenez, Jose I. Echevarria, Tiago Sousa, Diego Gutierrez
+ *
+ * http://www.iryoku.com/smaa/
+ *
+ * This file is based on smaa-cpp:
+ *
+ * https://github.com/iRi-E/smaa-cpp
+ *
+ * Currently only SMAA 1x mode is provided, so the operation will be done
+ * with no spatial multisampling nor temporal supersampling.
+ *
+ * Note: This program assumes the screen coordinates are DirectX style, so
+ * the vertical direction is upside-down. "top" and "bottom" actually mean
+ * bottom and top, respectively.
+ */
+
+/*-----------------------------------------------------------------------------*/
+/* Non-Configurable Defines */
+
+#define SMAA_AREATEX_SIZE 80
+#define SMAA_AREATEX_MAX_DISTANCE 20
+#define SMAA_AREATEX_MAX_DISTANCE_DIAG 20
+#define SMAA_MAX_SEARCH_STEPS 362 /* 362 - 1 = 19^2 */
+#define SMAA_MAX_SEARCH_STEPS_DIAG 19
+
+/*-----------------------------------------------------------------------------*/
+/* Internal Functions to Sample Pixel Color from Image */
+
+static inline void sample(SocketReader *reader, int x, int y, float color[4])
+{
+ if (x < 0 || x >= reader->getWidth() || y < 0 || y >= reader->getHeight()) {
+ color[0] = color[1] = color[2] = color[3] = 0.0;
+ return;
+ }
+
+ reader->read(color, x, y, nullptr);
+}
+
+static void sample_bilinear_vertical(
+ SocketReader *reader, int x, int y, float yoffset, float color[4])
+{
+ float iy = floorf(yoffset);
+ float fy = yoffset - iy;
+ y += (int)iy;
+
+ float color00[4], color01[4];
+
+ sample(reader, x + 0, y + 0, color00);
+ sample(reader, x + 0, y + 1, color01);
+
+ color[0] = interpf(color01[0], color00[0], fy);
+ color[1] = interpf(color01[1], color00[1], fy);
+ color[2] = interpf(color01[2], color00[2], fy);
+ color[3] = interpf(color01[3], color00[3], fy);
+}
+
+static void sample_bilinear_horizontal(
+ SocketReader *reader, int x, int y, float xoffset, float color[4])
+{
+ float ix = floorf(xoffset);
+ float fx = xoffset - ix;
+ x += (int)ix;
+
+ float color00[4], color10[4];
+
+ sample(reader, x + 0, y + 0, color00);
+ sample(reader, x + 1, y + 0, color10);
+
+ color[0] = interpf(color10[0], color00[0], fx);
+ color[1] = interpf(color10[1], color00[1], fx);
+ color[2] = interpf(color10[2], color00[2], fx);
+ color[3] = interpf(color10[3], color00[3], fx);
+}
+
+/*-----------------------------------------------------------------------------*/
+/* Internal Functions to Sample Blending Weights from AreaTex */
+
+static inline const float *areatex_sample_internal(const float *areatex, int x, int y)
+{
+ return &areatex[(CLAMPIS(x, 0, SMAA_AREATEX_SIZE - 1) +
+ CLAMPIS(y, 0, SMAA_AREATEX_SIZE - 1) * SMAA_AREATEX_SIZE) *
+ 2];
+}
+
+/**
+ * We have the distance and both crossing edges. So, what are the areas
+ * at each side of current edge?
+ */
+static void area(int d1, int d2, int e1, int e2, float weights[2])
+{
+ /* The areas texture is compressed quadratically: */
+ float x = (float)(SMAA_AREATEX_MAX_DISTANCE * e1) + sqrtf((float)d1);
+ float y = (float)(SMAA_AREATEX_MAX_DISTANCE * e2) + sqrtf((float)d2);
+
+ float ix = floorf(x), iy = floorf(y);
+ float fx = x - ix, fy = y - iy;
+ int X = (int)ix, Y = (int)iy;
+
+ const float *weights00 = areatex_sample_internal(areatex, X + 0, Y + 0);
+ const float *weights10 = areatex_sample_internal(areatex, X + 1, Y + 0);
+ const float *weights01 = areatex_sample_internal(areatex, X + 0, Y + 1);
+ const float *weights11 = areatex_sample_internal(areatex, X + 1, Y + 1);
+
+ weights[0] = interpf(
+ interpf(weights11[0], weights01[0], fx), interpf(weights10[0], weights00[0], fx), fy);
+ weights[1] = interpf(
+ interpf(weights11[1], weights01[1], fx), interpf(weights10[1], weights00[1], fx), fy);
+}
+
+/**
+ * Similar to area(), this calculates the area corresponding to a certain
+ * diagonal distance and crossing edges 'e'.
+ */
+static void area_diag(int d1, int d2, int e1, int e2, float weights[2])
+{
+ int x = SMAA_AREATEX_MAX_DISTANCE_DIAG * e1 + d1;
+ int y = SMAA_AREATEX_MAX_DISTANCE_DIAG * e2 + d2;
+
+ const float *w = areatex_sample_internal(areatex_diag, x, y);
+ copy_v2_v2(weights, w);
+}
+
+/*-----------------------------------------------------------------------------*/
+/* Edge Detection (First Pass) */
+/*-----------------------------------------------------------------------------*/
+
+SMAAEdgeDetectionOperation::SMAAEdgeDetectionOperation()
+{
+ this->addInputSocket(DataType::Color); /* image */
+ this->addInputSocket(DataType::Value); /* depth, material ID, etc. */
+ this->addOutputSocket(DataType::Color);
+ this->setComplex(true);
+ this->m_imageReader = nullptr;
+ this->m_valueReader = nullptr;
+ this->m_threshold = 0.1f;
+ this->m_contrast_limit = 2.0f;
+}
+
+void SMAAEdgeDetectionOperation::initExecution()
+{
+ this->m_imageReader = this->getInputSocketReader(0);
+ this->m_valueReader = this->getInputSocketReader(1);
+}
+
+void SMAAEdgeDetectionOperation::deinitExecution()
+{
+ this->m_imageReader = nullptr;
+ this->m_valueReader = nullptr;
+}
+
+void SMAAEdgeDetectionOperation::setThreshold(float threshold)
+{
+ /* UI values are between 0 and 1 for simplicity but algorithm expects values between 0 and 0.5 */
+ m_threshold = scalenorm(0, 0.5, threshold);
+}
+
+void SMAAEdgeDetectionOperation::setLocalContrastAdaptationFactor(float factor)
+{
+ /* UI values are between 0 and 1 for simplicity but algorithm expects values between 1 and 10 */
+ m_contrast_limit = scalenorm(1, 10, factor);
+}
+
+bool SMAAEdgeDetectionOperation::determineDependingAreaOfInterest(
+ rcti *input, ReadBufferOperation *readOperation, rcti *output)
+{
+ rcti newInput;
+ newInput.xmax = input->xmax + 1;
+ newInput.xmin = input->xmin - 2;
+ newInput.ymax = input->ymax + 1;
+ newInput.ymin = input->ymin - 2;
+
+ return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
+}
+
+void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, void * /*data*/)
+{
+ float color[4];
+
+ /* Calculate luma deltas: */
+ sample(m_imageReader, x, y, color);
+ float L = IMB_colormanagement_get_luminance(color);
+ sample(m_imageReader, x - 1, y, color);
+ float Lleft = IMB_colormanagement_get_luminance(color);
+ sample(m_imageReader, x, y - 1, color);
+ float Ltop = IMB_colormanagement_get_luminance(color);
+ float Dleft = fabsf(L - Lleft);
+ float Dtop = fabsf(L - Ltop);
+
+ /* We do the usual threshold: */
+ output[0] = (x > 0 && Dleft >= m_threshold) ? 1.0f : 0.0f;
+ output[1] = (y > 0 && Dtop >= m_threshold) ? 1.0f : 0.0f;
+ output[2] = 0.0f;
+ output[3] = 1.0f;
+
+ /* Then discard if there is no edge: */
+ if (is_zero_v2(output)) {
+ return;
+ }
+
+ /* Calculate right and bottom deltas: */
+ sample(m_imageReader, x + 1, y, color);
+ float Lright = IMB_colormanagement_get_luminance(color);
+ sample(m_imageReader, x, y + 1, color);
+ float Lbottom = IMB_colormanagement_get_luminance(color);
+ float Dright = fabsf(L - Lright);
+ float Dbottom = fabsf(L - Lbottom);
+
+ /* Calculate the maximum delta in the direct neighborhood: */
+ float maxDelta = fmaxf(fmaxf(Dleft, Dright), fmaxf(Dtop, Dbottom));
+
+ /* Calculate luma used for both left and top edges: */
+ sample(m_imageReader, x - 1, y - 1, color);
+ float Llefttop = IMB_colormanagement_get_luminance(color);
+
+ /* Left edge */
+ if (output[0] != 0.0f) {
+ /* Calculate deltas around the left pixel: */
+ sample(m_imageReader, x - 2, y, color);
+ float Lleftleft = IMB_colormanagement_get_luminance(color);
+ sample(m_imageReader, x - 1, y + 1, color);
+ float Lleftbottom = IMB_colormanagement_get_luminance(color);
+ float Dleftleft = fabsf(Lleft - Lleftleft);
+ float Dlefttop = fabsf(Lleft - Llefttop);
+ float Dleftbottom = fabsf(Lleft - Lleftbottom);
+
+ /* Calculate the final maximum delta: */
+ maxDelta = fmaxf(maxDelta, fmaxf(Dleftleft, fmaxf(Dlefttop, Dleftbottom)));
+
+ /* Local contrast adaptation: */
+ if (maxDelta > m_contrast_limit * Dleft) {
+ output[0] = 0.0f;
+ }
+ }
+
+ /* Top edge */
+ if (output[1] != 0.0f) {
+ /* Calculate top-top delta: */
+ sample(m_imageReader, x, y - 2, color);
+ float Ltoptop = IMB_colormanagement_get_luminance(color);
+ sample(m_imageReader, x + 1, y - 1, color);
+ float Ltopright = IMB_colormanagement_get_luminance(color);
+ float Dtoptop = fabsf(Ltop - Ltoptop);
+ float Dtopleft = fabsf(Ltop - Llefttop);
+ float Dtopright = fabsf(Ltop - Ltopright);
+
+ /* Calculate the final maximum delta: */
+ maxDelta = fmaxf(maxDelta, fmaxf(Dtoptop, fmaxf(Dtopleft, Dtopright)));
+
+ /* Local contrast adaptation: */
+ if (maxDelta > m_contrast_limit * Dtop) {
+ output[1] = 0.0f;
+ }
+ }
+
+}
+
+/*-----------------------------------------------------------------------------*/
+/* Blending Weight Calculation (Second Pass) */
+/*-----------------------------------------------------------------------------*/
+
+SMAABlendingWeightCalculationOperation::SMAABlendingWeightCalculationOperation()
+{
+ this->addInputSocket(DataType::Color); /* edges */
+ this->addOutputSocket(DataType::Color);
+ this->setComplex(true);
+ this->m_imageReader = nullptr;
+ this->m_corner_rounding = 25;
+}
+
+void *SMAABlendingWeightCalculationOperation::initializeTileData(rcti *rect)
+{
+ return getInputOperation(0)->initializeTileData(rect);
+}
+
+void SMAABlendingWeightCalculationOperation::initExecution()
+{
+ this->m_imageReader = this->getInputSocketReader(0);
+}
+
+void SMAABlendingWeightCalculationOperation::setCornerRounding(float rounding)
+{
+ /* UI values are between 0 and 1 for simplicity but algorithm expects values between 0 and 100 */
+ m_corner_rounding = static_cast<int>(scalenorm(0, 100, rounding));
+}
+
+void SMAABlendingWeightCalculationOperation::executePixel(float output[4],
+ int x,
+ int y,
+ void * /*data*/)
+{
+ float edges[4], c[4];
+
+ zero_v4(output);
+ sample(m_imageReader, x, y, edges);
+
+ /* Edge at north */
+ if (edges[1] > 0.0f) {
+ /* Diagonals have both north and west edges, so calculating weights for them */
+ /* in one of the boundaries is enough. */
+ calculateDiagWeights(x, y, edges, output);
+
+ /* We give priority to diagonals, so if we find a diagonal we skip */
+ /* horizontal/vertical processing. */
+ if (!is_zero_v2(output)) {
+ return;
+ }
+
+ /* Find the distance to the left and the right: */
+ int left = searchXLeft(x, y);
+ int right = searchXRight(x, y);
+ int d1 = x - left, d2 = right - x;
+
+ /* Fetch the left and right crossing edges: */
+ int e1 = 0, e2 = 0;
+ sample(m_imageReader, left, y - 1, c);
+ if (c[0] > 0.0) {
+ e1 += 1;
+ }
+ sample(m_imageReader, left, y, c);
+ if (c[0] > 0.0) {
+ e1 += 2;
+ }
+ sample(m_imageReader, right + 1, y - 1, c);
+ if (c[0] > 0.0) {
+ e2 += 1;
+ }
+ sample(m_imageReader, right + 1, y, c);
+ if (c[0] > 0.0) {
+ e2 += 2;
+ }
+
+ /* Ok, we know how this pattern looks like, now it is time for getting */
+ /* the actual area: */
+ area(d1, d2, e1, e2, output); /* R, G */
+
+ /* Fix corners: */
+ if (m_corner_rounding) {
+ detectHorizontalCornerPattern(output, left, right, y, d1, d2);
+ }
+ }
+
+ /* Edge at west */
+ if (edges[0] > 0.0f) {
+ /* Did we already do diagonal search for this west edge from the left neighboring pixel? */
+ if (isVerticalSearchUnneeded(x, y)) {
+ return;
+ }
+
+ /* Find the distance to the top and the bottom: */
+ int top = searchYUp(x, y);
+ int bottom = searchYDown(x, y);
+ int d1 = y - top, d2 = bottom - y;
+
+ /* Fetch the top ang bottom crossing edges: */
+ int e1 = 0, e2 = 0;
+ sample(m_imageReader, x - 1, top, c);
+ if (c[1] > 0.0) {
+ e1 += 1;
+ }
+ sample(m_imageReader, x, top, c);
+ if (c[1] > 0.0) {
+ e1 += 2;
+ }
+ sample(m_imageReader, x - 1, bottom + 1, c);
+ if (c[1] > 0.0) {
+ e2 += 1;
+ }
+ sample(m_imageReader, x, bottom + 1, c);
+ if (c[1] > 0.0) {
+ e2 += 2;
+ }
+
+ /* Get the area for this direction: */
+ area(d1, d2, e1, e2, output + 2); /* B, A */
+
+ /* Fix corners: */
+ if (m_corner_rounding) {
+ detectVerticalCornerPattern(output + 2, x, top, bottom, d1, d2);
+ }
+ }
+}
+
+void SMAABlendingWeightCalculationOperation::deinitExecution()
+{
+ this->m_imageReader = nullptr;
+}
+
+bool SMAABlendingWeightCalculationOperation::determineDependingAreaOfInterest(
+ rcti *input, ReadBufferOperation *readOperation, rcti *output)
+{
+ rcti newInput;
+
+ newInput.xmax = input->xmax + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG + 1);
+ newInput.xmin = input->xmin -
+ fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG + 1);
+ newInput.ymax = input->ymax + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG);
+ newInput.ymin = input->ymin -
+ fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG);
+
+ return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
+}
+
+/*-----------------------------------------------------------------------------*/
+/* Diagonal Search Functions */
+
+/**
+ * These functions allows to perform diagonal pattern searches.
+ */
+int SMAABlendingWeightCalculationOperation::searchDiag1(int x, int y, int dir, bool *found)
+{
+ float e[4];
+ int end = x + SMAA_MAX_SEARCH_STEPS_DIAG * dir;
+ *found = false;
+
+ while (x != end) {
+ x += dir;
+ y -= dir;
+ sample(m_imageReader, x, y, e);
+ if (e[1] == 0.0f) {
+ *found = true;
+ break;
+ }
+ if (e[0] == 0.0f) {
+ *found = true;
+ return (dir < 0) ? x : x - dir;
+ }
+ }
+
+ return x - dir;
+}
+
+int SMAABlendingWeightCalculationOperation::searchDiag2(int x, int y, int dir, bool *found)
+{
+ float e[4];
+ int end = x + SMAA_MAX_SEARCH_STEPS_DIAG * dir;
+ *found = false;
+
+ while (x != end) {
+ x += dir;
+ y += dir;
+ sample(m_imageReader, x, y, e);
+ if (e[1] == 0.0f) {
+ *found = true;
+ break;
+ }
+ sample(m_imageReader, x + 1, y, e);
+ if (e[0] == 0.0f) {
+ *found = true;
+ return (dir > 0) ? x : x - dir;
+ }
+ }
+
+ return x - dir;
+}
+
+/**
+ * This searches for diagonal patterns and returns the corresponding weights.
+ */
+void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x,
+ int y,
+ const float edges[2],
+ float weights[2])
+{
+ int d1, d2;
+ bool d1_found, d2_found;
+ float e[4], c[4];
+
+ zero_v2(weights);
+
+ if (SMAA_MAX_SEARCH_STEPS_DIAG <= 0) {
+ return;
+ }
+
+ /* Search for the line ends: */
+ if (edges[0] > 0.0f) {
+ d1 = x - searchDiag1(x, y, -1, &d1_found);
+ }
+ else {
+ d1 = 0;
+ d1_found = true;
+ }
+ d2 = searchDiag1(x, y, 1, &d2_found) - x;
+
+ if (d1 + d2 > 2) { /* d1 + d2 + 1 > 3 */
+ int e1 = 0, e2 = 0;
+
+ if (d1_found) {
+ /* Fetch the crossing edges: */
+ int left = x - d1, bottom = y + d1;
+
+ sample(m_imageReader, left - 1, bottom, c);
+ if (c[1] > 0.0) {
+ e1 += 2;
+ }
+ sample(m_imageReader, left, bottom, c);
+ if (c[0] > 0.0) {
+ e1 += 1;
+ }
+ }
+
+ if (d2_found) {
+ /* Fetch the crossing edges: */
+ int right = x + d2, top = y - d2;
+
+ sample(m_imageReader, right + 1, top, c);
+ if (c[1] > 0.0) {
+ e2 += 2;
+ }
+ sample(m_imageReader, right + 1, top - 1, c);
+ if (c[0] > 0.0) {
+ e2 += 1;
+ }
+ }
+
+ /* Fetch the areas for this line: */
+ area_diag(d1, d2, e1, e2, weights);
+ }
+
+ /* Search for the line ends: */
+ d1 = x - searchDiag2(x, y, -1, &d1_found);
+ sample(m_imageReader, x + 1, y, e);
+ if (e[0] > 0.0f) {
+ d2 = searchDiag2(x, y, 1, &d2_found) - x;
+ }
+ else {
+ d2 = 0;
+ d2_found = true;
+ }
+
+ if (d1 + d2 > 2) { /* d1 + d2 + 1 > 3 */
+ int e1 = 0, e2 = 0;
+
+ if (d1_found) {
+ /* Fetch the crossing edges: */
+ int left = x - d1, top = y - d1;
+
+ sample(m_imageReader, left - 1, top, c);
+ if (c[1] > 0.0) {
+ e1 += 2;
+ }
+ sample(m_imageReader, left, top - 1, c);
+ if (c[0] > 0.0) {
+ e1 += 1;
+ }
+ }
+
+ if (d2_found) {
+ /* Fetch the crossing edges: */
+ int right = x + d2, bottom = y + d2;
+
+ sample(m_imageReader, right + 1, bottom, c);
+ if (c[1] > 0.0) {
+ e2 += 2;
+ }
+ if (c[0] > 0.0) {
+ e2 += 1;
+ }
+ }
+
+ /* Fetch the areas for this line: */
+ float w[2];
+ area_diag(d1, d2, e1, e2, w);
+ weights[0] += w[1];
+ weights[1] += w[0];
+ }
+}
+
+bool SMAABlendingWeightCalculationOperation::isVerticalSearchUnneeded(int x, int y)
+{
+ int d1, d2;
+ bool found;
+ float e[4];
+
+ if (SMAA_MAX_SEARCH_STEPS_DIAG <= 0) {
+ return false;
+ }
+
+ /* Search for the line ends: */
+ sample(m_imageReader, x - 1, y, e);
+ if (e[1] > 0.0f) {
+ d1 = x - searchDiag2(x - 1, y, -1, &found);
+ }
+ else {
+ d1 = 0;
+ }
+ d2 = searchDiag2(x - 1, y, 1, &found) - x;
+
+ return (d1 + d2 > 2); /* d1 + d2 + 1 > 3 */
+}
+
+/*-----------------------------------------------------------------------------*/
+/* Horizontal/Vertical Search Functions */
+
+int SMAABlendingWeightCalculationOperation::searchXLeft(int x, int y)
+{
+ int end = x - SMAA_MAX_SEARCH_STEPS;
+ float e[4];
+
+ while (x > end) {
+ sample(m_imageReader, x, y, e);
+ if (e[1] == 0.0f) { /* Is the edge not activated? */
+ break;
+ }
+ if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ return x;
+ }
+ sample(m_imageReader, x, y - 1, e);
+ if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ return x;
+ }
+ x--;
+ }
+
+ return x + 1;
+}
+
+int SMAABlendingWeightCalculationOperation::searchXRight(int x, int y)
+{
+ int end = x + SMAA_MAX_SEARCH_STEPS;
+ float e[4];
+
+ while (x < end) {
+ x++;
+ sample(m_imageReader, x, y, e);
+ if (e[1] == 0.0f || /* Is the edge not activated? */
+ e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ break;
+ }
+ sample(m_imageReader, x, y - 1, e);
+ if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ break;
+ }
+ }
+
+ return x - 1;
+}
+
+int SMAABlendingWeightCalculationOperation::searchYUp(int x, int y)
+{
+ int end = y - SMAA_MAX_SEARCH_STEPS;
+ float e[4];
+
+ while (y > end) {
+ sample(m_imageReader, x, y, e);
+ if (e[0] == 0.0f) { /* Is the edge not activated? */
+ break;
+ }
+ if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ return y;
+ }
+ sample(m_imageReader, x - 1, y, e);
+ if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ return y;
+ }
+ y--;
+ }
+
+ return y + 1;
+}
+
+int SMAABlendingWeightCalculationOperation::searchYDown(int x, int y)
+{
+ int end = y + SMAA_MAX_SEARCH_STEPS;
+ float e[4];
+
+ while (y < end) {
+ y++;
+ sample(m_imageReader, x, y, e);
+ if (e[0] == 0.0f || /* Is the edge not activated? */
+ e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ break;
+ }
+ sample(m_imageReader, x - 1, y, e);
+ if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
+ break;
+ }
+ }
+
+ return y - 1;
+}
+
+/*-----------------------------------------------------------------------------*/
+/* Corner Detection Functions */
+
+void SMAABlendingWeightCalculationOperation::detectHorizontalCornerPattern(
+ float weights[2], int left, int right, int y, int d1, int d2)
+{
+ float factor[2] = {1.0f, 1.0f};
+ float rounding = m_corner_rounding / 100.0f;
+ float e[4];
+
+ /* Reduce blending for pixels in the center of a line. */
+ rounding *= (d1 == d2) ? 0.5f : 1.0f;
+
+ /* Near the left corner */
+ if (d1 <= d2) {
+ sample(m_imageReader, left, y + 1, e);
+ factor[0] -= rounding * e[0];
+ sample(m_imageReader, left, y - 2, e);
+ factor[1] -= rounding * e[0];
+ }
+ /* Near the right corner */
+ if (d1 >= d2) {
+ sample(m_imageReader, right + 1, y + 1, e);
+ factor[0] -= rounding * e[0];
+ sample(m_imageReader, right + 1, y - 2, e);
+ factor[1] -= rounding * e[0];
+ }
+
+ weights[0] *= CLAMPIS(factor[0], 0.0f, 1.0f);
+ weights[1] *= CLAMPIS(factor[1], 0.0f, 1.0f);
+}
+
+void SMAABlendingWeightCalculationOperation::detectVerticalCornerPattern(
+ float weights[2], int x, int top, int bottom, int d1, int d2)
+{
+ float factor[2] = {1.0f, 1.0f};
+ float rounding = m_corner_rounding / 100.0f;
+ float e[4];
+
+ /* Reduce blending for pixels in the center of a line. */
+ rounding *= (d1 == d2) ? 0.5f : 1.0f;
+
+ /* Near the top corner */
+ if (d1 <= d2) {
+ sample(m_imageReader, x + 1, top, e);
+ factor[0] -= rounding * e[1];
+ sample(m_imageReader, x - 2, top, e);
+ factor[1] -= rounding * e[1];
+ }
+ /* Near the bottom corner */
+ if (d1 >= d2) {
+ sample(m_imageReader, x + 1, bottom + 1, e);
+ factor[0] -= rounding * e[1];
+ sample(m_imageReader, x - 2, bottom + 1, e);
+ factor[1] -= rounding * e[1];
+ }
+
+ weights[0] *= CLAMPIS(factor[0], 0.0f, 1.0f);
+ weights[1] *= CLAMPIS(factor[1], 0.0f, 1.0f);
+}
+
+/*-----------------------------------------------------------------------------*/
+/* Neighborhood Blending (Third Pass) */
+/*-----------------------------------------------------------------------------*/
+
+SMAANeighborhoodBlendingOperation::SMAANeighborhoodBlendingOperation()
+{
+ this->addInputSocket(DataType::Color); /* image */
+ this->addInputSocket(DataType::Color); /* blend */
+ this->addOutputSocket(DataType::Color);
+ this->setComplex(true);
+ this->m_image1Reader = nullptr;
+ this->m_image2Reader = nullptr;
+}
+
+void *SMAANeighborhoodBlendingOperation::initializeTileData(rcti *rect)
+{
+ return getInputOperation(0)->initializeTileData(rect);
+}
+
+void SMAANeighborhoodBlendingOperation::initExecution()
+{
+ this->m_image1Reader = this->getInputSocketReader(0);
+ this->m_image2Reader = this->getInputSocketReader(1);
+}
+
+void SMAANeighborhoodBlendingOperation::executePixel(float output[4],
+ int x,
+ int y,
+ void * /*data*/)
+{
+ float w[4];
+
+ /* Fetch the blending weights for current pixel: */
+ sample(m_image2Reader, x, y, w);
+ float left = w[2], top = w[0];
+ sample(m_image2Reader, x + 1, y, w);
+ float right = w[3];
+ sample(m_image2Reader, x, y + 1, w);
+ float bottom = w[1];
+
+ /* Is there any blending weight with a value greater than 0.0? */
+ if (right + bottom + left + top < 1e-5f) {
+ sample(m_image1Reader, x, y, output);
+ return;
+ }
+
+ /* Calculate the blending offsets: */
+ void (*samplefunc)(SocketReader * reader, int x, int y, float xoffset, float color[4]);
+ float offset1, offset2, weight1, weight2, color1[4], color2[4];
+
+ if (fmaxf(right, left) > fmaxf(bottom, top)) { /* max(horizontal) > max(vertical) */
+ samplefunc = sample_bilinear_horizontal;
+ offset1 = right;
+ offset2 = -left;
+ weight1 = right / (right + left);
+ weight2 = left / (right + left);
+ }
+ else {
+ samplefunc = sample_bilinear_vertical;
+ offset1 = bottom;
+ offset2 = -top;
+ weight1 = bottom / (bottom + top);
+ weight2 = top / (bottom + top);
+ }
+
+ /* We exploit bilinear filtering to mix current pixel with the chosen neighbor: */
+ samplefunc(m_image1Reader, x, y, offset1, color1);
+ samplefunc(m_image1Reader, x, y, offset2, color2);
+
+ mul_v4_v4fl(output, color1, weight1);
+ madd_v4_v4fl(output, color2, weight2);
+}
+
+void SMAANeighborhoodBlendingOperation::deinitExecution()
+{
+ this->m_image1Reader = nullptr;
+ this->m_image2Reader = nullptr;
+}
+
+bool SMAANeighborhoodBlendingOperation::determineDependingAreaOfInterest(
+ rcti *input, ReadBufferOperation *readOperation, rcti *output)
+{
+ rcti newInput;
+
+ newInput.xmax = input->xmax + 1;
+ newInput.xmin = input->xmin - 1;
+ newInput.ymax = input->ymax + 1;
+ newInput.ymin = input->ymin - 1;
+
+ return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
+}
diff --git a/source/blender/compositor/operations/COM_SMAAOperation.h b/source/blender/compositor/operations/COM_SMAAOperation.h
new file mode 100644
index 00000000000..8ac6fea6818
--- /dev/null
+++ b/source/blender/compositor/operations/COM_SMAAOperation.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2017, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor: IRIE Shinsuke
+ */
+
+#ifndef _COM_SMAAOperation_h
+#define _COM_SMAAOperation_h
+#include "COM_NodeOperation.h"
+
+/*-----------------------------------------------------------------------------*/
+/* Edge Detection (First Pass) */
+
+class SMAAEdgeDetectionOperation : public NodeOperation {
+ protected:
+ SocketReader *m_imageReader;
+ SocketReader *m_valueReader;
+
+ float m_threshold;
+ float m_contrast_limit;
+
+ public:
+ SMAAEdgeDetectionOperation();
+
+ /**
+ * the inner loop of this program
+ */
+ virtual void executePixel(float output[4], int x, int y, void *data) override;
+
+ /**
+ * Initialize the execution
+ */
+ void initExecution() override;
+
+ /**
+ * Deinitialize the execution
+ */
+ void deinitExecution() override;
+
+ void setThreshold(float threshold);
+
+ void setLocalContrastAdaptationFactor(float factor);
+
+ bool determineDependingAreaOfInterest(rcti *input,
+ ReadBufferOperation *readOperation,
+ rcti *output) override;
+};
+
+/*-----------------------------------------------------------------------------*/
+/* Blending Weight Calculation (Second Pass) */
+
+class SMAABlendingWeightCalculationOperation : public NodeOperation {
+ private:
+ SocketReader *m_imageReader;
+
+ int m_corner_rounding;
+
+ public:
+ SMAABlendingWeightCalculationOperation();
+
+ /**
+ * the inner loop of this program
+ */
+ void executePixel(float output[4], int x, int y, void *data);
+
+ /**
+ * Initialize the execution
+ */
+ void initExecution();
+ void *initializeTileData(rcti *rect);
+
+ /**
+ * Deinitialize the execution
+ */
+ void deinitExecution();
+
+ void setCornerRounding(float rounding);
+
+ bool determineDependingAreaOfInterest(rcti *input,
+ ReadBufferOperation *readOperation,
+ rcti *output);
+
+ private:
+ /* Diagonal Search Functions */
+ int searchDiag1(int x, int y, int dir, bool *found);
+ int searchDiag2(int x, int y, int dir, bool *found);
+ void calculateDiagWeights(int x, int y, const float edges[2], float weights[2]);
+ bool isVerticalSearchUnneeded(int x, int y);
+
+ /* Horizontal/Vertical Search Functions */
+ int searchXLeft(int x, int y);
+ int searchXRight(int x, int y);
+ int searchYUp(int x, int y);
+ int searchYDown(int x, int y);
+
+ /* Corner Detection Functions */
+ void detectHorizontalCornerPattern(float weights[2], int left, int right, int y, int d1, int d2);
+ void detectVerticalCornerPattern(float weights[2], int x, int top, int bottom, int d1, int d2);
+};
+
+/*-----------------------------------------------------------------------------*/
+/* Neighborhood Blending (Third Pass) */
+
+class SMAANeighborhoodBlendingOperation : public NodeOperation {
+ private:
+ SocketReader *m_image1Reader;
+ SocketReader *m_image2Reader;
+
+ public:
+ SMAANeighborhoodBlendingOperation();
+
+ /**
+ * the inner loop of this program
+ */
+ void executePixel(float output[4], int x, int y, void *data);
+
+ /**
+ * Initialize the execution
+ */
+ void initExecution();
+ void *initializeTileData(rcti *rect);
+
+ /**
+ * Deinitialize the execution
+ */
+ void deinitExecution();
+
+ bool determineDependingAreaOfInterest(rcti *input,
+ ReadBufferOperation *readOperation,
+ rcti *output);
+};
+
+#endif
diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c
index 6864d34885a..ac6eab69864 100644
--- a/source/blender/editors/space_node/drawnode.c
+++ b/source/blender/editors/space_node/drawnode.c
@@ -1528,6 +1528,17 @@ static void node_composit_buts_defocus(uiLayout *layout, bContext *C, PointerRNA
uiItemR(sub, ptr, "z_scale", DEFAULT_FLAGS, NULL, ICON_NONE);
}
+static void node_composit_buts_antialiasing(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiLayout *col;
+
+ col = uiLayoutColumn(layout, false);
+
+ uiItemR(col, ptr, "threshold", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "contrast_limit", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "corner_rounding", 0, NULL, ICON_NONE);
+}
+
/* qdn: glare node */
static void node_composit_buts_glare(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
@@ -2799,6 +2810,9 @@ static void node_composit_set_butfunc(bNodeType *ntype)
case CMP_NODE_DEFOCUS:
ntype->draw_buttons = node_composit_buts_defocus;
break;
+ case CMP_NODE_ANTIALIASING:
+ ntype->draw_buttons = node_composit_buts_antialiasing;
+ break;
case CMP_NODE_GLARE:
ntype->draw_buttons = node_composit_buts_glare;
break;
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 1b7d1aaee9d..7b4788737f8 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -715,6 +715,12 @@ typedef struct NodeBilateralBlurData {
char _pad[2];
} NodeBilateralBlurData;
+typedef struct NodeAntiAliasingData {
+ float threshold;
+ float contrast_limit;
+ float corner_rounding;
+} NodeAntiAliasingData;
+
/* NOTE: Only for do-version code. */
typedef struct NodeHueSat {
float hue, sat, val;
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index b697d872cf3..32b75243537 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -8662,6 +8662,41 @@ static void def_cmp_denoise(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
+static void def_cmp_antialiasing(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ RNA_def_struct_sdna_from(srna, "NodeAntiAliasingData", "storage");
+
+ prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "threshold");
+ RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
+ RNA_def_property_ui_text(
+ prop,
+ "Threshold",
+ "Threshold to detect edges (smaller threshold makes more sensitive detection)");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+
+ prop = RNA_def_property(srna, "contrast_limit", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "contrast_limit");
+ RNA_def_property_range(prop, 0.0f, 1.0f);
+ RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
+ RNA_def_property_ui_text(
+ prop,
+ "Contrast Limit",
+ "How much to eliminate spurious edges to avoid artifacts (the larger value makes less "
+ "active; the value 2.0, for example, means discard a detected edge if there is a "
+ "neighboring edge that has 2.0 times bigger contrast than the current one)");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+
+ prop = RNA_def_property(srna, "corner_rounding", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "corner_rounding");
+ RNA_def_property_range(prop, 0.0f, 1.0f);
+ RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
+ RNA_def_property_ui_text(prop, "Corner Rounding", "How much sharp corners will be rounded");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
/* -- Texture Nodes --------------------------------------------------------- */
static void def_tex_output(StructRNA *srna)
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index aba3c3e041a..07380addaff 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -46,6 +46,7 @@ set(INC
set(SRC
composite/nodes/node_composite_alphaOver.c
+ composite/nodes/node_composite_antialiasing.c
composite/nodes/node_composite_bilateralblur.c
composite/nodes/node_composite_blur.c
composite/nodes/node_composite_bokehblur.c
diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h
index b0dd0edeec5..258e4c961c9 100644
--- a/source/blender/nodes/NOD_composite.h
+++ b/source/blender/nodes/NOD_composite.h
@@ -79,6 +79,7 @@ void register_node_type_cmp_inpaint(void);
void register_node_type_cmp_despeckle(void);
void register_node_type_cmp_defocus(void);
void register_node_type_cmp_denoise(void);
+void register_node_type_cmp_antialiasing(void);
void register_node_type_cmp_valtorgb(void);
void register_node_type_cmp_rgbtobw(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index b3463f79a6e..0b1efaca502 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -224,6 +224,7 @@ DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE, def_cmp_cryptomatte, "CRYPTO
DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE_LEGACY, def_cmp_cryptomatte_legacy, "CRYPTOMATTE", Cryptomatte, "Cryptomatte (Legacy)", "" )
DefNode(CompositorNode, CMP_NODE_DENOISE, def_cmp_denoise, "DENOISE", Denoise, "Denoise", "" )
DefNode(CompositorNode, CMP_NODE_EXPOSURE, 0, "EXPOSURE", Exposure, "Exposure", "" )
+DefNode(CompositorNode, CMP_NODE_ANTIALIASING, def_cmp_antialiasing, "ANTIALIASING", AntiAliasing, "Anti-Aliasing", "" )
DefNode(TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" )
DefNode(TextureNode, TEX_NODE_CHECKER, 0, "CHECKER", Checker, "Checker", "" )
diff --git a/source/blender/nodes/composite/nodes/node_composite_antialiasing.c b/source/blender/nodes/composite/nodes/node_composite_antialiasing.c
new file mode 100644
index 00000000000..7437496d878
--- /dev/null
+++ b/source/blender/nodes/composite/nodes/node_composite_antialiasing.c
@@ -0,0 +1,65 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2017 Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): IRIE Shinsuke
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/nodes/composite/nodes/node_composite_antialiasing.c
+ * \ingroup cmpnodes
+ */
+
+#include "node_composite_util.h"
+
+/* **************** Anti-Aliasing (SMAA 1x) ******************** */
+
+static bNodeSocketTemplate cmp_node_antialiasing_in[] = {
+ {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}};
+
+static bNodeSocketTemplate cmp_node_antialiasing_out[] = {{SOCK_RGBA, N_("Image")}, {-1, ""}};
+
+static void node_composit_init_antialiasing(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ NodeAntiAliasingData *data = MEM_callocN(sizeof(NodeAntiAliasingData), "node antialiasing data");
+
+ data->threshold = 1.0f;
+ data->contrast_limit = 0.2f;
+ data->corner_rounding = 0.25f;
+
+ node->storage = data;
+}
+
+void register_node_type_cmp_antialiasing(void)
+{
+ static bNodeType ntype;
+
+ cmp_node_type_base(
+ &ntype, CMP_NODE_ANTIALIASING, "Anti-Aliasing", NODE_CLASS_OP_FILTER, NODE_PREVIEW);
+ node_type_socket_templates(&ntype, cmp_node_antialiasing_in, cmp_node_antialiasing_out);
+ node_type_size(&ntype, 170, 140, 200);
+ node_type_init(&ntype, node_composit_init_antialiasing);
+ node_type_storage(
+ &ntype, "NodeAntiAliasingData", node_free_standard_storage, node_copy_standard_storage);
+
+ nodeRegisterType(&ntype);
+}