Без опису
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

ShaderGraphImporter.cs 42KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. using UnityEngine;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using UnityEditor.AssetImporters;
  9. using UnityEditor.Graphing;
  10. using UnityEditor.Graphing.Util;
  11. using UnityEditor.ShaderGraph.Internal;
  12. using UnityEditor.ShaderGraph.Serialization;
  13. using Object = System.Object;
  14. namespace UnityEditor.ShaderGraph
  15. {
  16. [ExcludeFromPreset]
  17. [ScriptedImporter(132, Extension, -902)]
  18. class ShaderGraphImporter : ScriptedImporter
  19. {
  20. public const string Extension = "shadergraph";
  21. public const string LegacyExtension = "ShaderGraph";
  22. public const string k_ErrorShader = @"
  23. Shader ""Hidden/GraphErrorShader2""
  24. {
  25. SubShader
  26. {
  27. Pass
  28. {
  29. CGPROGRAM
  30. #pragma vertex vert
  31. #pragma fragment frag
  32. #pragma target 2.0
  33. #pragma multi_compile _ UNITY_SINGLE_PASS_STEREO STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON
  34. #include ""UnityCG.cginc""
  35. struct appdata_t {
  36. float4 vertex : POSITION;
  37. UNITY_VERTEX_INPUT_INSTANCE_ID
  38. };
  39. struct v2f {
  40. float4 vertex : SV_POSITION;
  41. UNITY_VERTEX_OUTPUT_STEREO
  42. };
  43. v2f vert (appdata_t v)
  44. {
  45. v2f o;
  46. UNITY_SETUP_INSTANCE_ID(v);
  47. UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
  48. o.vertex = UnityObjectToClipPos(v.vertex);
  49. return o;
  50. }
  51. fixed4 frag (v2f i) : SV_Target
  52. {
  53. return fixed4(1,0,1,1);
  54. }
  55. ENDCG
  56. }
  57. }
  58. Fallback Off
  59. }";
  60. [SuppressMessage("ReSharper", "UnusedMember.Local")]
  61. static string[] GatherDependenciesFromSourceFile(string assetPath)
  62. {
  63. try
  64. {
  65. AssetCollection assetCollection = new AssetCollection();
  66. MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
  67. List<string> dependencyPaths = new List<string>();
  68. foreach (var asset in assetCollection.assets)
  69. {
  70. // only artifact dependencies need to be declared in GatherDependenciesFromSourceFile
  71. // to force their imports to run before ours
  72. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
  73. {
  74. var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
  75. // it is unfortunate that we can't declare these dependencies unless they have a path...
  76. // I asked AssetDatabase team for GatherDependenciesFromSourceFileByGUID()
  77. if (!string.IsNullOrEmpty(dependencyPath))
  78. dependencyPaths.Add(dependencyPath);
  79. }
  80. }
  81. return dependencyPaths.ToArray();
  82. }
  83. catch (Exception e)
  84. {
  85. Debug.LogException(e);
  86. return new string[0];
  87. }
  88. }
  89. Shader BuildAllShaders(
  90. AssetImportContext importContext,
  91. AssetImportErrorLog importErrorLog,
  92. AssetCollection allImportAssetDependencies,
  93. GraphData graph)
  94. {
  95. Shader primaryShader = null;
  96. string path = importContext.assetPath;
  97. var primaryShaderName = Path.GetFileNameWithoutExtension(path);
  98. try
  99. {
  100. // this will also add Target dependencies into the asset collection
  101. Generator generator;
  102. generator = new Generator(graph, graph.outputNode, GenerationMode.ForReals, primaryShaderName, assetCollection: allImportAssetDependencies);
  103. bool first = true;
  104. foreach (var generatedShader in generator.allGeneratedShaders)
  105. {
  106. var shaderString = generatedShader.codeString;
  107. // we only care if an error was reported for a node that we actually used
  108. if (graph.messageManager.AnyError((nodeId) => NodeWasUsedByGraph(nodeId, graph)) ||
  109. shaderString == null)
  110. {
  111. shaderString = k_ErrorShader.Replace("Hidden/GraphErrorShader2", generatedShader.shaderName);
  112. }
  113. var shader = ShaderUtil.CreateShaderAsset(importContext, shaderString, false);
  114. ReportErrors(graph, shader, path, importErrorLog);
  115. EditorMaterialUtility.SetShaderDefaults(
  116. shader,
  117. generatedShader.assignedTextures.Where(x => x.modifiable).Select(x => x.name).ToArray(),
  118. generatedShader.assignedTextures.Where(x => x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray());
  119. EditorMaterialUtility.SetShaderNonModifiableDefaults(
  120. shader,
  121. generatedShader.assignedTextures.Where(x => !x.modifiable).Select(x => x.name).ToArray(),
  122. generatedShader.assignedTextures.Where(x => !x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray());
  123. if (first)
  124. {
  125. // first shader is always the primary shader
  126. // we return the primary shader so it can be attached to the import context at the outer level
  127. // allowing it to bind a custom icon as well
  128. primaryShader = shader;
  129. // only the main shader gets a material created
  130. Material material = new Material(shader) { name = primaryShaderName };
  131. importContext.AddObjectToAsset("Material", material);
  132. first = false;
  133. }
  134. else
  135. {
  136. importContext.AddObjectToAsset($"Shader-{generatedShader.shaderName}", shader);
  137. }
  138. }
  139. foreach (var generatedComputeShader in generator.allGeneratedComputeShaders)
  140. {
  141. // Create the compute asset.
  142. var computeShader = ShaderUtil.CreateComputeShaderAsset(importContext, generatedComputeShader.codeString);
  143. // TODO: ReportErrors for Compute Shader. Will require ShaderUtil.GetComputeShaderMessages.
  144. computeShader.name = $"ComputeShader-{generatedComputeShader.shaderName}";
  145. importContext.AddObjectToAsset(computeShader.name, computeShader);
  146. }
  147. }
  148. catch (Exception e)
  149. {
  150. Debug.LogException(e);
  151. // ignored
  152. }
  153. return primaryShader;
  154. }
  155. internal static bool subtargetNotFoundError = false;
  156. public override void OnImportAsset(AssetImportContext ctx)
  157. {
  158. var importLog = new AssetImportErrorLog(ctx);
  159. string path = ctx.assetPath;
  160. AssetCollection assetCollection = new AssetCollection();
  161. MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
  162. var textGraph = File.ReadAllText(path, Encoding.UTF8);
  163. var graph = new GraphData
  164. {
  165. messageManager = new MessageManager(),
  166. assetGuid = AssetDatabase.AssetPathToGUID(path)
  167. };
  168. MultiJson.Deserialize(graph, textGraph);
  169. if (subtargetNotFoundError)
  170. {
  171. Debug.LogError($"{ctx.assetPath}: Import Error: Expected active subtarget not found, defaulting to first available.");
  172. subtargetNotFoundError = false;
  173. }
  174. graph.OnEnable();
  175. graph.ValidateGraph();
  176. UnityEngine.Object mainObject = null;
  177. #if VFX_GRAPH_10_0_0_OR_NEWER
  178. if (!graph.isOnlyVFXTarget)
  179. #endif
  180. {
  181. // build shaders
  182. mainObject = BuildAllShaders(ctx, importLog, assetCollection, graph);
  183. }
  184. #if VFX_GRAPH_10_0_0_OR_NEWER
  185. ShaderGraphVfxAsset vfxAsset = null;
  186. if (graph.hasVFXTarget)
  187. {
  188. vfxAsset = GenerateVfxShaderGraphAsset(graph);
  189. if (mainObject == null)
  190. {
  191. mainObject = vfxAsset;
  192. }
  193. else
  194. {
  195. //Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset
  196. vfxAsset.name = Path.GetFileNameWithoutExtension(path);
  197. ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset);
  198. }
  199. }
  200. #endif
  201. Texture2D texture = Resources.Load<Texture2D>("Icons/sg_graph_icon");
  202. ctx.AddObjectToAsset("MainAsset", mainObject, texture);
  203. ctx.SetMainObject(mainObject);
  204. var graphDataReadOnly = new GraphDataReadOnly(graph);
  205. foreach (var target in graph.activeTargets)
  206. {
  207. if (target is IHasMetadata iHasMetadata)
  208. {
  209. var metadata = iHasMetadata.GetMetadataObject(graphDataReadOnly);
  210. if (metadata == null)
  211. continue;
  212. metadata.hideFlags = HideFlags.HideInHierarchy;
  213. ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata);
  214. }
  215. }
  216. // In case a target couldn't be imported properly, we register a dependency to reimport this ShaderGraph when the current render pipeline type changes
  217. if (graph.allPotentialTargets.Any(t => t is MultiJsonInternal.UnknownTargetType))
  218. ctx.DependsOnCustomDependency(RenderPipelineChangedCallback.k_CustomDependencyKey);
  219. var sgMetadata = ScriptableObject.CreateInstance<ShaderGraphMetadata>();
  220. sgMetadata.hideFlags = HideFlags.HideInHierarchy;
  221. sgMetadata.assetDependencies = new List<UnityEngine.Object>();
  222. foreach (var asset in assetCollection.assets)
  223. {
  224. if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage))
  225. {
  226. // this sucks that we have to fully load these assets just to set the reference,
  227. // which then gets serialized as the GUID that we already have here. :P
  228. var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
  229. if (!string.IsNullOrEmpty(dependencyPath))
  230. {
  231. sgMetadata.assetDependencies.Add(
  232. AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object)));
  233. }
  234. }
  235. }
  236. List<GraphInputData> inputInspectorDataList = new List<GraphInputData>();
  237. foreach (AbstractShaderProperty property in graph.properties)
  238. {
  239. // Don't write out data for non-exposed blackboard items
  240. if (!property.isExposed)
  241. continue;
  242. // VTs are treated differently
  243. if (property is VirtualTextureShaderProperty virtualTextureShaderProperty)
  244. inputInspectorDataList.Add(MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty));
  245. else
  246. inputInspectorDataList.Add(new GraphInputData() { referenceName = property.referenceName, propertyType = property.propertyType, isKeyword = false });
  247. }
  248. foreach (ShaderKeyword keyword in graph.keywords)
  249. {
  250. // Don't write out data for non-exposed blackboard items
  251. if (!keyword.isExposed)
  252. continue;
  253. var sanitizedReferenceName = keyword.referenceName;
  254. if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON"))
  255. sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty);
  256. inputInspectorDataList.Add(new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true });
  257. }
  258. sgMetadata.categoryDatas = new List<MinimalCategoryData>();
  259. foreach (CategoryData categoryData in graph.categories)
  260. {
  261. // Don't write out empty categories
  262. if (categoryData.childCount == 0)
  263. continue;
  264. MinimalCategoryData mcd = new MinimalCategoryData()
  265. {
  266. categoryName = categoryData.name,
  267. propertyDatas = new List<GraphInputData>()
  268. };
  269. foreach (var input in categoryData.Children)
  270. {
  271. GraphInputData propData;
  272. // Only write out data for exposed blackboard items
  273. if (input.isExposed == false)
  274. continue;
  275. // VTs are treated differently
  276. if (input is VirtualTextureShaderProperty virtualTextureShaderProperty)
  277. {
  278. propData = MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty);
  279. inputInspectorDataList.RemoveAll(inputData => inputData.referenceName == propData.referenceName);
  280. mcd.propertyDatas.Add(propData);
  281. continue;
  282. }
  283. else if (input is ShaderKeyword keyword)
  284. {
  285. var sanitizedReferenceName = keyword.referenceName;
  286. if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON"))
  287. sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty);
  288. propData = new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true };
  289. }
  290. else
  291. {
  292. var prop = input as AbstractShaderProperty;
  293. propData = new GraphInputData() { referenceName = input.referenceName, propertyType = prop.propertyType, isKeyword = false };
  294. }
  295. mcd.propertyDatas.Add(propData);
  296. inputInspectorDataList.Remove(propData);
  297. }
  298. sgMetadata.categoryDatas.Add(mcd);
  299. }
  300. // Any uncategorized elements get tossed into an un-named category at the top as a fallback
  301. if (inputInspectorDataList.Count > 0)
  302. {
  303. sgMetadata.categoryDatas.Insert(0, new MinimalCategoryData() { categoryName = "", propertyDatas = inputInspectorDataList });
  304. }
  305. ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata);
  306. // declare dependencies
  307. foreach (var asset in assetCollection.assets)
  308. {
  309. if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency))
  310. {
  311. ctx.DependsOnSourceAsset(asset.Key);
  312. // I'm not sure if this warning below is actually used or not, keeping it to be safe
  313. var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key);
  314. // Ensure that dependency path is relative to project
  315. if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/"))
  316. {
  317. importLog.LogWarning($"Invalid dependency path: {assetPath}", mainObject);
  318. }
  319. }
  320. // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies
  321. // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies
  322. // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here.
  323. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
  324. {
  325. ctx.DependsOnArtifact(asset.Key);
  326. }
  327. }
  328. }
  329. internal class AssetImportErrorLog : MessageManager.IErrorLog
  330. {
  331. AssetImportContext ctx;
  332. public AssetImportErrorLog(AssetImportContext ctx)
  333. {
  334. this.ctx = ctx;
  335. }
  336. public void LogError(string message, UnityEngine.Object context = null)
  337. {
  338. // Note: if you get sent here by clicking on a ShaderGraph error message,
  339. // this is a bug in the scripted importer system, not being able to link import error messages to the imported asset
  340. ctx.LogImportError(message, context);
  341. }
  342. public void LogWarning(string message, UnityEngine.Object context = null)
  343. {
  344. ctx.LogImportWarning(message, context);
  345. }
  346. }
  347. static bool NodeWasUsedByGraph(string nodeId, GraphData graphData)
  348. {
  349. var node = graphData.GetNodeFromId(nodeId);
  350. return node?.wasUsedByGenerator ?? false;
  351. }
  352. // error messages should be reported through the asset import context, so that object references are translated properly (in the future), and the error is associated with the import
  353. static void ReportErrors(GraphData graph, Shader shader, string path, AssetImportErrorLog importLog)
  354. {
  355. // Grab any messages from the shader compiler
  356. var messages = ShaderUtil.GetShaderMessages(shader);
  357. var errors = graph.messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graph));
  358. int errCount = errors.Count();
  359. // Find the first compiler message that's an error
  360. int firstShaderUtilErrorIndex = -1;
  361. if (messages != null)
  362. firstShaderUtilErrorIndex = Array.FindIndex(messages, m => (m.severity == Rendering.ShaderCompilerMessageSeverity.Error));
  363. // Display only one message. Bias towards shader compiler messages over node messages and within that bias errors over warnings.
  364. if (firstShaderUtilErrorIndex != -1)
  365. {
  366. // if shader compiler reported an error, show that
  367. MessageManager.Log(path, messages[firstShaderUtilErrorIndex], shader, importLog);
  368. }
  369. else if (errCount > 0)
  370. {
  371. // otherwise show node errors
  372. var firstError = errors.FirstOrDefault();
  373. importLog.LogError($"Shader Graph at {path} has {errCount} error(s), the first is: {firstError}", shader);
  374. }
  375. else if (messages.Length != 0)
  376. {
  377. // otherwise show shader compiler warnings
  378. MessageManager.Log(path, messages[0], shader, importLog);
  379. }
  380. else if (graph.messageManager.nodeMessagesChanged)
  381. {
  382. // otherwise show node warnings
  383. var warnings = graph.messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graph), Rendering.ShaderCompilerMessageSeverity.Warning);
  384. var warnCount = warnings.Count();
  385. var firstWarning = warnings.FirstOrDefault();
  386. if (warnCount > 0)
  387. importLog.LogWarning($"Shader Graph at {path} has {warnCount} warning(s), the first is: {firstWarning}", shader);
  388. }
  389. }
  390. // this old path is still used by the old VFX path, so keeping it around for now
  391. internal static string GetShaderText(string path, out List<PropertyCollector.TextureInfo> configuredTextures, AssetCollection assetCollection, GraphData graph, GenerationMode mode = GenerationMode.ForReals, Target[] targets = null)
  392. {
  393. string shaderString = null;
  394. var shaderName = Path.GetFileNameWithoutExtension(path);
  395. try
  396. {
  397. Generator generator;
  398. generator = new Generator(graph, graph.outputNode, mode, shaderName, targets, assetCollection);
  399. shaderString = generator.generatedShader;
  400. configuredTextures = generator.configuredTextures;
  401. // we only care if an error was reported for a node that we actually used
  402. if (graph.messageManager.AnyError((nodeId) => NodeWasUsedByGraph(nodeId, graph)))
  403. {
  404. shaderString = null;
  405. }
  406. }
  407. catch (Exception e)
  408. {
  409. Debug.LogException(e);
  410. configuredTextures = new List<PropertyCollector.TextureInfo>();
  411. // ignored
  412. }
  413. if (shaderString == null)
  414. {
  415. shaderString = k_ErrorShader.Replace("Hidden/GraphErrorShader2", shaderName);
  416. }
  417. return shaderString;
  418. }
  419. // this function is used by tests
  420. internal static string GetShaderText(string path, out List<PropertyCollector.TextureInfo> configuredTextures, AssetCollection assetCollection, out GraphData graph)
  421. {
  422. var textGraph = File.ReadAllText(path, Encoding.UTF8);
  423. graph = new GraphData
  424. {
  425. messageManager = new MessageManager(),
  426. assetGuid = AssetDatabase.AssetPathToGUID(path)
  427. };
  428. MultiJson.Deserialize(graph, textGraph);
  429. graph.OnEnable();
  430. graph.ValidateGraph();
  431. return GetShaderText(path, out configuredTextures, assetCollection, graph);
  432. }
  433. /*
  434. internal static string GetShaderText(string path, out List<PropertyCollector.TextureInfo> configuredTextures)
  435. {
  436. var textGraph = File.ReadAllText(path, Encoding.UTF8);
  437. GraphData graph = new GraphData
  438. {
  439. messageManager = new MessageManager(),
  440. assetGuid = AssetDatabase.AssetPathToGUID(path)
  441. };
  442. MultiJson.Deserialize(graph, textGraph);
  443. graph.OnEnable();
  444. graph.ValidateGraph();
  445. return GetShaderText(path, out configuredTextures, null, graph);
  446. }
  447. */
  448. #if VFX_GRAPH_10_0_0_OR_NEWER
  449. // TODO: Fix this - VFX Graph can now use ShaderGraph as a code generation path. However, currently, the new
  450. // generation path still slightly depends on this container (The implementation of it was tightly coupled in VFXShaderGraphParticleOutput,
  451. // and we keep it now as there is no migration path for users yet). This will need to be decoupled so that we can eventually
  452. // remove this container.
  453. static ShaderGraphVfxAsset GenerateVfxShaderGraphAsset(GraphData graph)
  454. {
  455. var target = graph.activeTargets.FirstOrDefault(x => x.SupportsVFX());
  456. if (target == null)
  457. return null;
  458. var nl = Environment.NewLine;
  459. var indent = new string(' ', 4);
  460. var asset = ScriptableObject.CreateInstance<ShaderGraphVfxAsset>();
  461. var result = asset.compilationResult = new GraphCompilationResult();
  462. var mode = GenerationMode.ForReals;
  463. if (target is VFXTarget vfxTarget)
  464. {
  465. asset.lit = vfxTarget.lit;
  466. asset.alphaClipping = vfxTarget.alphaTest;
  467. asset.generatesWithShaderGraph = false;
  468. }
  469. else
  470. {
  471. asset.lit = true;
  472. asset.alphaClipping = false;
  473. asset.generatesWithShaderGraph = true;
  474. }
  475. var assetGuid = graph.assetGuid;
  476. var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
  477. var hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath));
  478. var ports = new List<MaterialSlot>();
  479. var nodes = new List<AbstractMaterialNode>();
  480. foreach (var vertexBlock in graph.vertexContext.blocks)
  481. {
  482. vertexBlock.value.GetInputSlots(ports);
  483. NodeUtils.DepthFirstCollectNodesFromNode(nodes, vertexBlock);
  484. }
  485. foreach (var fragmentBlock in graph.fragmentContext.blocks)
  486. {
  487. fragmentBlock.value.GetInputSlots(ports);
  488. NodeUtils.DepthFirstCollectNodesFromNode(nodes, fragmentBlock);
  489. }
  490. //Remove inactive blocks from legacy generation
  491. if (!asset.generatesWithShaderGraph)
  492. {
  493. var tmpCtx = new TargetActiveBlockContext(new List<BlockFieldDescriptor>(), null);
  494. // NOTE: For whatever reason, this call fails for custom interpolator ports (ie, active ones are not detected as active).
  495. // For the sake of compatibility with custom interpolator with shadergraph generation, skip the removal of inactive blocks.
  496. target.GetActiveBlocks(ref tmpCtx);
  497. ports.RemoveAll(materialSlot =>
  498. {
  499. return !tmpCtx.activeBlocks.Any(o => materialSlot.RawDisplayName() == o.displayName);
  500. });
  501. }
  502. var bodySb = new ShaderStringBuilder(1);
  503. var graphIncludes = new IncludeCollection();
  504. var registry = new FunctionRegistry(new ShaderStringBuilder(), graphIncludes, true);
  505. foreach (var properties in graph.properties)
  506. {
  507. properties.SetupConcretePrecision(graph.graphDefaultConcretePrecision);
  508. }
  509. foreach (var node in nodes)
  510. {
  511. if (node is IGeneratesBodyCode bodyGenerator)
  512. {
  513. bodySb.currentNode = node;
  514. bodyGenerator.GenerateNodeCode(bodySb, mode);
  515. bodySb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
  516. }
  517. if (node is IGeneratesFunction generatesFunction)
  518. {
  519. registry.builder.currentNode = node;
  520. generatesFunction.GenerateNodeFunction(registry, mode);
  521. }
  522. }
  523. bodySb.currentNode = null;
  524. var portNodeSets = new HashSet<AbstractMaterialNode>[ports.Count];
  525. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  526. {
  527. var port = ports[portIndex];
  528. var nodeSet = new HashSet<AbstractMaterialNode>();
  529. NodeUtils.CollectNodeSet(nodeSet, port);
  530. portNodeSets[portIndex] = nodeSet;
  531. }
  532. var portPropertySets = new HashSet<string>[ports.Count];
  533. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  534. {
  535. portPropertySets[portIndex] = new HashSet<string>();
  536. }
  537. foreach (var node in nodes)
  538. {
  539. if (!(node is PropertyNode propertyNode))
  540. {
  541. continue;
  542. }
  543. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  544. {
  545. var portNodeSet = portNodeSets[portIndex];
  546. if (portNodeSet.Contains(node))
  547. {
  548. portPropertySets[portIndex].Add(propertyNode.property.objectId);
  549. }
  550. }
  551. }
  552. var shaderProperties = new PropertyCollector();
  553. foreach (var node in nodes)
  554. {
  555. node.CollectShaderProperties(shaderProperties, GenerationMode.ForReals);
  556. }
  557. asset.SetTextureInfos(shaderProperties.GetConfiguredTextures());
  558. var codeSnippets = new List<string>();
  559. var portCodeIndices = new List<int>[ports.Count];
  560. var sharedCodeIndices = new List<int>();
  561. for (var i = 0; i < portCodeIndices.Length; i++)
  562. {
  563. portCodeIndices[i] = new List<int>();
  564. }
  565. sharedCodeIndices.Add(codeSnippets.Count);
  566. codeSnippets.Add($"#include \"Packages/com.unity.shadergraph/ShaderGraphLibrary/Functions.hlsl\"{nl}");
  567. foreach (var include in graphIncludes)
  568. {
  569. sharedCodeIndices.Add(codeSnippets.Count);
  570. codeSnippets.Add(include.value + nl);
  571. }
  572. for (var registryIndex = 0; registryIndex < registry.names.Count; registryIndex++)
  573. {
  574. var name = registry.names[registryIndex];
  575. var source = registry.sources[name];
  576. var precision = source.nodes.First().concretePrecision;
  577. var hasPrecisionMismatch = false;
  578. var nodeNames = new HashSet<string>();
  579. foreach (var node in source.nodes)
  580. {
  581. nodeNames.Add(node.name);
  582. if (node.concretePrecision != precision)
  583. {
  584. hasPrecisionMismatch = true;
  585. break;
  586. }
  587. }
  588. if (hasPrecisionMismatch)
  589. {
  590. var message = new StringBuilder($"Precision mismatch for function {name}:");
  591. foreach (var node in source.nodes)
  592. {
  593. message.AppendLine($"{node.name} ({node.objectId}): {node.concretePrecision}");
  594. }
  595. throw new InvalidOperationException(message.ToString());
  596. }
  597. var code = source.code.Replace(PrecisionUtil.Token, precision.ToShaderString());
  598. code = $"// Node: {string.Join(", ", nodeNames)}{nl}{code}";
  599. var codeIndex = codeSnippets.Count;
  600. codeSnippets.Add(code + nl);
  601. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  602. {
  603. var portNodeSet = portNodeSets[portIndex];
  604. foreach (var node in source.nodes)
  605. {
  606. if (portNodeSet.Contains(node))
  607. {
  608. portCodeIndices[portIndex].Add(codeIndex);
  609. break;
  610. }
  611. }
  612. }
  613. }
  614. foreach (var property in graph.properties)
  615. {
  616. if (property.isExposed)
  617. {
  618. continue;
  619. }
  620. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  621. {
  622. var portPropertySet = portPropertySets[portIndex];
  623. if (portPropertySet.Contains(property.objectId))
  624. {
  625. portCodeIndices[portIndex].Add(codeSnippets.Count);
  626. }
  627. }
  628. ShaderStringBuilder builder = new ShaderStringBuilder();
  629. property.ForeachHLSLProperty(h => h.AppendTo(builder));
  630. codeSnippets.Add($"// Property: {property.displayName}{nl}{builder.ToCodeBlock()}{nl}{nl}");
  631. }
  632. foreach (var prop in shaderProperties.properties)
  633. {
  634. if (!graph.properties.Contains(prop) && (prop is SamplerStateShaderProperty))
  635. {
  636. sharedCodeIndices.Add(codeSnippets.Count);
  637. ShaderStringBuilder builder = new ShaderStringBuilder();
  638. prop.ForeachHLSLProperty(h => h.AppendTo(builder));
  639. codeSnippets.Add($"// Property: {prop.displayName}{nl}{builder.ToCodeBlock()}{nl}{nl}");
  640. }
  641. }
  642. var inputStructName = $"SG_Input_{assetGuid}";
  643. var outputStructName = $"SG_Output_{assetGuid}";
  644. var evaluationFunctionName = $"SG_Evaluate_{assetGuid}";
  645. #region Input Struct
  646. sharedCodeIndices.Add(codeSnippets.Count);
  647. codeSnippets.Add($"struct {inputStructName}{nl}{{{nl}");
  648. #region Requirements
  649. var portRequirements = new ShaderGraphRequirements[ports.Count];
  650. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  651. {
  652. var requirementsNodes = portNodeSets[portIndex].ToList();
  653. requirementsNodes.Add(ports[portIndex].owner);
  654. portRequirements[portIndex] = ShaderGraphRequirements.FromNodes(requirementsNodes, ports[portIndex].stageCapability);
  655. }
  656. var portIndices = new List<int>();
  657. portIndices.Capacity = ports.Count;
  658. void AddRequirementsSnippet(Func<ShaderGraphRequirements, bool> predicate, string snippet)
  659. {
  660. portIndices.Clear();
  661. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  662. {
  663. if (predicate(portRequirements[portIndex]))
  664. {
  665. portIndices.Add(portIndex);
  666. }
  667. }
  668. if (portIndices.Count > 0)
  669. {
  670. foreach (var portIndex in portIndices)
  671. {
  672. portCodeIndices[portIndex].Add(codeSnippets.Count);
  673. }
  674. codeSnippets.Add($"{indent}{snippet};{nl}");
  675. }
  676. }
  677. void AddCoordinateSpaceSnippets(InterpolatorType interpolatorType, Func<ShaderGraphRequirements, NeededCoordinateSpace> selector)
  678. {
  679. foreach (var space in EnumInfo<CoordinateSpace>.values)
  680. {
  681. var neededSpace = space.ToNeededCoordinateSpace();
  682. AddRequirementsSnippet(r => (selector(r) & neededSpace) > 0, $"float3 {space.ToVariableName(interpolatorType)}");
  683. }
  684. }
  685. // TODO: Rework requirements system to make this better
  686. AddCoordinateSpaceSnippets(InterpolatorType.Normal, r => r.requiresNormal);
  687. AddCoordinateSpaceSnippets(InterpolatorType.Tangent, r => r.requiresTangent);
  688. AddCoordinateSpaceSnippets(InterpolatorType.BiTangent, r => r.requiresBitangent);
  689. AddCoordinateSpaceSnippets(InterpolatorType.ViewDirection, r => r.requiresViewDir);
  690. AddCoordinateSpaceSnippets(InterpolatorType.Position, r => r.requiresPosition);
  691. AddCoordinateSpaceSnippets(InterpolatorType.PositionPredisplacement, r => r.requiresPositionPredisplacement);
  692. AddRequirementsSnippet(r => r.requiresVertexColor, $"float4 {ShaderGeneratorNames.VertexColor}");
  693. AddRequirementsSnippet(r => r.requiresScreenPosition, $"float4 {ShaderGeneratorNames.ScreenPosition}");
  694. AddRequirementsSnippet(r => r.requiresNDCPosition, $"float2 {ShaderGeneratorNames.NDCPosition}");
  695. AddRequirementsSnippet(r => r.requiresPixelPosition, $"float2 {ShaderGeneratorNames.PixelPosition}");
  696. AddRequirementsSnippet(r => r.requiresFaceSign, $"float4 {ShaderGeneratorNames.FaceSign}");
  697. foreach (var uvChannel in EnumInfo<UVChannel>.values)
  698. {
  699. AddRequirementsSnippet(r => r.requiresMeshUVs.Contains(uvChannel), $"half4 {uvChannel.GetUVName()}");
  700. }
  701. AddRequirementsSnippet(r => r.requiresTime, $"float3 {ShaderGeneratorNames.TimeParameters}");
  702. #endregion
  703. sharedCodeIndices.Add(codeSnippets.Count);
  704. codeSnippets.Add($"}};{nl}{nl}");
  705. #endregion
  706. // VFX Code heavily relies on the slotId from the original MasterNodes
  707. // Since we keep these around for upgrades anyway, for now it is simpler to use them
  708. // Therefore we remap the output blocks back to the original Ids here
  709. var originialPortIds = new int[ports.Count];
  710. for (int i = 0; i < originialPortIds.Length; i++)
  711. {
  712. if (!VFXTarget.s_BlockMap.TryGetValue((ports[i].owner as BlockNode).descriptor, out var originalId))
  713. continue;
  714. // In Master Nodes we had a different BaseColor/Color slot id between Unlit/Lit
  715. // In the stack we use BaseColor for both cases. Catch this here.
  716. if (asset.lit && originalId == ShaderGraphVfxAsset.ColorSlotId)
  717. {
  718. originalId = ShaderGraphVfxAsset.BaseColorSlotId;
  719. }
  720. originialPortIds[i] = originalId;
  721. }
  722. #region Output Struct
  723. sharedCodeIndices.Add(codeSnippets.Count);
  724. codeSnippets.Add($"struct {outputStructName}{nl}{{");
  725. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  726. {
  727. var port = ports[portIndex];
  728. portCodeIndices[portIndex].Add(codeSnippets.Count);
  729. codeSnippets.Add($"{nl}{indent}{port.concreteValueType.ToShaderString(graph.graphDefaultConcretePrecision)} {port.shaderOutputName}_{originialPortIds[portIndex]};");
  730. }
  731. sharedCodeIndices.Add(codeSnippets.Count);
  732. codeSnippets.Add($"{nl}}};{nl}{nl}");
  733. #endregion
  734. #region Graph Function
  735. sharedCodeIndices.Add(codeSnippets.Count);
  736. codeSnippets.Add($"{outputStructName} {evaluationFunctionName}({nl}{indent}{inputStructName} IN");
  737. var inputProperties = new List<ShaderInput>();
  738. var portPropertyIndices = new List<int>[ports.Count];
  739. var propertiesStages = new List<ShaderStageCapability>();
  740. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  741. {
  742. portPropertyIndices[portIndex] = new List<int>();
  743. }
  744. // Fetch properties from the categories to keep the same order as in the shader graph blackboard
  745. // Union with the flat properties collection because previous shader graph version could store properties without category
  746. var sortedProperties = graph.categories
  747. .SelectMany(x => x.Children)
  748. .Union(graph.properties)
  749. .Where(x => x.isExposed);
  750. foreach (var property in sortedProperties)
  751. {
  752. var propertyIndex = inputProperties.Count;
  753. var codeIndex = codeSnippets.Count;
  754. ShaderStageCapability stageCapability = 0;
  755. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  756. {
  757. var portPropertySet = portPropertySets[portIndex];
  758. if (portPropertySet.Contains(property.objectId))
  759. {
  760. portCodeIndices[portIndex].Add(codeIndex);
  761. portPropertyIndices[portIndex].Add(propertyIndex);
  762. stageCapability |= ports[portIndex].stageCapability;
  763. }
  764. }
  765. propertiesStages.Add(stageCapability);
  766. inputProperties.Add(property);
  767. if (property is AbstractShaderProperty shaderProperty)
  768. codeSnippets.Add($",{nl}{indent}/* Property: {property.displayName} */ {shaderProperty.GetPropertyAsArgumentStringForVFX(shaderProperty.concretePrecision.ToShaderString())}");
  769. }
  770. sharedCodeIndices.Add(codeSnippets.Count);
  771. codeSnippets.Add($"){nl}{{");
  772. #region Node Code
  773. for (var mappingIndex = 0; mappingIndex < bodySb.mappings.Count; mappingIndex++)
  774. {
  775. var mapping = bodySb.mappings[mappingIndex];
  776. var code = bodySb.ToString(mapping.startIndex, mapping.count);
  777. if (string.IsNullOrWhiteSpace(code))
  778. {
  779. continue;
  780. }
  781. code = $"{nl}{indent}// Node: {mapping.node.name}{nl}{code}";
  782. var codeIndex = codeSnippets.Count;
  783. codeSnippets.Add(code);
  784. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  785. {
  786. var portNodeSet = portNodeSets[portIndex];
  787. if (portNodeSet.Contains(mapping.node))
  788. {
  789. portCodeIndices[portIndex].Add(codeIndex);
  790. }
  791. }
  792. }
  793. #endregion
  794. #region Output Mapping
  795. sharedCodeIndices.Add(codeSnippets.Count);
  796. codeSnippets.Add($"{nl}{indent}// VFXMasterNode{nl}{indent}{outputStructName} OUT;{nl}");
  797. // Output mapping
  798. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  799. {
  800. var port = ports[portIndex];
  801. portCodeIndices[portIndex].Add(codeSnippets.Count);
  802. codeSnippets.Add($"{indent}OUT.{port.shaderOutputName}_{originialPortIds[portIndex]} = {port.owner.GetSlotValue(port.id, GenerationMode.ForReals, graph.graphDefaultConcretePrecision)};{nl}");
  803. }
  804. #endregion
  805. // Function end
  806. sharedCodeIndices.Add(codeSnippets.Count);
  807. codeSnippets.Add($"{indent}return OUT;{nl}}}{nl}");
  808. #endregion
  809. result.codeSnippets = codeSnippets.ToArray();
  810. result.sharedCodeIndices = sharedCodeIndices.ToArray();
  811. result.outputCodeIndices = new IntArray[ports.Count];
  812. for (var i = 0; i < ports.Count; i++)
  813. {
  814. result.outputCodeIndices[i] = portCodeIndices[i].ToArray();
  815. }
  816. var outputMetadatas = new OutputMetadata[ports.Count];
  817. for (int portIndex = 0; portIndex < outputMetadatas.Length; portIndex++)
  818. {
  819. outputMetadatas[portIndex] = new OutputMetadata(portIndex, ports[portIndex].shaderOutputName, originialPortIds[portIndex]);
  820. }
  821. asset.SetOutputs(outputMetadatas);
  822. asset.evaluationFunctionName = evaluationFunctionName;
  823. asset.inputStructName = inputStructName;
  824. asset.outputStructName = outputStructName;
  825. asset.portRequirements = portRequirements;
  826. asset.m_PropertiesStages = propertiesStages.ToArray();
  827. asset.concretePrecision = graph.graphDefaultConcretePrecision;
  828. asset.SetProperties(inputProperties);
  829. asset.outputPropertyIndices = new IntArray[ports.Count];
  830. for (var portIndex = 0; portIndex < ports.Count; portIndex++)
  831. {
  832. asset.outputPropertyIndices[portIndex] = portPropertyIndices[portIndex].ToArray();
  833. }
  834. return asset;
  835. }
  836. #endif
  837. }
  838. }