From b424e75ac0971155896e0b28c7f9a23f934a15c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20=C4=90=E1=BB=A9c=20Long?= Date: Mon, 26 Sep 2022 18:59:07 +0700 Subject: [PATCH] [Feature] Subgraph node --- .../Editor/Views/SubgraphNodeView.cs | 146 +++++++++++++ .../Editor/Views/SubgraphNodeView.cs.meta | 11 + .../Runtime/Nodes/SubgraphNode.cs | 197 ++++++++++++++++++ .../Runtime/Nodes/SubgraphNode.cs.meta | 11 + 4 files changed, 365 insertions(+) create mode 100644 Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs create mode 100644 Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs.meta create mode 100644 Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs create mode 100644 Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs.meta diff --git a/Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs b/Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs new file mode 100644 index 00000000..ae6bdc66 --- /dev/null +++ b/Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs @@ -0,0 +1,146 @@ +using GraphProcessor; +using System.Linq; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Mixture +{ + [NodeCustomEditor(typeof(SubgraphNode))] + public class SubgraphNodeView : MixtureNodeView + { + SubgraphNode _node; + + DropdownField _previewOutputField; + + public override void Enable(bool fromInspector) + { + base.Enable(fromInspector); + + _node = nodeTarget as SubgraphNode; + + _previewOutputField = new DropdownField( + "Preview Output", + _node.subgraph?.outputNode?.outputTextureSettings?.Select(setting => setting.name)?.ToList(), + _node.previewOutputIndex + ) + { visible = _node.previewOutputIndex > -1 }; + _previewOutputField.RegisterValueChangedCallback(e => + { + _node.previewOutputIndex = _previewOutputField.index; + NotifyNodeChanged(); + }); + + var subgraphTextureField = new ObjectField("Subgraph Texture") + { + value = _node.subgraphTexture, + objectType = typeof(CustomRenderTexture) + }; + subgraphTextureField.RegisterValueChangedCallback(e => + { + _node.subgraphTexture = e.newValue as CustomRenderTexture; + UpdateSubgraph(); + title = _node.name; + }); + + controlsContainer.Add(_previewOutputField); + controlsContainer.Add(subgraphTextureField); + + _node.onAfterEdgeDisconnected += UpdateSubgraph; + + UpdateSubgraph(); + } + + public override void Disable() + { + base.Disable(); + + if (_node != null) _node.onAfterEdgeDisconnected -= UpdateSubgraph; + } + + void UpdateSubgraph(SerializableEdge _) => UpdateSubgraph(); + + void UpdateSubgraph() + { + if (_node.subgraph != null) + { + _node.subgraph.onExposedParameterModified -= UpdateSubgraphInputs; + _node.subgraph.onExposedParameterListChanged -= UpdateSubgraphInputs; + _node.subgraph.outputNode.onPortsUpdated -= UpdateSubgraphOutputs; + } + + _node.subgraph = MixtureDatabase.GetGraphFromTexture(_node.subgraphTexture); + + if (ValidateSubgraph()) + { + _node.subgraph.onExposedParameterModified += UpdateSubgraphInputs; + _node.subgraph.onExposedParameterListChanged += UpdateSubgraphInputs; + _node.subgraph.outputNode.onPortsUpdated += UpdateSubgraphOutputs; + + _node.UpdateOutputTextures(); + UpdatePreviewUI(); + } + else + { + _node.ReleaseOutputTextures(); + _node.previewOutputIndex = -1; + } + + _previewOutputField.visible = _node.previewOutputIndex > -1; + + ForceUpdatePorts(); + NotifyNodeChanged(); + } + + void UpdateSubgraphInputs() + { + ForceUpdatePorts(); + NotifyNodeChanged(); + } + + void UpdateSubgraphInputs(ExposedParameter _) => UpdateSubgraphInputs(); + + void UpdateSubgraphOutputs() + { + _node.UpdateOutputTextures(); + UpdatePreviewUI(); + ForceUpdatePorts(); + NotifyNodeChanged(); + } + + void UpdateSubgraphOutputs(string _) => UpdateSubgraphOutputs(); + + void UpdatePreviewUI() + { + var settings = _node.subgraph.outputNode.outputTextureSettings; + + _previewOutputField.choices = settings.Select(setting => setting.name).ToList(); + _node.previewOutputIndex = Mathf.Clamp(_node.previewOutputIndex, 0, settings.Count - 1); + _previewOutputField.index = _node.previewOutputIndex; + } + + bool ValidateSubgraph() + { + _node.ClearMessages(); + + if (_node.subgraphTexture == null) + { + return false; + } + + if (_node.subgraph == null) + { + _node.AddMessage($"Cannot find Mixture graph for texture: {_node.subgraphTexture.name}", NodeMessageType.Error); + return false; + } + + if (_node.subgraph == _node.graph) + { + _node.AddMessage($"Cannot execute graph recursively!", NodeMessageType.Error); + return false; + } + + return true; + } + } +} diff --git a/Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs.meta b/Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs.meta new file mode 100644 index 00000000..416c9b5b --- /dev/null +++ b/Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 089cce484dd09d44bb16d9ef8c704de3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs b/Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs new file mode 100644 index 00000000..dd43d33e --- /dev/null +++ b/Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs @@ -0,0 +1,197 @@ +using GraphProcessor; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Mixture +{ + [System.Serializable, NodeMenuItem("Subgraph")] + public class SubgraphNode : MixtureNode, ICreateNodeFrom + { + // Placeholders for custom port behaviours + [Input, System.NonSerialized] public int subgraphInputs; + [Output, System.NonSerialized] public int subgraphOutputs; + + public MixtureGraph subgraph; + public CustomRenderTexture subgraphTexture; + public int previewOutputIndex = -1; + + List _outputTextures = new List(); + + public override string name => subgraph?.name ?? "Subgraph"; + public override bool isRenamable => true; + public override bool hasPreview => -1 < previewOutputIndex && previewOutputIndex < _outputTextures.Count; + public override Texture previewTexture => hasPreview ? _outputTextures[previewOutputIndex] : null; + + public bool InitializeNodeFromObject(CustomRenderTexture texture) + { + subgraph = MixtureDatabase.GetGraphFromTexture(texture); + + if (subgraph == null) return false; + + subgraphTexture = texture; + previewOutputIndex = Mathf.Clamp(previewOutputIndex, 0, subgraph.outputNode.outputTextureSettings.Count - 1); + + return true; + } + + [CustomPortBehavior(nameof(subgraphInputs))] + public IEnumerable ListGraphInputs(List edges) + { + if (subgraph == null || subgraph == graph) yield break; + + for (var i = 0; i < subgraph.exposedParameters.Count; i++) + { + var parameter = subgraph.exposedParameters[i]; + + yield return new PortData + { + identifier = System.Convert.ToString(i), + displayName = parameter.name, + displayType = parameter.GetValueType(), + acceptMultipleEdges = false, + }; + } + } + + [CustomPortBehavior(nameof(subgraphOutputs))] + public IEnumerable ListGraphOutputs(List edges) + { + if (subgraph == null || subgraph == graph) yield break; + + var settings = subgraph.outputNode.outputTextureSettings; + var textureType = GetSubgraphTextureType(); + + for (var i = 0; i < settings.Count; i++) + { + yield return new PortData + { + identifier = System.Convert.ToString(i), + displayName = settings[i].name, + displayType = textureType, + acceptMultipleEdges = true, + }; + } + } + + [CustomPortInput(nameof(subgraphInputs), typeof(object))] + public void AssignGraphInputs(List edges) + { + foreach (var edge in edges) + { + var index = System.Convert.ToInt32(edge.inputPortIdentifier); + var parameter = subgraph.exposedParameters[index]; + + switch (edge.passThroughBuffer) + { + case float v: parameter.value = CoerceVectorValue(parameter, new Vector4(v, v, v, v)); break; + case Vector2 v: parameter.value = CoerceVectorValue(parameter, v); break; + case Vector3 v: parameter.value = CoerceVectorValue(parameter, v); break; + case Vector4 v: parameter.value = CoerceVectorValue(parameter, v); break; + default: parameter.value = edge.passThroughBuffer; break; + } + } + } + + [CustomPortOutput(nameof(subgraphOutputs), typeof(object))] + public void AssignGraphOutputs(List edges) + { + foreach (var edge in edges) + { + var index = System.Convert.ToInt32(edge.outputPortIdentifier); + edge.passThroughBuffer = _outputTextures[index]; + } + } + + public void UpdateOutputTextures() + { + ReleaseOutputTextures(); + + if (subgraph != null && subgraph != graph) GenerateOutputTextures(); + } + + public void GenerateOutputTextures() + { + var settings = subgraph.outputNode.outputTextureSettings; + + _outputTextures.Capacity = Mathf.Max(_outputTextures.Capacity, settings.Count); + + foreach (var setting in settings) + { + CustomRenderTexture outputTexture = null; + UpdateTempRenderTexture(ref outputTexture); + _outputTextures.Add(outputTexture); + } + } + + public void ReleaseOutputTextures() + { + foreach (var texture in _outputTextures) texture?.Release(); + + _outputTextures.Clear(); + } + + protected override void Enable() + { + base.Enable(); + + UpdateOutputTextures(); + } + + protected override void Disable() + { + base.Disable(); + + ReleaseOutputTextures(); + } + + public override bool canProcess => base.canProcess && subgraph != null && subgraph != graph; + + protected override bool ProcessNode(CommandBuffer cmd) + { + if (!base.ProcessNode(cmd)) return false; + + MixtureGraphProcessor.RunOnce(subgraph); + + using (var copyCmd = new CommandBuffer { name = $"{graph.name}/{subgraph.name}" }) + { + for (int i = 0; i < _outputTextures.Count; i++) + { + var outputTexture = _outputTextures[i]; + UpdateTempRenderTexture(ref outputTexture); + copyCmd.Blit(subgraph.outputNode.outputTextureSettings[i].finalCopyRT, outputTexture); + } + + Graphics.ExecuteCommandBuffer(copyCmd); + } + + return true; + } + + System.Type GetSubgraphTextureType() + { + var textureDimension = subgraph.settings.GetResolvedTextureDimension(subgraph); + + switch (textureDimension) + { + case UnityEngine.Rendering.TextureDimension.Tex2D: return typeof(Texture2D); + case UnityEngine.Rendering.TextureDimension.Tex3D: return typeof(Texture3D); + case UnityEngine.Rendering.TextureDimension.Cube: return typeof(Cubemap); + default: throw new System.Exception($"Texture dimension not supported: {textureDimension}"); + } + } + + object CoerceVectorValue(ExposedParameter parameter, Vector4 vector) + { + switch (parameter.value) + { + case float: return vector.x; + case Vector2: return (Vector2)vector; + case Vector3: return (Vector3)vector; + case Vector4: return vector; + default: throw new System.Exception($"Cannot cast vector to {parameter.GetValueType()}"); + } + } + } +} diff --git a/Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs.meta b/Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs.meta new file mode 100644 index 00000000..5d5b660a --- /dev/null +++ b/Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af92575172ca01046abd9c33bf5d217a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: