private string GetFunctionBody(MethodInfo info) { var args = new List <object>(); foreach (var param in info.GetParameters()) { args.Add(GetDefault(param.ParameterType)); } var result = info.Invoke(this, args.ToArray()) as string; if (string.IsNullOrEmpty(result)) { return(string.Empty); } using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetSlots(tempSlots); foreach (var slot in tempSlots) { var toReplace = string.Format("{{slot{0}dimension}}", slot.id); var replacement = NodeUtils.GetSlotDimension(slot.concreteValueType); result = result.Replace(toReplace, replacement); } } return(result); }
private string GetFunctionHeader() { string header = "void " + GetFunctionName() + "("; using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetSlots(tempSlots); tempSlots.Sort((slot1, slot2) => slot1.id.CompareTo(slot2.id)); var first = true; foreach (var slot in tempSlots) { if (!first) { header += ", "; } first = false; if (slot.isOutputSlot) { header += "out "; } header += slot.concreteValueType.ToShaderString() + " " + slot.shaderOutputName; } header += ")"; } return(header); }
private string GetFunctionHeader() { string header = "void " + GetFunctionName() + "("; using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetSlots(tempSlots); tempSlots.Sort((slot1, slot2) => slot1.id.CompareTo(slot2.id)); var first = true; foreach (var slot in tempSlots) { if (!first) { header += ", "; } first = false; if (slot.isOutputSlot) { header += "out "; } // always use generic precisions for parameters, they will get concretized by the system header += slot.concreteValueType.ToShaderString(PrecisionUtil.Token) + " " + slot.shaderOutputName; } header += ")"; } return(header); }
public NeededCoordinateSpace RequiresBitangent(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetInputSlots(tempSlots); var binding = NeededCoordinateSpace.None; foreach (var slot in tempSlots) { binding |= slot.RequiresBitangent(); } return(binding); } }
public bool RequiresMeshUV(Internal.UVChannel channel, ShaderStageCapability stageCapability) { using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) { if (slot.RequiresMeshUV(channel)) { return(true); } } return(false); } }
public bool RequiresPixelPosition(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) { if (slot.RequiresPixelPosition(stageCapability)) { return(true); } } return(false); } }
public bool RequiresMeshUV(UVChannel channel, ShaderStageCapability stageCapability) { var result = false; using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) { if (slot.RequiresMeshUV(channel)) { result = true; break; } } } return(result); }
public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode) { using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetOutputSlots(tempSlots); foreach (var outSlot in tempSlots) { sb.AppendLine(outSlot.concreteValueType.ToShaderString(PrecisionUtil.Token) + " " + GetVariableNameForSlot(outSlot.id) + ";"); } string call = GetFunctionName() + "("; bool first = true; tempSlots.Clear(); GetSlots(tempSlots); tempSlots.Sort((slot1, slot2) => slot1.id.CompareTo(slot2.id)); foreach (var slot in tempSlots) { if (!first) { call += ", "; } first = false; if (slot.isInputSlot) { call += GetSlotValue(slot.id, generationMode); } else { call += GetVariableNameForSlot(slot.id); } } call += ");"; sb.AppendLine(call); } }
static void ProcessSubGraph(SubGraphAsset asset, GraphData graph) { var registry = new FunctionRegistry(new ShaderStringBuilder(), true); registry.names.Clear(); asset.functions.Clear(); asset.isValid = true; graph.OnEnable(); graph.messageManager.ClearAll(); graph.ValidateGraph(); var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid); asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath)); asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}"; asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}"; asset.path = graph.path; var outputNode = graph.outputNode; var outputSlots = PooledList <MaterialSlot> .Get(); outputNode.GetInputSlots(outputSlots); List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>(); NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode); asset.effectiveShaderStage = ShaderStageCapability.All; foreach (var slot in outputSlots) { var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true); if (stage != ShaderStageCapability.All) { asset.effectiveShaderStage = stage; break; } } asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode); asset.requirements = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false); asset.graphPrecision = graph.concretePrecision; asset.outputPrecision = outputNode.concretePrecision; asset.previewMode = graph.previewMode; GatherDescendentsFromGraph(new GUID(asset.assetGuid), out var containsCircularDependency, out var descendents); asset.descendents.AddRange(descendents.Select(g => g.ToString())); asset.descendents.Sort(); // ensure deterministic order var childrenSet = new HashSet <string>(); var anyErrors = false; foreach (var node in nodes) { if (node is SubGraphNode subGraphNode) { var subGraphGuid = subGraphNode.subGraphGuid; childrenSet.Add(subGraphGuid); } if (node.hasError) { anyErrors = true; } asset.children = childrenSet.ToList(); asset.children.Sort(); // ensure deterministic order } if (!anyErrors && containsCircularDependency) { Debug.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset); anyErrors = true; } if (anyErrors) { asset.isValid = false; registry.ProvideFunction(asset.functionName, sb => { }); return; } foreach (var node in nodes) { if (node is IGeneratesFunction generatesFunction) { registry.builder.currentNode = node; generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals); registry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } // provide top level subgraph function registry.ProvideFunction(asset.functionName, sb => { GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName); sb.AppendNewLine(); // Generate arguments... first INPUTS var arguments = new List <string>(); foreach (var prop in graph.properties) { prop.ValidateConcretePrecision(asset.graphPrecision); arguments.Add(prop.GetPropertyAsArgumentString()); } // now pass surface inputs arguments.Add(string.Format("{0} IN", asset.inputStructName)); // Now generate outputs foreach (MaterialSlot output in outputSlots) { arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputPrecision)} {output.shaderOutputName}_{output.id}"); } // Vt Feedback arguments foreach (var output in asset.vtFeedbackVariables) { arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Single)} {output}_out"); } // Create the function prototype from the arguments sb.AppendLine("void {0}({1})" , asset.functionName , arguments.Aggregate((current, next) => $"{current}, {next}")); // now generate the function using (sb.BlockScope()) { // Just grab the body from the active nodes foreach (var node in nodes) { if (node is IGeneratesBodyCode generatesBodyCode) { sb.currentNode = node; generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals); sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } foreach (var slot in outputSlots) { sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals, asset.outputPrecision)};"); } foreach (var slot in asset.vtFeedbackVariables) { sb.AppendLine($"{slot}_out = {slot};"); } } }); asset.functions.AddRange(registry.names.Select(x => new FunctionPair(x, registry.sources[x].code))); var collector = new PropertyCollector(); foreach (var node in nodes) { int previousPropertyCount = Math.Max(0, collector.properties.Count - 1); node.CollectShaderProperties(collector, GenerationMode.ForReals); // This is a stop-gap to prevent the autogenerated values from JsonObject and ShaderInput from // resulting in non-deterministic import data. While we should move to local ids in the future, // this will prevent cascading shader recompilations. for (int i = previousPropertyCount; i < collector.properties.Count; ++i) { var prop = collector.properties[i]; var namespaceId = node.objectId; var nameId = prop.referenceName; prop.OverrideObjectId(namespaceId, nameId + "_ObjectId_" + i); prop.OverrideGuid(namespaceId, nameId + "_Guid_" + i); } } asset.WriteData(graph.properties, graph.keywords, collector.properties, outputSlots, graph.unsupportedTargets); outputSlots.Dispose(); }
void UpdatePorts() { switch (keyword.keywordType) { case KeywordType.Boolean: { // Boolean type has preset slots PooledList <MaterialSlot> temp = PooledList <MaterialSlot> .Get(); GetInputSlots(temp); if (temp.Any()) { temp.Dispose(); break; } else { temp.Dispose(); } AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero)); AddSlot(new DynamicVectorMaterialSlot(1, "On", "On", SlotType.Input, Vector4.zero)); AddSlot(new DynamicVectorMaterialSlot(2, "Off", "Off", SlotType.Input, Vector4.zero)); RemoveSlotsNameNotMatching(new int[] { 0, 1, 2 }); break; } case KeywordType.Enum: { // Get slots List <MaterialSlot> inputSlots = new List <MaterialSlot>(); GetInputSlots(inputSlots); // Store the edges Dictionary <MaterialSlot, List <IEdge> > edgeDict = new Dictionary <MaterialSlot, List <IEdge> >(); foreach (MaterialSlot slot in inputSlots) { edgeDict.Add(slot, (List <IEdge>)slot.owner.owner.GetEdges(slot.slotReference)); } // Remove old slots for (int i = 0; i < inputSlots.Count; i++) { RemoveSlot(inputSlots[i].id); } // Add output slot AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero)); // Add input slots int[] slotIds = new int[keyword.entries.Count + 1]; slotIds[keyword.entries.Count] = OutputSlotId; for (int i = 0; i < keyword.entries.Count; i++) { // Get slot based on entry id MaterialSlot slot = inputSlots.Where(x => x.id == keyword.entries[i].id && x.RawDisplayName() == keyword.entries[i].displayName && x.shaderOutputName == keyword.entries[i].referenceName).FirstOrDefault(); // If slot doesnt exist its new so create it if (slot == null) { slot = new DynamicVectorMaterialSlot(keyword.entries[i].id, keyword.entries[i].displayName, keyword.entries[i].referenceName, SlotType.Input, Vector4.zero); } AddSlot(slot); slotIds[i] = keyword.entries[i].id; } RemoveSlotsNameNotMatching(slotIds); // Reconnect the edges foreach (KeyValuePair <MaterialSlot, List <IEdge> > entry in edgeDict) { foreach (IEdge edge in entry.Value) { owner.Connect(edge.outputSlot, edge.inputSlot); } } break; } } ValidateNode(); }
static void ProcessSubGraph(SubGraphAsset asset, GraphData graph) { var graphIncludes = new IncludeCollection(); var registry = new FunctionRegistry(new ShaderStringBuilder(), graphIncludes, true); asset.functions.Clear(); asset.isValid = true; graph.OnEnable(); graph.messageManager.ClearAll(); graph.ValidateGraph(); var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid); asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath)); asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}_$precision"; asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}_$precision"; asset.path = graph.path; var outputNode = graph.outputNode; var outputSlots = PooledList <MaterialSlot> .Get(); outputNode.GetInputSlots(outputSlots); List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>(); NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode); asset.effectiveShaderStage = ShaderStageCapability.All; foreach (var slot in outputSlots) { var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true); if (stage != ShaderStageCapability.All) { asset.effectiveShaderStage = stage; break; } } asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode); asset.requirements = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false); // output precision is whatever the output node has as a graph precision, falling back to the graph default asset.outputGraphPrecision = outputNode.graphPrecision.GraphFallback(graph.graphDefaultPrecision); // this saves the graph precision, which indicates whether this subgraph is switchable or not asset.subGraphGraphPrecision = graph.graphDefaultPrecision; asset.previewMode = graph.previewMode; asset.includes = graphIncludes; GatherDescendentsFromGraph(new GUID(asset.assetGuid), out var containsCircularDependency, out var descendents); asset.descendents.AddRange(descendents.Select(g => g.ToString())); asset.descendents.Sort(); // ensure deterministic order var childrenSet = new HashSet <string>(); var anyErrors = false; foreach (var node in nodes) { if (node is SubGraphNode subGraphNode) { var subGraphGuid = subGraphNode.subGraphGuid; childrenSet.Add(subGraphGuid); } if (node.hasError) { anyErrors = true; } asset.children = childrenSet.ToList(); asset.children.Sort(); // ensure deterministic order } if (!anyErrors && containsCircularDependency) { Debug.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset); anyErrors = true; } if (anyErrors) { asset.isValid = false; registry.ProvideFunction(asset.functionName, sb => {}); return; } foreach (var node in nodes) { if (node is IGeneratesFunction generatesFunction) { registry.builder.currentNode = node; generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals); } } // provide top level subgraph function // NOTE: actual concrete precision here shouldn't matter, it's irrelevant when building the subgraph asset registry.ProvideFunction(asset.functionName, asset.subGraphGraphPrecision, ConcretePrecision.Single, sb => { GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName); sb.AppendNewLine(); // Generate the arguments... first INPUTS var arguments = new List <string>(); foreach (var prop in graph.properties) { // apply fallback to the graph default precision (but don't convert to concrete) // this means "graph switchable" properties will use the precision token GraphPrecision propGraphPrecision = prop.precision.ToGraphPrecision(graph.graphDefaultPrecision); string precisionString = propGraphPrecision.ToGenericString(); arguments.Add(prop.GetPropertyAsArgumentString(precisionString)); if (prop.isConnectionTestable) { arguments.Add($"bool {prop.GetConnectionStateHLSLVariableName()}"); } } { var dropdowns = graph.dropdowns; foreach (var dropdown in dropdowns) { arguments.Add($"int {dropdown.referenceName}"); } } // now pass surface inputs arguments.Add(string.Format("{0} IN", asset.inputStructName)); // Now generate output arguments foreach (MaterialSlot output in outputSlots) { arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputGraphPrecision.ToGenericString())} {output.shaderOutputName}_{output.id}"); } // Vt Feedback output arguments (always full float4) foreach (var output in asset.vtFeedbackVariables) { arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Single)} {output}_out"); } // Create the function prototype from the arguments sb.AppendLine("void {0}({1})" , asset.functionName , arguments.Aggregate((current, next) => $"{current}, {next}")); // now generate the function using (sb.BlockScope()) { // Just grab the body from the active nodes foreach (var node in nodes) { if (node is IGeneratesBodyCode generatesBodyCode) { sb.currentNode = node; generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals); if (node.graphPrecision == GraphPrecision.Graph) { // code generated by nodes that use graph precision stays in generic form with embedded tokens // those tokens are replaced when this subgraph function is pulled into a graph that defines the precision } else { sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } } foreach (var slot in outputSlots) { sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals)};"); } foreach (var slot in asset.vtFeedbackVariables) { sb.AppendLine($"{slot}_out = {slot};"); } } }); // save all of the node-declared functions to the subgraph asset foreach (var name in registry.names) { var source = registry.sources[name]; var func = new FunctionPair(name, source.code, source.graphPrecisionFlags); asset.functions.Add(func); } var collector = new PropertyCollector(); foreach (var node in nodes) { int previousPropertyCount = Math.Max(0, collector.propertyCount - 1); node.CollectShaderProperties(collector, GenerationMode.ForReals); // This is a stop-gap to prevent the autogenerated values from JsonObject and ShaderInput from // resulting in non-deterministic import data. While we should move to local ids in the future, // this will prevent cascading shader recompilations. for (int i = previousPropertyCount; i < collector.propertyCount; ++i) { var prop = collector.GetProperty(i); var namespaceId = node.objectId; var nameId = prop.referenceName; prop.OverrideObjectId(namespaceId, nameId + "_ObjectId_" + i); prop.OverrideGuid(namespaceId, nameId + "_Guid_" + i); } } asset.WriteData(graph.properties, graph.keywords, graph.dropdowns, collector.properties, outputSlots, graph.unsupportedTargets); outputSlots.Dispose(); }
public override void EvaluateDynamicMaterialSlots() { var dynamicInputSlotsToCompare = DictionaryPool <DynamicVectorMaterialSlot, ConcreteSlotValueType> .Get(); var skippedDynamicSlots = ListPool <DynamicVectorMaterialSlot> .Get(); var dynamicMatrixInputSlotsToCompare = DictionaryPool <DynamicMatrixMaterialSlot, ConcreteSlotValueType> .Get(); var skippedDynamicMatrixSlots = ListPool <DynamicMatrixMaterialSlot> .Get(); // iterate the input slots using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetInputSlots(tempSlots); foreach (var inputSlot in tempSlots) { inputSlot.hasError = false; // if there is a connection var edges = owner.GetEdges(inputSlot.slotReference).ToList(); if (!edges.Any()) { if (inputSlot is DynamicVectorMaterialSlot) { skippedDynamicSlots.Add(inputSlot as DynamicVectorMaterialSlot); } if (inputSlot is DynamicMatrixMaterialSlot) { skippedDynamicMatrixSlots.Add(inputSlot as DynamicMatrixMaterialSlot); } continue; } // get the output details var outputSlotRef = edges[0].outputSlot; var outputNode = owner.GetNodeFromGuid(outputSlotRef.nodeGuid); if (outputNode == null) { continue; } var outputSlot = outputNode.FindOutputSlot <MaterialSlot>(outputSlotRef.slotId); if (outputSlot == null) { continue; } if (outputSlot.hasError) { inputSlot.hasError = true; continue; } var outputConcreteType = outputSlot.concreteValueType; // dynamic input... depends on output from other node. // we need to compare ALL dynamic inputs to make sure they // are compatable. if (inputSlot is DynamicVectorMaterialSlot) { dynamicInputSlotsToCompare.Add((DynamicVectorMaterialSlot)inputSlot, outputConcreteType); continue; } else if (inputSlot is DynamicMatrixMaterialSlot) { dynamicMatrixInputSlotsToCompare.Add((DynamicMatrixMaterialSlot)inputSlot, outputConcreteType); continue; } } // and now dynamic matrices var dynamicMatrixType = ConvertDynamicMatrixInputTypeToConcrete(dynamicMatrixInputSlotsToCompare.Values); foreach (var dynamicKvP in dynamicMatrixInputSlotsToCompare) { dynamicKvP.Key.SetConcreteType(dynamicMatrixType); } foreach (var skippedSlot in skippedDynamicMatrixSlots) { skippedSlot.SetConcreteType(dynamicMatrixType); } // we can now figure out the dynamic slotType // from here set all the var dynamicType = SlotValueHelper.ConvertMatrixToVectorType(dynamicMatrixType); foreach (var dynamicKvP in dynamicInputSlotsToCompare) { dynamicKvP.Key.SetConcreteType(dynamicType); } foreach (var skippedSlot in skippedDynamicSlots) { skippedSlot.SetConcreteType(dynamicType); } tempSlots.Clear(); GetInputSlots(tempSlots); bool inputError = tempSlots.Any(x => x.hasError); if (inputError) { owner.AddConcretizationError(guid, string.Format("Node {0} had input error", guid)); hasError = true; } // configure the output slots now // their slotType will either be the default output slotType // or the above dynanic slotType for dynamic nodes // or error if there is an input error tempSlots.Clear(); GetOutputSlots(tempSlots); foreach (var outputSlot in tempSlots) { outputSlot.hasError = false; if (inputError) { outputSlot.hasError = true; continue; } if (outputSlot is DynamicVectorMaterialSlot) { (outputSlot as DynamicVectorMaterialSlot).SetConcreteType(dynamicType); continue; } else if (outputSlot is DynamicMatrixMaterialSlot) { (outputSlot as DynamicMatrixMaterialSlot).SetConcreteType(dynamicMatrixType); continue; } } tempSlots.Clear(); GetOutputSlots(tempSlots); if (tempSlots.Any(x => x.hasError)) { owner.AddConcretizationError(guid, string.Format("Node {0} had output error", guid)); hasError = true; } } CalculateNodeHasError(); ListPool <DynamicVectorMaterialSlot> .Release(skippedDynamicSlots); DictionaryPool <DynamicVectorMaterialSlot, ConcreteSlotValueType> .Release(dynamicInputSlotsToCompare); ListPool <DynamicMatrixMaterialSlot> .Release(skippedDynamicMatrixSlots); DictionaryPool <DynamicMatrixMaterialSlot, ConcreteSlotValueType> .Release(dynamicMatrixInputSlotsToCompare); }
static void ProcessSubGraph(SubGraphAsset asset, GraphData graph) { var registry = new FunctionRegistry(new ShaderStringBuilder(), true); registry.names.Clear(); asset.functions.Clear(); asset.isValid = true; graph.OnEnable(); graph.messageManager.ClearAll(); graph.ValidateGraph(); var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid); asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath)); asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}"; asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}"; asset.path = graph.path; var outputNode = (SubGraphOutputNode)graph.outputNode; var outputSlots = PooledList <MaterialSlot> .Get(); outputNode.GetInputSlots(outputSlots); List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>(); NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode); asset.effectiveShaderStage = ShaderStageCapability.All; foreach (var slot in outputSlots) { var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true); if (stage != ShaderStageCapability.All) { asset.effectiveShaderStage = stage; break; } } asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode); asset.requirements = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false); asset.graphPrecision = graph.concretePrecision; asset.outputPrecision = outputNode.concretePrecision; GatherFromGraph(assetPath, out var containsCircularDependency, out var descendents); asset.descendents.AddRange(descendents); var childrenSet = new HashSet <string>(); var anyErrors = false; foreach (var node in nodes) { if (node is SubGraphNode subGraphNode) { var subGraphGuid = subGraphNode.subGraphGuid; if (childrenSet.Add(subGraphGuid)) { asset.children.Add(subGraphGuid); } } if (node.hasError) { anyErrors = true; } } if (!anyErrors && containsCircularDependency) { Debug.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset); anyErrors = true; } if (anyErrors) { asset.isValid = false; registry.ProvideFunction(asset.functionName, sb => { }); return; } foreach (var node in nodes) { if (node is IGeneratesFunction generatesFunction) { registry.builder.currentNode = node; generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals); registry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } registry.ProvideFunction(asset.functionName, sb => { GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName); sb.AppendNewLine(); // Generate arguments... first INPUTS var arguments = new List <string>(); foreach (var prop in graph.properties) { prop.ValidateConcretePrecision(asset.graphPrecision); arguments.Add(string.Format("{0}", prop.GetPropertyAsArgumentString())); } // now pass surface inputs arguments.Add(string.Format("{0} IN", asset.inputStructName)); // Now generate outputs foreach (var output in outputSlots) { arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputPrecision)} {output.shaderOutputName}_{output.id}"); } // Vt Feedback arguments foreach (var output in asset.vtFeedbackVariables) { arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Float)} {output}_out"); } // Create the function prototype from the arguments sb.AppendLine("void {0}({1})" , asset.functionName , arguments.Aggregate((current, next) => $"{current}, {next}")); // now generate the function using (sb.BlockScope()) { // Just grab the body from the active nodes foreach (var node in nodes) { if (node is IGeneratesBodyCode generatesBodyCode) { sb.currentNode = node; generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals); sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } foreach (var slot in outputSlots) { sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals, asset.outputPrecision)};"); } foreach (var slot in asset.vtFeedbackVariables) { sb.AppendLine($"{slot}_out = {slot};"); } } }); asset.functions.AddRange(registry.names.Select(x => new FunctionPair(x, registry.sources[x].code))); var collector = new PropertyCollector(); foreach (var node in nodes) { node.CollectShaderProperties(collector, GenerationMode.ForReals); } asset.WriteData(graph.properties, graph.keywords, collector.properties, outputSlots); outputSlots.Dispose(); }
// Internal validation // ------------------------------------------------- public override void EvaluateDynamicMaterialSlots() { var dynamicInputSlotsToCompare = DictionaryPool <DynamicValueMaterialSlot, ConcreteSlotValueType> .Get(); var skippedDynamicSlots = ListPool <DynamicValueMaterialSlot> .Get(); // iterate the input slots using (var tempSlots = PooledList <MaterialSlot> .Get()) { GetInputSlots(tempSlots); foreach (var inputSlot in tempSlots) { inputSlot.hasError = false; // if there is a connection var edges = owner.GetEdges(inputSlot.slotReference).ToList(); if (!edges.Any()) { if (inputSlot is DynamicValueMaterialSlot) { skippedDynamicSlots.Add(inputSlot as DynamicValueMaterialSlot); } continue; } // get the output details var outputSlotRef = edges[0].outputSlot; var outputNode = owner.GetNodeFromGuid(outputSlotRef.nodeGuid); if (outputNode == null) { continue; } var outputSlot = outputNode.FindOutputSlot <MaterialSlot>(outputSlotRef.slotId); if (outputSlot == null) { continue; } if (outputSlot.hasError) { inputSlot.hasError = true; continue; } var outputConcreteType = outputSlot.concreteValueType; // dynamic input... depends on output from other node. // we need to compare ALL dynamic inputs to make sure they // are compatable. if (inputSlot is DynamicValueMaterialSlot) { dynamicInputSlotsToCompare.Add((DynamicValueMaterialSlot)inputSlot, outputConcreteType); continue; } } m_MultiplyType = GetMultiplyType(dynamicInputSlotsToCompare.Values); // Resolve dynamics depending on matrix/vector configuration switch (m_MultiplyType) { // If all matrix resolve as per dynamic matrix case MultiplyType.Matrix: var dynamicMatrixType = ConvertDynamicMatrixInputTypeToConcrete(dynamicInputSlotsToCompare.Values); foreach (var dynamicKvP in dynamicInputSlotsToCompare) { dynamicKvP.Key.SetConcreteType(dynamicMatrixType); } foreach (var skippedSlot in skippedDynamicSlots) { skippedSlot.SetConcreteType(dynamicMatrixType); } break; // If mixed handle differently: // Iterate all slots and set their concretes based on their edges // Find matrix slot and convert its type to a vector type // Reiterate all slots and set non matrix slots to the vector type case MultiplyType.Mixed: foreach (var dynamicKvP in dynamicInputSlotsToCompare) { SetConcreteValueTypeFromEdge(dynamicKvP.Key); } MaterialSlot matrixSlot = GetMatrixSlot(); ConcreteSlotValueType vectorType = SlotValueHelper.ConvertMatrixToVectorType(matrixSlot.concreteValueType); foreach (var dynamicKvP in dynamicInputSlotsToCompare) { if (dynamicKvP.Key != matrixSlot) { dynamicKvP.Key.SetConcreteType(vectorType); } } foreach (var skippedSlot in skippedDynamicSlots) { skippedSlot.SetConcreteType(vectorType); } break; // If all vector resolve as per dynamic vector default: var dynamicVectorType = ConvertDynamicVectorInputTypeToConcrete(dynamicInputSlotsToCompare.Values); foreach (var dynamicKvP in dynamicInputSlotsToCompare) { dynamicKvP.Key.SetConcreteType(dynamicVectorType); } foreach (var skippedSlot in skippedDynamicSlots) { skippedSlot.SetConcreteType(dynamicVectorType); } break; } tempSlots.Clear(); GetInputSlots(tempSlots); bool inputError = tempSlots.Any(x => x.hasError); if (inputError) { owner.AddConcretizationError(guid, string.Format("Node {0} had input error", guid)); hasError = true; } // configure the output slots now // their slotType will either be the default output slotType // or the above dynanic slotType for dynamic nodes // or error if there is an input error tempSlots.Clear(); GetOutputSlots(tempSlots); foreach (var outputSlot in tempSlots) { outputSlot.hasError = false; if (inputError) { outputSlot.hasError = true; continue; } if (outputSlot is DynamicValueMaterialSlot) { // Apply similar logic to output slot switch (m_MultiplyType) { // As per dynamic matrix case MultiplyType.Matrix: var dynamicMatrixType = ConvertDynamicMatrixInputTypeToConcrete(dynamicInputSlotsToCompare.Values); (outputSlot as DynamicValueMaterialSlot).SetConcreteType(dynamicMatrixType); break; // Mixed configuration // Find matrix slot and convert type to vector // Set output concrete to vector case MultiplyType.Mixed: MaterialSlot matrixSlot = GetMatrixSlot(); ConcreteSlotValueType vectorType = SlotValueHelper.ConvertMatrixToVectorType(matrixSlot.concreteValueType); (outputSlot as DynamicValueMaterialSlot).SetConcreteType(vectorType); break; // As per dynamic vector default: var dynamicVectorType = ConvertDynamicVectorInputTypeToConcrete(dynamicInputSlotsToCompare.Values); (outputSlot as DynamicValueMaterialSlot).SetConcreteType(dynamicVectorType); break; } continue; } } tempSlots.Clear(); GetOutputSlots(tempSlots); if (tempSlots.Any(x => x.hasError)) { owner.AddConcretizationError(guid, string.Format("Node {0} had output error", guid)); hasError = true; } } CalculateNodeHasError(); ListPool <DynamicValueMaterialSlot> .Release(skippedDynamicSlots); DictionaryPool <DynamicValueMaterialSlot, ConcreteSlotValueType> .Release(dynamicInputSlotsToCompare); }
// TODO: could get rid of this if we could run a codegen prepass (with proper keyword #ifdef) public static void GenerateVirtualTextureFeedback( List <AbstractMaterialNode> downstreamNodesIncludingRoot, List <int>[] keywordPermutationsPerNode, ShaderStringBuilder surfaceDescriptionFunction, KeywordCollector shaderKeywords) { // A note on how we handle vt feedback in combination with keywords: // We essentially generate a fully separate feedback path for each permutation of keywords // so per permutation we gather variables contribution to feedback and we generate // feedback gathering for each permutation individually. var feedbackVariablesPerPermutation = PooledList <PooledList <string> > .Get(); try { if (shaderKeywords.permutations.Count >= 1) { for (int i = 0; i < shaderKeywords.permutations.Count; i++) { feedbackVariablesPerPermutation.Add(PooledList <string> .Get()); } } else { // Create a dummy single permutation feedbackVariablesPerPermutation.Add(PooledList <string> .Get()); } int index = 0; //for keywordPermutationsPerNode foreach (var node in downstreamNodesIncludingRoot) { if (node is SampleVirtualTextureNode vtNode) { if (vtNode.noFeedback) { continue; } if (keywordPermutationsPerNode[index] == null) { Debug.Assert(shaderKeywords.permutations.Count == 0, $"Shader has {shaderKeywords.permutations.Count} permutations but keywordPermutationsPerNode of some nodes are null."); feedbackVariablesPerPermutation[0].Add(vtNode.GetFeedbackVariableName()); } else { foreach (int perm in keywordPermutationsPerNode[index]) { feedbackVariablesPerPermutation[perm].Add(vtNode.GetFeedbackVariableName()); } } } if (node is SubGraphNode sgNode) { if (sgNode.asset == null) { continue; } if (keywordPermutationsPerNode[index] == null) { Debug.Assert(shaderKeywords.permutations.Count == 0, $"Shader has {shaderKeywords.permutations.Count} permutations but keywordPermutationsPerNode of some nodes are null."); foreach (var feedbackSlot in sgNode.asset.vtFeedbackVariables) { feedbackVariablesPerPermutation[0].Add(node.GetVariableNameForNode() + "_" + feedbackSlot); } } else { foreach (var feedbackSlot in sgNode.asset.vtFeedbackVariables) { foreach (int perm in keywordPermutationsPerNode[index]) { feedbackVariablesPerPermutation[perm].Add(node.GetVariableNameForNode() + "_" + feedbackSlot); } } } } index++; } index = 0; foreach (var feedbackVariables in feedbackVariablesPerPermutation) { // If it's a dummy single always-on permutation don't put an ifdef around the code if (shaderKeywords.permutations.Count >= 1) { surfaceDescriptionFunction.AppendLine(KeywordUtil.GetKeywordPermutationConditional(index)); } using (surfaceDescriptionFunction.BlockScope()) { if (feedbackVariables.Count == 0) { string feedBackCode = "surface.VTPackedFeedback = float4(1.0f,1.0f,1.0f,1.0f);"; surfaceDescriptionFunction.AppendLine(feedBackCode); } else if (feedbackVariables.Count == 1) { string feedBackCode = "surface.VTPackedFeedback = GetPackedVTFeedback(" + feedbackVariables[0] + ");"; surfaceDescriptionFunction.AppendLine(feedBackCode); } else if (feedbackVariables.Count > 1) { surfaceDescriptionFunction.AppendLine("float4 VTFeedback_array[" + feedbackVariables.Count + "];"); int arrayIndex = 0; foreach (var variable in feedbackVariables) { surfaceDescriptionFunction.AppendLine("VTFeedback_array[" + arrayIndex + "] = " + variable + ";"); arrayIndex++; } // TODO: should read from NDCPosition instead... surfaceDescriptionFunction.AppendLine("uint pixelColumn = (IN.ScreenPosition.x / IN.ScreenPosition.w) * _ScreenParams.x;"); surfaceDescriptionFunction.AppendLine( "surface.VTPackedFeedback = GetPackedVTFeedback(VTFeedback_array[(pixelColumn + _FrameCount) % (uint)" + feedbackVariables.Count + "]);"); } } if (shaderKeywords.permutations.Count >= 1) { surfaceDescriptionFunction.AppendLine("#endif"); } index++; } } finally { foreach (var list in feedbackVariablesPerPermutation) { list.Dispose(); } feedbackVariablesPerPermutation.Dispose(); } }
void UpdatePorts() { switch (keyword.keywordType) { case KeywordType.Boolean: { // Boolean type has preset slots PooledList <MaterialSlot> temp = PooledList <MaterialSlot> .Get(); GetInputSlots(temp); if (temp.Any()) { temp.Dispose(); break; } else { temp.Dispose(); } AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero)); AddSlot(new DynamicVectorMaterialSlot(1, "On", "On", SlotType.Input, Vector4.zero)); AddSlot(new DynamicVectorMaterialSlot(2, "Off", "Off", SlotType.Input, Vector4.zero)); RemoveSlotsNameNotMatching(new int[] { 0, 1, 2 }); break; } case KeywordType.Enum: using (var inputSlots = PooledList <MaterialSlot> .Get()) using (var slotIDs = PooledList <int> .Get()) { // Get slots GetInputSlots(inputSlots); // Add output slot AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero)); slotIDs.Add(OutputSlotId); // Add input slots for (int i = 0; i < keyword.entries.Count; i++) { // Get slot based on entry id MaterialSlot slot = inputSlots.Find(x => x.id == keyword.entries[i].id && x.RawDisplayName() == keyword.entries[i].displayName && x.shaderOutputName == keyword.entries[i].referenceName); // If slot doesn't exist, it's new so create it if (slot == null) { slot = new DynamicVectorMaterialSlot(keyword.entries[i].id, keyword.entries[i].displayName, keyword.entries[i].referenceName, SlotType.Input, Vector4.zero); } AddSlot(slot); slotIDs.Add(keyword.entries[i].id); } RemoveSlotsNameNotMatching(slotIDs); bool orderChanged = SetSlotOrder(slotIDs); if (orderChanged) { // unfortunately there is no way to get the view to update slot order other than Topological Dirty(ModificationScope.Topological); } break; } } ValidateNode(); }