Skip to content

Commit

Permalink
[Feature] Subgraph node
Browse files Browse the repository at this point in the history
  • Loading branch information
Looooong committed Sep 26, 2022
1 parent 601dcff commit b424e75
Show file tree
Hide file tree
Showing 4 changed files with 365 additions and 0 deletions.
146 changes: 146 additions & 0 deletions Packages/com.alelievr.mixture/Editor/Views/SubgraphNodeView.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

197 changes: 197 additions & 0 deletions Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs
Original file line number Diff line number Diff line change
@@ -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<CustomRenderTexture>
{
// 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<CustomRenderTexture> _outputTextures = new List<CustomRenderTexture>();

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<PortData> ListGraphInputs(List<SerializableEdge> 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<PortData> ListGraphOutputs(List<SerializableEdge> 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<SerializableEdge> 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<SerializableEdge> 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()}");
}
}
}
}
11 changes: 11 additions & 0 deletions Packages/com.alelievr.mixture/Runtime/Nodes/SubgraphNode.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b424e75

Please sign in to comment.