initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c6721e5578adf964fa914cf0d66e0bdb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 625f186215c104763be7675aa2d941aa
|
||||
timeCreated: 1495357324
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b64ab828cd6c5b3479a4c575ca6617d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 60072b568d64c40a485e0fc55012dc9f
|
||||
timeCreated: 1495613811
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.ShaderGraph
|
||||
{
|
||||
class ShaderSubGraphMetadata : ScriptableObject
|
||||
{
|
||||
public List<Object> assetDependencies;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0169c6aa153625741a0b332e73a43fb2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,10 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b7e9b48c3a9534782bdc85af295da963
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 5910b62eb45374b03ab137b783dc1f6a, type: 3}
|
Loading…
Add table
Add a link
Reference in a new issue