[Bf-blender-cvs] [738f6d8] master: Cycles: Implement node deduplication routines

Sergey Sharybin noreply at git.blender.org
Mon Dec 28 14:23:36 CET 2015


Commit: 738f6d8127a18f5bbb748419bc30faec40550532
Author: Sergey Sharybin
Date:   Tue Dec 15 21:56:27 2015 +0500
Branches: master
https://developer.blender.org/rB738f6d8127a18f5bbb748419bc30faec40550532

Cycles: Implement node deduplication routines

The idea of this commit is to merge nodes which has identical settings
and matching inputs into a single node in order to minimize number of
SVM instructions.

This is quite simple bottom-top graph traversal and the trickiest part
is how to compare node settings without too much trouble which seems to
be solved is quite clean way.

Still possibilities for further improvements:

- Support comparison of BSDF nodes
- Support comparison of volume nodes
- Support comparison of curve mapping/ramp nodes

Reviewers: brecht, juicyfruit, dingto

Differential Revision: https://developer.blender.org/D1673

===================================================================

M	intern/cycles/render/graph.cpp
M	intern/cycles/render/graph.h
M	intern/cycles/render/nodes.h

===================================================================

diff --git a/intern/cycles/render/graph.cpp b/intern/cycles/render/graph.cpp
index 7bffb29..f0fb0a2 100644
--- a/intern/cycles/render/graph.cpp
+++ b/intern/cycles/render/graph.cpp
@@ -51,6 +51,42 @@ bool check_node_inputs_traversed(const ShaderNode *node,
 	return true;
 }
 
+bool check_node_inputs_equals(const ShaderNode *node_a,
+                              const ShaderNode *node_b)
+{
+	if(node_a->inputs.size() != node_b->inputs.size()) {
+		/* Happens with BSDF closure nodes which are currently sharing the same
+		 * name for all the BSDF types, making it impossible to filter out
+		 * incompatible nodes.
+		 */
+		return false;
+	}
+	for(int i = 0; i < node_a->inputs.size(); ++i) {
+		ShaderInput *input_a = node_a->inputs[i],
+		            *input_b = node_b->inputs[i];
+		assert(strcmp(input_a->name, input_b->name) == 0);
+		if(input_a->link == NULL && input_b->link == NULL) {
+			/* Unconnected inputs are expected to have the same value. */
+			if(input_a->value != input_b->value) {
+				return false;
+			}
+		}
+		else if(input_a->link != NULL && input_b->link != NULL) {
+			/* Expect links are to come from the same exact socket. */
+			if(input_a->link != input_b->link) {
+				return false;
+			}
+		}
+		else {
+			/* One socket has a link and another has not, inputs can't be
+			 * considered equal.
+			 */
+			return false;
+		}
+	}
+	return true;
+}
+
 }  /* namespace */
 
 /* Input and Output */
@@ -365,6 +401,7 @@ void ShaderGraph::copy_nodes(ShaderNodeSet& nodes, ShaderNodeMap& nnodemap)
 		}
 	}
 }
+
 /* Graph simplification */
 /* ******************** */
 
@@ -620,7 +657,7 @@ void ShaderGraph::constant_fold()
 	}
 }
 
-/* Step 3: Simplification.*/
+/* Step 3: Simplification. */
 void ShaderGraph::simplify_settings(Scene *scene)
 {
 	foreach(ShaderNode *node, nodes) {
@@ -628,6 +665,77 @@ void ShaderGraph::simplify_settings(Scene *scene)
 	}
 }
 
+/* Step 4: Deduplicate nodes with same settings. */
+void ShaderGraph::deduplicate_nodes()
+{
+	/* NOTES:
+	 * - Deduplication happens for nodes which has same exact settings and same
+	 *   exact input links configuration (either connected to same output or has
+	 *   the same exact default value).
+	 * - Deduplication happens in the bottom-top manner, so we know for fact that
+	 *   all traversed nodes are either can not be deduplicated at all or were
+	 *   already deduplicated.
+	 */
+
+	ShaderNodeSet done, scheduled;
+	queue<ShaderNode*> traverse_queue;
+
+	/* Schedule nodes which doesn't have any dependencies. */
+	foreach(ShaderNode *node, nodes) {
+		if(!check_node_inputs_has_links(node)) {
+			traverse_queue.push(node);
+			scheduled.insert(node);
+		}
+	}
+
+	while(!traverse_queue.empty()) {
+		ShaderNode *node = traverse_queue.front();
+		traverse_queue.pop();
+		done.insert(node);
+		/* Schedule the nodes which were depending on the current node. */
+		foreach(ShaderOutput *output, node->outputs) {
+			foreach(ShaderInput *input, output->links) {
+				if(scheduled.find(input->parent) != scheduled.end()) {
+					/* Node might not be optimized yet but scheduled already
+					 * by other dependencies. No need to re-schedule it.
+					 */
+					continue;
+				}
+				/* Schedule node if its inputs are fully done. */
+				if(check_node_inputs_traversed(input->parent, done)) {
+					traverse_queue.push(input->parent);
+					scheduled.insert(input->parent);
+				}
+			}
+		}
+		/* Try to merge this node with another one. */
+		foreach(ShaderNode *other_node, done) {
+			if(node == other_node) {
+				/* Don't merge with self. */
+				continue;
+			}
+			if(node->name != other_node->name) {
+				/* Can only de-duplicate nodes of the same type. */
+				continue;
+			}
+			if(!check_node_inputs_equals(node, other_node)) {
+				/* Node inputs are different, can't merge them, */
+				continue;
+			}
+			if(!node->equals(other_node)) {
+				/* Node settings are different. */
+				continue;
+			}
+			/* TODO(sergey): Consider making it an utility function. */
+			for(int i = 0; i < node->outputs.size(); ++i) {
+				vector<ShaderInput*> inputs = node->outputs[i]->links;
+				relink(node->inputs, inputs, other_node->outputs[i]);
+			}
+			break;
+		}
+	}
+}
+
 void ShaderGraph::break_cycles(ShaderNode *node, vector<bool>& visited, vector<bool>& on_stack)
 {
 	visited[node->id] = true;
@@ -671,7 +779,7 @@ void ShaderGraph::clean(Scene *scene)
 	simplify_settings(scene);
 
 	/* 4: De-duplication. */
-	/* TODO(dingto): Implement */
+	deduplicate_nodes();
 
 	/* we do two things here: find cycles and break them, and remove unused
 	 * nodes that don't feed into the output. how cycles are broken is
diff --git a/intern/cycles/render/graph.h b/intern/cycles/render/graph.h
index 2f852d6..0382cbc 100644
--- a/intern/cycles/render/graph.h
+++ b/intern/cycles/render/graph.h
@@ -238,6 +238,21 @@ public:
 	 * nodes group.
 	 */
 	virtual int get_feature() { return bump == SHADER_BUMP_NONE ? 0 : NODE_FEATURE_BUMP; }
+
+	/* Check whether settings of the node equals to another one.
+	 *
+	 * This is mainly used to check whether two nodes can be merged
+	 * together. Meaning, runtime stuff like node id and unbound slots
+	 * will be ignored for comparison.
+	 *
+	 * NOTE: If some node can't be de-duplicated for whatever reason it
+	 * is to be handled in the subclass.
+	 */
+	virtual bool equals(const ShaderNode *other)
+	{
+		return name == other->name &&
+		       bump == other->bump;
+	}
 };
 
 
@@ -311,13 +326,16 @@ protected:
 	void copy_nodes(ShaderNodeSet& nodes, ShaderNodeMap& nnodemap);
 
 	void break_cycles(ShaderNode *node, vector<bool>& visited, vector<bool>& on_stack);
-	void clean(Scene *scene);
-	void simplify_settings(Scene *scene);
-	void constant_fold();
 	void bump_from_displacement();
 	void refine_bump_nodes();
 	void default_inputs(bool do_osl);
 	void transform_multi_closure(ShaderNode *node, ShaderOutput *weight_out, bool volume);
+
+	/* Graph simplification routines. */
+	void clean(Scene *scene);
+	void constant_fold();
+	void simplify_settings(Scene *scene);
+	void deduplicate_nodes();
 };
 
 CCL_NAMESPACE_END
diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h
index b88227a..4eddc90 100644
--- a/intern/cycles/render/nodes.h
+++ b/intern/cycles/render/nodes.h
@@ -52,6 +52,20 @@ public:
 
 	enum Projection { FLAT, CUBE, TUBE, SPHERE };
 	Projection projection;
+
+	bool equals(const TextureMapping& other) {
+		return translation == other.translation &&
+		       rotation == other.rotation &&
+		       scale == other.scale &&
+		       use_minmax == other.use_minmax &&
+		       min == other.min &&
+		       max == other.max &&
+		       type == other.type &&
+		       x_mapping == other.x_mapping &&
+		       y_mapping == other.y_mapping &&
+		       z_mapping == other.z_mapping &&
+		       projection == other.projection;
+	}
 };
 
 /* Nodes */
@@ -69,12 +83,22 @@ class TextureNode : public ShaderNode {
 public:
 	TextureNode(const char *name_) : ShaderNode(name_) {}
 	TextureMapping tex_mapping;
+
+	virtual bool equals(const ShaderNode *other) {
+		return ShaderNode::equals(other) &&
+		       tex_mapping.equals(((const TextureNode*)other)->tex_mapping);
+	}
 };
 
 class ImageSlotTextureNode : public ImageSlotNode {
 public:
 	ImageSlotTextureNode(const char *name_) : ImageSlotNode(name_) {}
 	TextureMapping tex_mapping;
+
+	virtual bool equals(const ShaderNode *other) {
+		return ShaderNode::equals(other) &&
+		       tex_mapping.equals(((const ImageSlotTextureNode*)other)->tex_mapping);
+	}
 };
 
 class ImageTextureNode : public ImageSlotTextureNode {
@@ -99,6 +123,20 @@ public:
 
 	static ShaderEnum color_space_enum;
 	static ShaderEnum projection_enum;
+
+	virtual bool equals(const ShaderNode *other) {
+		const ImageTextureNode *image_node = (const ImageTextureNode*)other;
+		return ImageSlotTextureNode::equals(other) &&
+		       use_alpha == image_node->use_alpha &&
+		       filename == image_node->filename &&
+		       builtin_data == image_node->builtin_data &&
+		       color_space == image_node->color_space &&
+		       projection == image_node->projection &&
+		       interpolation == image_node->interpolation &&
+		       extension == image_node->extension &&
+		       projection_blend == image_node->projection_blend &&
+		       animated == image_node->animated;
+	}
 };
 
 class EnvironmentTextureNode : public ImageSlotTextureNode {
@@ -122,6 +160,18 @@ public:
 
 	static ShaderEnum color_space_enum;
 	static ShaderEnum projection_enum;
+
+	virtual bool equals(const ShaderNode *other) {
+		const EnvironmentTextureNode *env_node = (const EnvironmentTextureNode*)other;
+		return ImageSlotTextureNode::equals(other) &&
+		       use_alpha == env_node->use_alpha &&
+		       filename == env_node->filename &&
+		       builtin_data == env_node->builtin_data &&
+		       color_space == env_node->color_space &&
+		       projection == env_node->projection &&
+		       interpolation == env_node->interpolation &&
+		       animated == env_node->animated;
+	}
 };
 
 class SkyTextureNode : public TextureNode {
@@ -133,14 +183,26 @@ public:
 	float3 sun_direction;
 	float turbidity;
 	float ground_albedo;
-	
+
 	ustring type;
 	static ShaderEnum type_enum;
+
+	virtual bool equals(const ShaderNode *other) {
+		const SkyTextureNode *sky_node = (const SkyTextureNode*)other;
+		return TextureNode::equals(other) &&
+		       sun_direction == sky_node->sun_direction &&
+		       turbidity == sky_node->turbidity &&
+		       ground_albedo == sky_node->ground_albedo &&
+		       type == sky_node->type;
+	}
 };
 
 class OutputNode : public ShaderNode {
 public:
 	SHADER_NODE_CLASS(OutputNode)
+
+	/* Don't allow output node de-duplication. */
+	virtual bool equals(const ShaderNode * /*other*/) { return false; }
 };
 
 class GradientTextureNode : public TextureNode {
@@ -151,6 +213,12 @@ public:
 
 	ustring type;
 	static ShaderEnum type_enum;
+
+	virtual bool equals(const ShaderNode *other) {
+		const GradientTextureNode *gradient_node = (const GradientTextureNode*)other;
+		return TextureNode::equals(other) &&
+		       type == gradient_node->type;
+	}
 };
 
 class NoiseTextureNode : public TextureNode {
@@ -167,6 +235,12 @@ public:
 	ustring coloring;
 
 	static Sh

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list