A game about forced loneliness, made by TACStudios
at master 11 kB view raw
1using System; 2using System.Linq; 3using System.Collections.Generic; 4using UnityEngine; 5using UnityEditor.Graphing; 6using UnityEditor.ShaderGraph.Internal; 7using UnityEditor.Rendering; 8 9namespace UnityEditor.ShaderGraph 10{ 11 [Serializable] 12 [Title("Custom Interpolators", "Instance")] 13 class CustomInterpolatorNode : AbstractMaterialNode 14 { 15 [SerializeField] 16 internal string customBlockNodeName = "K_INVALID"; 17 18 [SerializeField] 19 private BlockNode.CustomBlockType serializedType = BlockNode.CustomBlockType.Vector4; 20 21 public override bool hasPreview { get { return true; } } 22 23 internal override bool ExposeToSearcher { get => false; } // This is exposed in a special way. 24 25 public override bool allowedInSubGraph { get => false; } 26 27 internal BlockNode e_targetBlockNode // weak indirection via customBlockNodeName 28 { 29 get => (owner?.vertexContext.blocks.Find(cib => cib.value.descriptor.name == customBlockNodeName))?.value ?? null; 30 } 31 32 public CustomInterpolatorNode() 33 { 34 UpdateNodeAfterDeserialization(); 35 } 36 37 internal void ConnectToCustomBlock(BlockNode node) 38 { 39 // if somehow we were already connected, be sure to unregister. 40 if (e_targetBlockNode != null) 41 { 42 e_targetBlockNode.UnregisterCallback(OnCustomBlockModified); 43 } 44 // if a new cib is renamed to match us when we didn't have a target (unusual case, but covering all bases here). 45 if (node?.isCustomBlock ?? false) 46 { 47 name = node.customName + " (Custom Interpolator)"; 48 customBlockNodeName = node.customName; 49 serializedType = node.customWidth; 50 BuildSlot(); 51 node.RegisterCallback(OnCustomBlockModified); 52 } 53 } 54 55 internal void ConnectToCustomBlockByName(string customBlockName) 56 { 57 // see above 58 if (e_targetBlockNode != null) 59 { 60 e_targetBlockNode.UnregisterCallback(OnCustomBlockModified); 61 } 62 63 name = customBlockName + " (Custom Interpolator)"; 64 customBlockNodeName = customBlockName; 65 if (e_targetBlockNode != null) 66 { 67 serializedType = e_targetBlockNode.customWidth; 68 BuildSlot(); 69 e_targetBlockNode.RegisterCallback(OnCustomBlockModified); 70 } 71 else 72 { 73 // We should get badged in OnValidate. 74 } 75 } 76 77 void OnCustomBlockModified(AbstractMaterialNode node, Graphing.ModificationScope scope) 78 { 79 if (node is BlockNode bnode) 80 { 81 if (bnode?.isCustomBlock ?? false) 82 { 83 name = bnode.customName + " (Custom Interpolator)"; 84 customBlockNodeName = bnode.customName; 85 if (e_targetBlockNode != null && e_targetBlockNode.owner != null) 86 { 87 serializedType = e_targetBlockNode.customWidth; 88 BuildSlot(); 89 Dirty(ModificationScope.Node); 90 Dirty(ModificationScope.Topological); 91 } 92 } 93 } 94 // bnode information we got is somehow invalid, this is probably case for an exception. 95 } 96 97 public override void ValidateNode() 98 { 99 // Our node was deleted or we had bad deserialization, we need to badge. 100 if (e_targetBlockNode == null || e_targetBlockNode.owner == null) 101 { 102 e_targetBlockNode?.UnregisterCallback(OnCustomBlockModified); 103 owner.AddValidationError(objectId, String.Format("Custom Block Interpolator '{0}' not found.", customBlockNodeName), ShaderCompilerMessageSeverity.Error); 104 } 105 else 106 { 107 // our blockNode reference is somehow valid again after it wasn't, 108 // we can reconnect and everything should be restored. 109 ConnectToCustomBlockByName(customBlockNodeName); 110 } 111 } 112 113 public override void UpdateNodeAfterDeserialization() 114 { 115 // our e_targetBlockNode is unsafe here, so we build w/our serialization info and hope for the best! 116 BuildSlot(); 117 base.UpdateNodeAfterDeserialization(); 118 } 119 120 void BuildSlot() 121 { 122 switch (serializedType) 123 { 124 case BlockNode.CustomBlockType.Float: 125 AddSlot(new Vector1MaterialSlot(0, "Out", "Out", SlotType.Output, default(float), ShaderStageCapability.Fragment)); 126 break; 127 case BlockNode.CustomBlockType.Vector2: 128 AddSlot(new Vector2MaterialSlot(0, "Out", "Out", SlotType.Output, default(Vector2), ShaderStageCapability.Fragment)); 129 break; 130 case BlockNode.CustomBlockType.Vector3: 131 AddSlot(new Vector3MaterialSlot(0, "Out", "Out", SlotType.Output, default(Vector3), ShaderStageCapability.Fragment)); 132 break; 133 case BlockNode.CustomBlockType.Vector4: 134 AddSlot(new Vector4MaterialSlot(0, "Out", "Out", SlotType.Output, default(Vector4), ShaderStageCapability.Fragment)); 135 break; 136 } 137 RemoveSlotsNameNotMatching(new[] { 0 }); 138 } 139 140 public override string GetVariableNameForSlot(int slotid) 141 { 142 var slotRef = GetSlotReference(0); 143 return GetOutputForSlot(slotRef, slotRef.slot.concreteValueType, GenerationMode.Preview); 144 } 145 146 protected internal override string GetOutputForSlot(SlotReference fromSocketRef, ConcreteSlotValueType valueType, GenerationMode generationMode) 147 { 148 // check to see if we can inline a value. 149 List<PreviewProperty> props = new List<PreviewProperty>(); 150 e_targetBlockNode?.CollectPreviewMaterialProperties(props); 151 152 // if the cib is inActive, this node still might be in an active branch. 153 bool isActive = e_targetBlockNode?.isActive ?? false; 154 155 // if the cib has no input node, we can use the input property to inline a magic value. 156 bool canInline = e_targetBlockNode?.GetInputNodeFromSlot(0) == null && props.Count != 0; 157 158 // vector width of target slot 159 int toWidth = SlotTypeToWidth(valueType); 160 161 string finalResult = ""; 162 163 // If cib is inactive (or doesn't exist), then we default to black (as is the case for other nodes). 164 if (!isActive || CustomInterpolatorUtils.generatorSkipFlag) 165 { 166 finalResult = ConvertVector("$precision4(0,0,0,0)", 4, toWidth); 167 } 168 // cib has no input; we can directly use the inline value instead. 169 else if (canInline) 170 { 171 Vector4 v = default; 172 if (props[0].propType != PropertyType.Float) 173 v = props[0].vector4Value; 174 175 int outWidth = 4; 176 string result; 177 switch (props[0].propType) 178 { 179 case PropertyType.Float: 180 result = $" $precision1({props[0].floatValue}) "; 181 outWidth = 1; 182 break; 183 default: 184 result = $" $precision4({v.x},{v.y},{v.z},{v.w}) "; 185 outWidth = 4; 186 break; 187 } 188 finalResult = ConvertVector(result, outWidth, toWidth); 189 } 190 // Preview Node doesn't support CI, but we can fake it by asking the cib's source input for it's value instead. 191 else if (CustomInterpolatorUtils.generatorNodeOnly) 192 { 193 var sourceSlot = FindSourceSlot(out var found); 194 // CIB's type needs to constrain the incoming value (eg. vec2(out)->float(cib) | float(cin)->vec2(in)) 195 // If we didn't do this next line, we'd get vec2(out)->vec2(in), which would ignore the truncation in the preview. 196 var result = sourceSlot.node.GetOutputForSlot(sourceSlot, FindSlot<MaterialSlot>(0).concreteValueType, GenerationMode.Preview); 197 finalResult = ConvertVector(result, (int)e_targetBlockNode.customWidth, toWidth); 198 } 199 // If we made it this far, then cib is in a valid and meaningful configuration in the SDI struct. 200 else 201 { 202 // pull directly out of the SDI and just use it. 203 var result = string.Format("IN.{0}", customBlockNodeName); 204 finalResult = ConvertVector(result, (int)e_targetBlockNode.customWidth, toWidth); 205 } 206 return finalResult.Replace(PrecisionUtil.Token, concretePrecision.ToShaderString()); 207 } 208 209 SlotReference FindSourceSlot(out bool found) 210 { 211 try 212 { 213 found = true; 214 return owner.GetEdges(e_targetBlockNode).First().outputSlot; 215 } 216 catch 217 { 218 found = false; 219 return default; 220 } 221 } 222 223 private static int SlotTypeToWidth(ConcreteSlotValueType valueType) 224 { 225 switch (valueType) 226 { 227 case ConcreteSlotValueType.Boolean: 228 case ConcreteSlotValueType.Vector1: return 1; 229 case ConcreteSlotValueType.Vector2: return 2; 230 case ConcreteSlotValueType.Vector3: return 3; 231 default: return 4; 232 } 233 } 234 235 private static string ConvertVector(string name, int fromLen, int toLen) 236 { 237 if (fromLen == toLen) 238 return name; 239 240 var key = new char[] { 'x', 'y', 'z', 'w' }; 241 242 string begin = $"$precision{toLen}({name}."; 243 var mid = ""; 244 string end = ")"; 245 246 if (toLen == 4) 247 { 248 // We assume homogenous coordinates for some reason. 249 end = ", 1.0)"; 250 toLen -= 1; 251 } 252 253 if (fromLen == 1) 254 { 255 // we expand floats for each component for some reason. 256 fromLen = toLen; 257 key = new char[] { 'x', 'x', 'x' }; 258 } 259 260 // expand the swizzle 261 int swizzLen = Math.Min(fromLen, toLen); 262 for (int i = 0; i < swizzLen; ++i) 263 mid += key[i]; 264 265 // fill gaps 266 for (int i = fromLen; i < toLen; ++i) 267 mid += ", 0.0"; 268 269 // float<toLen>(<name>.<swizz>, <gap...>, 1.0)" 270 return $"({begin}{mid}{end})"; 271 } 272 } 273}