KeywordNode.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEditor.Graphing;
  6. using UnityEngine.Serialization;
  7. using UnityEditor.ShaderGraph.Internal;
  8. namespace UnityEditor.ShaderGraph
  9. {
  10. [Title("Utility", "Keyword")]
  11. class KeywordNode : AbstractMaterialNode, IOnAssetEnabled, IGeneratesBodyCode
  12. {
  13. internal const int k_MinEnumEntries = 2;
  14. internal const int k_MaxEnumEntries = 8;
  15. public KeywordNode()
  16. {
  17. UpdateNodeAfterDeserialization();
  18. }
  19. [SerializeField]
  20. private string m_KeywordGuidSerialized;
  21. private Guid m_KeywordGuid;
  22. public Guid keywordGuid
  23. {
  24. get { return m_KeywordGuid; }
  25. set
  26. {
  27. if (m_KeywordGuid == value)
  28. return;
  29. m_KeywordGuid = value;
  30. UpdateNode();
  31. Dirty(ModificationScope.Topological);
  32. }
  33. }
  34. public override bool canSetPrecision => false;
  35. public override bool hasPreview => true;
  36. public const int OutputSlotId = 0;
  37. public void OnEnable()
  38. {
  39. UpdateNode();
  40. }
  41. public void UpdateNode()
  42. {
  43. var keyword = owner.keywords.FirstOrDefault(x => x.guid == keywordGuid);
  44. if (keyword == null)
  45. return;
  46. name = keyword.displayName;
  47. UpdatePorts(keyword);
  48. }
  49. void UpdatePorts(ShaderKeyword keyword)
  50. {
  51. switch(keyword.keywordType)
  52. {
  53. case KeywordType.Boolean:
  54. {
  55. // Boolean type has preset slots
  56. AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero));
  57. AddSlot(new DynamicVectorMaterialSlot(1, "On", "On", SlotType.Input, Vector4.zero));
  58. AddSlot(new DynamicVectorMaterialSlot(2, "Off", "Off", SlotType.Input, Vector4.zero));
  59. RemoveSlotsNameNotMatching(new int[] {0, 1, 2});
  60. break;
  61. }
  62. case KeywordType.Enum:
  63. {
  64. // Get slots
  65. List<MaterialSlot> inputSlots = new List<MaterialSlot>();
  66. GetInputSlots(inputSlots);
  67. // Store the edges
  68. Dictionary<MaterialSlot, List<IEdge>> edgeDict = new Dictionary<MaterialSlot, List<IEdge>>();
  69. foreach (MaterialSlot slot in inputSlots)
  70. edgeDict.Add(slot, (List<IEdge>)slot.owner.owner.GetEdges(slot.slotReference));
  71. // Remove old slots
  72. for(int i = 0; i < inputSlots.Count; i++)
  73. {
  74. RemoveSlot(inputSlots[i].id);
  75. }
  76. // Add output slot
  77. AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero));
  78. // Add input slots
  79. int[] slotIds = new int[keyword.entries.Count + 1];
  80. slotIds[keyword.entries.Count] = OutputSlotId;
  81. for(int i = 0; i < keyword.entries.Count; i++)
  82. {
  83. // Get slot based on entry id
  84. MaterialSlot slot = inputSlots.Where(x =>
  85. x.id == keyword.entries[i].id &&
  86. x.RawDisplayName() == keyword.entries[i].displayName &&
  87. x.shaderOutputName == keyword.entries[i].referenceName).FirstOrDefault();
  88. // If slot doesnt exist its new so create it
  89. if(slot == null)
  90. {
  91. slot = new DynamicVectorMaterialSlot(keyword.entries[i].id, keyword.entries[i].displayName, keyword.entries[i].referenceName, SlotType.Input, Vector4.zero);
  92. }
  93. AddSlot(slot);
  94. slotIds[i] = keyword.entries[i].id;
  95. }
  96. RemoveSlotsNameNotMatching(slotIds);
  97. // Reconnect the edges
  98. foreach (KeyValuePair<MaterialSlot, List<IEdge>> entry in edgeDict)
  99. {
  100. foreach (IEdge edge in entry.Value)
  101. {
  102. owner.Connect(edge.outputSlot, edge.inputSlot);
  103. }
  104. }
  105. break;
  106. }
  107. }
  108. ValidateNode();
  109. }
  110. public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
  111. {
  112. var keyword = owner.keywords.FirstOrDefault(x => x.guid == keywordGuid);
  113. if (keyword == null)
  114. return;
  115. var outputSlot = FindOutputSlot<MaterialSlot>(OutputSlotId);
  116. switch(keyword.keywordType)
  117. {
  118. case KeywordType.Boolean:
  119. {
  120. // Get values
  121. var onValue = GetSlotValue(1, generationMode);
  122. var offValue = GetSlotValue(2, generationMode);
  123. // Append code
  124. sb.AppendLine($"#if defined({keyword.referenceName})");
  125. sb.AppendLine(string.Format($"{outputSlot.concreteValueType.ToShaderString()} {GetVariableNameForSlot(OutputSlotId)} = {onValue};"));
  126. sb.AppendLine("#else");
  127. sb.AppendLine(string.Format($"{outputSlot.concreteValueType.ToShaderString()} {GetVariableNameForSlot(OutputSlotId)} = {offValue};"));
  128. sb.AppendLine("#endif");
  129. break;
  130. }
  131. case KeywordType.Enum:
  132. {
  133. // Iterate all entries in the keyword
  134. for(int i = 0; i < keyword.entries.Count; i++)
  135. {
  136. // Insert conditional
  137. if(i == 0)
  138. {
  139. sb.AppendLine($"#if defined({keyword.referenceName}_{keyword.entries[i].referenceName})");
  140. }
  141. else if(i == keyword.entries.Count - 1)
  142. {
  143. sb.AppendLine("#else");
  144. }
  145. else
  146. {
  147. sb.AppendLine($"#elif defined({keyword.referenceName}_{keyword.entries[i].referenceName})");
  148. }
  149. // Append per-slot code
  150. var value = GetSlotValue(GetSlotIdForPermutation(new KeyValuePair<ShaderKeyword, int>(keyword, i)), generationMode);
  151. sb.AppendLine(string.Format($"{outputSlot.concreteValueType.ToShaderString()} {GetVariableNameForSlot(OutputSlotId)} = {value};"));
  152. }
  153. // End condition
  154. sb.AppendLine("#endif");
  155. break;
  156. }
  157. default:
  158. throw new ArgumentOutOfRangeException();
  159. }
  160. }
  161. public int GetSlotIdForPermutation(KeyValuePair<ShaderKeyword, int> permutation)
  162. {
  163. switch(permutation.Key.keywordType)
  164. {
  165. // Slot 0 is output
  166. case KeywordType.Boolean:
  167. return 1 + permutation.Value;
  168. // Ids are stored manually as slots are added
  169. case KeywordType.Enum:
  170. return permutation.Key.entries[permutation.Value].id;
  171. default:
  172. throw new ArgumentOutOfRangeException();
  173. }
  174. }
  175. protected override bool CalculateNodeHasError(ref string errorMessage)
  176. {
  177. if (!keywordGuid.Equals(Guid.Empty) && !owner.keywords.Any(x => x.guid == keywordGuid))
  178. return true;
  179. return false;
  180. }
  181. public override void OnBeforeSerialize()
  182. {
  183. base.OnBeforeSerialize();
  184. // Handle keyword guid serialization
  185. m_KeywordGuidSerialized = m_KeywordGuid.ToString();
  186. }
  187. public override void OnAfterDeserialize()
  188. {
  189. base.OnAfterDeserialize();
  190. // Handle keyword guid serialization
  191. if (!string.IsNullOrEmpty(m_KeywordGuidSerialized))
  192. {
  193. m_KeywordGuid = new Guid(m_KeywordGuidSerialized);
  194. }
  195. }
  196. }
  197. }