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]); } }
// gather all asset dependencies declared by nodes in the given (shadergraph or shadersubgraph) asset // by reading the source file from disk, and doing a minimal parse public static void GatherMinimalDependenciesFromFile(string assetPath, AssetCollection assetCollection) { var textGraph = File.ReadAllText(assetPath, Encoding.UTF8); var entries = MultiJsonInternal.Parse(textGraph); if (string.IsNullOrWhiteSpace(entries[0].type)) { var minimalGraphData = JsonUtility.FromJson <MinimalGraphData>(textGraph); entries.Clear(); foreach (var node in minimalGraphData.m_SerializableNodes) { entries.Add(new MultiJsonEntry(node.typeInfo.fullName, null, node.JSONnodeData)); AbstractMaterialNode0 amn = new AbstractMaterialNode0(); JsonUtility.FromJsonOverwrite(node.JSONnodeData, amn); foreach (var slot in amn.m_SerializableSlots) { entries.Add(new MultiJsonEntry(slot.typeInfo.fullName, null, slot.JSONnodeData)); } } } foreach (var entry in entries) { if (s_MinimalTypeMap.TryGetValue(entry.type, out var minimalType)) { var instance = (IHasDependencies)Activator.CreateInstance(minimalType); JsonUtility.FromJsonOverwrite(entry.json, instance); instance.GetSourceAssetDependencies(assetCollection); } } }
public void GetSourceAssetDependencies(AssetCollection assetCollection) { var guidString = m_Cubemap.guid; if (!string.IsNullOrEmpty(guidString) && GUID.TryParse(guidString, out var guid)) { assetCollection.AddAssetDependency(guid, AssetCollection.Flags.IncludeInExportPackage); } }
public TemplatePreprocessor(ActiveFields activeFields, Dictionary <string, string> namedFragments, bool isDebug, string[] templatePaths, AssetCollection assetCollection, ShaderStringBuilder outShaderCodeResult = null) { this.activeFields = activeFields; this.namedFragments = namedFragments; this.isDebug = isDebug; this.templatePaths = templatePaths; this.assetCollection = assetCollection; this.result = outShaderCodeResult ?? new ShaderStringBuilder(); includedFiles = new HashSet <string>(); }
void Generate(GenerationMode mode, string name, AssetCollection assetCollection, Target[] targets) { m_Mode = mode; m_Name = name; m_Builder = new ShaderStringBuilder(); m_ConfiguredTextures = new List <PropertyCollector.TextureInfo>(); m_assetCollection = assetCollection; m_ActiveBlocks = m_GraphData.GetNodes <BlockNode>().ToList(); m_TemporaryBlocks = new List <BlockNode>(); m_Targets = targets; BuildShader(); }
static void GatherDescendentsFromGraph(GUID rootAssetGuid, out bool containsCircularDependency, out HashSet <GUID> descendentGuids) { var dependencyMap = new Dictionary <GUID, GUID[]>(); AssetCollection tempAssetCollection = new AssetCollection(); using (var tempList = ListPool <GUID> .GetDisposable()) { GatherDependencyMap(rootAssetGuid, dependencyMap, tempAssetCollection); containsCircularDependency = ContainsCircularDependency(rootAssetGuid, dependencyMap, tempList.value); } descendentGuids = new HashSet <GUID>(); GatherDescendentsUsingDependencyMap(rootAssetGuid, descendentGuids, dependencyMap); }
public Generator(GraphData graphData, AbstractMaterialNode outputNode, GenerationMode mode, string name, AssetCollection assetCollection) { m_GraphData = graphData; m_OutputNode = outputNode; m_Mode = mode; m_Name = name; m_Builder = new ShaderStringBuilder(); m_ConfiguredTextures = new List <PropertyCollector.TextureInfo>(); m_assetCollection = assetCollection; m_Blocks = graphData.GetNodes <BlockNode>().ToList(); GetTargetImplementations(); BuildShader(); }
public void GetSourceAssetDependencies(AssetCollection assetCollection) { if (m_SourceType == HlslSourceType.File) { m_FunctionSource = UpgradeFunctionSource(m_FunctionSource); if (IsValidFunction(m_SourceType, m_FunctionName, m_FunctionSource, null)) { if (GUID.TryParse(m_FunctionSource, out GUID guid)) { // as this is just #included into the generated .shader file // it doesn't actually need to be a dependency, other than for export package assetCollection.AddAssetDependency(guid, AssetCollection.Flags.IncludeInExportPackage); } } } }
public void GetSourceAssetDependencies(AssetCollection assetCollection) { var assetReference = JsonUtility.FromJson <SubGraphAssetReference>(m_SerializedSubGraph); string guidString = assetReference?.subGraph?.guid; if (!string.IsNullOrEmpty(guidString) && GUID.TryParse(guidString, out GUID guid)) { // subgraphs are read as artifacts // they also should be pulled into .unitypackages assetCollection.AddAssetDependency( guid, AssetCollection.Flags.ArtifactDependency | AssetCollection.Flags.IsSubGraph | AssetCollection.Flags.IncludeInExportPackage); } }
public override void OnImportAsset(AssetImportContext 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); } catch (Exception e) { graphAsset.isValid = false; Debug.LogException(e, graphAsset); } finally { if (messageManager.AnyError()) { graphAsset.isValid = false; foreach (var pair in messageManager.GetNodeMessages()) { var node = graphData.GetNodeFromId(pair.Key); foreach (var message in pair.Value) { MessageManager.Log(node, subGraphPath, message, graphAsset); } } } messageManager.ClearAll(); } Texture2D texture = Resources.Load <Texture2D>("Icons/sg_subgraph_icon@64"); 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/")) { Debug.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 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); } } } }
public override void OnImportAsset(AssetImportContext ctx) { var importLog = new AssetImportErrorLog(ctx); 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(); UnityEngine.Object mainObject = null; #if VFX_GRAPH_10_0_0_OR_NEWER if (!graph.isOnlyVFXTarget) #endif { // build shaders mainObject = BuildAllShaders(ctx, importLog, assetCollection, graph); } #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); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); 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 static string GetShaderText(string path, out List <PropertyCollector.TextureInfo> configuredTextures, AssetCollection assetCollection, GraphData graph) { string shaderString = null; var shaderName = Path.GetFileNameWithoutExtension(path); try { if (!string.IsNullOrEmpty(graph.path)) { shaderName = graph.path + "/" + shaderName; } var generator = new Generator(graph, graph.outputNode, GenerationMode.ForReals, shaderName, assetCollection); shaderString = generator.generatedShader; configuredTextures = generator.configuredTextures; if (graph.messageManager.AnyError()) { shaderString = null; } } catch (Exception e) { Debug.LogException(e); configuredTextures = new List <PropertyCollector.TextureInfo>(); // ignored } return(shaderString ?? k_ErrorShader.Replace("Hidden/GraphErrorShader2", shaderName)); }
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)); }
// this old path is still used by the old VFX path, so keeping it around for now 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 { Generator generator; generator = new Generator(graph, graph.outputNode, mode, shaderName, targets, 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); }
public override void OnImportAsset(AssetImportContext ctx) { var oldShader = AssetDatabase.LoadAssetAtPath <Shader>(ctx.assetPath); if (oldShader != null) { ShaderUtil.ClearShaderMessages(oldShader); } 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 if (graph.messageManager.nodeMessagesChanged) { foreach (var pair in graph.messageManager.GetNodeMessages()) { var node = graph.GetNodeFromId(pair.Key); MessageManager.Log(node, path, pair.Value.First(), shader); } } 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); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); 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))); } } } 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/")) { Debug.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); } } }
public Generator(GraphData graphData, AbstractMaterialNode outputNode, GenerationMode mode, string name, AssetCollection assetCollection, Target[] targets) { m_GraphData = graphData; m_OutputNode = outputNode; Generate(mode, name, assetCollection, targets); }
public Generator(GraphData graphData, AbstractMaterialNode outputNode, GenerationMode mode, string name, AssetCollection assetCollection) { m_GraphData = graphData; m_OutputNode = outputNode; Generate(mode, name, assetCollection, GetTargetImplementations()); }
public override void OnImportAsset(AssetImportContext ctx) { var oldShader = AssetDatabase.LoadAssetAtPath <Shader>(ctx.assetPath); if (oldShader != null) { ShaderUtil.ClearShaderMessages(oldShader); } 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); 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); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); 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 <MinimalCategoryData.GraphInputData> inputInspectorDataList = new List <MinimalCategoryData.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 MinimalCategoryData.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 MinimalCategoryData.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 <MinimalCategoryData.GraphInputData>() }; foreach (var input in categoryData.Children) { MinimalCategoryData.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 MinimalCategoryData.GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }; } else { var prop = input as AbstractShaderProperty; propData = new MinimalCategoryData.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/")) { Debug.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); } } }
Shader BuildAllShaders( AssetImportContext importContext, AssetImportErrorLog importErrorLog, AssetCollection allImportAssetDependencies, GraphData graph) { Shader primaryShader = null; string path = importContext.assetPath; var primaryShaderName = Path.GetFileNameWithoutExtension(path); try { // this will also add Target dependencies into the asset collection Generator generator; generator = new Generator(graph, graph.outputNode, GenerationMode.ForReals, primaryShaderName, assetCollection: allImportAssetDependencies); bool first = true; foreach (var generatedShader in generator.allGeneratedShaders) { var shaderString = generatedShader.codeString; // 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) { shaderString = k_ErrorShader.Replace("Hidden/GraphErrorShader2", generatedShader.shaderName); } var shader = ShaderUtil.CreateShaderAsset(importContext, shaderString, false); ReportErrors(graph, shader, path, importErrorLog); EditorMaterialUtility.SetShaderDefaults( shader, generatedShader.assignedTextures.Where(x => x.modifiable).Select(x => x.name).ToArray(), generatedShader.assignedTextures.Where(x => x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); EditorMaterialUtility.SetShaderNonModifiableDefaults( shader, generatedShader.assignedTextures.Where(x => !x.modifiable).Select(x => x.name).ToArray(), generatedShader.assignedTextures.Where(x => !x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); if (first) { // first shader is always the primary shader // we return the primary shader so it can be attached to the import context at the outer level // allowing it to bind a custom icon as well primaryShader = shader; // only the main shader gets a material created Material material = new Material(shader) { name = primaryShaderName + " Material" }; importContext.AddObjectToAsset("Material", material); first = false; } else { importContext.AddObjectToAsset($"Shader-{generatedShader.shaderName}", shader); } } } catch (Exception e) { Debug.LogException(e); // ignored } return(primaryShader); }
// pass a HashSet to the constructor to have it gather asset dependency GUIDs public TargetSetupContext(AssetCollection assetCollection = null) { subShaders = new List <SubShaderDescriptor>(); this.assetCollection = assetCollection; }