Parcourir la source

[shgraph] Variables are now shader graph whide, global variable support

Clément Espeute il y a 7 mois
Parent
commit
ff568ae554

+ 62 - 31
hide/view/shadereditor/ShaderEditor.hx

@@ -222,7 +222,7 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 			if (e.dataTransfer.types.contains(variableList.getDragKeyName())) {
 				var index = Std.parseInt(e.dataTransfer.getData(variableList.getDragKeyName()));
 				var hasAnyWrite = false;
-				currentGraph.mapShaderVar((v) -> {
+				shaderGraph.mapShaderVar((v) -> {
 					if (v.varId == index && Std.downcast(v, hrt.shgraph.nodes.VarWrite) != null) {
 						hasAnyWrite = true;
 						return false;
@@ -294,7 +294,7 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 
 		variableList = new hide.comp.FancyArray(null, rightPannel.find(".variables"), "variables", "variables");
 
-		variableList.getItems = () -> currentGraph.variables;
+		variableList.getItems = () -> shaderGraph.variables;
 		variableList.getItemName = (v: ShaderGraphVariable) -> v.name;
 		variableList.reorderItem = moveVariable;
 		variableList.removeItem = removeVariable;
@@ -488,6 +488,7 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 			domainSelection.val(haxe.EnumTools.EnumValueTools.getName(curr));
 			graphEditor.reload();
 			graphEditor.centerView();
+			requestRecompile();
 		}
 
 		exec(false);
@@ -501,8 +502,8 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		var name = "New Variable";
 		var i = 0;
 		var index = 0;
-		while(i < currentGraph.variables.length) {
-			if (currentGraph.variables[i].name == name) {
+		while(i < shaderGraph.variables.length) {
+			if (shaderGraph.variables[i].name == name) {
 				i = 0;
 				index ++;
 				name = 'New Variable ($index)';
@@ -517,14 +518,13 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 			defValue: hrt.shgraph.ShaderGraph.getSgTypeDefVal(type),
 			isColor: isColor,
 		}
-		var graph = currentGraph;
 
 		function exec(isUndo: Bool) {
 			if (!isUndo) {
-				graph.variables.push(variable);
+				shaderGraph.variables.push(variable);
 			}
 			else {
-				graph.variables.remove(variable);
+				shaderGraph.variables.remove(variable);
 			}
 			variableList.refresh();
 			requestRecompile();
@@ -533,14 +533,22 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		undo.change(Custom(exec));
 	}
 
+	var validNameCheck = ~/^[_a-zA-Z][_a-zA-Z0-9]*$/;
+
 	function renameVariable(variable: ShaderGraphVariable, newName: String) {
-		var graph = currentGraph;
+		if (!validNameCheck.match(newName))
+		{
+			variableList.refresh();
+			ide.quickError('"$newName" is not a valid variable name (must start with _ or a letter, and only contains letters, numbers and underscores)');
+			return;
+		}
+
 		var oldName = variable.name;
 		function exec(isUndo: Bool) {
 			variable.name = !isUndo ? newName : oldName;
 			variableList.refresh();
 
-			var index = graph.variables.indexOf(variable);
+			var index = shaderGraph.variables.indexOf(variable);
 			currentGraph.mapShaderVar((variable: hrt.shgraph.nodes.ShaderVar) -> {
 				if (variable.varId == index) {
 					graphEditor.refreshBox(variable.id);
@@ -569,8 +577,8 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 				}
 
 				if (n >= 3) {
-					new Element('<div>Is Color</div>').appendTo(e);
-					hide.comp.PropsEditor.makePropEl({name: "isColor", t: PBool}, e);
+					var colorCheckbox = new Element('<div>Is Color</div>').appendTo(e);
+					hide.comp.PropsEditor.makePropEl({name: "isColor", t: PBool}, colorCheckbox);
 				}
 			case SgInt:
 				hide.comp.PropsEditor.makePropEl({name: "defValue", t: PInt()}, e);
@@ -578,6 +586,29 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 				throw "Unsupported variable type";
 		}
 
+		var globalCheckbox = new Element('<div title="If the variable is set to global, it will use the exact same name in the generated shader code, allowing it to be shared between multiple shaders in the shaderlist">Is Global <input type="checkbox"/></div>').appendTo(e);
+		var cb = globalCheckbox.find("input");
+		cb.prop("checked", variable.isGlobal);
+		cb.on("change", (e) -> {
+			var old = variable.isGlobal;
+			var val = cb.prop("checked");
+			variable.isGlobal = val;
+			for (graph in shaderGraph.graphs) {
+				if (graph.hasCycle()) {
+					variable.isGlobal = old;
+					ide.quickError('Cannot change isGlobal because variable write and reads are dependant on each other, and isGlobal change the order of the read and writes and it would create a cycle', 10.0);
+					variableList.refresh();
+					return;
+				}
+			}
+
+			undo.change(Field(variable, "isGlobal", old), () -> {
+				requestRecompile();
+				variableList.refresh();
+			});
+			requestRecompile();
+		});
+
 		var editRoot = new Element();
 		var edit = new hide.comp.PropsEditor(undo, editRoot);
 		edit.add(e, variable, (name: String) -> {
@@ -596,24 +627,24 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 		var remap: Array<Int> = [];
 		function exec(isUndo: Bool) {
 			if (!isUndo) {
-				var oldOrder = graph.variables.copy();
-				var rem = graph.variables.splice(oldIndex, 1);
-				graph.variables.insert(newIndex, rem[0]);
+				var oldOrder = shaderGraph.variables.copy();
+				var rem = shaderGraph.variables.splice(oldIndex, 1);
+				shaderGraph.variables.insert(newIndex, rem[0]);
 
 				for (oldIndex => v in oldOrder) {
-					remap[oldIndex] = graph.variables.indexOf(v);
+					remap[oldIndex] = shaderGraph.variables.indexOf(v);
 				}
 
-				graph.mapShaderVar((v) -> {
+				shaderGraph.mapShaderVar((v) -> {
 					v.varId = remap[v.varId];
 					return true;
 				});
 			}
 			else {
-				var rem = graph.variables.splice(newIndex, 1);
-				graph.variables.insert(oldIndex, rem[0]);
+				var rem = shaderGraph.variables.splice(newIndex, 1);
+				shaderGraph.variables.insert(oldIndex, rem[0]);
 
-				graph.mapShaderVar((v) -> {
+				shaderGraph.mapShaderVar((v) -> {
 					v.varId = remap.indexOf(v.varId);
 					return true;
 				});
@@ -627,26 +658,26 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 
 	function removeVariable(index: Int) {
 		var usedInGraph = false;
-		for (node in currentGraph.nodes) {
-			if (Std.downcast(node, hrt.shgraph.nodes.VarRead)?.varId == index ||
-				Std.downcast(node, hrt.shgraph.nodes.VarWrite)?.varId == index) {
+		shaderGraph.mapShaderVar((v) -> {
+			if (v.varId == index) {
 				usedInGraph = true;
-				break;
+				return false;
 			}
-		}
+			return true;
+		});
+
 		if (usedInGraph) {
-			hide.Ide.inst.quickError("Variable is used in graph");
+			hide.Ide.inst.quickError("Can't remove, variable is used in this Shader Graph");
 			return;
 		}
 
-		var graph = currentGraph;
-		var variable = graph.variables[index];
+		var variable = shaderGraph.variables[index];
 		function exec(isUndo: Bool) {
 			if (!isUndo) {
-				graph.variables.splice(index, 1);
+				shaderGraph.variables.splice(index, 1);
 
 				// fix id of variables above ours
-				graph.mapShaderVar((v) -> {
+				shaderGraph.mapShaderVar((v) -> {
 					if (v.varId > index) {
 						v.varId --;
 					}
@@ -654,10 +685,10 @@ class ShaderEditor extends hide.view.FileView implements GraphInterface.IGraphEd
 				});
 			}
 			else {
-				graph.variables.insert(index, variable);
+				shaderGraph.variables.insert(index, variable);
 
 				// fix id of variables above ours
-				graph.mapShaderVar((v) -> {
+				shaderGraph.mapShaderVar((v) -> {
 					if (v.varId > index-1) {
 						v.varId ++;
 					}

+ 9 - 5
hrt/shgraph/NodeGenContext.hx

@@ -123,13 +123,17 @@ class NodeGenContext {
 		expressions.push(makeAssign(v, expr));
 	}
 
-	public function getLocalTVar(id: Int, init: TExpr = null) : TVar {
-		var graphVar = graph.variables[id];
+	public function getShaderVariable(id: Int, init: TExpr = null) : TVar {
+		var graphVar = graph.parent.variables[id];
 		var type = ShaderGraph.sgTypeToType(graphVar.type);
-		var variable = MapUtils.getOrPut(localVars, id, {variable: {id: hxsl.Ast.Tools.allocVarId(), name: '_sg_local_$id', type: type, kind: Local}, isInit: false});
+		var variable = MapUtils.getOrPut(shaderVariables, id, {
+			var varId = hxsl.Ast.Tools.allocVarId();
+			var name = if (graphVar.isGlobal) ${graphVar.name} else '_local_${graphVar.name}_$varId';
+			{variable: {id: varId, name: name, type: type, kind: Local}, isInit: false}
+		});
 		if (init != null && !variable.isInit) {
 			variable.isInit = true;
-			addExpr(AstTools.makeVarDecl(variable.variable, init));
+			addExpr(AstTools.makeAssign(AstTools.makeVar(variable.variable), init));
 		}
 		return variable.variable;
 	}
@@ -378,5 +382,5 @@ class NodeGenContext {
 
 	var nodeInputInfo : Array<InputInfo>;
 	var globalVars: Map<String, ShaderGraph.ExternVarDef> = [];
-	var localVars: Map<Int, {variable: TVar, isInit: Bool}> = [];
+	var shaderVariables: Map<Int, {variable: TVar, isInit: Bool}> = [];
 }

+ 110 - 56
hrt/shgraph/ShaderGraph.hx

@@ -214,11 +214,13 @@ class ShaderGraphVariable {
 	var type: SgType;
 	var defValue: Dynamic;
 	var isColor: Bool = false;
+	var isGlobal: Bool = false;
 }
 
 @:access(hrt.shgraph.Graph)
 class ShaderGraphGenContext {
 	var graph : Graph;
+
 	var includePreviews : Bool;
 
 	public function new(graph: Graph, includePreviews: Bool = false) {
@@ -285,14 +287,6 @@ class ShaderGraphGenContext {
 			global.paramIndex = p.index;
 		}
 
-		// Default init uninitialized local vars
-		for (id => variable in genContext.localVars) {
-			if (variable.isInit)
-				continue;
-			var initExpr = AstTools.makeVarDecl(variable.variable, AstTools.makeDynamic(variable.variable.type, graph.variables[id].defValue));
-			expressions.unshift(initExpr);
-		}
-
 		return AstTools.makeExpr(TBlock(expressions), TVoid);
 	}
 
@@ -318,15 +312,36 @@ class ShaderGraphGenContext {
 			var empty = true;
 			var inputs = inst.getInputs();
 
-			// Add a dependency in the node topology between matchin Var Write and Var Read
-			// so the write is done before all the reads
-			var write = graph.findLocalVarWrite(Std.downcast(node.node, hrt.shgraph.nodes.VarRead));
-			if (write != null) {
-				nodeTopology[write.id].to.push(id);
-				nodeTopology[id].incoming ++;
-				empty = false;
+			var asShaderVar = Std.downcast(node.node, hrt.shgraph.nodes.ShaderVar);
+			if (asShaderVar != null) {
+				var variable = graph.parent.variables[asShaderVar.varId];
+
+				if (variable.isGlobal) {
+					// Global Write depend on their Read counterpart (because the write must happen after all the reads)
+					var write = Std.downcast(asShaderVar, hrt.shgraph.nodes.VarWrite);
+					if (write != null) {
+						for (node in nodes) {
+							var asRead = Std.downcast(node?.node, hrt.shgraph.nodes.VarRead);
+							if (asRead == null || asRead.varId != write.varId)
+								continue;
+							nodeTopology[asRead.id].to.push(id);
+							nodeTopology[id].incoming ++;
+							empty = false;
+						}
+					}
+
+				} else {
+					// Local Reads depend on their Write counterpart (because all the reads must happen after the write)
+					var write = graph.findLocalVarWrite(Std.downcast(node.node, hrt.shgraph.nodes.VarRead));
+					if (write != null) {
+						nodeTopology[write.id].to.push(id);
+						nodeTopology[id].incoming ++;
+						empty = false;
+					}
+				}
 			}
 
+
 			for (inputId => connection in inst.connections) {
 				if (connection == null)
 					continue;
@@ -380,10 +395,11 @@ class ShaderGraphGenContext {
 		return sortedNodes;
 	}
 }
-
+@:privateAccess(hrt.shgraph.Graph)
 class ShaderGraph extends hrt.prefab.Prefab {
 
 	var graphs : Array<Graph> = [];
+	public var variables : Array<ShaderGraphVariable> = [];
 
 	var cachedDef : hrt.prefab.Cache.ShaderDef = null;
 
@@ -395,6 +411,16 @@ class ShaderGraph extends hrt.prefab.Prefab {
 		parametersAvailable = [];
 		parametersKeys = [];
 
+		for (variable in json.variables ?? []) {
+			variables.push({
+				name: variable.name,
+				type: unserializeSgType(variable.type),
+				defValue: variable.defValue,
+				isColor: variable.isColor,
+				isGlobal: variable.isGlobal,
+			});
+		}
+
 		loadParameters(json.parameters ?? []);
 		for (domain in haxe.EnumTools.getConstructors(Domain)) {
 			var graph = new Graph(this, haxe.EnumTools.createByName(Domain, domain));
@@ -418,6 +444,18 @@ class ShaderGraph extends hrt.prefab.Prefab {
 			for (p in parametersAvailable) { id : p.id, name : p.name, type : [p.type.getName(), p.type.getParameters().toString()], defaultValue : p.defaultValue, index : p.index, internal : p.internal }
 		];
 
+		json.variables = [
+			for (variable in variables) {
+				{
+					name: variable.name,
+					type: serializeSgType(variable.type),
+					defValue: variable.defValue,
+					isColor: variable.isColor,
+					isGlobal: variable.isGlobal,
+				}
+			}
+		];
+
 		for (graph in graphs) {
 			var serName = EnumValueTools.getName(graph.domain);
 			Reflect.setField(json, serName, graph.saveToDynamic());
@@ -577,6 +615,12 @@ class ShaderGraph extends hrt.prefab.Prefab {
 			}
 		}
 
+		for (id => variable in nodeGen.shaderVariables) {
+			var initExpr = AstTools.makeAssign(AstTools.makeVar(variable.variable), AstTools.makeDynamic(variable.variable.type, this.variables[id].defValue));
+			__init__exprs.push(initExpr);
+			shaderData.vars.push(variable.variable);
+		}
+
 		if (__init__exprs.length != 0) {
 			var funcVar : TVar = {
 				name : "__init__",
@@ -730,6 +774,16 @@ class ShaderGraph extends hrt.prefab.Prefab {
 		}
 	}
 
+	/**
+		Iterate on all the shaderVars in the graph, breaking if the cb return false
+	**/
+	public function mapShaderVar(cb: (v: hrt.shgraph.nodes.ShaderVar) -> Bool) {
+		for (graph in graphs) {
+			if (!graph.mapShaderVar(cb))
+				return;
+		}
+	}
+
 	public function getGraph(domain: Domain) {
 		return graphs[domain.getIndex()];
 	}
@@ -742,8 +796,6 @@ class Graph {
 	var current_node_id = 0;
 	var nodes : Map<Int, ShaderNode> = [];
 
-	public var variables : Array<ShaderGraphVariable> = [];
-
 	public var parent : ShaderGraph = null;
 
 	public var domain : Domain = Fragment;
@@ -756,15 +808,6 @@ class Graph {
 
 	public function load(json : Dynamic) {
 		nodes = [];
-		for (variable in json.variables ?? []) {
-			variables.push({
-				name: variable.name,
-				type: unserializeSgType(variable.type),
-				defValue: variable.defValue,
-				isColor: variable.isColor,
-			});
-		}
-
 		generate(Reflect.getProperty(json, "nodes"), Reflect.getProperty(json, "edges"));
 	}
 
@@ -821,12 +864,32 @@ class Graph {
 				}
 			}
 
-			var write = findLocalVarWrite(Std.downcast(node, hrt.shgraph.nodes.VarRead));
-			if (write != null) {
-				if (hasCycle(write, visited))
-					return true;
-			}
+			var asShaderVar = Std.downcast(node, hrt.shgraph.nodes.ShaderVar);
+			if (asShaderVar != null) {
+				var variable = parent.variables[asShaderVar.varId];
+
+				if (variable.isGlobal) {
+					// Global Write depend on their Read counterpart (because the write must happen after all the reads)
+					var write = Std.downcast(asShaderVar, hrt.shgraph.nodes.VarWrite);
+					if (write != null) {
+						for (node in nodes) {
+							var asRead = Std.downcast(node, hrt.shgraph.nodes.VarRead);
+							if (asRead == null || asRead.varId != write.varId)
+								continue;
+							if (hasCycle(asRead, visited))
+								return true;
+						}
+					}
 
+				} else {
+					// Local Reads depend on their Write counterpart (because all the reads must happen after the write)
+					var write = findLocalVarWrite(Std.downcast(node, hrt.shgraph.nodes.VarRead));
+					if (write != null) {
+						if (hasCycle(write, visited))
+							return true;
+					}
+				}
+			}
 
 			return false;
 		}
@@ -953,20 +1016,6 @@ class Graph {
 		return parent.getParameter(id);
 	}
 
-	/**
-		Iterate on all the shaderVars in the graph, breaking if the cb return false
-	**/
-	public function mapShaderVar(cb: (v: hrt.shgraph.nodes.ShaderVar) -> Bool) {
-		for (node in nodes) {
-			var asVar = Std.downcast(node, hrt.shgraph.nodes.ShaderVar);
-			if (asVar != null) {
-				if (cb(asVar) == false) {
-					break;
-				}
-			}
-		}
-	}
-
 	public function hasCycle() : Bool {
 		var ctx = new ShaderGraphGenContext(this, false);
 		@:privateAccess ctx.initNodes();
@@ -987,25 +1036,30 @@ class Graph {
 				edgesJson.push({ outputNodeId: connection.from.id, nameOutput: connection.from.getOutputs()[outputId].name, inputNodeId: n.id, nameInput: n.getInputs()[inputId].name, inputId: inputId, outputId: outputId });
 			}
 		}
-		var variablesJson = [];
-		for (variable in variables) {
-			variablesJson.push({
-				name: variable.name,
-				type: serializeSgType(variable.type),
-				defValue: variable.defValue,
-				isColor: variable.isColor,
-			});
-		}
 
 		var json = {
 			nodes: [
 				for (n in nodes) n.serializeToDynamic(),
 			],
 			edges: edgesJson,
-			variables: variablesJson,
 		};
 
 		return json;
 	}
 
+	/**
+		Iterate on all the shaderVars in the graph, breaking if the cb return false
+	**/
+	public function mapShaderVar(cb: (v: hrt.shgraph.nodes.ShaderVar) -> Bool) {
+		for (node in nodes) {
+			var asVar = Std.downcast(node, hrt.shgraph.nodes.ShaderVar);
+			if (asVar != null) {
+				if (cb(asVar) == false) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
 }

+ 8 - 14
hrt/shgraph/nodes/VarRead.hx

@@ -1,9 +1,9 @@
 package hrt.shgraph.nodes;
 
 
-@name("Read Var")
+@name("Var Read")
 @description("Read a value from a local variable")
-@width(130)
+@width(80)
 @group("Variables")
 class VarRead extends ShaderVar {
 
@@ -13,26 +13,20 @@ class VarRead extends ShaderVar {
 	var outputs: Array<ShaderNode.OutputInfo>;
 	override public function getOutputs() : Array<ShaderNode.OutputInfo> {
 		if (outputs == null) {
-			outputs  = [{name: "output", type: graph.variables[varId].type}];
+			// cache the output array to avoid multiple allocations
+			outputs = [{name:"error", type: SgBool}];
 		}
+		// reassign name and type in case they have changed since the last getOutput
+		outputs[0].name = graph.parent.variables[varId]?.name ?? "error";
+		outputs[0].type = graph.parent.variables[varId]?.type ?? SgBool;
 		return outputs;
 	}
 
 	override function generate(ctx:NodeGenContext) {
-		var out = AstTools.makeVar(ctx.getLocalTVar(varId));
+		var out = AstTools.makeVar(ctx.getShaderVariable(varId));
 		ctx.setOutput(0, out);
 		#if editor
 		ctx.addPreview(out);
 		#end
 	}
-
-	#if editor
-	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
-		var info = super.getInfo();
-		if (editor != null) {
-			info.name = "Read: " + @:privateAccess (cast editor.editor: hide.view.shadereditor.ShaderEditor).currentGraph.variables[varId].name;
-		}
-		return info;
-	}
-	#end
 }

+ 7 - 14
hrt/shgraph/nodes/VarWrite.hx

@@ -1,8 +1,8 @@
 package hrt.shgraph.nodes;
 
-@name("Write Var")
+@name("Var Write")
 @description("Write a value to a local variable")
-@width(130)
+@width(80)
 @group("Variables")
 class VarWrite extends ShaderVar {
 
@@ -12,24 +12,17 @@ class VarWrite extends ShaderVar {
 	var inputs: Array<ShaderNode.InputInfo>;
 	override public function getInputs() : Array<ShaderNode.InputInfo> {
 		if (inputs == null) {
-			inputs = [{name: "input", type: graph.variables[varId].type}];
+			inputs = [{name:"error", type: SgBool}];
 		}
+		// reassign name and type in case they have changed since the last getInput
+		inputs[0].name = graph.parent.variables[varId]?.name ?? "error";
+		inputs[0].type = graph.parent.variables[varId]?.type ?? SgBool;
 		return inputs;
 	}
 
 	override function generate(ctx:NodeGenContext) {
 		var input = ctx.getInput(0);
-		var tVar = ctx.getLocalTVar(varId, input);
+		var tVar = ctx.getShaderVariable(varId, input);
 		ctx.addPreview(AstTools.makeVar(tVar));
 	}
-
-	#if editor
-	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
-		var info = super.getInfo();
-		if (editor != null) {
-			info.name = "Write: " + @:privateAccess (cast editor.editor: hide.view.shadereditor.ShaderEditor).currentGraph.variables[varId].name;
-		}
-		return info;
-	}
-	#end
 }