설명 없음
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ShaderSubGraphImporter.cs 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  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. #if UNITY_2020_2_OR_NEWER
  8. using UnityEditor.AssetImporters;
  9. #else
  10. using UnityEditor.Experimental.AssetImporters;
  11. #endif
  12. using UnityEngine;
  13. using UnityEditor.Graphing;
  14. using UnityEditor.Graphing.Util;
  15. using UnityEditor.ShaderGraph.Internal;
  16. using UnityEditor.ShaderGraph.Serialization;
  17. using UnityEngine.Pool;
  18. namespace UnityEditor.ShaderGraph
  19. {
  20. [ExcludeFromPreset]
  21. [ScriptedImporter(30, Extension, -905)]
  22. class ShaderSubGraphImporter : ScriptedImporter
  23. {
  24. public const string Extension = "shadersubgraph";
  25. [SuppressMessage("ReSharper", "UnusedMember.Local")]
  26. static string[] GatherDependenciesFromSourceFile(string assetPath)
  27. {
  28. try
  29. {
  30. AssetCollection assetCollection = new AssetCollection();
  31. MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
  32. List<string> dependencyPaths = new List<string>();
  33. foreach (var asset in assetCollection.assets)
  34. {
  35. // only artifact dependencies need to be declared in GatherDependenciesFromSourceFile
  36. // to force their imports to run before ours
  37. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
  38. {
  39. var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
  40. // it is unfortunate that we can't declare these dependencies unless they have a path...
  41. // I asked AssetDatabase team for GatherDependenciesFromSourceFileByGUID()
  42. if (!string.IsNullOrEmpty(dependencyPath))
  43. dependencyPaths.Add(dependencyPath);
  44. }
  45. }
  46. return dependencyPaths.ToArray();
  47. }
  48. catch (Exception e)
  49. {
  50. Debug.LogException(e);
  51. return new string[0];
  52. }
  53. }
  54. static bool NodeWasUsedByGraph(string nodeId, GraphData graphData)
  55. {
  56. var node = graphData.GetNodeFromId(nodeId);
  57. return node?.wasUsedByGenerator ?? false;
  58. }
  59. public override void OnImportAsset(AssetImportContext ctx)
  60. {
  61. var importLog = new ShaderGraphImporter.AssetImportErrorLog(ctx);
  62. var graphAsset = ScriptableObject.CreateInstance<SubGraphAsset>();
  63. var subGraphPath = ctx.assetPath;
  64. var subGraphGuid = AssetDatabase.AssetPathToGUID(subGraphPath);
  65. graphAsset.assetGuid = subGraphGuid;
  66. var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8);
  67. var messageManager = new MessageManager();
  68. var graphData = new GraphData
  69. {
  70. isSubGraph = true,
  71. assetGuid = subGraphGuid,
  72. messageManager = messageManager
  73. };
  74. MultiJson.Deserialize(graphData, textGraph);
  75. try
  76. {
  77. ProcessSubGraph(graphAsset, graphData, importLog);
  78. }
  79. catch (Exception e)
  80. {
  81. graphAsset.isValid = false;
  82. Debug.LogException(e, graphAsset);
  83. }
  84. finally
  85. {
  86. var errors = messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graphData));
  87. int errCount = errors.Count();
  88. if (errCount > 0)
  89. {
  90. var firstError = errors.FirstOrDefault();
  91. importLog.LogError($"Sub Graph at {subGraphPath} has {errCount} error(s), the first is: {firstError}", graphAsset);
  92. graphAsset.isValid = false;
  93. }
  94. else
  95. {
  96. var warnings = messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graphData), Rendering.ShaderCompilerMessageSeverity.Warning);
  97. int warningCount = warnings.Count();
  98. if (warningCount > 0)
  99. {
  100. var firstWarning = warnings.FirstOrDefault();
  101. importLog.LogWarning($"Sub Graph at {subGraphPath} has {warningCount} warning(s), the first is: {firstWarning}", graphAsset);
  102. }
  103. }
  104. messageManager.ClearAll();
  105. }
  106. Texture2D texture = Resources.Load<Texture2D>("Icons/sg_subgraph_icon");
  107. ctx.AddObjectToAsset("MainAsset", graphAsset, texture);
  108. ctx.SetMainObject(graphAsset);
  109. var metadata = ScriptableObject.CreateInstance<ShaderSubGraphMetadata>();
  110. metadata.hideFlags = HideFlags.HideInHierarchy;
  111. metadata.assetDependencies = new List<UnityEngine.Object>();
  112. AssetCollection assetCollection = new AssetCollection();
  113. MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
  114. foreach (var asset in assetCollection.assets)
  115. {
  116. if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage))
  117. {
  118. // this sucks that we have to fully load these assets just to set the reference,
  119. // which then gets serialized as the GUID that we already have here. :P
  120. var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
  121. if (!string.IsNullOrEmpty(dependencyPath))
  122. {
  123. metadata.assetDependencies.Add(
  124. AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object)));
  125. }
  126. }
  127. }
  128. ctx.AddObjectToAsset("Metadata", metadata);
  129. // declare dependencies
  130. foreach (var asset in assetCollection.assets)
  131. {
  132. if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency))
  133. {
  134. ctx.DependsOnSourceAsset(asset.Key);
  135. // I'm not sure if this warning below is actually used or not, keeping it to be safe
  136. var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key);
  137. // Ensure that dependency path is relative to project
  138. if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/"))
  139. {
  140. importLog.LogWarning($"Invalid dependency path: {assetPath}", graphAsset);
  141. }
  142. }
  143. // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies
  144. // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies
  145. // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here.
  146. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
  147. {
  148. ctx.DependsOnArtifact(asset.Key);
  149. }
  150. }
  151. }
  152. static void ProcessSubGraph(SubGraphAsset asset, GraphData graph, ShaderGraphImporter.AssetImportErrorLog importLog)
  153. {
  154. var graphIncludes = new IncludeCollection();
  155. var registry = new FunctionRegistry(new ShaderStringBuilder(), graphIncludes, true);
  156. asset.functions.Clear();
  157. asset.isValid = true;
  158. graph.OnEnable();
  159. graph.messageManager.ClearAll();
  160. graph.ValidateGraph();
  161. var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid);
  162. asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath));
  163. asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}_$precision";
  164. asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}_$precision";
  165. asset.path = graph.path;
  166. var outputNode = graph.outputNode;
  167. var outputSlots = PooledList<MaterialSlot>.Get();
  168. outputNode.GetInputSlots(outputSlots);
  169. List<AbstractMaterialNode> nodes = new List<AbstractMaterialNode>();
  170. NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode);
  171. // flag the used nodes so we can filter out errors from unused nodes
  172. foreach (var node in nodes)
  173. node.SetUsedByGenerator();
  174. // Start with a clean slate for the input/output capabilities and dependencies
  175. asset.inputCapabilities.Clear();
  176. asset.outputCapabilities.Clear();
  177. asset.slotDependencies.Clear();
  178. ShaderStageCapability effectiveShaderStage = ShaderStageCapability.All;
  179. var shaderStageCapabilityCache = new Dictionary<SlotReference, ShaderStageCapability>();
  180. foreach (var slot in outputSlots)
  181. {
  182. var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true, shaderStageCapabilityCache);
  183. if (effectiveShaderStage == ShaderStageCapability.All && stage != ShaderStageCapability.All)
  184. effectiveShaderStage = stage;
  185. asset.outputCapabilities.Add(new SlotCapability { slotName = slot.RawDisplayName(), capabilities = stage });
  186. // Find all unique property nodes used by this slot and record a dependency for this input/output pair
  187. var inputPropertyNames = new HashSet<string>();
  188. var nodeSet = new HashSet<AbstractMaterialNode>();
  189. NodeUtils.CollectNodeSet(nodeSet, slot);
  190. foreach (var node in nodeSet)
  191. {
  192. if (node is PropertyNode propNode && !inputPropertyNames.Contains(propNode.property.displayName))
  193. {
  194. inputPropertyNames.Add(propNode.property.displayName);
  195. var slotDependency = new SlotDependencyPair();
  196. slotDependency.inputSlotName = propNode.property.displayName;
  197. slotDependency.outputSlotName = slot.RawDisplayName();
  198. asset.slotDependencies.Add(slotDependency);
  199. }
  200. }
  201. }
  202. CollectInputCapabilities(asset, graph);
  203. asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode);
  204. asset.requirements = ShaderGraphRequirements.FromNodes(nodes, effectiveShaderStage, false);
  205. // output precision is whatever the output node has as a graph precision, falling back to the graph default
  206. asset.outputGraphPrecision = outputNode.graphPrecision.GraphFallback(graph.graphDefaultPrecision);
  207. // this saves the graph precision, which indicates whether this subgraph is switchable or not
  208. asset.subGraphGraphPrecision = graph.graphDefaultPrecision;
  209. asset.previewMode = graph.previewMode;
  210. asset.includes = graphIncludes;
  211. GatherDescendentsFromGraph(new GUID(asset.assetGuid), out var containsCircularDependency, out var descendents);
  212. asset.descendents.AddRange(descendents.Select(g => g.ToString()));
  213. asset.descendents.Sort(); // ensure deterministic order
  214. var childrenSet = new HashSet<string>();
  215. var anyErrors = false;
  216. foreach (var node in nodes)
  217. {
  218. if (node is SubGraphNode subGraphNode)
  219. {
  220. var subGraphGuid = subGraphNode.subGraphGuid;
  221. childrenSet.Add(subGraphGuid);
  222. }
  223. if (node.hasError)
  224. {
  225. anyErrors = true;
  226. }
  227. asset.children = childrenSet.ToList();
  228. asset.children.Sort(); // ensure deterministic order
  229. }
  230. if (!anyErrors && containsCircularDependency)
  231. {
  232. importLog.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset);
  233. anyErrors = true;
  234. }
  235. if (anyErrors)
  236. {
  237. asset.isValid = false;
  238. registry.ProvideFunction(asset.functionName, sb => { });
  239. return;
  240. }
  241. foreach (var node in nodes)
  242. {
  243. if (node is IGeneratesFunction generatesFunction)
  244. {
  245. registry.builder.currentNode = node;
  246. generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals);
  247. }
  248. }
  249. // Need to order the properties so that they are in the same order on a subgraph node in a shadergraph
  250. // as they are in the blackboard for the subgraph itself. The (blackboard) categories keep that ordering,
  251. // so traverse those and add those items to the ordered properties list. Needs to be used to set up the
  252. // function _and_ to write out the final asset data so that the function call parameter order matches as well.
  253. var orderedProperties = new List<AbstractShaderProperty>();
  254. var propertiesList = graph.properties.ToList();
  255. foreach (var category in graph.categories)
  256. {
  257. foreach (var child in category.Children)
  258. {
  259. var prop = propertiesList.Find(p => p.guid == child.guid);
  260. // Not all properties in the category are actually on the graph.
  261. // In particular, it seems as if keywords are not properties on sub-graphs.
  262. if (prop != null && !orderedProperties.Contains(prop))
  263. orderedProperties.Add(prop);
  264. }
  265. }
  266. // If we are importing an older file that has not had categories generated for it yet, include those now.
  267. orderedProperties.AddRange(graph.properties.Except(orderedProperties));
  268. // provide top level subgraph function
  269. // NOTE: actual concrete precision here shouldn't matter, it's irrelevant when building the subgraph asset
  270. registry.ProvideFunction(asset.functionName, asset.subGraphGraphPrecision, ConcretePrecision.Single, sb =>
  271. {
  272. GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName);
  273. sb.AppendNewLine();
  274. // Generate the arguments... first INPUTS
  275. var arguments = new List<string>();
  276. foreach (var prop in orderedProperties)
  277. {
  278. // apply fallback to the graph default precision (but don't convert to concrete)
  279. // this means "graph switchable" properties will use the precision token
  280. GraphPrecision propGraphPrecision = prop.precision.ToGraphPrecision(graph.graphDefaultPrecision);
  281. string precisionString = propGraphPrecision.ToGenericString();
  282. arguments.Add(prop.GetPropertyAsArgumentString(precisionString));
  283. if (prop.isConnectionTestable)
  284. {
  285. arguments.Add($"bool {prop.GetConnectionStateHLSLVariableName()}");
  286. }
  287. }
  288. {
  289. var dropdowns = graph.dropdowns;
  290. foreach (var dropdown in dropdowns)
  291. arguments.Add($"int {dropdown.referenceName}");
  292. }
  293. // now pass surface inputs
  294. arguments.Add(string.Format("{0} IN", asset.inputStructName));
  295. // Now generate output arguments
  296. foreach (MaterialSlot output in outputSlots)
  297. arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputGraphPrecision.ToGenericString())} {output.shaderOutputName}_{output.id}");
  298. // Vt Feedback output arguments (always full float4)
  299. foreach (var output in asset.vtFeedbackVariables)
  300. arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Single)} {output}_out");
  301. // Create the function prototype from the arguments
  302. sb.AppendLine("void {0}({1})"
  303. , asset.functionName
  304. , arguments.Aggregate((current, next) => $"{current}, {next}"));
  305. // now generate the function
  306. using (sb.BlockScope())
  307. {
  308. // Just grab the body from the active nodes
  309. foreach (var node in nodes)
  310. {
  311. if (node is IGeneratesBodyCode generatesBodyCode)
  312. {
  313. sb.currentNode = node;
  314. generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals);
  315. if (node.graphPrecision == GraphPrecision.Graph)
  316. {
  317. // code generated by nodes that use graph precision stays in generic form with embedded tokens
  318. // those tokens are replaced when this subgraph function is pulled into a graph that defines the precision
  319. }
  320. else
  321. {
  322. sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
  323. }
  324. }
  325. }
  326. foreach (var slot in outputSlots)
  327. {
  328. sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals)};");
  329. }
  330. foreach (var slot in asset.vtFeedbackVariables)
  331. {
  332. sb.AppendLine($"{slot}_out = {slot};");
  333. }
  334. }
  335. });
  336. // save all of the node-declared functions to the subgraph asset
  337. foreach (var name in registry.names)
  338. {
  339. var source = registry.sources[name];
  340. var func = new FunctionPair(name, source.code, source.graphPrecisionFlags);
  341. asset.functions.Add(func);
  342. }
  343. var collector = new PropertyCollector();
  344. foreach (var node in nodes)
  345. {
  346. int previousPropertyCount = Math.Max(0, collector.propertyCount - 1);
  347. node.CollectShaderProperties(collector, GenerationMode.ForReals);
  348. // This is a stop-gap to prevent the autogenerated values from JsonObject and ShaderInput from
  349. // resulting in non-deterministic import data. While we should move to local ids in the future,
  350. // this will prevent cascading shader recompilations.
  351. for (int i = previousPropertyCount; i < collector.propertyCount; ++i)
  352. {
  353. var prop = collector.GetProperty(i);
  354. var namespaceId = node.objectId;
  355. var nameId = prop.referenceName;
  356. prop.OverrideObjectId(namespaceId, nameId + "_ObjectId_" + i);
  357. prop.OverrideGuid(namespaceId, nameId + "_Guid_" + i);
  358. }
  359. }
  360. asset.WriteData(orderedProperties, graph.keywords, graph.dropdowns, collector.properties, outputSlots, graph.unsupportedTargets);
  361. outputSlots.Dispose();
  362. }
  363. static void GatherDescendentsFromGraph(GUID rootAssetGuid, out bool containsCircularDependency, out HashSet<GUID> descendentGuids)
  364. {
  365. var dependencyMap = new Dictionary<GUID, GUID[]>();
  366. AssetCollection tempAssetCollection = new AssetCollection();
  367. using (ListPool<GUID>.Get(out var tempList))
  368. {
  369. GatherDependencyMap(rootAssetGuid, dependencyMap, tempAssetCollection);
  370. containsCircularDependency = ContainsCircularDependency(rootAssetGuid, dependencyMap, tempList);
  371. }
  372. descendentGuids = new HashSet<GUID>();
  373. GatherDescendentsUsingDependencyMap(rootAssetGuid, descendentGuids, dependencyMap);
  374. }
  375. static void GatherDependencyMap(GUID rootAssetGUID, Dictionary<GUID, GUID[]> dependencyMap, AssetCollection tempAssetCollection)
  376. {
  377. if (!dependencyMap.ContainsKey(rootAssetGUID))
  378. {
  379. // if it is a subgraph, try to recurse into it
  380. var assetPath = AssetDatabase.GUIDToAssetPath(rootAssetGUID);
  381. if (!string.IsNullOrEmpty(assetPath) && assetPath.EndsWith(Extension, true, null))
  382. {
  383. tempAssetCollection.Clear();
  384. MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, tempAssetCollection);
  385. var subgraphGUIDs = tempAssetCollection.assets.Where(asset => asset.Value.HasFlag(AssetCollection.Flags.IsSubGraph)).Select(asset => asset.Key).ToArray();
  386. dependencyMap[rootAssetGUID] = subgraphGUIDs;
  387. foreach (var guid in subgraphGUIDs)
  388. {
  389. GatherDependencyMap(guid, dependencyMap, tempAssetCollection);
  390. }
  391. }
  392. }
  393. }
  394. static void GatherDescendentsUsingDependencyMap(GUID rootAssetGUID, HashSet<GUID> descendentGuids, Dictionary<GUID, GUID[]> dependencyMap)
  395. {
  396. var dependencies = dependencyMap[rootAssetGUID];
  397. foreach (GUID dependency in dependencies)
  398. {
  399. if (descendentGuids.Add(dependency))
  400. {
  401. GatherDescendentsUsingDependencyMap(dependency, descendentGuids, dependencyMap);
  402. }
  403. }
  404. }
  405. static bool ContainsCircularDependency(GUID assetGUID, Dictionary<GUID, GUID[]> dependencyMap, List<GUID> ancestors)
  406. {
  407. if (ancestors.Contains(assetGUID))
  408. {
  409. return true;
  410. }
  411. ancestors.Add(assetGUID);
  412. foreach (var dependencyGUID in dependencyMap[assetGUID])
  413. {
  414. if (ContainsCircularDependency(dependencyGUID, dependencyMap, ancestors))
  415. {
  416. return true;
  417. }
  418. }
  419. ancestors.RemoveAt(ancestors.Count - 1);
  420. return false;
  421. }
  422. static void CollectInputCapabilities(SubGraphAsset asset, GraphData graph)
  423. {
  424. // Collect each input's capabilities. There can be multiple property nodes
  425. // contributing to the same input, so we cache these in a map while building
  426. var inputCapabilities = new Dictionary<string, SlotCapability>();
  427. var shaderStageCapabilityCache = new Dictionary<SlotReference, ShaderStageCapability>();
  428. // Walk all property node output slots, computing and caching the capabilities for that slot
  429. var propertyNodes = graph.GetNodes<PropertyNode>();
  430. foreach (var propertyNode in propertyNodes)
  431. {
  432. foreach (var slot in propertyNode.GetOutputSlots<MaterialSlot>())
  433. {
  434. var slotName = slot.RawDisplayName();
  435. SlotCapability capabilityInfo;
  436. if (!inputCapabilities.TryGetValue(slotName, out capabilityInfo))
  437. {
  438. capabilityInfo = new SlotCapability();
  439. capabilityInfo.slotName = slotName;
  440. inputCapabilities.Add(propertyNode.property.displayName, capabilityInfo);
  441. }
  442. capabilityInfo.capabilities &= NodeUtils.GetEffectiveShaderStageCapability(slot, false, shaderStageCapabilityCache);
  443. }
  444. }
  445. asset.inputCapabilities.AddRange(inputCapabilities.Values);
  446. }
  447. }
  448. }