Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine;
  6. using UnityEditor.Graphing;
  7. using UnityEditor.Rendering;
  8. using UnityEngine.UIElements;
  9. using UnityEditor.ShaderGraph.Drawing;
  10. using System.Text;
  11. namespace UnityEditor.ShaderGraph
  12. {
  13. [HasDependencies(typeof(MinimalCustomFunctionNode))]
  14. [Title("Utility", "Custom Function")]
  15. class CustomFunctionNode : AbstractMaterialNode, IGeneratesBodyCode, IGeneratesFunction, IMayRequireTransform
  16. {
  17. // 0 original version
  18. // 1 differentiate between struct-based UnityTexture2D and bare Texture2D resources (for all texture and samplerstate resources)
  19. public override int latestVersion => 1;
  20. public override IEnumerable<int> allowedNodeVersions => new int[] { 1 };
  21. [Serializable]
  22. public class MinimalCustomFunctionNode : IHasDependencies
  23. {
  24. [SerializeField]
  25. HlslSourceType m_SourceType = HlslSourceType.File;
  26. [SerializeField]
  27. string m_FunctionName = k_DefaultFunctionName;
  28. [SerializeField]
  29. string m_FunctionSource = null;
  30. public void GetSourceAssetDependencies(AssetCollection assetCollection)
  31. {
  32. if (m_SourceType == HlslSourceType.File)
  33. {
  34. m_FunctionSource = UpgradeFunctionSource(m_FunctionSource);
  35. if (IsValidFunction(m_SourceType, m_FunctionName, m_FunctionSource, null))
  36. {
  37. if (GUID.TryParse(m_FunctionSource, out GUID guid))
  38. {
  39. // as this is just #included into the generated .shader file
  40. // it doesn't actually need to be a dependency, other than for export package
  41. assetCollection.AddAssetDependency(guid, AssetCollection.Flags.IncludeInExportPackage);
  42. }
  43. }
  44. }
  45. }
  46. }
  47. enum SourceFileStatus
  48. {
  49. Empty, // No File specified
  50. DoesNotExist, // Either file doesn't exist (empty name) or guid points to a non-existant file
  51. Invalid, // File exists but isn't of a valid type (such as wrong extension)
  52. Valid
  53. };
  54. // With ShaderInclude asset type, it should no longer be necessary to soft-check the extension.
  55. public static string[] s_ValidExtensions = { ".hlsl", ".cginc", ".cg" };
  56. const string k_InvalidFileType = "Source file is not a valid file type. Valid file extensions are .hlsl, .cginc, and .cg";
  57. const string k_MissingFile = "Source file does not exist. A valid .hlsl, .cginc, or .cg file must be referenced";
  58. const string k_MissingOutputSlot = "A Custom Function Node must have at least one output slot";
  59. public CustomFunctionNode()
  60. {
  61. UpdateNodeName();
  62. synonyms = new string[] { "code", "HLSL" };
  63. }
  64. void UpdateNodeName()
  65. {
  66. if ((functionName == defaultFunctionName) || (functionName == null))
  67. name = "Custom Function";
  68. else
  69. name = functionName + " (Custom Function)";
  70. }
  71. public override bool hasPreview => true;
  72. [SerializeField]
  73. HlslSourceType m_SourceType = HlslSourceType.File;
  74. public HlslSourceType sourceType
  75. {
  76. get => m_SourceType;
  77. set => m_SourceType = value;
  78. }
  79. [SerializeField]
  80. string m_FunctionName = k_DefaultFunctionName;
  81. const string k_DefaultFunctionName = "Enter function name here...";
  82. public string functionName
  83. {
  84. get => m_FunctionName;
  85. set
  86. {
  87. m_FunctionName = value;
  88. UpdateNodeName();
  89. }
  90. }
  91. public string hlslFunctionName
  92. {
  93. get => m_FunctionName + "_$precision";
  94. }
  95. public static string defaultFunctionName => k_DefaultFunctionName;
  96. [SerializeField]
  97. string m_FunctionSource;
  98. const string k_DefaultFunctionSource = "Enter function source file path here...";
  99. public string functionSource
  100. {
  101. get => m_FunctionSource;
  102. set => m_FunctionSource = value;
  103. }
  104. [SerializeField]
  105. string m_FunctionBody = k_DefaultFunctionBody;
  106. const string k_DefaultFunctionBody = "Enter function body here...";
  107. public string functionBody
  108. {
  109. get => m_FunctionBody;
  110. set => m_FunctionBody = value;
  111. }
  112. public static string defaultFunctionBody => k_DefaultFunctionBody;
  113. public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
  114. {
  115. using (var inputSlots = PooledList<MaterialSlot>.Get())
  116. using (var outputSlots = PooledList<MaterialSlot>.Get())
  117. {
  118. GetInputSlots<MaterialSlot>(inputSlots);
  119. GetOutputSlots<MaterialSlot>(outputSlots);
  120. if (!IsValidFunction())
  121. {
  122. // invalid functions generate special preview code.. (why?)
  123. if (generationMode == GenerationMode.Preview && outputSlots.Count != 0)
  124. {
  125. outputSlots.OrderBy(s => s.id);
  126. var hlslVariableType = outputSlots[0].concreteValueType.ToShaderString();
  127. sb.AppendLine("{0} {1};",
  128. hlslVariableType,
  129. GetVariableNameForSlot(outputSlots[0].id));
  130. }
  131. return;
  132. }
  133. // declare output variables
  134. foreach (var output in outputSlots)
  135. {
  136. sb.AppendLine("{0} {1};",
  137. output.concreteValueType.ToShaderString(),
  138. GetVariableNameForSlot(output.id));
  139. if (output.bareResource)
  140. AssignDefaultBareResource(output, sb);
  141. }
  142. // call function
  143. sb.TryAppendIndentation();
  144. sb.Append(hlslFunctionName);
  145. sb.Append("(");
  146. bool first = true;
  147. foreach (var input in inputSlots)
  148. {
  149. if (!first)
  150. sb.Append(", ");
  151. first = false;
  152. sb.Append(SlotInputValue(input, generationMode));
  153. // fixup input for Bare types
  154. if (input.bareResource)
  155. {
  156. if (input is SamplerStateMaterialSlot)
  157. sb.Append(".samplerstate");
  158. else
  159. sb.Append(".tex");
  160. }
  161. }
  162. foreach (var output in outputSlots)
  163. {
  164. if (!first)
  165. sb.Append(", ");
  166. first = false;
  167. sb.Append(GetVariableNameForSlot(output.id));
  168. // fixup output for Bare types
  169. if (output.bareResource)
  170. {
  171. if (output is SamplerStateMaterialSlot)
  172. sb.Append(".samplerstate");
  173. else
  174. sb.Append(".tex");
  175. }
  176. }
  177. sb.Append(");");
  178. sb.AppendNewLine();
  179. }
  180. }
  181. void AssignDefaultBareResource(MaterialSlot slot, ShaderStringBuilder sb)
  182. {
  183. switch (slot.concreteValueType)
  184. {
  185. case ConcreteSlotValueType.Texture2D:
  186. {
  187. var slotVariable = GetVariableNameForSlot(slot.id);
  188. sb.TryAppendIndentation();
  189. sb.Append(slotVariable);
  190. sb.Append(".samplerstate = default_sampler_Linear_Repeat;");
  191. sb.AppendNewLine();
  192. sb.TryAppendIndentation();
  193. sb.Append(slotVariable);
  194. sb.Append(".texelSize = float4(1.0f/128.0f, 1.0f/128.0f, 128.0f, 128.0f);");
  195. sb.AppendNewLine();
  196. sb.TryAppendIndentation();
  197. sb.Append(slotVariable);
  198. sb.Append(".scaleTranslate = float4(1.0f, 1.0f, 0.0f, 0.0f);");
  199. sb.AppendNewLine();
  200. }
  201. break;
  202. case ConcreteSlotValueType.Texture3D:
  203. case ConcreteSlotValueType.Texture2DArray:
  204. case ConcreteSlotValueType.Cubemap:
  205. {
  206. var slotVariable = GetVariableNameForSlot(slot.id);
  207. sb.TryAppendIndentation();
  208. sb.Append(slotVariable);
  209. sb.Append(".samplerstate = default_sampler_Linear_Repeat;");
  210. sb.AppendNewLine();
  211. }
  212. break;
  213. }
  214. }
  215. public void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode)
  216. {
  217. if (!IsValidFunction())
  218. return;
  219. switch (sourceType)
  220. {
  221. case HlslSourceType.File:
  222. string path = AssetDatabase.GUIDToAssetPath(functionSource);
  223. // This is required for upgrading without console errors
  224. if (string.IsNullOrEmpty(path))
  225. path = functionSource;
  226. registry.RequiresIncludePath(path);
  227. break;
  228. case HlslSourceType.String:
  229. registry.ProvideFunction(hlslFunctionName, builder =>
  230. {
  231. // add a hint for the analytic derivative code to ignore user functions
  232. builder.AddLine("// unity-custom-func-begin");
  233. GetFunctionHeader(builder);
  234. using (builder.BlockScope())
  235. {
  236. builder.AppendLines(functionBody);
  237. }
  238. builder.AddLine("// unity-custom-func-end");
  239. });
  240. break;
  241. default:
  242. throw new ArgumentOutOfRangeException();
  243. }
  244. }
  245. void GetFunctionHeader(ShaderStringBuilder sb)
  246. {
  247. using (var inputSlots = PooledList<MaterialSlot>.Get())
  248. using (var outputSlots = PooledList<MaterialSlot>.Get())
  249. {
  250. GetInputSlots(inputSlots);
  251. GetOutputSlots(outputSlots);
  252. sb.Append("void ");
  253. sb.Append(hlslFunctionName);
  254. sb.Append("(");
  255. var first = true;
  256. foreach (var argument in inputSlots)
  257. {
  258. if (!first)
  259. sb.Append(", ");
  260. first = false;
  261. argument.AppendHLSLParameterDeclaration(sb, argument.shaderOutputName);
  262. }
  263. foreach (var argument in outputSlots)
  264. {
  265. if (!first)
  266. sb.Append(", ");
  267. first = false;
  268. sb.Append("out ");
  269. argument.AppendHLSLParameterDeclaration(sb, argument.shaderOutputName);
  270. }
  271. sb.Append(")");
  272. }
  273. }
  274. string SlotInputValue(MaterialSlot port, GenerationMode generationMode)
  275. {
  276. IEdge[] edges = port.owner.owner.GetEdges(port.slotReference).ToArray();
  277. if (edges.Any())
  278. {
  279. var fromSocketRef = edges[0].outputSlot;
  280. var fromNode = fromSocketRef.node;
  281. if (fromNode == null)
  282. return string.Empty;
  283. return fromNode.GetOutputForSlot(fromSocketRef, port.concreteValueType, generationMode);
  284. }
  285. return port.GetDefaultValue(generationMode);
  286. }
  287. bool IsValidFunction()
  288. {
  289. return IsValidFunction(sourceType, functionName, functionSource, functionBody);
  290. }
  291. static bool IsValidFunction(HlslSourceType sourceType, string functionName, string functionSource, string functionBody)
  292. {
  293. bool validFunctionName = !string.IsNullOrEmpty(functionName) && functionName != k_DefaultFunctionName;
  294. if (sourceType == HlslSourceType.String)
  295. {
  296. bool validFunctionBody = !string.IsNullOrEmpty(functionBody) && functionBody != k_DefaultFunctionBody;
  297. return validFunctionName & validFunctionBody;
  298. }
  299. else
  300. {
  301. if (!validFunctionName || string.IsNullOrEmpty(functionSource) || functionSource == k_DefaultFunctionSource)
  302. return false;
  303. string path = AssetDatabase.GUIDToAssetPath(functionSource);
  304. if (string.IsNullOrEmpty(path))
  305. path = functionSource;
  306. string extension = Path.GetExtension(path);
  307. return s_ValidExtensions.Contains(extension);
  308. }
  309. }
  310. void ValidateSlotName()
  311. {
  312. using (var slots = PooledList<MaterialSlot>.Get())
  313. {
  314. GetSlots(slots);
  315. foreach (var slot in slots)
  316. {
  317. // check for bad slot names
  318. var error = NodeUtils.ValidateSlotName(slot.RawDisplayName(), out string errorMessage);
  319. if (error)
  320. {
  321. owner.AddValidationError(objectId, errorMessage);
  322. break;
  323. }
  324. }
  325. }
  326. }
  327. void ValidateBareTextureSlots()
  328. {
  329. using (var outputSlots = PooledList<MaterialSlot>.Get())
  330. {
  331. GetOutputSlots(outputSlots);
  332. foreach (var slot in outputSlots)
  333. {
  334. if (slot.bareResource)
  335. {
  336. owner.AddValidationError(objectId, "This node uses Bare Texture or SamplerState outputs, which may produce unexpected results when fed to other nodes. Please convert the node to use the non-Bare struct-based outputs (see the structs defined in com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl)", ShaderCompilerMessageSeverity.Warning);
  337. break;
  338. }
  339. }
  340. }
  341. }
  342. public override void ValidateNode()
  343. {
  344. bool hasAnyOutputs = this.GetOutputSlots<MaterialSlot>().Any();
  345. if (sourceType == HlslSourceType.File)
  346. {
  347. SourceFileStatus fileStatus = SourceFileStatus.Empty;
  348. if (!string.IsNullOrEmpty(functionSource))
  349. {
  350. string path = AssetDatabase.GUIDToAssetPath(functionSource);
  351. if (!string.IsNullOrEmpty(path) && AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path) != null)
  352. {
  353. string extension = path.Substring(path.LastIndexOf('.'));
  354. if (!s_ValidExtensions.Contains(extension))
  355. {
  356. fileStatus = SourceFileStatus.Invalid;
  357. }
  358. else
  359. {
  360. fileStatus = SourceFileStatus.Valid;
  361. }
  362. }
  363. else
  364. fileStatus = SourceFileStatus.DoesNotExist;
  365. }
  366. if (fileStatus == SourceFileStatus.DoesNotExist || (fileStatus == SourceFileStatus.Empty && hasAnyOutputs))
  367. owner.AddValidationError(objectId, k_MissingFile, ShaderCompilerMessageSeverity.Error);
  368. else if (fileStatus == SourceFileStatus.Invalid)
  369. owner.AddValidationError(objectId, k_InvalidFileType, ShaderCompilerMessageSeverity.Error);
  370. else if (fileStatus == SourceFileStatus.Valid)
  371. owner.ClearErrorsForNode(this);
  372. }
  373. if (!hasAnyOutputs)
  374. {
  375. owner.AddValidationError(objectId, k_MissingOutputSlot, ShaderCompilerMessageSeverity.Warning);
  376. }
  377. ValidateSlotName();
  378. ValidateBareTextureSlots();
  379. base.ValidateNode();
  380. }
  381. public bool Reload(HashSet<string> changedFileDependencyGUIDs)
  382. {
  383. if (changedFileDependencyGUIDs.Contains(m_FunctionSource))
  384. {
  385. owner.ClearErrorsForNode(this);
  386. ValidateNode();
  387. Dirty(ModificationScope.Graph);
  388. return true;
  389. }
  390. return false;
  391. }
  392. public static string UpgradeFunctionSource(string functionSource)
  393. {
  394. // Handle upgrade from legacy asset path version
  395. // If functionSource is not empty or a guid then assume it is legacy version
  396. // If asset can be loaded from path then get its guid
  397. // Otherwise it was the default string so set to empty
  398. Guid guid;
  399. if (!string.IsNullOrEmpty(functionSource) && !Guid.TryParse(functionSource, out guid))
  400. {
  401. // not sure why we don't use AssetDatabase.AssetPathToGUID...
  402. // I guess we are testing that it actually exists and can be loaded here before converting?
  403. string guidString = string.Empty;
  404. ShaderInclude shaderInclude = AssetDatabase.LoadAssetAtPath<ShaderInclude>(functionSource);
  405. if (shaderInclude != null)
  406. {
  407. long localId;
  408. AssetDatabase.TryGetGUIDAndLocalFileIdentifier(shaderInclude, out guidString, out localId);
  409. }
  410. functionSource = guidString;
  411. }
  412. return functionSource;
  413. }
  414. public override void OnAfterDeserialize()
  415. {
  416. base.OnAfterDeserialize();
  417. functionSource = UpgradeFunctionSource(functionSource);
  418. UpdateNodeName();
  419. }
  420. public override void OnAfterMultiDeserialize(string json)
  421. {
  422. if (sgVersion < 1)
  423. {
  424. // any Texture2D slots used prior to version 1 should be flagged as "bare" so we can
  425. // generate backwards compatible code
  426. var slots = new List<MaterialSlot>();
  427. GetSlots(slots);
  428. foreach (var slot in slots)
  429. {
  430. slot.bareResource = true;
  431. }
  432. ChangeVersion(1);
  433. }
  434. }
  435. public NeededTransform[] RequiresTransform(ShaderStageCapability stageCapability = ShaderStageCapability.All)
  436. {
  437. return new[]
  438. {
  439. NeededTransform.ObjectToWorld,
  440. NeededTransform.WorldToObject
  441. };
  442. }
  443. }
  444. }