ShaderSubGraphImporter.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using UnityEditor.Experimental.AssetImporters;
  8. using UnityEngine;
  9. using UnityEditor.Graphing;
  10. using UnityEditor.Graphing.Util;
  11. using UnityEditor.ShaderGraph.Internal;
  12. namespace UnityEditor.ShaderGraph
  13. {
  14. [ScriptedImporter(10, Extension)]
  15. class ShaderSubGraphImporter : ScriptedImporter
  16. {
  17. public const string Extension = "shadersubgraph";
  18. [SuppressMessage("ReSharper", "UnusedMember.Local")]
  19. static string[] GatherDependenciesFromSourceFile(string assetPath)
  20. {
  21. try
  22. {
  23. return MinimalGraphData.GetDependencyPaths(assetPath);
  24. }
  25. catch (Exception e)
  26. {
  27. Debug.LogException(e);
  28. return new string[0];
  29. }
  30. }
  31. public override void OnImportAsset(AssetImportContext ctx)
  32. {
  33. var graphAsset = ScriptableObject.CreateInstance<SubGraphAsset>();
  34. var subGraphPath = ctx.assetPath;
  35. var subGraphGuid = AssetDatabase.AssetPathToGUID(subGraphPath);
  36. graphAsset.assetGuid = subGraphGuid;
  37. var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8);
  38. var graphData = new GraphData { isSubGraph = true, assetGuid = subGraphGuid };
  39. var messageManager = new MessageManager();
  40. graphData.messageManager = messageManager;
  41. JsonUtility.FromJsonOverwrite(textGraph, graphData);
  42. try
  43. {
  44. ProcessSubGraph(graphAsset, graphData);
  45. }
  46. catch (Exception e)
  47. {
  48. graphAsset.isValid = false;
  49. Debug.LogException(e, graphAsset);
  50. }
  51. finally
  52. {
  53. if (messageManager.nodeMessagesChanged)
  54. {
  55. graphAsset.isValid = false;
  56. foreach (var pair in messageManager.GetNodeMessages())
  57. {
  58. var node = graphData.GetNodeFromTempId(pair.Key);
  59. foreach (var message in pair.Value)
  60. {
  61. MessageManager.Log(node, subGraphPath, message, graphAsset);
  62. }
  63. }
  64. }
  65. messageManager.ClearAll();
  66. }
  67. Texture2D texture = Resources.Load<Texture2D>("Icons/sg_subgraph_icon@64");
  68. ctx.AddObjectToAsset("MainAsset", graphAsset, texture);
  69. ctx.SetMainObject(graphAsset);
  70. }
  71. static void ProcessSubGraph(SubGraphAsset asset, GraphData graph)
  72. {
  73. var registry = new FunctionRegistry(new ShaderStringBuilder(), true);
  74. registry.names.Clear();
  75. asset.functions.Clear();
  76. asset.nodeProperties.Clear();
  77. asset.isValid = true;
  78. graph.OnEnable();
  79. graph.messageManager.ClearAll();
  80. graph.ValidateGraph();
  81. var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid);
  82. asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath));
  83. asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}";
  84. asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}";
  85. asset.path = graph.path;
  86. var outputNode = (SubGraphOutputNode)graph.outputNode;
  87. asset.outputs.Clear();
  88. outputNode.GetInputSlots(asset.outputs);
  89. List<AbstractMaterialNode> nodes = new List<AbstractMaterialNode>();
  90. NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode);
  91. asset.effectiveShaderStage = ShaderStageCapability.All;
  92. foreach (var slot in asset.outputs)
  93. {
  94. var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true);
  95. if (stage != ShaderStageCapability.All)
  96. {
  97. asset.effectiveShaderStage = stage;
  98. break;
  99. }
  100. }
  101. asset.requirements = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false);
  102. asset.inputs = graph.properties.ToList();
  103. asset.keywords = graph.keywords.ToList();
  104. asset.graphPrecision = graph.concretePrecision;
  105. asset.outputPrecision = outputNode.concretePrecision;
  106. GatherFromGraph(assetPath, out var containsCircularDependency, out var descendents);
  107. asset.descendents.AddRange(descendents);
  108. var childrenSet = new HashSet<string>();
  109. var anyErrors = false;
  110. foreach (var node in nodes)
  111. {
  112. if (node is SubGraphNode subGraphNode)
  113. {
  114. var subGraphGuid = subGraphNode.subGraphGuid;
  115. if (childrenSet.Add(subGraphGuid))
  116. {
  117. asset.children.Add(subGraphGuid);
  118. }
  119. }
  120. if (node.hasError)
  121. {
  122. anyErrors = true;
  123. }
  124. }
  125. if (!anyErrors && containsCircularDependency)
  126. {
  127. Debug.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset);
  128. anyErrors = true;
  129. }
  130. if (anyErrors)
  131. {
  132. asset.isValid = false;
  133. registry.ProvideFunction(asset.functionName, sb => { });
  134. return;
  135. }
  136. foreach (var node in nodes)
  137. {
  138. if (node is IGeneratesFunction generatesFunction)
  139. {
  140. registry.builder.currentNode = node;
  141. generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals);
  142. registry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
  143. }
  144. }
  145. registry.ProvideFunction(asset.functionName, sb =>
  146. {
  147. SubShaderGenerator.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName);
  148. sb.AppendNewLine();
  149. // Generate arguments... first INPUTS
  150. var arguments = new List<string>();
  151. foreach (var prop in asset.inputs)
  152. {
  153. prop.ValidateConcretePrecision(asset.graphPrecision);
  154. arguments.Add(string.Format("{0}", prop.GetPropertyAsArgumentString()));
  155. }
  156. // now pass surface inputs
  157. arguments.Add(string.Format("{0} IN", asset.inputStructName));
  158. // Now generate outputs
  159. foreach (var output in asset.outputs)
  160. arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputPrecision)} {output.shaderOutputName}_{output.id}");
  161. // Create the function prototype from the arguments
  162. sb.AppendLine("void {0}({1})"
  163. , asset.functionName
  164. , arguments.Aggregate((current, next) => $"{current}, {next}"));
  165. // now generate the function
  166. using (sb.BlockScope())
  167. {
  168. // Just grab the body from the active nodes
  169. foreach (var node in nodes)
  170. {
  171. if (node is IGeneratesBodyCode generatesBodyCode)
  172. {
  173. sb.currentNode = node;
  174. generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals);
  175. sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
  176. }
  177. }
  178. foreach (var slot in asset.outputs)
  179. {
  180. sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals, asset.outputPrecision)};");
  181. }
  182. }
  183. });
  184. asset.functions.AddRange(registry.names.Select(x => new FunctionPair(x, registry.sources[x].code)));
  185. var collector = new PropertyCollector();
  186. asset.nodeProperties = collector.properties;
  187. foreach (var node in nodes)
  188. {
  189. node.CollectShaderProperties(collector, GenerationMode.ForReals);
  190. }
  191. asset.OnBeforeSerialize();
  192. }
  193. static void GatherFromGraph(string assetPath, out bool containsCircularDependency, out HashSet<string> descendentGuids)
  194. {
  195. var dependencyMap = new Dictionary<string, string[]>();
  196. using (var tempList = ListPool<string>.GetDisposable())
  197. {
  198. GatherDependencies(assetPath, dependencyMap, tempList.value);
  199. containsCircularDependency = ContainsCircularDependency(assetPath, dependencyMap, tempList.value);
  200. }
  201. descendentGuids = new HashSet<string>();
  202. GatherDescendents(assetPath, descendentGuids, dependencyMap);
  203. }
  204. static void GatherDependencies(string assetPath, Dictionary<string, string[]> dependencyMap, List<string> dependencies)
  205. {
  206. if (!dependencyMap.ContainsKey(assetPath))
  207. {
  208. if(assetPath.EndsWith(Extension))
  209. MinimalGraphData.GetDependencyPaths(assetPath, dependencies);
  210. var dependencyPaths = dependencyMap[assetPath] = dependencies.ToArray();
  211. dependencies.Clear();
  212. foreach (var dependencyPath in dependencyPaths)
  213. {
  214. GatherDependencies(dependencyPath, dependencyMap, dependencies);
  215. }
  216. }
  217. }
  218. static void GatherDescendents(string assetPath, HashSet<string> descendentGuids, Dictionary<string, string[]> dependencyMap)
  219. {
  220. var dependencies = dependencyMap[assetPath];
  221. foreach (var dependency in dependencies)
  222. {
  223. if (descendentGuids.Add(AssetDatabase.AssetPathToGUID(dependency)))
  224. {
  225. GatherDescendents(dependency, descendentGuids, dependencyMap);
  226. }
  227. }
  228. }
  229. static bool ContainsCircularDependency(string assetPath, Dictionary<string, string[]> dependencyMap, List<string> ancestors)
  230. {
  231. if (ancestors.Contains(assetPath))
  232. {
  233. return true;
  234. }
  235. ancestors.Add(assetPath);
  236. foreach (var dependencyPath in dependencyMap[assetPath])
  237. {
  238. if (ContainsCircularDependency(dependencyPath, dependencyMap, ancestors))
  239. {
  240. return true;
  241. }
  242. }
  243. ancestors.RemoveAt(ancestors.Count - 1);
  244. return false;
  245. }
  246. }
  247. }