Skip to content

Commit b424e75

Browse files
committed
[Feature] Subgraph node
1 parent 601dcff commit b424e75

File tree

4 files changed

+365
-0
lines changed

4 files changed

+365
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using GraphProcessor;
2+
using System.Linq;
3+
using UnityEditor.UIElements;
4+
using UnityEngine;
5+
using UnityEngine.UIElements;
6+
7+
namespace Mixture
8+
{
9+
[NodeCustomEditor(typeof(SubgraphNode))]
10+
public class SubgraphNodeView : MixtureNodeView
11+
{
12+
SubgraphNode _node;
13+
14+
DropdownField _previewOutputField;
15+
16+
public override void Enable(bool fromInspector)
17+
{
18+
base.Enable(fromInspector);
19+
20+
_node = nodeTarget as SubgraphNode;
21+
22+
_previewOutputField = new DropdownField(
23+
"Preview Output",
24+
_node.subgraph?.outputNode?.outputTextureSettings?.Select(setting => setting.name)?.ToList(),
25+
_node.previewOutputIndex
26+
)
27+
{ visible = _node.previewOutputIndex > -1 };
28+
_previewOutputField.RegisterValueChangedCallback(e =>
29+
{
30+
_node.previewOutputIndex = _previewOutputField.index;
31+
NotifyNodeChanged();
32+
});
33+
34+
var subgraphTextureField = new ObjectField("Subgraph Texture")
35+
{
36+
value = _node.subgraphTexture,
37+
objectType = typeof(CustomRenderTexture)
38+
};
39+
subgraphTextureField.RegisterValueChangedCallback(e =>
40+
{
41+
_node.subgraphTexture = e.newValue as CustomRenderTexture;
42+
UpdateSubgraph();
43+
title = _node.name;
44+
});
45+
46+
controlsContainer.Add(_previewOutputField);
47+
controlsContainer.Add(subgraphTextureField);
48+
49+
_node.onAfterEdgeDisconnected += UpdateSubgraph;
50+
51+
UpdateSubgraph();
52+
}
53+
54+
public override void Disable()
55+
{
56+
base.Disable();
57+
58+
if (_node != null) _node.onAfterEdgeDisconnected -= UpdateSubgraph;
59+
}
60+
61+
void UpdateSubgraph(SerializableEdge _) => UpdateSubgraph();
62+
63+
void UpdateSubgraph()
64+
{
65+
if (_node.subgraph != null)
66+
{
67+
_node.subgraph.onExposedParameterModified -= UpdateSubgraphInputs;
68+
_node.subgraph.onExposedParameterListChanged -= UpdateSubgraphInputs;
69+
_node.subgraph.outputNode.onPortsUpdated -= UpdateSubgraphOutputs;
70+
}
71+
72+
_node.subgraph = MixtureDatabase.GetGraphFromTexture(_node.subgraphTexture);
73+
74+
if (ValidateSubgraph())
75+
{
76+
_node.subgraph.onExposedParameterModified += UpdateSubgraphInputs;
77+
_node.subgraph.onExposedParameterListChanged += UpdateSubgraphInputs;
78+
_node.subgraph.outputNode.onPortsUpdated += UpdateSubgraphOutputs;
79+
80+
_node.UpdateOutputTextures();
81+
UpdatePreviewUI();
82+
}
83+
else
84+
{
85+
_node.ReleaseOutputTextures();
86+
_node.previewOutputIndex = -1;
87+
}
88+
89+
_previewOutputField.visible = _node.previewOutputIndex > -1;
90+
91+
ForceUpdatePorts();
92+
NotifyNodeChanged();
93+
}
94+
95+
void UpdateSubgraphInputs()
96+
{
97+
ForceUpdatePorts();
98+
NotifyNodeChanged();
99+
}
100+
101+
void UpdateSubgraphInputs(ExposedParameter _) => UpdateSubgraphInputs();
102+
103+
void UpdateSubgraphOutputs()
104+
{
105+
_node.UpdateOutputTextures();
106+
UpdatePreviewUI();
107+
ForceUpdatePorts();
108+
NotifyNodeChanged();
109+
}
110+
111+
void UpdateSubgraphOutputs(string _) => UpdateSubgraphOutputs();
112+
113+
void UpdatePreviewUI()
114+
{
115+
var settings = _node.subgraph.outputNode.outputTextureSettings;
116+
117+
_previewOutputField.choices = settings.Select(setting => setting.name).ToList();
118+
_node.previewOutputIndex = Mathf.Clamp(_node.previewOutputIndex, 0, settings.Count - 1);
119+
_previewOutputField.index = _node.previewOutputIndex;
120+
}
121+
122+
bool ValidateSubgraph()
123+
{
124+
_node.ClearMessages();
125+
126+
if (_node.subgraphTexture == null)
127+
{
128+
return false;
129+
}
130+
131+
if (_node.subgraph == null)
132+
{
133+
_node.AddMessage($"Cannot find Mixture graph for texture: {_node.subgraphTexture.name}", NodeMessageType.Error);
134+
return false;
135+
}
136+
137+
if (_node.subgraph == _node.graph)
138+
{
139+
_node.AddMessage($"Cannot execute graph recursively!", NodeMessageType.Error);
140+
return false;
141+
}
142+
143+
return true;
144+
}
145+
}
146+
}

Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
using GraphProcessor;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using UnityEngine;
5+
using UnityEngine.Rendering;
6+
7+
namespace Mixture
8+
{
9+
[System.Serializable, NodeMenuItem("Subgraph")]
10+
public class SubgraphNode : MixtureNode, ICreateNodeFrom<CustomRenderTexture>
11+
{
12+
// Placeholders for custom port behaviours
13+
[Input, System.NonSerialized] public int subgraphInputs;
14+
[Output, System.NonSerialized] public int subgraphOutputs;
15+
16+
public MixtureGraph subgraph;
17+
public CustomRenderTexture subgraphTexture;
18+
public int previewOutputIndex = -1;
19+
20+
List<CustomRenderTexture> _outputTextures = new List<CustomRenderTexture>();
21+
22+
public override string name => subgraph?.name ?? "Subgraph";
23+
public override bool isRenamable => true;
24+
public override bool hasPreview => -1 < previewOutputIndex && previewOutputIndex < _outputTextures.Count;
25+
public override Texture previewTexture => hasPreview ? _outputTextures[previewOutputIndex] : null;
26+
27+
public bool InitializeNodeFromObject(CustomRenderTexture texture)
28+
{
29+
subgraph = MixtureDatabase.GetGraphFromTexture(texture);
30+
31+
if (subgraph == null) return false;
32+
33+
subgraphTexture = texture;
34+
previewOutputIndex = Mathf.Clamp(previewOutputIndex, 0, subgraph.outputNode.outputTextureSettings.Count - 1);
35+
36+
return true;
37+
}
38+
39+
[CustomPortBehavior(nameof(subgraphInputs))]
40+
public IEnumerable<PortData> ListGraphInputs(List<SerializableEdge> edges)
41+
{
42+
if (subgraph == null || subgraph == graph) yield break;
43+
44+
for (var i = 0; i < subgraph.exposedParameters.Count; i++)
45+
{
46+
var parameter = subgraph.exposedParameters[i];
47+
48+
yield return new PortData
49+
{
50+
identifier = System.Convert.ToString(i),
51+
displayName = parameter.name,
52+
displayType = parameter.GetValueType(),
53+
acceptMultipleEdges = false,
54+
};
55+
}
56+
}
57+
58+
[CustomPortBehavior(nameof(subgraphOutputs))]
59+
public IEnumerable<PortData> ListGraphOutputs(List<SerializableEdge> edges)
60+
{
61+
if (subgraph == null || subgraph == graph) yield break;
62+
63+
var settings = subgraph.outputNode.outputTextureSettings;
64+
var textureType = GetSubgraphTextureType();
65+
66+
for (var i = 0; i < settings.Count; i++)
67+
{
68+
yield return new PortData
69+
{
70+
identifier = System.Convert.ToString(i),
71+
displayName = settings[i].name,
72+
displayType = textureType,
73+
acceptMultipleEdges = true,
74+
};
75+
}
76+
}
77+
78+
[CustomPortInput(nameof(subgraphInputs), typeof(object))]
79+
public void AssignGraphInputs(List<SerializableEdge> edges)
80+
{
81+
foreach (var edge in edges)
82+
{
83+
var index = System.Convert.ToInt32(edge.inputPortIdentifier);
84+
var parameter = subgraph.exposedParameters[index];
85+
86+
switch (edge.passThroughBuffer)
87+
{
88+
case float v: parameter.value = CoerceVectorValue(parameter, new Vector4(v, v, v, v)); break;
89+
case Vector2 v: parameter.value = CoerceVectorValue(parameter, v); break;
90+
case Vector3 v: parameter.value = CoerceVectorValue(parameter, v); break;
91+
case Vector4 v: parameter.value = CoerceVectorValue(parameter, v); break;
92+
default: parameter.value = edge.passThroughBuffer; break;
93+
}
94+
}
95+
}
96+
97+
[CustomPortOutput(nameof(subgraphOutputs), typeof(object))]
98+
public void AssignGraphOutputs(List<SerializableEdge> edges)
99+
{
100+
foreach (var edge in edges)
101+
{
102+
var index = System.Convert.ToInt32(edge.outputPortIdentifier);
103+
edge.passThroughBuffer = _outputTextures[index];
104+
}
105+
}
106+
107+
public void UpdateOutputTextures()
108+
{
109+
ReleaseOutputTextures();
110+
111+
if (subgraph != null && subgraph != graph) GenerateOutputTextures();
112+
}
113+
114+
public void GenerateOutputTextures()
115+
{
116+
var settings = subgraph.outputNode.outputTextureSettings;
117+
118+
_outputTextures.Capacity = Mathf.Max(_outputTextures.Capacity, settings.Count);
119+
120+
foreach (var setting in settings)
121+
{
122+
CustomRenderTexture outputTexture = null;
123+
UpdateTempRenderTexture(ref outputTexture);
124+
_outputTextures.Add(outputTexture);
125+
}
126+
}
127+
128+
public void ReleaseOutputTextures()
129+
{
130+
foreach (var texture in _outputTextures) texture?.Release();
131+
132+
_outputTextures.Clear();
133+
}
134+
135+
protected override void Enable()
136+
{
137+
base.Enable();
138+
139+
UpdateOutputTextures();
140+
}
141+
142+
protected override void Disable()
143+
{
144+
base.Disable();
145+
146+
ReleaseOutputTextures();
147+
}
148+
149+
public override bool canProcess => base.canProcess && subgraph != null && subgraph != graph;
150+
151+
protected override bool ProcessNode(CommandBuffer cmd)
152+
{
153+
if (!base.ProcessNode(cmd)) return false;
154+
155+
MixtureGraphProcessor.RunOnce(subgraph);
156+
157+
using (var copyCmd = new CommandBuffer { name = $"{graph.name}/{subgraph.name}" })
158+
{
159+
for (int i = 0; i < _outputTextures.Count; i++)
160+
{
161+
var outputTexture = _outputTextures[i];
162+
UpdateTempRenderTexture(ref outputTexture);
163+
copyCmd.Blit(subgraph.outputNode.outputTextureSettings[i].finalCopyRT, outputTexture);
164+
}
165+
166+
Graphics.ExecuteCommandBuffer(copyCmd);
167+
}
168+
169+
return true;
170+
}
171+
172+
System.Type GetSubgraphTextureType()
173+
{
174+
var textureDimension = subgraph.settings.GetResolvedTextureDimension(subgraph);
175+
176+
switch (textureDimension)
177+
{
178+
case UnityEngine.Rendering.TextureDimension.Tex2D: return typeof(Texture2D);
179+
case UnityEngine.Rendering.TextureDimension.Tex3D: return typeof(Texture3D);
180+
case UnityEngine.Rendering.TextureDimension.Cube: return typeof(Cubemap);
181+
default: throw new System.Exception($"Texture dimension not supported: {textureDimension}");
182+
}
183+
}
184+
185+
object CoerceVectorValue(ExposedParameter parameter, Vector4 vector)
186+
{
187+
switch (parameter.value)
188+
{
189+
case float: return vector.x;
190+
case Vector2: return (Vector2)vector;
191+
case Vector3: return (Vector3)vector;
192+
case Vector4: return vector;
193+
default: throw new System.Exception($"Cannot cast vector to {parameter.GetValueType()}");
194+
}
195+
}
196+
}
197+
}

Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)