// 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); } } }
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); }
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); } } }