initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,107 @@
using UnityEngine;
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.ShaderGraph.Drawing;
namespace UnityEditor.ShaderGraph
{
class ShaderGraphAssetPostProcessor : AssetPostprocessor
{
static void RegisterShaders(string[] paths)
{
foreach (var path in paths)
{
if (!path.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase))
continue;
var mainObj = AssetDatabase.LoadMainAssetAtPath(path);
if (mainObj is Shader)
ShaderUtil.RegisterShader((Shader)mainObj);
var objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
foreach (var obj in objs)
{
if (obj is Shader)
ShaderUtil.RegisterShader((Shader)obj);
}
}
}
static void UpdateAfterAssetChange(string[] newNames)
{
// This will change the title of the window.
MaterialGraphEditWindow[] windows = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
foreach (var matGraphEditWindow in windows)
{
for (int i = 0; i < newNames.Length; ++i)
{
if (matGraphEditWindow.selectedGuid == AssetDatabase.AssetPathToGUID(newNames[i]))
matGraphEditWindow.UpdateTitle();
}
}
}
static void DisplayDeletionDialog(string[] deletedAssets)
{
MaterialGraphEditWindow[] windows = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
foreach (var matGraphEditWindow in windows)
{
for (int i = 0; i < deletedAssets.Length; ++i)
{
if (matGraphEditWindow.selectedGuid == AssetDatabase.AssetPathToGUID(deletedAssets[i]))
matGraphEditWindow.AssetWasDeleted();
}
}
}
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
RegisterShaders(importedAssets);
// Moved assets
bool anyMovedShaders = movedAssets.Any(val => val.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
anyMovedShaders |= movedAssets.Any(val => val.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
if (anyMovedShaders)
UpdateAfterAssetChange(movedAssets);
// Deleted assets
bool anyRemovedShaders = deletedAssets.Any(val => val.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
anyRemovedShaders |= deletedAssets.Any(val => val.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
if (anyRemovedShaders)
DisplayDeletionDialog(deletedAssets);
var windows = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
var changedGraphGuids = importedAssets
.Where(x => x.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase)
|| x.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase))
.Select(AssetDatabase.AssetPathToGUID)
.ToList();
foreach (var window in windows)
{
if (changedGraphGuids.Contains(window.selectedGuid))
{
window.CheckForChanges();
}
}
// moved or imported subgraphs or HLSL files should notify open shadergraphs that they need to handle them
var changedFileGUIDs = movedAssets.Concat(importedAssets).Concat(deletedAssets)
.Where(x => x.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase)
|| CustomFunctionNode.s_ValidExtensions.Contains(Path.GetExtension(x)))
.Select(AssetDatabase.AssetPathToGUID)
.Distinct()
.ToList();
if (changedFileGUIDs.Count > 0)
{
foreach (var window in windows)
{
window.ReloadSubGraphsOnNextUpdate(changedFileGUIDs);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6721e5578adf964fa914cf0d66e0bdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,955 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
#if UNITY_2020_2_OR_NEWER
using UnityEditor.AssetImporters;
#else
using UnityEditor.Experimental.AssetImporters;
#endif
using UnityEditor.Graphing;
using UnityEditor.Graphing.Util;
using UnityEditor.ShaderGraph.Internal;
using UnityEditor.ShaderGraph.Serialization;
using Object = System.Object;
namespace UnityEditor.ShaderGraph
{
[ExcludeFromPreset]
#if ENABLE_HYBRID_RENDERER_V2
// Bump the version number when Hybrid Renderer V2 is enabled, to make
// sure that all shader graphs get re-imported. Re-importing is required,
// because the shader graph codegen is different for V2.
// This ifdef can be removed once V2 is the only option.
[ScriptedImporter(124, Extension, -902)]
#else
[ScriptedImporter(53, Extension, -902)]
#endif
class ShaderGraphImporter : ScriptedImporter
{
public const string Extension = "shadergraph";
public const string LegacyExtension = "ShaderGraph";
public const string k_ErrorShader = @"
Shader ""Hidden/GraphErrorShader2""
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile _ UNITY_SINGLE_PASS_STEREO STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON
#include ""UnityCG.cginc""
struct appdata_t {
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
float4 vertex : SV_POSITION;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert (appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(1,0,1,1);
}
ENDCG
}
}
Fallback Off
}";
[SuppressMessage("ReSharper", "UnusedMember.Local")]
static string[] GatherDependenciesFromSourceFile(string assetPath)
{
try
{
AssetCollection assetCollection = new AssetCollection();
MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
List<string> dependencyPaths = new List<string>();
foreach (var asset in assetCollection.assets)
{
// only artifact dependencies need to be declared in GatherDependenciesFromSourceFile
// to force their imports to run before ours
if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
{
var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
// it is unfortunate that we can't declare these dependencies unless they have a path...
// I asked AssetDatabase team for GatherDependenciesFromSourceFileByGUID()
if (!string.IsNullOrEmpty(dependencyPath))
dependencyPaths.Add(dependencyPath);
}
}
return dependencyPaths.ToArray();
}
catch (Exception e)
{
Debug.LogException(e);
return new string[0];
}
}
public override void OnImportAsset(AssetImportContext ctx)
{
var oldShader = AssetDatabase.LoadAssetAtPath<Shader>(ctx.assetPath);
if (oldShader != null)
ShaderUtil.ClearShaderMessages(oldShader);
var importLog = new AssetImportErrorLog(ctx);
List<PropertyCollector.TextureInfo> configuredTextures;
string path = ctx.assetPath;
AssetCollection assetCollection = new AssetCollection();
MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
var textGraph = File.ReadAllText(path, Encoding.UTF8);
var graph = new GraphData
{
messageManager = new MessageManager(),
assetGuid = AssetDatabase.AssetPathToGUID(path)
};
MultiJson.Deserialize(graph, textGraph);
graph.OnEnable();
graph.ValidateGraph();
Shader shader = null;
#if VFX_GRAPH_10_0_0_OR_NEWER
if (!graph.isOnlyVFXTarget)
#endif
{
// build the shader text
// this will also add Target dependencies into the asset collection
var text = GetShaderText(path, out configuredTextures, assetCollection, graph);
#if UNITY_2021_1_OR_NEWER
// 2021.1 or later is guaranteed to have the new version of this function
shader = ShaderUtil.CreateShaderAsset(ctx, text, false);
#else
// earlier builds of Unity may or may not have it
// here we try to invoke the new version via reflection
var createShaderAssetMethod = typeof(ShaderUtil).GetMethod(
"CreateShaderAsset",
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.ExactBinding,
null,
new Type[] { typeof(AssetImportContext), typeof(string), typeof(bool) },
null);
if (createShaderAssetMethod != null)
{
shader = createShaderAssetMethod.Invoke(null, new Object[] { ctx, text, false }) as Shader;
}
else
{
// method doesn't exist in this version of Unity, call old version
// this doesn't create dependencies properly, but is the best that we can do
shader = ShaderUtil.CreateShaderAsset(text, false);
}
#endif
ReportErrors(graph, shader, path, importLog);
EditorMaterialUtility.SetShaderDefaults(
shader,
configuredTextures.Where(x => x.modifiable).Select(x => x.name).ToArray(),
configuredTextures.Where(x => x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray());
EditorMaterialUtility.SetShaderNonModifiableDefaults(
shader,
configuredTextures.Where(x => !x.modifiable).Select(x => x.name).ToArray(),
configuredTextures.Where(x => !x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray());
}
UnityEngine.Object mainObject = shader;
#if VFX_GRAPH_10_0_0_OR_NEWER
ShaderGraphVfxAsset vfxAsset = null;
if (graph.hasVFXTarget)
{
vfxAsset = GenerateVfxShaderGraphAsset(graph);
if (mainObject == null)
{
mainObject = vfxAsset;
}
else
{
//Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset
vfxAsset.name = Path.GetFileNameWithoutExtension(path);
ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset);
}
}
#endif
Texture2D texture = Resources.Load<Texture2D>("Icons/sg_graph_icon");
ctx.AddObjectToAsset("MainAsset", mainObject, texture);
ctx.SetMainObject(mainObject);
var graphDataReadOnly = new GraphDataReadOnly(graph);
foreach (var target in graph.activeTargets)
{
if (target is IHasMetadata iHasMetadata)
{
var metadata = iHasMetadata.GetMetadataObject(graphDataReadOnly);
if (metadata == null)
continue;
metadata.hideFlags = HideFlags.HideInHierarchy;
ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata);
}
}
var sgMetadata = ScriptableObject.CreateInstance<ShaderGraphMetadata>();
sgMetadata.hideFlags = HideFlags.HideInHierarchy;
sgMetadata.assetDependencies = new List<UnityEngine.Object>();
foreach (var asset in assetCollection.assets)
{
if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage))
{
// this sucks that we have to fully load these assets just to set the reference,
// which then gets serialized as the GUID that we already have here. :P
var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
if (!string.IsNullOrEmpty(dependencyPath))
{
sgMetadata.assetDependencies.Add(
AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object)));
}
}
}
List<GraphInputData> inputInspectorDataList = new List<GraphInputData>();
foreach (AbstractShaderProperty property in graph.properties)
{
// Don't write out data for non-exposed blackboard items
if (!property.isExposed)
continue;
// VTs are treated differently
if (property is VirtualTextureShaderProperty virtualTextureShaderProperty)
inputInspectorDataList.Add(MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty));
else
inputInspectorDataList.Add(new GraphInputData() { referenceName = property.referenceName, propertyType = property.propertyType, isKeyword = false });
}
foreach (ShaderKeyword keyword in graph.keywords)
{
// Don't write out data for non-exposed blackboard items
if (!keyword.isExposed)
continue;
var sanitizedReferenceName = keyword.referenceName;
if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON"))
sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty);
inputInspectorDataList.Add(new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true });
}
sgMetadata.categoryDatas = new List<MinimalCategoryData>();
foreach (CategoryData categoryData in graph.categories)
{
// Don't write out empty categories
if (categoryData.childCount == 0)
continue;
MinimalCategoryData mcd = new MinimalCategoryData()
{
categoryName = categoryData.name,
propertyDatas = new List<GraphInputData>()
};
foreach (var input in categoryData.Children)
{
GraphInputData propData;
// Only write out data for exposed blackboard items
if (input.isExposed == false)
continue;
// VTs are treated differently
if (input is VirtualTextureShaderProperty virtualTextureShaderProperty)
{
propData = MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty);
inputInspectorDataList.RemoveAll(inputData => inputData.referenceName == propData.referenceName);
mcd.propertyDatas.Add(propData);
continue;
}
else if (input is ShaderKeyword keyword)
{
var sanitizedReferenceName = keyword.referenceName;
if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON"))
sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty);
propData = new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true };
}
else
{
var prop = input as AbstractShaderProperty;
propData = new GraphInputData() { referenceName = input.referenceName, propertyType = prop.propertyType, isKeyword = false };
}
mcd.propertyDatas.Add(propData);
inputInspectorDataList.Remove(propData);
}
sgMetadata.categoryDatas.Add(mcd);
}
// Any uncategorized elements get tossed into an un-named category at the top as a fallback
if (inputInspectorDataList.Count > 0)
{
sgMetadata.categoryDatas.Insert(0, new MinimalCategoryData() { categoryName = "", propertyDatas = inputInspectorDataList });
}
ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata);
// declare dependencies
foreach (var asset in assetCollection.assets)
{
if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency))
{
ctx.DependsOnSourceAsset(asset.Key);
// I'm not sure if this warning below is actually used or not, keeping it to be safe
var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key);
// Ensure that dependency path is relative to project
if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/"))
{
importLog.LogWarning($"Invalid dependency path: {assetPath}", mainObject);
}
}
// NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies
// HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies
// on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here.
if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
{
ctx.DependsOnArtifact(asset.Key);
}
}
}
internal class AssetImportErrorLog : MessageManager.IErrorLog
{
AssetImportContext ctx;
public AssetImportErrorLog(AssetImportContext ctx)
{
this.ctx = ctx;
}
public void LogError(string message, UnityEngine.Object context = null)
{
// Note: if you get sent here by clicking on a ShaderGraph error message,
// this is a bug in the scripted importer system, not being able to link import error messages to the imported asset
ctx.LogImportError(message, context);
}
public void LogWarning(string message, UnityEngine.Object context = null)
{
ctx.LogImportWarning(message, context);
}
}
static bool NodeWasUsedByGraph(string nodeId, GraphData graphData)
{
var node = graphData.GetNodeFromId(nodeId);
return node?.wasUsedByGenerator ?? false;
}
// error messages should be reported through the asset import context, so that object references are translated properly (in the future), and the error is associated with the import
static void ReportErrors(GraphData graph, Shader shader, string path, AssetImportErrorLog importLog)
{
// Grab any messages from the shader compiler
var messages = ShaderUtil.GetShaderMessages(shader);
var errors = graph.messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graph));
int errCount = errors.Count();
// Find the first compiler message that's an error
int firstShaderUtilErrorIndex = -1;
if (messages != null)
firstShaderUtilErrorIndex = Array.FindIndex(messages, m => (m.severity == Rendering.ShaderCompilerMessageSeverity.Error));
// Display only one message. Bias towards shader compiler messages over node messages and within that bias errors over warnings.
if (firstShaderUtilErrorIndex != -1)
{
// if shader compiler reported an error, show that
MessageManager.Log(path, messages[firstShaderUtilErrorIndex], shader, importLog);
}
else if (errCount > 0)
{
// otherwise show node errors
var firstError = errors.FirstOrDefault();
importLog.LogError($"Shader Graph at {path} has {errCount} error(s), the first is: {firstError}", shader);
}
else if (messages.Length != 0)
{
// otherwise show shader compiler warnings
MessageManager.Log(path, messages[0], shader, importLog);
}
else if (graph.messageManager.nodeMessagesChanged)
{
// otherwise show node warnings
var warnings = graph.messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graph), Rendering.ShaderCompilerMessageSeverity.Warning);
var warnCount = warnings.Count();
var firstWarning = warnings.FirstOrDefault();
if (warnCount > 0)
importLog.LogWarning($"Shader Graph at {path} has {warnCount} warning(s), the first is: {firstWarning}", shader);
}
}
internal static string GetShaderText(string path, out List<PropertyCollector.TextureInfo> configuredTextures, AssetCollection assetCollection, GraphData graph, GenerationMode mode = GenerationMode.ForReals, Target[] targets = null)
{
string shaderString = null;
var shaderName = Path.GetFileNameWithoutExtension(path);
try
{
if (!string.IsNullOrEmpty(graph.path))
shaderName = graph.path + "/" + shaderName;
Generator generator;
if (targets != null)
generator = new Generator(graph, graph.outputNode, mode, shaderName, assetCollection, targets);
else
generator = new Generator(graph, graph.outputNode, mode, shaderName, assetCollection);
shaderString = generator.generatedShader;
configuredTextures = generator.configuredTextures;
// we only care if an error was reported for a node that we actually used
if (graph.messageManager.AnyError((nodeId) => NodeWasUsedByGraph(nodeId, graph)))
{
shaderString = null;
}
}
catch (Exception e)
{
Debug.LogException(e);
configuredTextures = new List<PropertyCollector.TextureInfo>();
// ignored
}
if (shaderString == null)
{
shaderString = k_ErrorShader.Replace("Hidden/GraphErrorShader2", shaderName);
}
return shaderString;
}
internal static string GetShaderText(string path, out List<PropertyCollector.TextureInfo> configuredTextures, AssetCollection assetCollection, out GraphData graph)
{
var textGraph = File.ReadAllText(path, Encoding.UTF8);
graph = new GraphData
{
messageManager = new MessageManager(),
assetGuid = AssetDatabase.AssetPathToGUID(path)
};
MultiJson.Deserialize(graph, textGraph);
graph.OnEnable();
graph.ValidateGraph();
return GetShaderText(path, out configuredTextures, assetCollection, graph);
}
internal static string GetShaderText(string path, out List<PropertyCollector.TextureInfo> configuredTextures)
{
var textGraph = File.ReadAllText(path, Encoding.UTF8);
GraphData graph = new GraphData
{
messageManager = new MessageManager(),
assetGuid = AssetDatabase.AssetPathToGUID(path)
};
MultiJson.Deserialize(graph, textGraph);
graph.OnEnable();
graph.ValidateGraph();
return GetShaderText(path, out configuredTextures, null, graph);
}
#if VFX_GRAPH_10_0_0_OR_NEWER
// TODO: Fix this - VFX Graph can now use ShaderGraph as a code generation path. However, currently, the new
// generation path still slightly depends on this container (The implementation of it was tightly coupled in VFXShaderGraphParticleOutput,
// and we keep it now as there is no migration path for users yet). This will need to be decoupled so that we can eventually
// remove this container.
static ShaderGraphVfxAsset GenerateVfxShaderGraphAsset(GraphData graph)
{
var target = graph.activeTargets.FirstOrDefault(x => x.SupportsVFX());
if (target == null)
return null;
var nl = Environment.NewLine;
var indent = new string(' ', 4);
var asset = ScriptableObject.CreateInstance<ShaderGraphVfxAsset>();
var result = asset.compilationResult = new GraphCompilationResult();
var mode = GenerationMode.ForReals;
if (target is VFXTarget vfxTarget)
{
asset.lit = vfxTarget.lit;
asset.alphaClipping = vfxTarget.alphaTest;
asset.generatesWithShaderGraph = false;
}
else
{
asset.lit = true;
asset.alphaClipping = false;
asset.generatesWithShaderGraph = true;
}
var assetGuid = graph.assetGuid;
var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
var hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath));
var ports = new List<MaterialSlot>();
var nodes = new List<AbstractMaterialNode>();
foreach (var vertexBlock in graph.vertexContext.blocks)
{
vertexBlock.value.GetInputSlots(ports);
NodeUtils.DepthFirstCollectNodesFromNode(nodes, vertexBlock);
}
foreach (var fragmentBlock in graph.fragmentContext.blocks)
{
fragmentBlock.value.GetInputSlots(ports);
NodeUtils.DepthFirstCollectNodesFromNode(nodes, fragmentBlock);
}
//Remove inactive blocks from legacy generation
if (!asset.generatesWithShaderGraph)
{
var tmpCtx = new TargetActiveBlockContext(new List<BlockFieldDescriptor>(), null);
// NOTE: For whatever reason, this call fails for custom interpolator ports (ie, active ones are not detected as active).
// For the sake of compatibility with custom interpolator with shadergraph generation, skip the removal of inactive blocks.
target.GetActiveBlocks(ref tmpCtx);
ports.RemoveAll(materialSlot =>
{
return !tmpCtx.activeBlocks.Any(o => materialSlot.RawDisplayName() == o.displayName);
});
}
var bodySb = new ShaderStringBuilder(1);
var graphIncludes = new IncludeCollection();
var registry = new FunctionRegistry(new ShaderStringBuilder(), graphIncludes, true);
foreach (var properties in graph.properties)
{
properties.SetupConcretePrecision(graph.graphDefaultConcretePrecision);
}
foreach (var node in nodes)
{
if (node is IGeneratesBodyCode bodyGenerator)
{
bodySb.currentNode = node;
bodyGenerator.GenerateNodeCode(bodySb, mode);
bodySb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
}
if (node is IGeneratesFunction generatesFunction)
{
registry.builder.currentNode = node;
generatesFunction.GenerateNodeFunction(registry, mode);
}
}
bodySb.currentNode = null;
var portNodeSets = new HashSet<AbstractMaterialNode>[ports.Count];
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var port = ports[portIndex];
var nodeSet = new HashSet<AbstractMaterialNode>();
NodeUtils.CollectNodeSet(nodeSet, port);
portNodeSets[portIndex] = nodeSet;
}
var portPropertySets = new HashSet<string>[ports.Count];
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
portPropertySets[portIndex] = new HashSet<string>();
}
foreach (var node in nodes)
{
if (!(node is PropertyNode propertyNode))
{
continue;
}
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var portNodeSet = portNodeSets[portIndex];
if (portNodeSet.Contains(node))
{
portPropertySets[portIndex].Add(propertyNode.property.objectId);
}
}
}
var shaderProperties = new PropertyCollector();
foreach (var node in nodes)
{
node.CollectShaderProperties(shaderProperties, GenerationMode.ForReals);
}
asset.SetTextureInfos(shaderProperties.GetConfiguredTextures());
var codeSnippets = new List<string>();
var portCodeIndices = new List<int>[ports.Count];
var sharedCodeIndices = new List<int>();
for (var i = 0; i < portCodeIndices.Length; i++)
{
portCodeIndices[i] = new List<int>();
}
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"#include \"Packages/com.unity.shadergraph/ShaderGraphLibrary/Functions.hlsl\"{nl}");
foreach (var include in graphIncludes)
{
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add(include.value + nl);
}
for (var registryIndex = 0; registryIndex < registry.names.Count; registryIndex++)
{
var name = registry.names[registryIndex];
var source = registry.sources[name];
var precision = source.nodes.First().concretePrecision;
var hasPrecisionMismatch = false;
var nodeNames = new HashSet<string>();
foreach (var node in source.nodes)
{
nodeNames.Add(node.name);
if (node.concretePrecision != precision)
{
hasPrecisionMismatch = true;
break;
}
}
if (hasPrecisionMismatch)
{
var message = new StringBuilder($"Precision mismatch for function {name}:");
foreach (var node in source.nodes)
{
message.AppendLine($"{node.name} ({node.objectId}): {node.concretePrecision}");
}
throw new InvalidOperationException(message.ToString());
}
var code = source.code.Replace(PrecisionUtil.Token, precision.ToShaderString());
code = $"// Node: {string.Join(", ", nodeNames)}{nl}{code}";
var codeIndex = codeSnippets.Count;
codeSnippets.Add(code + nl);
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var portNodeSet = portNodeSets[portIndex];
foreach (var node in source.nodes)
{
if (portNodeSet.Contains(node))
{
portCodeIndices[portIndex].Add(codeIndex);
break;
}
}
}
}
foreach (var property in graph.properties)
{
if (property.isExposed)
{
continue;
}
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var portPropertySet = portPropertySets[portIndex];
if (portPropertySet.Contains(property.objectId))
{
portCodeIndices[portIndex].Add(codeSnippets.Count);
}
}
ShaderStringBuilder builder = new ShaderStringBuilder();
property.ForeachHLSLProperty(h => h.AppendTo(builder));
codeSnippets.Add($"// Property: {property.displayName}{nl}{builder.ToCodeBlock()}{nl}{nl}");
}
foreach (var prop in shaderProperties.properties)
{
if (!graph.properties.Contains(prop) && (prop is SamplerStateShaderProperty))
{
sharedCodeIndices.Add(codeSnippets.Count);
ShaderStringBuilder builder = new ShaderStringBuilder();
prop.ForeachHLSLProperty(h => h.AppendTo(builder));
codeSnippets.Add($"// Property: {prop.displayName}{nl}{builder.ToCodeBlock()}{nl}{nl}");
}
}
var inputStructName = $"SG_Input_{assetGuid}";
var outputStructName = $"SG_Output_{assetGuid}";
var evaluationFunctionName = $"SG_Evaluate_{assetGuid}";
#region Input Struct
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"struct {inputStructName}{nl}{{{nl}");
#region Requirements
var portRequirements = new ShaderGraphRequirements[ports.Count];
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var requirementsNodes = portNodeSets[portIndex].ToList();
requirementsNodes.Add(ports[portIndex].owner);
portRequirements[portIndex] = ShaderGraphRequirements.FromNodes(requirementsNodes, ports[portIndex].stageCapability);
}
var portIndices = new List<int>();
portIndices.Capacity = ports.Count;
void AddRequirementsSnippet(Func<ShaderGraphRequirements, bool> predicate, string snippet)
{
portIndices.Clear();
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
if (predicate(portRequirements[portIndex]))
{
portIndices.Add(portIndex);
}
}
if (portIndices.Count > 0)
{
foreach (var portIndex in portIndices)
{
portCodeIndices[portIndex].Add(codeSnippets.Count);
}
codeSnippets.Add($"{indent}{snippet};{nl}");
}
}
void AddCoordinateSpaceSnippets(InterpolatorType interpolatorType, Func<ShaderGraphRequirements, NeededCoordinateSpace> selector)
{
foreach (var space in EnumInfo<CoordinateSpace>.values)
{
var neededSpace = space.ToNeededCoordinateSpace();
AddRequirementsSnippet(r => (selector(r) & neededSpace) > 0, $"float3 {space.ToVariableName(interpolatorType)}");
}
}
// TODO: Rework requirements system to make this better
AddCoordinateSpaceSnippets(InterpolatorType.Normal, r => r.requiresNormal);
AddCoordinateSpaceSnippets(InterpolatorType.Tangent, r => r.requiresTangent);
AddCoordinateSpaceSnippets(InterpolatorType.BiTangent, r => r.requiresBitangent);
AddCoordinateSpaceSnippets(InterpolatorType.ViewDirection, r => r.requiresViewDir);
AddCoordinateSpaceSnippets(InterpolatorType.Position, r => r.requiresPosition);
AddCoordinateSpaceSnippets(InterpolatorType.PositionPredisplacement, r => r.requiresPositionPredisplacement);
AddRequirementsSnippet(r => r.requiresVertexColor, $"float4 {ShaderGeneratorNames.VertexColor}");
AddRequirementsSnippet(r => r.requiresScreenPosition, $"float4 {ShaderGeneratorNames.ScreenPosition}");
AddRequirementsSnippet(r => r.requiresFaceSign, $"float4 {ShaderGeneratorNames.FaceSign}");
foreach (var uvChannel in EnumInfo<UVChannel>.values)
{
AddRequirementsSnippet(r => r.requiresMeshUVs.Contains(uvChannel), $"half4 {uvChannel.GetUVName()}");
}
AddRequirementsSnippet(r => r.requiresTime, $"float3 {ShaderGeneratorNames.TimeParameters}");
#endregion
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"}};{nl}{nl}");
#endregion
// VFX Code heavily relies on the slotId from the original MasterNodes
// Since we keep these around for upgrades anyway, for now it is simpler to use them
// Therefore we remap the output blocks back to the original Ids here
var originialPortIds = new int[ports.Count];
for (int i = 0; i < originialPortIds.Length; i++)
{
if (!VFXTarget.s_BlockMap.TryGetValue((ports[i].owner as BlockNode).descriptor, out var originalId))
continue;
// In Master Nodes we had a different BaseColor/Color slot id between Unlit/Lit
// In the stack we use BaseColor for both cases. Catch this here.
if (asset.lit && originalId == ShaderGraphVfxAsset.ColorSlotId)
{
originalId = ShaderGraphVfxAsset.BaseColorSlotId;
}
originialPortIds[i] = originalId;
}
#region Output Struct
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"struct {outputStructName}{nl}{{");
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var port = ports[portIndex];
portCodeIndices[portIndex].Add(codeSnippets.Count);
codeSnippets.Add($"{nl}{indent}{port.concreteValueType.ToShaderString(graph.graphDefaultConcretePrecision)} {port.shaderOutputName}_{originialPortIds[portIndex]};");
}
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"{nl}}};{nl}{nl}");
#endregion
#region Graph Function
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"{outputStructName} {evaluationFunctionName}({nl}{indent}{inputStructName} IN");
var inputProperties = new List<AbstractShaderProperty>();
var portPropertyIndices = new List<int>[ports.Count];
var propertiesStages = new List<ShaderStageCapability>();
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
portPropertyIndices[portIndex] = new List<int>();
}
// Fetch properties from the categories to keep the same order as in the shader graph blackboard
// Union with the flat properties collection because previous shader graph version could store properties without category
var sortedProperties = graph.categories
.SelectMany(x => x.Children)
.OfType<AbstractShaderProperty>()
.Union(graph.properties)
.Where(x => x.isExposed);
foreach (var property in sortedProperties)
{
var propertyIndex = inputProperties.Count;
var codeIndex = codeSnippets.Count;
ShaderStageCapability stageCapability = 0;
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var portPropertySet = portPropertySets[portIndex];
if (portPropertySet.Contains(property.objectId))
{
portCodeIndices[portIndex].Add(codeIndex);
portPropertyIndices[portIndex].Add(propertyIndex);
stageCapability |= ports[portIndex].stageCapability;
}
}
propertiesStages.Add(stageCapability);
inputProperties.Add(property);
codeSnippets.Add($",{nl}{indent}/* Property: {property.displayName} */ {property.GetPropertyAsArgumentStringForVFX(property.concretePrecision.ToShaderString())}");
}
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"){nl}{{");
#region Node Code
for (var mappingIndex = 0; mappingIndex < bodySb.mappings.Count; mappingIndex++)
{
var mapping = bodySb.mappings[mappingIndex];
var code = bodySb.ToString(mapping.startIndex, mapping.count);
if (string.IsNullOrWhiteSpace(code))
{
continue;
}
code = $"{nl}{indent}// Node: {mapping.node.name}{nl}{code}";
var codeIndex = codeSnippets.Count;
codeSnippets.Add(code);
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var portNodeSet = portNodeSets[portIndex];
if (portNodeSet.Contains(mapping.node))
{
portCodeIndices[portIndex].Add(codeIndex);
}
}
}
#endregion
#region Output Mapping
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"{nl}{indent}// VFXMasterNode{nl}{indent}{outputStructName} OUT;{nl}");
// Output mapping
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
var port = ports[portIndex];
portCodeIndices[portIndex].Add(codeSnippets.Count);
codeSnippets.Add($"{indent}OUT.{port.shaderOutputName}_{originialPortIds[portIndex]} = {port.owner.GetSlotValue(port.id, GenerationMode.ForReals, graph.graphDefaultConcretePrecision)};{nl}");
}
#endregion
// Function end
sharedCodeIndices.Add(codeSnippets.Count);
codeSnippets.Add($"{indent}return OUT;{nl}}}{nl}");
#endregion
result.codeSnippets = codeSnippets.ToArray();
result.sharedCodeIndices = sharedCodeIndices.ToArray();
result.outputCodeIndices = new IntArray[ports.Count];
for (var i = 0; i < ports.Count; i++)
{
result.outputCodeIndices[i] = portCodeIndices[i].ToArray();
}
var outputMetadatas = new OutputMetadata[ports.Count];
for (int portIndex = 0; portIndex < outputMetadatas.Length; portIndex++)
{
outputMetadatas[portIndex] = new OutputMetadata(portIndex, ports[portIndex].shaderOutputName, originialPortIds[portIndex]);
}
asset.SetOutputs(outputMetadatas);
asset.evaluationFunctionName = evaluationFunctionName;
asset.inputStructName = inputStructName;
asset.outputStructName = outputStructName;
asset.portRequirements = portRequirements;
asset.m_PropertiesStages = propertiesStages.ToArray();
asset.concretePrecision = graph.graphDefaultConcretePrecision;
asset.SetProperties(inputProperties);
asset.outputPropertyIndices = new IntArray[ports.Count];
for (var portIndex = 0; portIndex < ports.Count; portIndex++)
{
asset.outputPropertyIndices[portIndex] = portPropertyIndices[portIndex].ToArray();
}
return asset;
}
#endif
}
}

View file

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 625f186215c104763be7675aa2d941aa
timeCreated: 1495357324
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,154 @@
using System;
using System.IO;
using UnityEditor.Callbacks;
#if UNITY_2020_2_OR_NEWER
using UnityEditor.AssetImporters;
#else
using UnityEditor.Experimental.AssetImporters;
#endif
using UnityEditor.ShaderGraph.Drawing;
using UnityEngine;
using UnityEditor.Graphing;
using System.Text;
using UnityEditor.ShaderGraph.Serialization;
namespace UnityEditor.ShaderGraph
{
[CustomEditor(typeof(ShaderGraphImporter))]
class ShaderGraphImporterEditor : ScriptedImporterEditor
{
protected override bool needsApplyRevert => false;
public override void OnInspectorGUI()
{
GraphData GetGraphData(AssetImporter importer)
{
var textGraph = File.ReadAllText(importer.assetPath, Encoding.UTF8);
var graphObject = CreateInstance<GraphObject>();
graphObject.hideFlags = HideFlags.HideAndDontSave;
bool isSubGraph;
var extension = Path.GetExtension(importer.assetPath).Replace(".", "");
switch (extension)
{
case ShaderGraphImporter.Extension:
isSubGraph = false;
break;
case ShaderGraphImporter.LegacyExtension:
isSubGraph = false;
break;
case ShaderSubGraphImporter.Extension:
isSubGraph = true;
break;
default:
throw new Exception($"Invalid file extension {extension}");
}
var assetGuid = AssetDatabase.AssetPathToGUID(importer.assetPath);
graphObject.graph = new GraphData
{
assetGuid = assetGuid,
isSubGraph = isSubGraph,
messageManager = null
};
MultiJson.Deserialize(graphObject.graph, textGraph);
graphObject.graph.OnEnable();
graphObject.graph.ValidateGraph();
return graphObject.graph;
}
if (GUILayout.Button("Open Shader Editor"))
{
AssetImporter importer = target as AssetImporter;
Debug.Assert(importer != null, "importer != null");
ShowGraphEditWindow(importer.assetPath);
}
using (var horizontalScope = new GUILayout.HorizontalScope("box"))
{
AssetImporter importer = target as AssetImporter;
string assetName = Path.GetFileNameWithoutExtension(importer.assetPath);
string path = String.Format("Temp/GeneratedFromGraph-{0}.shader", assetName.Replace(" ", ""));
bool alreadyExists = File.Exists(path);
bool update = false;
bool open = false;
if (GUILayout.Button("View Generated Shader"))
{
update = true;
open = true;
}
if (alreadyExists && GUILayout.Button("Regenerate"))
update = true;
if (update)
{
var graphData = GetGraphData(importer);
var generator = new Generator(graphData, null, GenerationMode.ForReals, assetName, null, true);
if (!GraphUtil.WriteToFile(path, generator.generatedShader))
open = false;
}
if (open)
GraphUtil.OpenFile(path);
}
if (Unsupported.IsDeveloperMode())
{
if (GUILayout.Button("View Preview Shader"))
{
AssetImporter importer = target as AssetImporter;
string assetName = Path.GetFileNameWithoutExtension(importer.assetPath);
string path = String.Format("Temp/GeneratedFromGraph-{0}-Preview.shader", assetName.Replace(" ", ""));
var graphData = GetGraphData(importer);
var generator = new Generator(graphData, null, GenerationMode.Preview, $"{assetName}-Preview", null, true);
if (GraphUtil.WriteToFile(path, generator.generatedShader))
GraphUtil.OpenFile(path);
}
}
if (GUILayout.Button("Copy Shader"))
{
AssetImporter importer = target as AssetImporter;
string assetName = Path.GetFileNameWithoutExtension(importer.assetPath);
var graphData = GetGraphData(importer);
var generator = new Generator(graphData, null, GenerationMode.ForReals, assetName, null, true);
GUIUtility.systemCopyBuffer = generator.generatedShader;
}
ApplyRevertGUI();
}
internal static bool ShowGraphEditWindow(string path)
{
var guid = AssetDatabase.AssetPathToGUID(path);
var extension = Path.GetExtension(path);
if (string.IsNullOrEmpty(extension))
return false;
// Path.GetExtension returns the extension prefixed with ".", so we remove it. We force lower case such that
// the comparison will be case-insensitive.
extension = extension.Substring(1).ToLowerInvariant();
if (extension != ShaderGraphImporter.Extension && extension != ShaderSubGraphImporter.Extension)
return false;
foreach (var w in Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>())
{
if (w.selectedGuid == guid)
{
w.Focus();
return true;
}
}
var window = EditorWindow.CreateWindow<MaterialGraphEditWindow>(typeof(MaterialGraphEditWindow), typeof(SceneView));
window.Initialize(guid);
window.Focus();
return true;
}
[OnOpenAsset(0)]
public static bool OnOpenAsset(int instanceID, int line)
{
var path = AssetDatabase.GetAssetPath(instanceID);
return ShowGraphEditWindow(path);
}
}
}

View file

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 575791f1eb8b54602829004c8bcbe802
timeCreated: 1503169819
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UnityEditor.ShaderGraph
{
[Serializable]
struct GraphInputData
{
public string referenceName;
public bool isKeyword;
public PropertyType propertyType;
public KeywordType keywordType;
public bool isCompoundProperty;
public List<SubPropertyData> subProperties;
}
[Serializable]
struct SubPropertyData
{
public string referenceName;
public PropertyType propertyType;
}
[Serializable]
class MinimalCategoryData
{
// ShaderLab doesn't understand virtual texture inputs, they need to be processed to replace the virtual texture input with the layers that compose it instead
public static GraphInputData ProcessVirtualTextureProperty(VirtualTextureShaderProperty virtualTextureShaderProperty)
{
var layerReferenceNames = new List<string>();
virtualTextureShaderProperty.GetPropertyReferenceNames(layerReferenceNames);
var virtualTextureLayerDataList = new List<SubPropertyData>();
// Skip the first entry in this list as that's the Virtual Texture reference name itself, which won't exist in ShaderLab
foreach (var referenceName in layerReferenceNames.Skip(1))
{
var layerPropertyData = new SubPropertyData() { referenceName = referenceName, propertyType = PropertyType.Texture2D };
virtualTextureLayerDataList.Add(layerPropertyData);
}
var virtualTexturePropertyData = new GraphInputData() { referenceName = virtualTextureShaderProperty.displayName, propertyType = PropertyType.VirtualTexture, isKeyword = false };
virtualTexturePropertyData.isCompoundProperty = true;
virtualTexturePropertyData.subProperties = virtualTextureLayerDataList;
return virtualTexturePropertyData;
}
public string categoryName;
public List<GraphInputData> propertyDatas;
[NonSerialized]
public bool expanded = true;
}
class ShaderGraphMetadata : ScriptableObject
{
public string outputNodeTypeName;
// these asset dependencies are stored here as a way for "Export Package..." to discover them
// and automatically pull them in to the .unitypackage
public List<Object> assetDependencies;
public List<MinimalCategoryData> categoryDatas;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b64ab828cd6c5b3479a4c575ca6617d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,516 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
#if UNITY_2020_2_OR_NEWER
using UnityEditor.AssetImporters;
#else
using UnityEditor.Experimental.AssetImporters;
#endif
using UnityEngine;
using UnityEditor.Graphing;
using UnityEditor.Graphing.Util;
using UnityEditor.ShaderGraph.Internal;
using UnityEditor.ShaderGraph.Serialization;
using UnityEngine.Pool;
namespace UnityEditor.ShaderGraph
{
[ExcludeFromPreset]
[ScriptedImporter(30, Extension, -905)]
class ShaderSubGraphImporter : ScriptedImporter
{
public const string Extension = "shadersubgraph";
[SuppressMessage("ReSharper", "UnusedMember.Local")]
static string[] GatherDependenciesFromSourceFile(string assetPath)
{
try
{
AssetCollection assetCollection = new AssetCollection();
MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
List<string> dependencyPaths = new List<string>();
foreach (var asset in assetCollection.assets)
{
// only artifact dependencies need to be declared in GatherDependenciesFromSourceFile
// to force their imports to run before ours
if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
{
var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
// it is unfortunate that we can't declare these dependencies unless they have a path...
// I asked AssetDatabase team for GatherDependenciesFromSourceFileByGUID()
if (!string.IsNullOrEmpty(dependencyPath))
dependencyPaths.Add(dependencyPath);
}
}
return dependencyPaths.ToArray();
}
catch (Exception e)
{
Debug.LogException(e);
return new string[0];
}
}
static bool NodeWasUsedByGraph(string nodeId, GraphData graphData)
{
var node = graphData.GetNodeFromId(nodeId);
return node?.wasUsedByGenerator ?? false;
}
public override void OnImportAsset(AssetImportContext ctx)
{
var importLog = new ShaderGraphImporter.AssetImportErrorLog(ctx);
var graphAsset = ScriptableObject.CreateInstance<SubGraphAsset>();
var subGraphPath = ctx.assetPath;
var subGraphGuid = AssetDatabase.AssetPathToGUID(subGraphPath);
graphAsset.assetGuid = subGraphGuid;
var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8);
var messageManager = new MessageManager();
var graphData = new GraphData
{
isSubGraph = true,
assetGuid = subGraphGuid,
messageManager = messageManager
};
MultiJson.Deserialize(graphData, textGraph);
try
{
ProcessSubGraph(graphAsset, graphData, importLog);
}
catch (Exception e)
{
graphAsset.isValid = false;
Debug.LogException(e, graphAsset);
}
finally
{
var errors = messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graphData));
int errCount = errors.Count();
if (errCount > 0)
{
var firstError = errors.FirstOrDefault();
importLog.LogError($"Sub Graph at {subGraphPath} has {errCount} error(s), the first is: {firstError}", graphAsset);
graphAsset.isValid = false;
}
else
{
var warnings = messageManager.ErrorStrings((nodeId) => NodeWasUsedByGraph(nodeId, graphData), Rendering.ShaderCompilerMessageSeverity.Warning);
int warningCount = warnings.Count();
if (warningCount > 0)
{
var firstWarning = warnings.FirstOrDefault();
importLog.LogWarning($"Sub Graph at {subGraphPath} has {warningCount} warning(s), the first is: {firstWarning}", graphAsset);
}
}
messageManager.ClearAll();
}
Texture2D texture = Resources.Load<Texture2D>("Icons/sg_subgraph_icon");
ctx.AddObjectToAsset("MainAsset", graphAsset, texture);
ctx.SetMainObject(graphAsset);
var metadata = ScriptableObject.CreateInstance<ShaderSubGraphMetadata>();
metadata.hideFlags = HideFlags.HideInHierarchy;
metadata.assetDependencies = new List<UnityEngine.Object>();
AssetCollection assetCollection = new AssetCollection();
MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection);
foreach (var asset in assetCollection.assets)
{
if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage))
{
// this sucks that we have to fully load these assets just to set the reference,
// which then gets serialized as the GUID that we already have here. :P
var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key);
if (!string.IsNullOrEmpty(dependencyPath))
{
metadata.assetDependencies.Add(
AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object)));
}
}
}
ctx.AddObjectToAsset("Metadata", metadata);
// declare dependencies
foreach (var asset in assetCollection.assets)
{
if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency))
{
ctx.DependsOnSourceAsset(asset.Key);
// I'm not sure if this warning below is actually used or not, keeping it to be safe
var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key);
// Ensure that dependency path is relative to project
if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/"))
{
importLog.LogWarning($"Invalid dependency path: {assetPath}", graphAsset);
}
}
// NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies
// HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies
// on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here.
if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency))
{
ctx.DependsOnArtifact(asset.Key);
}
}
}
static void ProcessSubGraph(SubGraphAsset asset, GraphData graph, ShaderGraphImporter.AssetImportErrorLog importLog)
{
var graphIncludes = new IncludeCollection();
var registry = new FunctionRegistry(new ShaderStringBuilder(), graphIncludes, true);
asset.functions.Clear();
asset.isValid = true;
graph.OnEnable();
graph.messageManager.ClearAll();
graph.ValidateGraph();
var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid);
asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath));
asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}_$precision";
asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}_$precision";
asset.path = graph.path;
var outputNode = graph.outputNode;
var outputSlots = PooledList<MaterialSlot>.Get();
outputNode.GetInputSlots(outputSlots);
List<AbstractMaterialNode> nodes = new List<AbstractMaterialNode>();
NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode);
// flag the used nodes so we can filter out errors from unused nodes
foreach (var node in nodes)
node.SetUsedByGenerator();
// Start with a clean slate for the input/output capabilities and dependencies
asset.inputCapabilities.Clear();
asset.outputCapabilities.Clear();
asset.slotDependencies.Clear();
ShaderStageCapability effectiveShaderStage = ShaderStageCapability.All;
foreach (var slot in outputSlots)
{
var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true);
if (effectiveShaderStage == ShaderStageCapability.All && stage != ShaderStageCapability.All)
effectiveShaderStage = stage;
asset.outputCapabilities.Add(new SlotCapability { slotName = slot.RawDisplayName(), capabilities = stage });
// Find all unique property nodes used by this slot and record a dependency for this input/output pair
var inputPropertyNames = new HashSet<string>();
var nodeSet = new HashSet<AbstractMaterialNode>();
NodeUtils.CollectNodeSet(nodeSet, slot);
foreach (var node in nodeSet)
{
if (node is PropertyNode propNode && !inputPropertyNames.Contains(propNode.property.displayName))
{
inputPropertyNames.Add(propNode.property.displayName);
var slotDependency = new SlotDependencyPair();
slotDependency.inputSlotName = propNode.property.displayName;
slotDependency.outputSlotName = slot.RawDisplayName();
asset.slotDependencies.Add(slotDependency);
}
}
}
CollectInputCapabilities(asset, graph);
asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode);
asset.requirements = ShaderGraphRequirements.FromNodes(nodes, effectiveShaderStage, false);
// output precision is whatever the output node has as a graph precision, falling back to the graph default
asset.outputGraphPrecision = outputNode.graphPrecision.GraphFallback(graph.graphDefaultPrecision);
// this saves the graph precision, which indicates whether this subgraph is switchable or not
asset.subGraphGraphPrecision = graph.graphDefaultPrecision;
asset.previewMode = graph.previewMode;
asset.includes = graphIncludes;
GatherDescendentsFromGraph(new GUID(asset.assetGuid), out var containsCircularDependency, out var descendents);
asset.descendents.AddRange(descendents.Select(g => g.ToString()));
asset.descendents.Sort(); // ensure deterministic order
var childrenSet = new HashSet<string>();
var anyErrors = false;
foreach (var node in nodes)
{
if (node is SubGraphNode subGraphNode)
{
var subGraphGuid = subGraphNode.subGraphGuid;
childrenSet.Add(subGraphGuid);
}
if (node.hasError)
{
anyErrors = true;
}
asset.children = childrenSet.ToList();
asset.children.Sort(); // ensure deterministic order
}
if (!anyErrors && containsCircularDependency)
{
importLog.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset);
anyErrors = true;
}
if (anyErrors)
{
asset.isValid = false;
registry.ProvideFunction(asset.functionName, sb => { });
return;
}
foreach (var node in nodes)
{
if (node is IGeneratesFunction generatesFunction)
{
registry.builder.currentNode = node;
generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals);
}
}
// Need to order the properties so that they are in the same order on a subgraph node in a shadergraph
// as they are in the blackboard for the subgraph itself. The (blackboard) categories keep that ordering,
// so traverse those and add those items to the ordered properties list. Needs to be used to set up the
// function _and_ to write out the final asset data so that the function call parameter order matches as well.
var orderedProperties = new List<AbstractShaderProperty>();
var propertiesList = graph.properties.ToList();
foreach (var category in graph.categories)
{
foreach (var child in category.Children)
{
var prop = propertiesList.Find(p => p.guid == child.guid);
// Not all properties in the category are actually on the graph.
// In particular, it seems as if keywords are not properties on sub-graphs.
if (prop != null)
orderedProperties.Add(prop);
}
}
// If we are importing an older file that has not had categories generated for it yet, include those now.
orderedProperties.AddRange(graph.properties.Except(orderedProperties));
// provide top level subgraph function
// NOTE: actual concrete precision here shouldn't matter, it's irrelevant when building the subgraph asset
registry.ProvideFunction(asset.functionName, asset.subGraphGraphPrecision, ConcretePrecision.Single, sb =>
{
GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName);
sb.AppendNewLine();
// Generate the arguments... first INPUTS
var arguments = new List<string>();
foreach (var prop in orderedProperties)
{
// apply fallback to the graph default precision (but don't convert to concrete)
// this means "graph switchable" properties will use the precision token
GraphPrecision propGraphPrecision = prop.precision.ToGraphPrecision(graph.graphDefaultPrecision);
string precisionString = propGraphPrecision.ToGenericString();
arguments.Add(prop.GetPropertyAsArgumentString(precisionString));
if (prop.isConnectionTestable)
{
arguments.Add($"bool {prop.GetConnectionStateHLSLVariableName()}");
}
}
{
var dropdowns = graph.dropdowns;
foreach (var dropdown in dropdowns)
arguments.Add($"int {dropdown.referenceName}");
}
// now pass surface inputs
arguments.Add(string.Format("{0} IN", asset.inputStructName));
// Now generate output arguments
foreach (MaterialSlot output in outputSlots)
arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputGraphPrecision.ToGenericString())} {output.shaderOutputName}_{output.id}");
// Vt Feedback output arguments (always full float4)
foreach (var output in asset.vtFeedbackVariables)
arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Single)} {output}_out");
// Create the function prototype from the arguments
sb.AppendLine("void {0}({1})"
, asset.functionName
, arguments.Aggregate((current, next) => $"{current}, {next}"));
// now generate the function
using (sb.BlockScope())
{
// Just grab the body from the active nodes
foreach (var node in nodes)
{
if (node is IGeneratesBodyCode generatesBodyCode)
{
sb.currentNode = node;
generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals);
if (node.graphPrecision == GraphPrecision.Graph)
{
// code generated by nodes that use graph precision stays in generic form with embedded tokens
// those tokens are replaced when this subgraph function is pulled into a graph that defines the precision
}
else
{
sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
}
}
}
foreach (var slot in outputSlots)
{
sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals)};");
}
foreach (var slot in asset.vtFeedbackVariables)
{
sb.AppendLine($"{slot}_out = {slot};");
}
}
});
// save all of the node-declared functions to the subgraph asset
foreach (var name in registry.names)
{
var source = registry.sources[name];
var func = new FunctionPair(name, source.code, source.graphPrecisionFlags);
asset.functions.Add(func);
}
var collector = new PropertyCollector();
foreach (var node in nodes)
{
int previousPropertyCount = Math.Max(0, collector.propertyCount - 1);
node.CollectShaderProperties(collector, GenerationMode.ForReals);
// This is a stop-gap to prevent the autogenerated values from JsonObject and ShaderInput from
// resulting in non-deterministic import data. While we should move to local ids in the future,
// this will prevent cascading shader recompilations.
for (int i = previousPropertyCount; i < collector.propertyCount; ++i)
{
var prop = collector.GetProperty(i);
var namespaceId = node.objectId;
var nameId = prop.referenceName;
prop.OverrideObjectId(namespaceId, nameId + "_ObjectId_" + i);
prop.OverrideGuid(namespaceId, nameId + "_Guid_" + i);
}
}
asset.WriteData(orderedProperties, graph.keywords, graph.dropdowns, collector.properties, outputSlots, graph.unsupportedTargets);
outputSlots.Dispose();
}
static void GatherDescendentsFromGraph(GUID rootAssetGuid, out bool containsCircularDependency, out HashSet<GUID> descendentGuids)
{
var dependencyMap = new Dictionary<GUID, GUID[]>();
AssetCollection tempAssetCollection = new AssetCollection();
using (ListPool<GUID>.Get(out var tempList))
{
GatherDependencyMap(rootAssetGuid, dependencyMap, tempAssetCollection);
containsCircularDependency = ContainsCircularDependency(rootAssetGuid, dependencyMap, tempList);
}
descendentGuids = new HashSet<GUID>();
GatherDescendentsUsingDependencyMap(rootAssetGuid, descendentGuids, dependencyMap);
}
static void GatherDependencyMap(GUID rootAssetGUID, Dictionary<GUID, GUID[]> dependencyMap, AssetCollection tempAssetCollection)
{
if (!dependencyMap.ContainsKey(rootAssetGUID))
{
// if it is a subgraph, try to recurse into it
var assetPath = AssetDatabase.GUIDToAssetPath(rootAssetGUID);
if (!string.IsNullOrEmpty(assetPath) && assetPath.EndsWith(Extension, true, null))
{
tempAssetCollection.Clear();
MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, tempAssetCollection);
var subgraphGUIDs = tempAssetCollection.assets.Where(asset => asset.Value.HasFlag(AssetCollection.Flags.IsSubGraph)).Select(asset => asset.Key).ToArray();
dependencyMap[rootAssetGUID] = subgraphGUIDs;
foreach (var guid in subgraphGUIDs)
{
GatherDependencyMap(guid, dependencyMap, tempAssetCollection);
}
}
}
}
static void GatherDescendentsUsingDependencyMap(GUID rootAssetGUID, HashSet<GUID> descendentGuids, Dictionary<GUID, GUID[]> dependencyMap)
{
var dependencies = dependencyMap[rootAssetGUID];
foreach (GUID dependency in dependencies)
{
if (descendentGuids.Add(dependency))
{
GatherDescendentsUsingDependencyMap(dependency, descendentGuids, dependencyMap);
}
}
}
static bool ContainsCircularDependency(GUID assetGUID, Dictionary<GUID, GUID[]> dependencyMap, List<GUID> ancestors)
{
if (ancestors.Contains(assetGUID))
{
return true;
}
ancestors.Add(assetGUID);
foreach (var dependencyGUID in dependencyMap[assetGUID])
{
if (ContainsCircularDependency(dependencyGUID, dependencyMap, ancestors))
{
return true;
}
}
ancestors.RemoveAt(ancestors.Count - 1);
return false;
}
static void CollectInputCapabilities(SubGraphAsset asset, GraphData graph)
{
// Collect each input's capabilities. There can be multiple property nodes
// contributing to the same input, so we cache these in a map while building
var inputCapabilities = new Dictionary<string, SlotCapability>();
// Walk all property node output slots, computing and caching the capabilities for that slot
var propertyNodes = graph.GetNodes<PropertyNode>();
foreach (var propertyNode in propertyNodes)
{
foreach (var slot in propertyNode.GetOutputSlots<MaterialSlot>())
{
var slotName = slot.RawDisplayName();
SlotCapability capabilityInfo;
if (!inputCapabilities.TryGetValue(slotName, out capabilityInfo))
{
capabilityInfo = new SlotCapability();
capabilityInfo.slotName = slotName;
inputCapabilities.Add(propertyNode.property.displayName, capabilityInfo);
}
capabilityInfo.capabilities &= NodeUtils.GetEffectiveShaderStageCapability(slot, false);
}
}
asset.inputCapabilities.AddRange(inputCapabilities.Values);
}
}
}

View file

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 60072b568d64c40a485e0fc55012dc9f
timeCreated: 1495613811
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,30 @@
using UnityEditor;
#if UNITY_2020_2_OR_NEWER
using UnityEditor.AssetImporters;
#else
using UnityEditor.Experimental.AssetImporters;
#endif
using UnityEngine;
using Debug = System.Diagnostics.Debug;
namespace UnityEditor.ShaderGraph
{
[CustomEditor(typeof(ShaderSubGraphImporter))]
class ShaderSubGraphImporterEditor : ScriptedImporterEditor
{
public override bool showImportedObject => Unsupported.IsDeveloperMode();
protected override bool needsApplyRevert => false;
public override void OnInspectorGUI()
{
if (GUILayout.Button("Open Shader Editor"))
{
AssetImporter importer = target as AssetImporter;
Debug.Assert(importer != null, "importer != null");
ShaderGraphImporterEditor.ShowGraphEditWindow(importer.assetPath);
}
ApplyRevertGUI();
}
}
}

View file

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ca6b9d3e5c1927e4cb49fc3107f72c1c
timeCreated: 1503169819
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.ShaderGraph
{
class ShaderSubGraphMetadata : ScriptableObject
{
public List<Object> assetDependencies;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0169c6aa153625741a0b332e73a43fb2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b7e9b48c3a9534782bdc85af295da963
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 5910b62eb45374b03ab137b783dc1f6a, type: 3}