예제 #1
0
        static bool NodeWasUsedByGraph(string nodeId, GraphData graphData)
        {
            var node = graphData.GetNodeFromId(nodeId);

            return(node?.wasUsedByGenerator ?? false);
        }
예제 #2
0
        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@64");
            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);
                }
            }
        }
예제 #3
0
        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));
        }
예제 #4
0
        void DragGraphInput(GraphData graphData)
        {
            AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out DragGraphInputAction");
            AssertHelpers.IsNotNull(graphInputBeingDraggedIn, "GraphInputBeingDraggedIn is null while carrying out DragGraphInputAction");
            graphData.owner.RegisterCompleteObjectUndo("Drag Graph Input");

            switch (graphInputBeingDraggedIn)
            {
            case AbstractShaderProperty property:
            {
                if (property is MultiJsonInternal.UnknownShaderPropertyType)
                {
                    break;
                }

                // This could be from another graph, in which case we add a copy of the ShaderInput to this graph.
                if (graphData.properties.FirstOrDefault(p => p == property) == null)
                {
                    var copyShaderInputAction = new CopyShaderInputAction();
                    copyShaderInputAction.shaderInputToCopy = property;
                    graphData.owner.graphDataStore.Dispatch(copyShaderInputAction);
                    property = (AbstractShaderProperty)copyShaderInputAction.copiedShaderInput;
                }

                var node      = new PropertyNode();
                var drawState = node.drawState;
                drawState.position = new Rect(nodePosition, drawState.position.size);
                node.drawState     = drawState;
                graphData.AddNode(node);

                // Setting the guid requires the graph to be set first.
                node.property = property;
                break;
            }

            case ShaderKeyword keyword:
            {
                // This could be from another graph, in which case we add a copy of the ShaderInput to this graph.
                if (graphData.keywords.FirstOrDefault(k => k == keyword) == null)
                {
                    var copyShaderInputAction = new CopyShaderInputAction();
                    copyShaderInputAction.shaderInputToCopy = keyword;
                    graphData.owner.graphDataStore.Dispatch(copyShaderInputAction);
                    keyword = (ShaderKeyword)copyShaderInputAction.copiedShaderInput;
                }

                var node      = new KeywordNode();
                var drawState = node.drawState;
                drawState.position = new Rect(nodePosition, drawState.position.size);
                node.drawState     = drawState;
                graphData.AddNode(node);

                // Setting the guid requires the graph to be set first.
                node.keyword = keyword;
                break;
            }

            case ShaderDropdown dropdown:
            {
                if (graphData.IsInputAllowedInGraph(dropdown))
                {
                    // This could be from another graph, in which case we add a copy of the ShaderInput to this graph.
                    if (graphData.dropdowns.FirstOrDefault(d => d == dropdown) == null)
                    {
                        var copyShaderInputAction = new CopyShaderInputAction();
                        copyShaderInputAction.shaderInputToCopy = dropdown;
                        graphData.owner.graphDataStore.Dispatch(copyShaderInputAction);
                        dropdown = (ShaderDropdown)copyShaderInputAction.copiedShaderInput;
                    }

                    var node      = new DropdownNode();
                    var drawState = node.drawState;
                    drawState.position = new Rect(nodePosition, drawState.position.size);
                    node.drawState     = drawState;
                    graphData.AddNode(node);

                    // Setting the guid requires the graph to be set first.
                    node.dropdown = dropdown;
                }
                break;
            }

            default:
                throw new ArgumentOutOfRangeException();
            }
        }
예제 #5
0
 public Generator(GraphData graphData, AbstractMaterialNode outputNode, GenerationMode mode, string name, AssetCollection assetCollection)
 {
     m_GraphData  = graphData;
     m_OutputNode = outputNode;
     Generate(mode, name, assetCollection, GetTargetImplementations());
 }
예제 #6
0
        static void ProcessSubGraph(SubGraphAsset asset, GraphData graph)
        {
            var registry = new FunctionRegistry(new ShaderStringBuilder(), true);

            registry.names.Clear();
            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}";
            asset.functionName    = $"SG_{asset.hlslName}_{asset.assetGuid}";
            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);

            asset.effectiveShaderStage = ShaderStageCapability.All;
            foreach (var slot in outputSlots)
            {
                var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true);
                if (stage != ShaderStageCapability.All)
                {
                    asset.effectiveShaderStage = stage;
                    break;
                }
            }

            asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode);
            asset.requirements        = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false);
            asset.graphPrecision      = graph.concretePrecision;
            asset.outputPrecision     = outputNode.concretePrecision;

            GatherFromGraph(assetPath, out var containsCircularDependency, out var descendents);
            asset.descendents.AddRange(descendents);

            var childrenSet = new HashSet <string>();
            var anyErrors   = false;

            foreach (var node in nodes)
            {
                if (node is SubGraphNode subGraphNode)
                {
                    var subGraphGuid = subGraphNode.subGraphGuid;
                    if (childrenSet.Add(subGraphGuid))
                    {
                        asset.children.Add(subGraphGuid);
                    }
                }

                if (node.hasError)
                {
                    anyErrors = true;
                }
            }

            if (!anyErrors && containsCircularDependency)
            {
                Debug.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);
                    registry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
                }
            }

            registry.ProvideFunction(asset.functionName, sb =>
            {
                GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName);
                sb.AppendNewLine();

                // Generate arguments... first INPUTS
                var arguments = new List <string>();
                foreach (var prop in graph.properties)
                {
                    prop.ValidateConcretePrecision(asset.graphPrecision);
                    arguments.Add(string.Format("{0}", prop.GetPropertyAsArgumentString()));
                }

                // now pass surface inputs
                arguments.Add(string.Format("{0} IN", asset.inputStructName));

                // Now generate outputs
                foreach (var output in outputSlots)
                {
                    arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputPrecision)} {output.shaderOutputName}_{output.id}");
                }

                // Vt Feedback arguments
                foreach (var output in asset.vtFeedbackVariables)
                {
                    arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Float)} {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);
                            sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
                        }
                    }

                    foreach (var slot in outputSlots)
                    {
                        sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals, asset.outputPrecision)};");
                    }

                    foreach (var slot in asset.vtFeedbackVariables)
                    {
                        sb.AppendLine($"{slot}_out = {slot};");
                    }
                }
            });

            asset.functions.AddRange(registry.names.Select(x => new FunctionPair(x, registry.sources[x].code)));

            var collector = new PropertyCollector();

            foreach (var node in nodes)
            {
                int previousPropertyCount = Math.Max(0, collector.properties.Count - 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.properties.Count; ++i)
                {
                    var prop        = collector.properties[i];
                    var namespaceId = node.objectId;
                    var nameId      = prop.referenceName;

                    prop.OverrideObjectId(namespaceId, nameId + "_ObjectId_" + i);
                    prop.OverrideGuid(namespaceId, nameId + "_Guid_" + i);
                }
            }
            asset.WriteData(graph.properties, graph.keywords, collector.properties, outputSlots, graph.unsupportedTargets);
            outputSlots.Dispose();
        }
예제 #7
0
        internal static void AddMainTextureMenuOptions(ContextualMenuPopulateEvent evt, Texture2DShaderProperty texProp, GraphData graphData, Action inspectorUpdateAction)
        {
            if (!graphData.isSubGraph)
            {
                if (!texProp.isMainTexture)
                {
                    evt.menu.AppendAction(
                        "Set as Main Texture",
                        e =>
                    {
                        Texture2DShaderProperty tex = graphData.GetMainTexture();
                        if (tex.isMainTexture)
                        {
                            if (EditorUtility.DisplayDialog("Change Main Texture Action", $"Are you sure you want to change the Main Texture from {tex.displayName} to {texProp.displayName}?", "Yes", "Cancel"))
                            {
                                graphData.owner.RegisterCompleteObjectUndo("Change Main Texture");
                                tex.isMainTexture     = false;
                                texProp.isMainTexture = true;
                                inspectorUpdateAction();
                            }
                            return;
                        }

                        graphData.owner.RegisterCompleteObjectUndo("Set Main Texture");
                        texProp.isMainTexture = true;
                        inspectorUpdateAction();
                    });
                }
                else
                {
                    evt.menu.AppendAction(
                        "Clear Main Texture",
                        e =>
                    {
                        graphData.owner.RegisterCompleteObjectUndo("Clear Main Texture");
                        texProp.isMainTexture = false;
                        inspectorUpdateAction();
                    });
                }
            }
        }
예제 #8
0
        static void ProcessSubGraph(SubGraphAsset asset, GraphData graph)
        {
            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);

            asset.effectiveShaderStage = ShaderStageCapability.All;
            foreach (var slot in outputSlots)
            {
                var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true);
                if (stage != ShaderStageCapability.All)
                {
                    asset.effectiveShaderStage = stage;
                    break;
                }
            }

            asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode);
            asset.requirements        = ShaderGraphRequirements.FromNodes(nodes, asset.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)
            {
                Debug.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);
                }
            }

            // 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 graph.properties)
                {
                    // 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));
                }

                // 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(graph.properties, graph.keywords, collector.properties, outputSlots, graph.unsupportedTargets);
            outputSlots.Dispose();
        }
예제 #9
0
        // The only situation in which an input has an identical reference name to another input in a category, while not being the same instance, is if they are duplicates
        public bool IsInputDuplicatedFromCategory(ShaderInput shaderInput, CategoryData inputCategory, GraphData targetGraphData)
        {
            foreach (var child in inputCategory.Children)
            {
                if (child.referenceName.Equals(shaderInput.referenceName, StringComparison.Ordinal) && child.objectId != shaderInput.objectId)
                {
                    return(true);
                }
            }

            // Need to check if they share same graph owner as well, if not then we can early out
            bool inputBelongsToTargetGraph = targetGraphData.ContainsInput(shaderInput);

            if (inputBelongsToTargetGraph == false)
            {
                return(false);
            }

            return(false);
        }
예제 #10
0
 protected abstract void ModelChanged(GraphData graphData);
예제 #11
0
 public static void ConcretizeGraph(GraphData graph)
 {
     ConcretizeProperties(graph);
     GraphDataUtils.ApplyActionLeafFirst(graph, ConcretizeNode);
 }
예제 #12
0
 protected SGViewController(T viewModel, GraphData graphData)
 {
     m_ViewModel = viewModel;
     m_Model     = graphData;
     m_ViewModel.ConstructFromModel(m_Model);
 }
예제 #13
0
        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);
        }
예제 #14
0
        // 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);
        }
        internal static string GetShaderText(string path, out List <PropertyCollector.TextureInfo> configuredTextures, List <string> sourceAssetDependencyPaths, out GraphData graph)
        {
            var textGraph = File.ReadAllText(path, Encoding.UTF8);

            graph = JsonUtility.FromJson <GraphData>(textGraph);
            graph.messageManager = new MessageManager();
            graph.assetGuid      = AssetDatabase.AssetPathToGUID(path);
            graph.OnEnable();
            graph.ValidateGraph();

            return(GetShaderText(path, out configuredTextures, sourceAssetDependencyPaths, graph));
        }
예제 #16
0
        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");

            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);
                }
            }
        }
        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;
            var    sourceAssetDependencyPaths = new List <string>();

            UnityEngine.Object mainObject;

            var       textGraph = File.ReadAllText(path, Encoding.UTF8);
            GraphData graph     = JsonUtility.FromJson <GraphData>(textGraph);

            graph.messageManager = new MessageManager();
            graph.assetGuid      = AssetDatabase.AssetPathToGUID(path);
            graph.OnEnable();
            graph.ValidateGraph();

            if (graph.outputNode is VfxMasterNode vfxMasterNode)
            {
                var vfxAsset = GenerateVfxShaderGraphAsset(vfxMasterNode);

                mainObject = vfxAsset;
            }
            else
            {
                var text   = GetShaderText(path, out configuredTextures, sourceAssetDependencyPaths, graph);
                var shader = ShaderUtil.CreateShaderAsset(text, false);

                if (graph != null && graph.messageManager.nodeMessagesChanged)
                {
                    foreach (var pair in graph.messageManager.GetNodeMessages())
                    {
                        var node = graph.GetNodeFromTempId(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());

                mainObject = shader;
            }
            Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon@64");

            ctx.AddObjectToAsset("MainAsset", mainObject, texture);
            ctx.SetMainObject(mainObject);

            var metadata = ScriptableObject.CreateInstance <ShaderGraphMetadata>();

            metadata.hideFlags = HideFlags.HideInHierarchy;
            if (graph != null)
            {
                metadata.outputNodeTypeName = graph.outputNode.GetType().FullName;
            }
            ctx.AddObjectToAsset("Metadata", metadata);

            foreach (var sourceAssetDependencyPath in sourceAssetDependencyPaths.Distinct())
            {
                // Ensure that dependency path is relative to project
                if (!sourceAssetDependencyPath.StartsWith("Packages/") && !sourceAssetDependencyPath.StartsWith("Assets/"))
                {
                    Debug.LogWarning($"Invalid dependency path: {sourceAssetDependencyPath}", mainObject);
                    continue;
                }

                ctx.DependsOnSourceAsset(sourceAssetDependencyPath);
            }
        }
예제 #18
0
 public static GenerationResults GetPreviewShader(this GraphData graph, AbstractMaterialNode node)
 {
     return(graph.GetShader(node, GenerationMode.Preview, String.Format("hidden/preview/{0}", node.GetVariableNameForNode())));
 }
예제 #19
0
        internal static void AddMainColorMenuOptions(ContextualMenuPopulateEvent evt, ColorShaderProperty colorProp, GraphData graphData, Action inspectorUpdateAction)
        {
            if (!graphData.isSubGraph)
            {
                if (!colorProp.isMainColor)
                {
                    evt.menu.AppendAction(
                        "Set as Main Color",
                        e =>
                    {
                        ColorShaderProperty col = graphData.GetMainColor();
                        if (col != null)
                        {
                            if (EditorUtility.DisplayDialog("Change Main Color Action", $"Are you sure you want to change the Main Color from {col.displayName} to {colorProp.displayName}?", "Yes", "Cancel"))
                            {
                                graphData.owner.RegisterCompleteObjectUndo("Change Main Color");
                                col.isMainColor       = false;
                                colorProp.isMainColor = true;
                                inspectorUpdateAction();
                            }
                            return;
                        }

                        graphData.owner.RegisterCompleteObjectUndo("Set Main Color");
                        colorProp.isMainColor = true;
                        inspectorUpdateAction();
                    });
                }
                else
                {
                    evt.menu.AppendAction(
                        "Clear Main Color",
                        e =>
                    {
                        graphData.owner.RegisterCompleteObjectUndo("Clear Main Color");
                        colorProp.isMainColor = false;
                        inspectorUpdateAction();
                    });
                }
            }
        }
예제 #20
0
        public static GenerationResults GetShader(this GraphData graph, AbstractMaterialNode node, GenerationMode mode, string name)
        {
            // ----------------------------------------------------- //
            //                         SETUP                         //
            // ----------------------------------------------------- //

            // -------------------------------------
            // String builders

            var finalShader = new ShaderStringBuilder();
            var results     = new GenerationResults();

            var shaderProperties          = new PropertyCollector();
            var shaderKeywords            = new KeywordCollector();
            var shaderPropertyUniforms    = new ShaderStringBuilder();
            var shaderKeywordDeclarations = new ShaderStringBuilder();
            var shaderKeywordPermutations = new ShaderStringBuilder(1);

            var functionBuilder  = new ShaderStringBuilder();
            var functionRegistry = new FunctionRegistry(functionBuilder);

            var vertexDescriptionFunction = new ShaderStringBuilder(0);

            var surfaceDescriptionInputStruct = new ShaderStringBuilder(0);
            var surfaceDescriptionStruct      = new ShaderStringBuilder(0);
            var surfaceDescriptionFunction    = new ShaderStringBuilder(0);

            var vertexInputs = new ShaderStringBuilder(0);

            graph.CollectShaderKeywords(shaderKeywords, mode);

            if (graph.GetKeywordPermutationCount() > ShaderGraphPreferences.variantLimit)
            {
                graph.AddValidationError(node.tempId, ShaderKeyword.kVariantLimitWarning, Rendering.ShaderCompilerMessageSeverity.Error);

                results.configuredTextures = shaderProperties.GetConfiguredTexutres();
                results.shader             = string.Empty;
                return(results);
            }

            // -------------------------------------
            // Get Slot and Node lists

            var activeNodeList = ListPool <AbstractMaterialNode> .Get();

            NodeUtils.DepthFirstCollectNodesFromNode(activeNodeList, node);

            var slots = new List <MaterialSlot>();

            if (node is IMasterNode || node is SubGraphOutputNode)
            {
                slots.AddRange(node.GetInputSlots <MaterialSlot>());
            }
            else
            {
                var outputSlots = node.GetOutputSlots <MaterialSlot>().ToList();
                if (outputSlots.Count > 0)
                {
                    slots.Add(outputSlots[0]);
                }
            }

            // -------------------------------------
            // Get Requirements

            var requirements = ShaderGraphRequirements.FromNodes(activeNodeList, ShaderStageCapability.Fragment);

            // ----------------------------------------------------- //
            //                         KEYWORDS                      //
            // ----------------------------------------------------- //

            // -------------------------------------
            // Get keyword permutations

            graph.CollectShaderKeywords(shaderKeywords, mode);

            // Track permutation indicies for all nodes and requirements
            List <int>[] keywordPermutationsPerNode = new List <int> [activeNodeList.Count];

            // -------------------------------------
            // Evaluate all permutations

            for (int i = 0; i < shaderKeywords.permutations.Count; i++)
            {
                // Get active nodes for this permutation
                var localNodes = ListPool <AbstractMaterialNode> .Get();

                NodeUtils.DepthFirstCollectNodesFromNode(localNodes, node, keywordPermutation: shaderKeywords.permutations[i]);

                // Track each pixel node in this permutation
                foreach (AbstractMaterialNode pixelNode in localNodes)
                {
                    int nodeIndex = activeNodeList.IndexOf(pixelNode);

                    if (keywordPermutationsPerNode[nodeIndex] == null)
                    {
                        keywordPermutationsPerNode[nodeIndex] = new List <int>();
                    }
                    keywordPermutationsPerNode[nodeIndex].Add(i);
                }

                // Get active requirements for this permutation
                var localSurfaceRequirements = ShaderGraphRequirements.FromNodes(localNodes, ShaderStageCapability.Fragment, false);
                var localPixelRequirements   = ShaderGraphRequirements.FromNodes(localNodes, ShaderStageCapability.Fragment);
            }


            // ----------------------------------------------------- //
            //                START VERTEX DESCRIPTION               //
            // ----------------------------------------------------- //

            // -------------------------------------
            // Generate Vertex Description function

            vertexDescriptionFunction.AppendLine("GraphVertexInput PopulateVertexData(GraphVertexInput v)");
            using (vertexDescriptionFunction.BlockScope())
            {
                vertexDescriptionFunction.AppendLine("return v;");
            }

            // ----------------------------------------------------- //
            //               START SURFACE DESCRIPTION               //
            // ----------------------------------------------------- //

            // -------------------------------------
            // Generate Input structure for Surface Description function
            // Surface Description Input requirements are needed to exclude intermediate translation spaces

            GenerateSurfaceInputStruct(surfaceDescriptionInputStruct, requirements, "SurfaceDescriptionInputs");

            results.previewMode = PreviewMode.Preview2D;
            foreach (var pNode in activeNodeList)
            {
                if (pNode.previewMode == PreviewMode.Preview3D)
                {
                    results.previewMode = PreviewMode.Preview3D;
                    break;
                }
            }

            // -------------------------------------
            // Generate Output structure for Surface Description function

            GenerateSurfaceDescriptionStruct(surfaceDescriptionStruct, slots, useIdsInNames: !(node is IMasterNode));

            // -------------------------------------
            // Generate Surface Description function

            GenerateSurfaceDescriptionFunction(
                activeNodeList,
                keywordPermutationsPerNode,
                node,
                graph,
                surfaceDescriptionFunction,
                functionRegistry,
                shaderProperties,
                shaderKeywords,
                mode,
                outputIdProperty: results.outputIdProperty);

            // ----------------------------------------------------- //
            //           GENERATE VERTEX > PIXEL PIPELINE            //
            // ----------------------------------------------------- //

            // -------------------------------------
            // Keyword declarations

            shaderKeywords.GetKeywordsDeclaration(shaderKeywordDeclarations, mode);

            // -------------------------------------
            // Property uniforms

            shaderProperties.GetPropertiesDeclaration(shaderPropertyUniforms, mode, graph.concretePrecision);

            // -------------------------------------
            // Generate Input structure for Vertex shader

            GenerateApplicationVertexInputs(requirements, vertexInputs);

            // ----------------------------------------------------- //
            //                      FINALIZE                         //
            // ----------------------------------------------------- //

            // -------------------------------------
            // Build final shader

            finalShader.AppendLine(@"Shader ""{0}""", name);
            using (finalShader.BlockScope())
            {
                SubShaderGenerator.GeneratePropertiesBlock(finalShader, shaderProperties, shaderKeywords, mode);
                finalShader.AppendNewLine();

                finalShader.AppendLine(@"HLSLINCLUDE");
                finalShader.AppendLine(@"#include ""Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.render-pipelines.core/ShaderLibrary/Packing.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.render-pipelines.core/ShaderLibrary/NormalSurfaceGradient.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.render-pipelines.core/ShaderLibrary/EntityLighting.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.shadergraph/ShaderGraphLibrary/ShaderVariables.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.shadergraph/ShaderGraphLibrary/ShaderVariablesFunctions.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.unity.shadergraph/ShaderGraphLibrary/Functions.hlsl""");
                finalShader.AppendLine(@"#include ""Packages/com.seasun.idsystem/Shaders/URP/Common/URPCommon.hlsl""");

                finalShader.AppendLines(shaderKeywordDeclarations.ToString());
                finalShader.AppendLine(@"#define SHADERGRAPH_PREVIEW 1");
                finalShader.AppendNewLine();

                finalShader.AppendLines(shaderKeywordPermutations.ToString());

                finalShader.AppendLines(shaderPropertyUniforms.ToString());
                finalShader.AppendNewLine();

                finalShader.AppendLines(surfaceDescriptionInputStruct.ToString());
                finalShader.AppendNewLine();

                finalShader.Concat(functionBuilder);
                finalShader.AppendNewLine();

                finalShader.AppendLines(surfaceDescriptionStruct.ToString());
                finalShader.AppendNewLine();
                finalShader.AppendLines(surfaceDescriptionFunction.ToString());
                finalShader.AppendNewLine();

                finalShader.AppendLines(vertexInputs.ToString());
                finalShader.AppendNewLine();
                finalShader.AppendLines(vertexDescriptionFunction.ToString());
                finalShader.AppendNewLine();

                finalShader.AppendLine(@"ENDHLSL");

                finalShader.AppendLines(ShaderGenerator.GetPreviewSubShader(node, requirements));
                ListPool <AbstractMaterialNode> .Release(activeNodeList);
            }

            // -------------------------------------
            // Finalize

            results.configuredTextures = shaderProperties.GetConfiguredTexutres();
            ShaderSourceMap sourceMap;

            results.shader    = finalShader.ToString(out sourceMap);
            results.sourceMap = sourceMap;
            return(results);
        }
        internal static string GetShaderText(string path, out List <PropertyCollector.TextureInfo> configuredTextures, List <string> sourceAssetDependencyPaths, out GraphData graph)
        {
            graph = null;
            string shaderString = null;
            var    shaderName   = Path.GetFileNameWithoutExtension(path);

            try
            {
                var textGraph = File.ReadAllText(path, Encoding.UTF8);
                graph = JsonUtility.FromJson <GraphData>(textGraph);
                graph.messageManager = new MessageManager();
                graph.assetGuid      = AssetDatabase.AssetPathToGUID(path);
                graph.OnEnable();
                graph.ValidateGraph();

                if (!string.IsNullOrEmpty(graph.path))
                {
                    shaderName = graph.path + "/" + shaderName;
                }
                shaderString = ((IMasterNode)graph.outputNode).GetShader(GenerationMode.ForReals, shaderName, out configuredTextures, sourceAssetDependencyPaths);

                if (sourceAssetDependencyPaths != null)
                {
                    foreach (var node in graph.GetNodes <AbstractMaterialNode>())
                    {
                        node.GetSourceAssetDependencies(sourceAssetDependencyPaths);
                    }
                }

                if (graph.messageManager.nodeMessagesChanged)
                {
                    shaderString = null;
                }
            }
            catch (Exception)
            {
                configuredTextures = new List <PropertyCollector.TextureInfo>();

                // ignored
            }

            return(shaderString ?? k_ErrorShader.Replace("Hidden/GraphErrorShader2", shaderName));
        }
예제 #22
0
        public override void OnImportAsset(AssetImportContext ctx)
        {
            var currentTime = DateTime.Now.Ticks;

            if (ctx.assetPath != path)
            {
                ctx.LogImportError("The sgpostsubgraph extension may only be used internally by Shader Graph.");
                return;
            }

            if (SubGraphDatabase.instance == null)
            {
                SubGraphDatabase.instance = ScriptableObject.CreateInstance <SubGraphDatabase>();
            }
            var database = SubGraphDatabase.instance;

            var allSubGraphGuids = AssetDatabase.FindAssets($"t:{nameof(SubGraphAsset)}").ToList();

            allSubGraphGuids.Sort();
            var subGraphMap  = new Dictionary <string, SubGraphData>();
            var graphDataMap = new Dictionary <string, GraphData>();

            foreach (var subGraphData in database.subGraphs)
            {
                if (allSubGraphGuids.BinarySearch(subGraphData.assetGuid) >= 0)
                {
                    subGraphMap.Add(subGraphData.assetGuid, subGraphData);
                }
            }

            var dirtySubGraphGuids = new List <string>();

            foreach (var subGraphGuid in allSubGraphGuids)
            {
                var subGraphAsset = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(AssetDatabase.GUIDToAssetPath(subGraphGuid));
                if (!subGraphMap.TryGetValue(subGraphGuid, out var subGraphData))
                {
                    subGraphData = new SubGraphData();
                }

                if (subGraphAsset.importedAt > subGraphData.processedAt)
                {
                    dirtySubGraphGuids.Add(subGraphGuid);
                    subGraphData.Reset();
                    subGraphData.processedAt = currentTime;
                    var subGraphPath = AssetDatabase.GUIDToAssetPath(subGraphGuid);
                    var textGraph    = File.ReadAllText(subGraphPath, Encoding.UTF8);
                    var graphData    = new GraphData {
                        isSubGraph = true, assetGuid = subGraphGuid
                    };
                    JsonUtility.FromJsonOverwrite(textGraph, graphData);
                    subGraphData.children.AddRange(graphData.GetNodes <SubGraphNode>().Select(x => x.subGraphGuid).Distinct());
                    subGraphData.assetGuid     = subGraphGuid;
                    subGraphMap[subGraphGuid]  = subGraphData;
                    graphDataMap[subGraphGuid] = graphData;
                }
                else
                {
                    subGraphData.ancestors.Clear();
                    subGraphData.descendents.Clear();
                    subGraphData.isRecursive = false;
                }
            }

            database.subGraphs.Clear();
            database.subGraphs.AddRange(subGraphMap.Values);
            database.subGraphs.Sort((s1, s2) => s1.assetGuid.CompareTo(s2.assetGuid));
            database.subGraphGuids.Clear();
            database.subGraphGuids.AddRange(database.subGraphs.Select(x => x.assetGuid));

            var permanentMarks = new HashSet <string>();
            var stack          = new Stack <string>(allSubGraphGuids.Count);

            // Detect recursion, and populate `ancestors` and `descendents` per sub graph.
            foreach (var rootSubGraphData in database.subGraphs)
            {
                var rootSubGraphGuid = rootSubGraphData.assetGuid;
                stack.Push(rootSubGraphGuid);
                while (stack.Count > 0)
                {
                    var subGraphGuid = stack.Pop();
                    if (!permanentMarks.Add(subGraphGuid))
                    {
                        continue;
                    }

                    var subGraphData = subGraphMap[subGraphGuid];
                    if (subGraphData != rootSubGraphData)
                    {
                        subGraphData.ancestors.Add(rootSubGraphGuid);
                        rootSubGraphData.descendents.Add(subGraphGuid);
                    }
                    foreach (var childSubGraphGuid in subGraphData.children)
                    {
                        if (childSubGraphGuid == rootSubGraphGuid)
                        {
                            rootSubGraphData.isRecursive = true;
                        }
                        else if (subGraphMap.ContainsKey(childSubGraphGuid))
                        {
                            stack.Push(childSubGraphGuid);
                        }
                    }
                }
                permanentMarks.Clear();
            }

            // Next up we build a list of sub graphs to be processed, which will later be sorted topologically.
            var sortedSubGraphs = new List <SubGraphData>();

            foreach (var subGraphGuid in dirtySubGraphGuids)
            {
                var subGraphData = subGraphMap[subGraphGuid];
                if (permanentMarks.Add(subGraphGuid))
                {
                    sortedSubGraphs.Add(subGraphData);
                }

                // Note that we're traversing up the graph via ancestors rather than descendents, because all Sub Graphs using the current sub graph needs to be re-processed.
                foreach (var ancestorGuid in subGraphData.ancestors)
                {
                    if (permanentMarks.Add(ancestorGuid))
                    {
                        var ancestorSubGraphData = subGraphMap[ancestorGuid];
                        sortedSubGraphs.Add(ancestorSubGraphData);
                    }
                }
            }
            permanentMarks.Clear();

            // Sort topologically. At this stage we can assume there are no loops because all recursive sub graphs have been filtered out.
            sortedSubGraphs.Sort((s1, s2) => s1.descendents.Contains(s2.assetGuid) ? 1 : s2.descendents.Contains(s1.assetGuid) ? -1 : 0);

            // Finally process the topologically sorted sub graphs without recursion.
            var registry       = new FunctionRegistry(new ShaderStringBuilder(), true);
            var messageManager = new MessageManager();

            foreach (var subGraphData in sortedSubGraphs)
            {
                try
                {
                    var subGraphPath = AssetDatabase.GUIDToAssetPath(subGraphData.assetGuid);
                    if (!graphDataMap.TryGetValue(subGraphData.assetGuid, out var graphData))
                    {
                        var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8);
                        graphData = new GraphData {
                            isSubGraph = true, assetGuid = subGraphData.assetGuid
                        };
                        JsonUtility.FromJsonOverwrite(textGraph, graphData);
                    }

                    graphData.messageManager = messageManager;
                    ProcessSubGraph(subGraphMap, registry, subGraphData, graphData);
                    if (messageManager.nodeMessagesChanged)
                    {
                        var subGraphAsset = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(AssetDatabase.GUIDToAssetPath(subGraphData.assetGuid));
                        foreach (var pair in messageManager.GetNodeMessages())
                        {
                            var node = graphData.GetNodeFromTempId(pair.Key);
                            foreach (var message in pair.Value)
                            {
                                MessageManager.Log(node, subGraphPath, message, subGraphAsset);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    subGraphData.isValid = false;
                    var subGraphAsset = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(AssetDatabase.GUIDToAssetPath(subGraphData.assetGuid));
                    Debug.LogException(e, subGraphAsset);
                }
                finally
                {
                    subGraphData.processedAt = currentTime;
                    messageManager.ClearAll();
                }
            }

            // Carry over functions used by sub-graphs that were not re-processed in this import.
            foreach (var subGraphData in database.subGraphs)
            {
                foreach (var functionName in subGraphData.functionNames)
                {
                    if (!registry.sources.ContainsKey(functionName))
                    {
                        registry.sources.Add(functionName, database.functionSources[database.functionNames.BinarySearch(functionName)]);
                    }
                }
            }

            var functions = registry.sources.ToList();

            functions.Sort((p1, p2) => p1.Key.CompareTo(p2.Key));
            database.functionNames.Clear();
            database.functionSources.Clear();
            foreach (var pair in functions)
            {
                database.functionNames.Add(pair.Key);
                database.functionSources.Add(pair.Value);
            }

            ctx.AddObjectToAsset("MainAsset", database);
            ctx.SetMainObject(database);

            SubGraphDatabase.instance = null;
        }
예제 #23
0
        static void ProcessSubGraph(SubGraphAsset asset, GraphData graph)
        {
            var registry = new FunctionRegistry(new ShaderStringBuilder(), true);

            registry.names.Clear();
            asset.functions.Clear();
            asset.nodeProperties.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}";
            asset.functionName    = $"SG_{asset.hlslName}_{asset.assetGuid}";
            asset.path            = graph.path;

            var outputNode = (SubGraphOutputNode)graph.outputNode;

            asset.outputs.Clear();
            outputNode.GetInputSlots(asset.outputs);

            List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>();

            NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode);

            asset.effectiveShaderStage = ShaderStageCapability.All;
            foreach (var slot in asset.outputs)
            {
                var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true);
                if (stage != ShaderStageCapability.All)
                {
                    asset.effectiveShaderStage = stage;
                    break;
                }
            }

            asset.requirements    = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false);
            asset.inputs          = graph.properties.ToList();
            asset.keywords        = graph.keywords.ToList();
            asset.graphPrecision  = graph.concretePrecision;
            asset.outputPrecision = outputNode.concretePrecision;

            GatherFromGraph(assetPath, out var containsCircularDependency, out var descendents);
            asset.descendents.AddRange(descendents);

            var childrenSet = new HashSet <string>();
            var anyErrors   = false;

            foreach (var node in nodes)
            {
                if (node is SubGraphNode subGraphNode)
                {
                    var subGraphGuid = subGraphNode.subGraphGuid;
                    if (childrenSet.Add(subGraphGuid))
                    {
                        asset.children.Add(subGraphGuid);
                    }
                }

                if (node.hasError)
                {
                    anyErrors = true;
                }
            }

            if (!anyErrors && containsCircularDependency)
            {
                Debug.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, new GraphContext(asset.inputStructName), GenerationMode.ForReals);
                    registry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
                }
            }

            registry.ProvideFunction(asset.functionName, sb =>
            {
                var graphContext = new GraphContext(asset.inputStructName);

                GraphUtil.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName);
                sb.AppendNewLine();

                // Generate arguments... first INPUTS
                var arguments = new List <string>();
                foreach (var prop in asset.inputs)
                {
                    prop.ValidateConcretePrecision(asset.graphPrecision);
                    arguments.Add(string.Format("{0}", prop.GetPropertyAsArgumentString()));
                }

                // now pass surface inputs
                arguments.Add(string.Format("{0} IN", asset.inputStructName));

                // Now generate outputs
                foreach (var output in asset.outputs)
                {
                    arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputPrecision)} {output.shaderOutputName}_{output.id}");
                }

                // 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, graphContext, GenerationMode.ForReals);
                            sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString());
                        }
                    }

                    foreach (var slot in asset.outputs)
                    {
                        sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals, asset.outputPrecision)};");
                    }
                }
            });

            asset.functions.AddRange(registry.names.Select(x => new FunctionPair(x, registry.sources[x])));

            var collector = new PropertyCollector();

            asset.nodeProperties = collector.properties;
            foreach (var node in nodes)
            {
                node.CollectShaderProperties(collector, GenerationMode.ForReals);
            }

            asset.OnBeforeSerialize();
        }
예제 #24
0
        static void ProcessSubGraph(Dictionary <string, SubGraphData> subGraphMap, FunctionRegistry registry, SubGraphData subGraphData, GraphData graph)
        {
            registry.names.Clear();
            subGraphData.functionNames.Clear();
            subGraphData.nodeProperties.Clear();
            subGraphData.isValid = true;

            graph.OnEnable();
            graph.messageManager.ClearAll();
            graph.ValidateGraph();

            var assetPath = AssetDatabase.GUIDToAssetPath(subGraphData.assetGuid);

            subGraphData.hlslName        = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath));
            subGraphData.inputStructName = $"Bindings_{subGraphData.hlslName}_{subGraphData.assetGuid}";
            subGraphData.functionName    = $"SG_{subGraphData.hlslName}_{subGraphData.assetGuid}";
            subGraphData.path            = graph.path;

            var outputNode = (SubGraphOutputNode)graph.outputNode;

            subGraphData.outputs.Clear();
            outputNode.GetInputSlots(subGraphData.outputs);

            List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>();

            NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode);

            subGraphData.effectiveShaderStage = ShaderStageCapability.All;
            foreach (var slot in subGraphData.outputs)
            {
                var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true);
                if (stage != ShaderStageCapability.All)
                {
                    subGraphData.effectiveShaderStage = stage;
                    break;
                }
            }

            subGraphData.requirements = ShaderGraphRequirements.FromNodes(nodes, subGraphData.effectiveShaderStage, false);
            subGraphData.inputs       = graph.properties.ToList();

            foreach (var node in nodes)
            {
                if (node.hasError)
                {
                    subGraphData.isValid = false;
                    registry.ProvideFunction(subGraphData.functionName, sb => { });
                    return;
                }
            }

            foreach (var node in nodes)
            {
                if (node is SubGraphNode subGraphNode)
                {
                    var nestedData = subGraphMap[subGraphNode.subGraphGuid];

                    foreach (var functionName in nestedData.functionNames)
                    {
                        registry.names.Add(functionName);
                    }
                }
                else if (node is IGeneratesFunction generatesFunction)
                {
                    generatesFunction.GenerateNodeFunction(registry, new GraphContext(subGraphData.inputStructName), GenerationMode.ForReals);
                }
            }

            registry.ProvideFunction(subGraphData.functionName, sb =>
            {
                var graphContext = new GraphContext(subGraphData.inputStructName);

                GraphUtil.GenerateSurfaceInputStruct(sb, subGraphData.requirements, subGraphData.inputStructName);
                sb.AppendNewLine();

                // Generate arguments... first INPUTS
                var arguments = new List <string>();
                foreach (var prop in subGraphData.inputs)
                {
                    arguments.Add(string.Format("{0}", prop.GetPropertyAsArgumentString()));
                }

                // now pass surface inputs
                arguments.Add(string.Format("{0} IN", subGraphData.inputStructName));

                // Now generate outputs
                foreach (var output in subGraphData.outputs)
                {
                    arguments.Add($"out {output.concreteValueType.ToString(outputNode.precision)} {output.shaderOutputName}_{output.id}");
                }

                // Create the function prototype from the arguments
                sb.AppendLine("void {0}({1})"
                              , subGraphData.functionName
                              , arguments.Aggregate((current, next) => $"{current}, {next}"));

                // now generate the function
                using (sb.BlockScope())
                {
                    // Just grab the body from the active nodes
                    var bodyGenerator = new ShaderGenerator();
                    foreach (var node in nodes)
                    {
                        if (node is IGeneratesBodyCode)
                        {
                            (node as IGeneratesBodyCode).GenerateNodeCode(bodyGenerator, graphContext, GenerationMode.ForReals);
                        }
                    }

                    foreach (var slot in subGraphData.outputs)
                    {
                        bodyGenerator.AddShaderChunk($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals)};");
                    }

                    sb.Append(bodyGenerator.GetShaderString(1));
                }
            });

            subGraphData.functionNames.AddRange(registry.names.Distinct());

            var collector = new PropertyCollector();

            subGraphData.nodeProperties = collector.properties;
            foreach (var node in nodes)
            {
                node.CollectShaderProperties(collector, GenerationMode.ForReals);
            }

            subGraphData.OnBeforeSerialize();
        }
예제 #25
0
 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);
 }
예제 #26
0
        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;
            var    sourceAssetDependencyPaths = new List <string>();

            UnityEngine.Object mainObject;

            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();

            // TODO: How to handle this?
            if (graph.isVFXTarget)
            {
                var vfxAsset = GenerateVfxShaderGraphAsset(graph);
                mainObject = vfxAsset;
            }
            else
            {
                var text   = GetShaderText(path, out configuredTextures, sourceAssetDependencyPaths, graph);
                var shader = ShaderUtil.CreateShaderAsset(text, false);


                if (graph != null && 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());

                mainObject = shader;
            }
            Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon@64");

            ctx.AddObjectToAsset("MainAsset", mainObject, texture);
            ctx.SetMainObject(mainObject);

            if (graph != null)
            {
                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>();
            var deps = GatherDependenciesFromSourceFile(ctx.assetPath);

            foreach (string dependency in deps)
            {
                sgMetadata.assetDependencies.Add(AssetDatabase.LoadAssetAtPath(dependency, typeof(UnityEngine.Object)));
            }
            ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata);


            foreach (var sourceAssetDependencyPath in sourceAssetDependencyPaths.Distinct())
            {
                // Ensure that dependency path is relative to project
                if (!sourceAssetDependencyPath.StartsWith("Packages/") && !sourceAssetDependencyPath.StartsWith("Assets/"))
                {
                    Debug.LogWarning($"Invalid dependency path: {sourceAssetDependencyPath}", mainObject);
                    continue;
                }

                ctx.DependsOnSourceAsset(sourceAssetDependencyPath);
            }
        }
예제 #27
0
        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, List <string> sourceAssetDependencyPaths, GraphData graph)
        {
            string shaderString = null;
            var    shaderName   = Path.GetFileNameWithoutExtension(path);

            try
            {
                if (!string.IsNullOrEmpty(graph.path))
                {
                    shaderName = graph.path + "/" + shaderName;
                }
                shaderString = ((IMasterNode)graph.outputNode).GetShader(GenerationMode.ForReals, shaderName, out configuredTextures, sourceAssetDependencyPaths);

                if (graph.messageManager.nodeMessagesChanged)
                {
                    shaderString = null;
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                configuredTextures = new List <PropertyCollector.TextureInfo>();

                // ignored
            }

            return(shaderString ?? k_ErrorShader.Replace("Hidden/GraphErrorShader2", shaderName));
        }
예제 #29
0
        // TODO: Fix this
        static ShaderGraphVfxAsset GenerateVfxShaderGraphAsset(GraphData graph)
        {
            var target = graph.activeTargets.FirstOrDefault(x => x is VFXTarget) as VFXTarget;

            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;

            asset.lit           = target.lit;
            asset.alphaClipping = target.alphaTest;

            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 generation
            {
                var tmpCtx = new TargetActiveBlockContext(new List <BlockFieldDescriptor>(), null);
                target.GetActiveBlocks(ref tmpCtx);
                ports.RemoveAll(materialSlot =>
                {
                    return(!tmpCtx.activeBlocks.Any(o => materialSlot.RawDisplayName() == o.displayName));
                });
            }

            var bodySb   = new ShaderStringBuilder(1);
            var registry = new FunctionRegistry(new ShaderStringBuilder(), true);

            foreach (var properties in graph.properties)
            {
                properties.ValidateConcretePrecision(graph.concretePrecision);
            }

            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 \"Assets/com.unity.shadergraph/ShaderGraphLibrary/Functions.hlsl\"{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.isExposable && property.generatePropertyBlock)
                {
                    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}");
            }



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

            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.concretePrecision)} {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];
            for (var portIndex = 0; portIndex < ports.Count; portIndex++)
            {
                portPropertyIndices[portIndex] = new List <int>();
            }

            foreach (var property in graph.properties)
            {
                if (!property.isExposable || !property.generatePropertyBlock)
                {
                    continue;
                }

                var propertyIndex = inputProperties.Count;
                var codeIndex     = codeSnippets.Count;

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

                inputProperties.Add(property);
                codeSnippets.Add($",{nl}{indent}/* Property: {property.displayName} */ {property.GetPropertyAsArgumentStringForVFX()}");
            }

            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.concretePrecision)};{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.concretePrecision      = graph.concretePrecision;
            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);
        }
예제 #30
0
        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);
                }
            }
        }