public void TestSerializableSlotCanSerialize() { var toSerialize = new List <MaterialSlot>() { new TestSlot(0, "InSlot", SlotType.Input, 0), new TestSlot(1, "OutSlot", SlotType.Output, 5), }; DummyJsonHolder dummyJsonHolder = new DummyJsonHolder(toSerialize); var serialized = MultiJson.Serialize(dummyJsonHolder); DummyJsonHolder dummyJsonHolder1 = new DummyJsonHolder(); MultiJson.Deserialize(dummyJsonHolder1, serialized); Assert.AreEqual(2, dummyJsonHolder1.testSlots.Count); var loaded = new List <MaterialSlot>(dummyJsonHolder1.testSlots.SelectValue()); Assert.IsInstanceOf <MaterialSlot>(loaded[0]); Assert.IsInstanceOf <MaterialSlot>(loaded[1]); Assert.AreEqual(0, loaded[0].id); Assert.AreEqual("InSlot(4)", loaded[0].displayName); Assert.IsTrue(loaded[0].isInputSlot); Assert.AreEqual(1, loaded[1].id); Assert.AreEqual("OutSlot(4)", loaded[1].displayName); Assert.IsTrue(loaded[1].isOutputSlot); }
// returns true only when the graph in this window would serialize different from the last time we loaded or saved it internal bool GraphHasChangedSinceLastSerialization() { Assert.IsTrue(graphObject?.graph != null); // this should be checked by calling code var currentGraphJson = MultiJson.Serialize(graphObject.graph); return(!string.Equals(currentGraphJson, m_LastSerializedFileContents, StringComparison.Ordinal)); }
public override void OnImportAsset(AssetImportContext ctx) { var graphAsset = ScriptableObject.CreateInstance <SubGraphAsset>(); var subGraphPath = ctx.assetPath; var subGraphGuid = AssetDatabase.AssetPathToGUID(subGraphPath); graphAsset.assetGuid = subGraphGuid; var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8); var messageManager = new MessageManager(); var graphData = new GraphData { isSubGraph = true, assetGuid = subGraphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, textGraph); try { ProcessSubGraph(graphAsset, graphData); } catch (Exception e) { graphAsset.isValid = false; Debug.LogException(e, graphAsset); } finally { if (messageManager.AnyError()) { graphAsset.isValid = false; foreach (var pair in messageManager.GetNodeMessages()) { var node = graphData.GetNodeFromId(pair.Key); foreach (var message in pair.Value) { MessageManager.Log(node, subGraphPath, message, graphAsset); } } } messageManager.ClearAll(); } Texture2D texture = Resources.Load <Texture2D>("Icons/sg_subgraph_icon@64"); ctx.AddObjectToAsset("MainAsset", graphAsset, texture); ctx.SetMainObject(graphAsset); var metadata = ScriptableObject.CreateInstance <ShaderSubGraphMetadata>(); metadata.hideFlags = HideFlags.HideInHierarchy; metadata.assetDependencies = new List <UnityEngine.Object>(); var deps = GatherDependenciesFromSourceFile(ctx.assetPath); foreach (string dependency in deps) { metadata.assetDependencies.Add(AssetDatabase.LoadAssetAtPath(dependency, typeof(UnityEngine.Object))); } ctx.AddObjectToAsset("Metadata", metadata); }
public void CopyOverAndImport(string assetPath) { string fileName = Path.GetFileName(assetPath); string fileNameNoExtension = Path.GetFileNameWithoutExtension(assetPath); string fileContents = File.ReadAllText(assetPath); string fileExtension = Path.GetExtension(assetPath).ToLower(); bool isSubgraph = (fileExtension == "shadersubgraph"); string localFilePath = "Assets/Testing/ImportTests/" + fileName; string localFilePathNoExtension = "Assets/Testing/ImportTests/" + fileNameNoExtension; File.WriteAllText(Application.dataPath + "/Testing/ImportTests/" + fileName, fileContents); AssetDatabase.ImportAsset(localFilePath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); var graphGuid = AssetDatabase.AssetPathToGUID(localFilePath); var messageManager = new MessageManager(); GraphData graphData = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, fileContents); graphData.OnEnable(); graphData.ValidateGraph(); if (isSubgraph) { // check that the SubGraphAsset is the same after versioning twice // this is important to ensure we're not importing subgraphs non-deterministically when they are out-of-date on disk AssetDatabase.ImportAsset(localFilePath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); var subGraph = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(localFilePath); var serialized = EditorJsonUtility.ToJson(subGraph); AssetDatabase.ImportAsset(localFilePath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); var subGraph2 = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(localFilePath); var serialized2 = EditorJsonUtility.ToJson(subGraph2); Assert.AreEqual(serialized, serialized2, $"Importing the subgraph {localFilePath} twice resulted in different subgraph assets."); } else { // check that the generated shader is the same after versioning twice // this is important to ensure we're not importing shaders non-deterministically when they are out-of-date on disk var generator = new Generator(graphData, graphData.outputNode, GenerationMode.ForReals, fileNameNoExtension, null); string shader = generator.generatedShader; // version again GraphData graphData2 = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData2, fileContents); graphData2.OnEnable(); graphData2.ValidateGraph(); var generator2 = new Generator(graphData2, graphData2.outputNode, GenerationMode.ForReals, fileNameNoExtension, null); string shader2 = generator2.generatedShader; Assert.AreEqual(shader, shader2, $"Importing the graph {localFilePath} twice resulted in different generated shaders."); } }
public void InvalidConnectionsTest() { var graphPath = targetUnityDirectoryPath + "/SubShaderInvalidCapabilities_Graph.shadergraph"; string fileContents = File.ReadAllText(graphPath); var graphGuid = AssetDatabase.AssetPathToGUID(graphPath); var messageManager = new MessageManager(); GraphData graphData = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, fileContents); graphData.OnEnable(); void ValidateSlotError(MaterialSlot slotA, MaterialSlot slotB, AbstractMaterialNode nodeWithError) { Assert.IsNotNull(slotA, "Expected slotA to not be null"); Assert.IsNotNull(slotB, "Expected slotB to not be null"); var edge = graphData.Connect(slotA.slotReference, slotB.slotReference); bool foundNode = false; foreach (var message in graphData.messageManager.GetNodeMessages()) { if (message.Key.Equals(nodeWithError.objectId)) { foundNode = true; break; } } Assert.IsTrue(foundNode, $"Expected node {nodeWithError.name} didn't have an error"); // Put the graph back in a clean state graphData.messageManager.ClearAll(); graphData.RemoveEdge(edge); } var subGraphNode = FindFirstNodeOfType <SubGraphNode>(graphData, "SubShaderInvalidCapabilities_SubGraph"); var vertexIdNode = FindFirstNodeOfType <VertexIDNode>(graphData); var sampleTextureNode = FindFirstNodeOfType <SampleTexture2DNode>(graphData); var baseColorNode = FindFirstNodeOfType <BlockNode>(graphData, $"{BlockFields.SurfaceDescription.BaseColor.tag}.{BlockFields.SurfaceDescription.BaseColor.name}"); var positionNode = FindFirstNodeOfType <BlockNode>(graphData, $"{BlockFields.VertexDescription.Position.tag}.{BlockFields.VertexDescription.Position.name}"); var vertexLockedSlot = FindNamedSlot(subGraphNode, "VertexLocked_Out"); var fragmentLockedSlot = FindNamedSlot(subGraphNode, "FragmentLocked_Out"); var outputA = FindNamedSlot(subGraphNode, "OutputA"); var baseColorSlot = FindNamedSlot(baseColorNode, "Base Color"); var positionSlot = FindNamedSlot(positionNode, "Position"); // Hook up a (internal) vertex locked slot to a fragment output. The error should be on the sub graph node ValidateSlotError(vertexLockedSlot, baseColorSlot, subGraphNode); // Hook up a (internal) fragment locked slot to a vertex output. The error should be on the sub graph node ValidateSlotError(fragmentLockedSlot, positionSlot, subGraphNode); // Hook up: Sample Texture -> Add -> SubGraph -> Position (out). Error should be on the SampleTexture node. ValidateSlotError(outputA, positionSlot, sampleTextureNode); // Hook up: VertexId -> Add -> SubGraph -> Position (out). Error should be on the VertexId node. ValidateSlotError(outputA, baseColorSlot, vertexIdNode); }
public void LoadGraphData() { if (!String.IsNullOrEmpty(m_SerializedSubGraphData.JSONnodeData)) { m_SubGraphData = new SubGraphData(); MultiJson.Deserialize(m_SubGraphData, m_SerializedSubGraphData.JSONnodeData); } }
// returns true only when saving the graph in this window would serialize different from the file on disk internal bool GraphIsDifferentFromFileOnDisk() { Assert.IsTrue(graphObject?.graph != null); // this should be checked by calling code var currentGraphJson = MultiJson.Serialize(graphObject.graph); var currentFileJson = ReadAssetFile(); return(!string.Equals(currentGraphJson, currentFileJson, StringComparison.Ordinal)); }
private static StringBuilder TryBuildFromShaderGraph(VFXShaderGraphParticleOutput context, VFXContextCompiledData contextData) { var stringBuilder = new StringBuilder(); // Reconstruct the ShaderGraph. var path = AssetDatabase.GetAssetPath(context.GetOrRefreshShaderGraphObject()); List <PropertyCollector.TextureInfo> configuredTextures; AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(path, 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(); // Check the validity of the shader graph (unsupported keywords or shader property usage). if (VFXLibrary.currentSRPBinder == null || !VFXLibrary.currentSRPBinder.IsGraphDataValid(graph)) { return(null); } var target = graph.activeTargets.Where(o => { if (o.SupportsVFX()) { //We are assuming the target has been implemented in the same package than srp binder. var srpBinderAssembly = VFXLibrary.currentSRPBinder.GetType().Assembly; var targetAssembly = o.GetType().Assembly; if (srpBinderAssembly == targetAssembly) { return(true); } } return(false); }).FirstOrDefault(); if (target == null || !target.TryConfigureContextData(context, contextData)) { return(null); } // Use ShaderGraph to generate the VFX shader. var text = ShaderGraphImporter.GetShaderText(path, out configuredTextures, assetCollection, graph, GenerationMode.VFX, new[] { target }); // Append the shader + strip the name header (VFX stamps one in later on). stringBuilder.Append(text); stringBuilder.Remove(0, text.IndexOf("{", StringComparison.Ordinal)); return(stringBuilder); }
public static bool WriteShaderGraphToDisk(string path, GraphData data) { if (data == null) { throw new ArgumentNullException(nameof(data)); } return(WriteToDisk(path, MultiJson.Serialize(data))); }
bool IsDirty() { if (m_Deleted || graphObject.graph == null) { return(false); // Not dirty; it's gone. } var currentJson = MultiJson.Serialize(graphObject.graph); var fileJson = File.ReadAllText(AssetDatabase.GUIDToAssetPath(selectedGuid)); return(!string.Equals(currentJson, fileJson, StringComparison.Ordinal)); }
GraphData DeserializeGraph() { var json = m_SerializedGraph.JSONnodeData; var deserializedGraph = new GraphData { isSubGraph = m_IsSubGraph, assetGuid = m_AssetGuid }; MultiJson.Deserialize(deserializedGraph, json); m_DeserializedVersion = m_SerializedVersion; m_SerializedGraph = default; return(deserializedGraph); }
public void OnBeforeSerialize() { if (graph != null) { var json = MultiJson.Serialize(graph); m_SerializedGraph = new SerializationHelper.JSONSerializedElement { JSONnodeData = json }; m_IsSubGraph = graph.isSubGraph; m_AssetGuid = graph.assetGuid; } }
public void WriteData(IEnumerable <AbstractShaderProperty> inputs, IEnumerable <ShaderKeyword> keywords, IEnumerable <ShaderDropdown> dropdowns, IEnumerable <AbstractShaderProperty> nodeProperties, IEnumerable <MaterialSlot> outputs, IEnumerable <Target> unsupportedTargets) { if (m_SubGraphData == null) { m_SubGraphData = new SubGraphData(); m_SubGraphData.OverrideObjectId(assetGuid, "_subGraphData"); } m_SubGraphData.inputs.Clear(); m_SubGraphData.keywords.Clear(); m_SubGraphData.dropdowns.Clear(); m_SubGraphData.nodeProperties.Clear(); m_SubGraphData.outputs.Clear(); m_SubGraphData.unsupportedTargets.Clear(); foreach (var input in inputs) { m_SubGraphData.inputs.Add(input); } foreach (var keyword in keywords) { m_SubGraphData.keywords.Add(keyword); } foreach (var dropdown in dropdowns) { m_SubGraphData.dropdowns.Add(dropdown); } foreach (var nodeProperty in nodeProperties) { m_SubGraphData.nodeProperties.Add(nodeProperty); } foreach (var output in outputs) { m_SubGraphData.outputs.Add(output); } foreach (var unsupportedTarget in unsupportedTargets) { m_SubGraphData.unsupportedTargets.Add(unsupportedTarget); } var json = MultiJson.Serialize(m_SubGraphData); m_SerializedSubGraphData = new SerializationHelper.JSONSerializedElement() { JSONnodeData = json }; m_SubGraphData = null; }
public void Initialize(string graphPath) { hideFlags = HideFlags.HideAndDontSave; var textGraph = File.ReadAllText(graphPath, Encoding.UTF8); graph = new GraphData(); graph.messageManager = new MessageManager(); graph.assetGuid = AssetDatabase.AssetPathToGUID(graphPath); MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); }
internal static string GetShaderText(string path, out List <PropertyCollector.TextureInfo> configuredTextures) { var textGraph = File.ReadAllText(path, Encoding.UTF8); GraphData graph = new GraphData { messageManager = new MessageManager(), assetGuid = AssetDatabase.AssetPathToGUID(path) }; MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); return(GetShaderText(path, out configuredTextures, null, graph)); }
internal static CopyPasteGraph FromJson(string copyBuffer, GraphData targetGraph) { try { var graph = new CopyPasteGraph(); MultiJson.Deserialize(graph, copyBuffer, targetGraph, true); return(graph); } catch { // ignored. just means copy buffer was not a graph :( return(null); } }
public override void OnImportAsset(AssetImportContext ctx) { var graphAsset = ScriptableObject.CreateInstance <SubGraphAsset>(); var subGraphPath = ctx.assetPath; var subGraphGuid = AssetDatabase.AssetPathToGUID(subGraphPath); graphAsset.assetGuid = subGraphGuid; var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8); var messageManager = new MessageManager(); var graphData = new GraphData { isSubGraph = true, assetGuid = subGraphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, textGraph); try { ProcessSubGraph(graphAsset, graphData); } catch (Exception e) { graphAsset.isValid = false; Debug.LogException(e, graphAsset); } finally { if (messageManager.AnyError()) { graphAsset.isValid = false; foreach (var pair in messageManager.GetNodeMessages()) { var node = graphData.GetNodeFromId(pair.Key); foreach (var message in pair.Value) { MessageManager.Log(node, subGraphPath, message, graphAsset); } } } messageManager.ClearAll(); } Texture2D texture = Resources.Load <Texture2D>("Icons/sg_subgraph_icon@64"); ctx.AddObjectToAsset("MainAsset", graphAsset, texture); ctx.SetMainObject(graphAsset); }
public void CopyOverAndImport(string assetPath) { string fileName = Path.GetFileName(assetPath); string fileContents = File.ReadAllText(assetPath); string localFilePath = "Assets/Testing/ImportTests/" + fileName; File.WriteAllText(Application.dataPath + "/Testing/ImportTests/" + fileName, fileContents); AssetDatabase.ImportAsset(localFilePath); var graphGuid = AssetDatabase.AssetPathToGUID(localFilePath); var messageManager = new MessageManager(); GraphData graphData = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, fileContents); graphData.OnEnable(); graphData.ValidateGraph(); }
// if successfully written to disk, returns the serialized file contents as a string // on failure, returns null public static string WriteShaderGraphToDisk(string path, GraphData data) { if (data == null) { // Returning false may be better than throwing this exception, in terms of preserving data. // But if GraphData is null, it's likely we don't have any data to preserve anyways. // So this exception seems fine for now. throw new ArgumentNullException(nameof(data)); } var text = MultiJson.Serialize(data); if (WriteToDisk(path, text)) { return(text); } else { return(null); } }
public void WriteData(IEnumerable <AbstractShaderProperty> inputs, IEnumerable <ShaderKeyword> keywords, IEnumerable <AbstractShaderProperty> nodeProperties, IEnumerable <MaterialSlot> outputs) { if (m_SubGraphData == null) { m_SubGraphData = new SubGraphData(); } m_SubGraphData.inputs.Clear(); m_SubGraphData.keywords.Clear(); m_SubGraphData.nodeProperties.Clear(); m_SubGraphData.outputs.Clear(); foreach (var input in inputs) { m_SubGraphData.inputs.Add(input); } foreach (var keyword in keywords) { m_SubGraphData.keywords.Add(keyword); } foreach (var nodeProperty in nodeProperties) { m_SubGraphData.nodeProperties.Add(nodeProperty); } foreach (var output in outputs) { m_SubGraphData.outputs.Add(output); } var json = MultiJson.Serialize(m_SubGraphData); m_SerializedSubGraphData = new SerializationHelper.JSONSerializedElement() { JSONnodeData = json }; m_SubGraphData = null; }
public override void OnImportAsset(AssetImportContext ctx) { var graphAsset = ScriptableObject.CreateInstance <SubGraphAsset>(); var subGraphPath = ctx.assetPath; var subGraphGuid = AssetDatabase.AssetPathToGUID(subGraphPath); graphAsset.assetGuid = subGraphGuid; var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8); var messageManager = new MessageManager(); var graphData = new GraphData { isSubGraph = true, assetGuid = subGraphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, textGraph); try { ProcessSubGraph(graphAsset, graphData); } catch (Exception e) { graphAsset.isValid = false; Debug.LogException(e, graphAsset); } finally { if (messageManager.AnyError()) { graphAsset.isValid = false; foreach (var pair in messageManager.GetNodeMessages()) { var node = graphData.GetNodeFromId(pair.Key); foreach (var message in pair.Value) { MessageManager.Log(node, subGraphPath, message, graphAsset); } } } messageManager.ClearAll(); } Texture2D texture = Resources.Load <Texture2D>("Icons/sg_subgraph_icon@64"); ctx.AddObjectToAsset("MainAsset", graphAsset, texture); ctx.SetMainObject(graphAsset); var metadata = ScriptableObject.CreateInstance <ShaderSubGraphMetadata>(); metadata.hideFlags = HideFlags.HideInHierarchy; metadata.assetDependencies = new List <UnityEngine.Object>(); AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { metadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } ctx.AddObjectToAsset("Metadata", metadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { Debug.LogWarning($"Invalid dependency path: {assetPath}", graphAsset); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }
public void Initialize(string assetGuid) { try { m_ColorSpace = PlayerSettings.colorSpace; m_RenderPipelineAsset = GraphicsSettings.renderPipelineAsset; var asset = AssetDatabase.LoadAssetAtPath <Object>(AssetDatabase.GUIDToAssetPath(assetGuid)); if (asset == null) { return; } if (!EditorUtility.IsPersistent(asset)) { return; } if (selectedGuid == assetGuid) { return; } var path = AssetDatabase.GetAssetPath(asset); var extension = Path.GetExtension(path); if (extension == null) { return; } // Path.GetExtension returns the extension prefixed with ".", so we remove it. We force lower case such that // the comparison will be case-insensitive. extension = extension.Substring(1).ToLowerInvariant(); bool isSubGraph; switch (extension) { case ShaderGraphImporter.Extension: isSubGraph = false; break; case ShaderSubGraphImporter.Extension: isSubGraph = true; break; default: return; } selectedGuid = assetGuid; using (GraphLoadMarker.Auto()) { var textGraph = File.ReadAllText(path, Encoding.UTF8); graphObject = CreateInstance <GraphObject>(); graphObject.hideFlags = HideFlags.HideAndDontSave; graphObject.graph = new GraphData { assetGuid = assetGuid, isSubGraph = isSubGraph, messageManager = messageManager }; MultiJson.Deserialize(graphObject.graph, textGraph); graphObject.graph.OnEnable(); graphObject.graph.ValidateGraph(); } using (CreateGraphEditorViewMarker.Auto()) { graphEditorView = new GraphEditorView(this, m_GraphObject.graph, messageManager) { viewDataKey = selectedGuid, assetName = asset.name.Split('/').Last() }; } Texture2D icon = GetThemeIcon(graphObject.graph); // This is adding the icon at the front of the tab titleContent = EditorGUIUtility.TrTextContentWithIcon(selectedGuid, icon); UpdateTitle(); Repaint(); } catch (Exception) { m_HasError = true; m_GraphEditorView = null; graphObject = null; throw; } }
public void ToSubGraph() { var graphView = graphEditorView.graphView; string path; string sessionStateResult = SessionState.GetString(k_PrevSubGraphPathKey, k_PrevSubGraphPathDefaultValue); string pathToOriginSG = Path.GetDirectoryName(AssetDatabase.GUIDToAssetPath(selectedGuid)); if (!sessionStateResult.Equals(k_PrevSubGraphPathDefaultValue)) { path = sessionStateResult; } else { path = pathToOriginSG; } path = EditorUtility.SaveFilePanelInProject("Save Sub Graph", "New Shader Sub Graph", ShaderSubGraphImporter.Extension, "", path); path = path.Replace(Application.dataPath, "Assets"); if (path.Length == 0) { return; } graphObject.RegisterCompleteObjectUndo("Convert To Subgraph"); var nodes = graphView.selection.OfType <IShaderNodeView>().Where(x => !(x.node is PropertyNode || x.node is SubGraphOutputNode)).Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(); var bounds = Rect.MinMaxRect(float.PositiveInfinity, float.PositiveInfinity, float.NegativeInfinity, float.NegativeInfinity); foreach (var node in nodes) { var center = node.drawState.position.center; bounds = Rect.MinMaxRect( Mathf.Min(bounds.xMin, center.x), Mathf.Min(bounds.yMin, center.y), Mathf.Max(bounds.xMax, center.x), Mathf.Max(bounds.yMax, center.y)); } var middle = bounds.center; bounds.center = Vector2.zero; // Collect graph inputs var graphInputs = graphView.selection.OfType <BlackboardField>().Select(x => x.userData as ShaderInput); // Collect the property nodes and get the corresponding properties var propertyNodes = graphView.selection.OfType <IShaderNodeView>().Where(x => (x.node is PropertyNode)).Select(x => ((PropertyNode)x.node).property); var metaProperties = graphView.graph.properties.Where(x => propertyNodes.Contains(x)); // Collect the keyword nodes and get the corresponding keywords var keywordNodes = graphView.selection.OfType <IShaderNodeView>().Where(x => (x.node is KeywordNode)).Select(x => ((KeywordNode)x.node).keyword); var metaKeywords = graphView.graph.keywords.Where(x => keywordNodes.Contains(x)); var copyPasteGraph = new CopyPasteGraph(graphView.selection.OfType <ShaderGroup>().Select(x => x.userData), graphView.selection.OfType <IShaderNodeView>().Where(x => !(x.node is PropertyNode || x.node is SubGraphOutputNode)).Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(), graphView.selection.OfType <Edge>().Select(x => x.userData as Graphing.Edge), graphInputs, metaProperties, metaKeywords, graphView.selection.OfType <StickyNote>().Select(x => x.userData), true); // why do we serialize and deserialize only to make copies of everything in the steps below? // is this just to clear out all non-serialized data? var deserialized = CopyPasteGraph.FromJson(MultiJson.Serialize(copyPasteGraph), graphView.graph); if (deserialized == null) { return; } var subGraph = new GraphData { isSubGraph = true, path = "Sub Graphs" }; var subGraphOutputNode = new SubGraphOutputNode(); { var drawState = subGraphOutputNode.drawState; drawState.position = new Rect(new Vector2(bounds.xMax + 200f, 0f), drawState.position.size); subGraphOutputNode.drawState = drawState; } subGraph.AddNode(subGraphOutputNode); subGraph.outputNode = subGraphOutputNode; // Always copy deserialized keyword inputs foreach (ShaderKeyword keyword in deserialized.metaKeywords) { var copiedInput = (ShaderKeyword)keyword.Copy(); subGraph.SanitizeGraphInputName(copiedInput); subGraph.SanitizeGraphInputReferenceName(copiedInput, keyword.overrideReferenceName); subGraph.AddGraphInput(copiedInput); // Update the keyword nodes that depends on the copied keyword var dependentKeywordNodes = deserialized.GetNodes <KeywordNode>().Where(x => x.keyword == keyword); foreach (var node in dependentKeywordNodes) { node.owner = graphView.graph; node.keyword = copiedInput; } } foreach (GroupData groupData in deserialized.groups) { subGraph.CreateGroup(groupData); } foreach (var node in deserialized.GetNodes <AbstractMaterialNode>()) { var drawState = node.drawState; drawState.position = new Rect(drawState.position.position - middle, drawState.position.size); node.drawState = drawState; // Checking if the group guid is also being copied. // If not then nullify that guid if (node.group != null && !subGraph.groups.Contains(node.group)) { node.group = null; } subGraph.AddNode(node); } foreach (var note in deserialized.stickyNotes) { if (note.group != null && !subGraph.groups.Contains(note.group)) { note.group = null; } subGraph.AddStickyNote(note); } // figure out what needs remapping var externalOutputSlots = new List <Graphing.Edge>(); var externalInputSlots = new List <Graphing.Edge>(); foreach (var edge in deserialized.edges) { var outputSlot = edge.outputSlot; var inputSlot = edge.inputSlot; var outputSlotExistsInSubgraph = subGraph.ContainsNode(outputSlot.node); var inputSlotExistsInSubgraph = subGraph.ContainsNode(inputSlot.node); // pasting nice internal links! if (outputSlotExistsInSubgraph && inputSlotExistsInSubgraph) { subGraph.Connect(outputSlot, inputSlot); } // one edge needs to go to outside world else if (outputSlotExistsInSubgraph) { externalInputSlots.Add(edge); } else if (inputSlotExistsInSubgraph) { externalOutputSlots.Add(edge); } } // Find the unique edges coming INTO the graph var uniqueIncomingEdges = externalOutputSlots.GroupBy( edge => edge.outputSlot, edge => edge, (key, edges) => new { slotRef = key, edges = edges.ToList() }); var externalInputNeedingConnection = new List <KeyValuePair <IEdge, AbstractShaderProperty> >(); var amountOfProps = uniqueIncomingEdges.Count(); const int height = 40; const int subtractHeight = 20; var propPos = new Vector2(0, -((amountOfProps / 2) + height) - subtractHeight); foreach (var group in uniqueIncomingEdges) { var sr = group.slotRef; var fromNode = sr.node; var fromSlot = sr.slot; var materialGraph = graphObject.graph; var fromProperty = fromNode is PropertyNode fromPropertyNode ? materialGraph.properties.FirstOrDefault(p => p == fromPropertyNode.property) : null; AbstractShaderProperty prop; switch (fromSlot.concreteValueType) { case ConcreteSlotValueType.Texture2D: prop = new Texture2DShaderProperty(); break; case ConcreteSlotValueType.Texture2DArray: prop = new Texture2DArrayShaderProperty(); break; case ConcreteSlotValueType.Texture3D: prop = new Texture3DShaderProperty(); break; case ConcreteSlotValueType.Cubemap: prop = new CubemapShaderProperty(); break; case ConcreteSlotValueType.Vector4: prop = new Vector4ShaderProperty(); break; case ConcreteSlotValueType.Vector3: prop = new Vector3ShaderProperty(); break; case ConcreteSlotValueType.Vector2: prop = new Vector2ShaderProperty(); break; case ConcreteSlotValueType.Vector1: prop = new Vector1ShaderProperty(); break; case ConcreteSlotValueType.Boolean: prop = new BooleanShaderProperty(); break; case ConcreteSlotValueType.Matrix2: prop = new Matrix2ShaderProperty(); break; case ConcreteSlotValueType.Matrix3: prop = new Matrix3ShaderProperty(); break; case ConcreteSlotValueType.Matrix4: prop = new Matrix4ShaderProperty(); break; case ConcreteSlotValueType.SamplerState: prop = new SamplerStateShaderProperty(); break; case ConcreteSlotValueType.Gradient: prop = new GradientShaderProperty(); break; case ConcreteSlotValueType.VirtualTexture: prop = new VirtualTextureShaderProperty() { // also copy the VT settings over from the original property (if there is one) value = (fromProperty as VirtualTextureShaderProperty)?.value ?? new SerializableVirtualTexture() }; break; default: throw new ArgumentOutOfRangeException(); } prop.displayName = fromProperty != null ? fromProperty.displayName : fromSlot.concreteValueType.ToString(); prop.displayName = GraphUtil.SanitizeName(subGraph.addedInputs.Select(p => p.displayName), "{0} ({1})", prop.displayName); subGraph.AddGraphInput(prop); var propNode = new PropertyNode(); { var drawState = propNode.drawState; drawState.position = new Rect(new Vector2(bounds.xMin - 300f, 0f) + propPos, drawState.position.size); propPos += new Vector2(0, height); propNode.drawState = drawState; } subGraph.AddNode(propNode); propNode.property = prop; foreach (var edge in group.edges) { subGraph.Connect( new SlotReference(propNode, PropertyNode.OutputSlotId), edge.inputSlot); externalInputNeedingConnection.Add(new KeyValuePair <IEdge, AbstractShaderProperty>(edge, prop)); } } var uniqueOutgoingEdges = externalInputSlots.GroupBy( edge => edge.outputSlot, edge => edge, (key, edges) => new { slot = key, edges = edges.ToList() }); var externalOutputsNeedingConnection = new List <KeyValuePair <IEdge, IEdge> >(); foreach (var group in uniqueOutgoingEdges) { var outputNode = subGraph.outputNode as SubGraphOutputNode; AbstractMaterialNode node = group.edges[0].outputSlot.node; MaterialSlot slot = node.FindSlot <MaterialSlot>(group.edges[0].outputSlot.slotId); var slotId = outputNode.AddSlot(slot.concreteValueType); var inputSlotRef = new SlotReference(outputNode, slotId); foreach (var edge in group.edges) { var newEdge = subGraph.Connect(edge.outputSlot, inputSlotRef); externalOutputsNeedingConnection.Add(new KeyValuePair <IEdge, IEdge>(edge, newEdge)); } } if (FileUtilities.WriteShaderGraphToDisk(path, subGraph)) { AssetDatabase.ImportAsset(path); } // Store path for next time if (!pathToOriginSG.Equals(Path.GetDirectoryName(path))) { SessionState.SetString(k_PrevSubGraphPathKey, Path.GetDirectoryName(path)); } else { // Or continue to make it so that next time it will open up in the converted-from SG's directory SessionState.EraseString(k_PrevSubGraphPathKey); } var loadedSubGraph = AssetDatabase.LoadAssetAtPath(path, typeof(SubGraphAsset)) as SubGraphAsset; if (loadedSubGraph == null) { return; } var subGraphNode = new SubGraphNode(); var ds = subGraphNode.drawState; ds.position = new Rect(middle - new Vector2(100f, 150f), Vector2.zero); subGraphNode.drawState = ds; // Add the subgraph into the group if the nodes was all in the same group group var firstNode = copyPasteGraph.GetNodes <AbstractMaterialNode>().FirstOrDefault(); if (firstNode != null && copyPasteGraph.GetNodes <AbstractMaterialNode>().All(x => x.group == firstNode.group)) { subGraphNode.group = firstNode.group; } subGraphNode.asset = loadedSubGraph; graphObject.graph.AddNode(subGraphNode); foreach (var edgeMap in externalInputNeedingConnection) { graphObject.graph.Connect(edgeMap.Key.outputSlot, new SlotReference(subGraphNode, edgeMap.Value.guid.GetHashCode())); } foreach (var edgeMap in externalOutputsNeedingConnection) { graphObject.graph.Connect(new SlotReference(subGraphNode, edgeMap.Value.inputSlot.slotId), edgeMap.Key.inputSlot); } graphObject.graph.RemoveElements( graphView.selection.OfType <IShaderNodeView>().Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(), new IEdge[] {}, new GroupData[] {}, graphView.selection.OfType <StickyNote>().Select(x => x.userData).ToArray()); graphObject.graph.ValidateGraph(); }
public static T Deserialize <T>(JSONSerializedElement item, Dictionary <TypeSerializationInfo, TypeSerializationInfo> remapper, params object[] constructorArgs) where T : class { T instance; if (typeof(T) == typeof(JsonObject) || typeof(T).IsSubclassOf(typeof(JsonObject))) { try { var culture = CultureInfo.CurrentCulture; var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; instance = Activator.CreateInstance(typeof(T), flags, null, constructorArgs, culture) as T; } catch (Exception e) { throw new Exception(string.Format("Could not construct instance of: {0}", typeof(T)), e); } MultiJson.Deserialize(instance as JsonObject, item.JSONnodeData); return(instance); } if (!item.typeInfo.IsValid() || string.IsNullOrEmpty(item.JSONnodeData)) { throw new ArgumentException(string.Format("Can not deserialize {0}, it is invalid", item)); } TypeSerializationInfo info = item.typeInfo; info.fullName = info.fullName.Replace("UnityEngine.MaterialGraph", "UnityEditor.ShaderGraph"); info.fullName = info.fullName.Replace("UnityEngine.Graphing", "UnityEditor.Graphing"); if (remapper != null) { info = DoTypeRemap(info, remapper); } var type = GetTypeFromSerializedString(info); //if type is null but T is an abstract material node, instead we create an unknowntype node if (type == null) { throw new ArgumentException(string.Format("Can not deserialize ({0}), type is invalid", info.fullName)); } try { var culture = CultureInfo.CurrentCulture; var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; instance = Activator.CreateInstance(type, flags, null, constructorArgs, culture) as T; } catch (Exception e) { throw new Exception(string.Format("Could not construct instance of: {0}", type), e); } if (instance != null) { JsonUtility.FromJsonOverwrite(item.JSONnodeData, instance); return(instance); } Debug.Log("UhOh"); return(null); }
public void TestImportAsset(string unityLocalPath, string fullPath) { unityLocalPath = unityLocalPath.Replace("\\", "/"); Debug.Log("Testing file: " + unityLocalPath); // invoke an import AssetDatabase.ImportAsset(unityLocalPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); // double check we can load it up and validate it string fileContents = File.ReadAllText(fullPath); Assert.Greater(fileContents.Length, 0); var graphGuid = AssetDatabase.AssetPathToGUID(unityLocalPath); var messageManager = new MessageManager(); GraphData graphData = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, fileContents); graphData.OnEnable(); graphData.ValidateGraph(); string fileExtension = Path.GetExtension(fullPath).ToLower(); bool isSubgraph = (fileExtension == "shadersubgraph"); if (isSubgraph) { // check that the SubGraphAsset is the same after versioning twice // this is important to ensure we're not importing subgraphs non-deterministically when they are out-of-date on disk AssetDatabase.ImportAsset(unityLocalPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); var subGraph = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(unityLocalPath); var serialized = EditorJsonUtility.ToJson(subGraph); AssetDatabase.ImportAsset(unityLocalPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); var subGraph2 = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(unityLocalPath); var serialized2 = EditorJsonUtility.ToJson(subGraph2); Assert.AreEqual(serialized, serialized2, $"Importing the subgraph {unityLocalPath} twice resulted in different subgraph assets."); } else { // check that the generated shader is the same after versioning twice // this is important to ensure we're not importing shaders non-deterministically when they are out-of-date on disk string fileNameNoExtension = Path.GetFileNameWithoutExtension(fullPath); var generator = new Generator(graphData, graphData.outputNode, GenerationMode.ForReals, fileNameNoExtension, null); string shader = generator.generatedShader; // version again GraphData graphData2 = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData2, fileContents); graphData2.OnEnable(); graphData2.ValidateGraph(); var generator2 = new Generator(graphData2, graphData2.outputNode, GenerationMode.ForReals, fileNameNoExtension, null); string shader2 = generator2.generatedShader; Assert.AreEqual(shader, shader2, $"Importing the graph {unityLocalPath} twice resulted in different generated shaders."); // Texture test won't work on platforms that don't support more than 16 samplers bool isGL = (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore) || (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES2) || (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3); bool isOSX = (Application.platform == RuntimePlatform.OSXEditor) || (Application.platform == RuntimePlatform.OSXPlayer); bool samplersSupported = !(isOSX && isGL); if (!samplersSupported && fullPath.Contains("TextureTest")) { // skip the compile test -- we know this shader won't compile on these platforms } else { // now create a Unity Shader from the string var compiledShader = ShaderUtil.CreateShaderAsset(shader, true); compiledShader.hideFlags = HideFlags.HideAndDontSave; Assert.NotNull(compiledShader); // compile all the shader passes to see if there are any errors var mat = new Material(compiledShader) { hideFlags = HideFlags.HideAndDontSave }; for (int pass = 0; pass < mat.passCount; pass++) { ShaderUtil.CompilePass(mat, pass, true); } } } }
public override void OnImportAsset(AssetImportContext ctx) { var oldShader = AssetDatabase.LoadAssetAtPath <Shader>(ctx.assetPath); if (oldShader != null) { ShaderUtil.ClearShaderMessages(oldShader); } List <PropertyCollector.TextureInfo> configuredTextures; string path = ctx.assetPath; AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = new GraphData { messageManager = new MessageManager(), assetGuid = AssetDatabase.AssetPathToGUID(path) }; MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); Shader shader = null; #if VFX_GRAPH_10_0_0_OR_NEWER if (!graph.isOnlyVFXTarget) #endif { // build the shader text // this will also add Target dependencies into the asset collection var text = GetShaderText(path, out configuredTextures, assetCollection, graph); #if UNITY_2021_1_OR_NEWER // 2021.1 or later is guaranteed to have the new version of this function shader = ShaderUtil.CreateShaderAsset(ctx, text, false); #else // earlier builds of Unity may or may not have it // here we try to invoke the new version via reflection var createShaderAssetMethod = typeof(ShaderUtil).GetMethod( "CreateShaderAsset", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.ExactBinding, null, new Type[] { typeof(AssetImportContext), typeof(string), typeof(bool) }, null); if (createShaderAssetMethod != null) { shader = createShaderAssetMethod.Invoke(null, new Object[] { ctx, text, false }) as Shader; } else { // method doesn't exist in this version of Unity, call old version // this doesn't create dependencies properly, but is the best that we can do shader = ShaderUtil.CreateShaderAsset(text, false); } #endif if (graph.messageManager.nodeMessagesChanged) { foreach (var pair in graph.messageManager.GetNodeMessages()) { var node = graph.GetNodeFromId(pair.Key); MessageManager.Log(node, path, pair.Value.First(), shader); } } EditorMaterialUtility.SetShaderDefaults( shader, configuredTextures.Where(x => x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); EditorMaterialUtility.SetShaderNonModifiableDefaults( shader, configuredTextures.Where(x => !x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => !x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); } UnityEngine.Object mainObject = shader; #if VFX_GRAPH_10_0_0_OR_NEWER ShaderGraphVfxAsset vfxAsset = null; if (graph.hasVFXTarget) { vfxAsset = GenerateVfxShaderGraphAsset(graph); if (mainObject == null) { mainObject = vfxAsset; } else { //Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset vfxAsset.name = Path.GetFileNameWithoutExtension(path); ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset); } } #endif Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon"); ctx.AddObjectToAsset("MainAsset", mainObject, texture); ctx.SetMainObject(mainObject); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); if (metadata == null) { continue; } metadata.hideFlags = HideFlags.HideInHierarchy; ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata); } } var sgMetadata = ScriptableObject.CreateInstance <ShaderGraphMetadata>(); sgMetadata.hideFlags = HideFlags.HideInHierarchy; sgMetadata.assetDependencies = new List <UnityEngine.Object>(); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { sgMetadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { Debug.LogWarning($"Invalid dependency path: {assetPath}", mainObject); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }
public void TestImportAsset(string unityLocalPath, string fullPath) { unityLocalPath = unityLocalPath.Replace("\\", "/"); Debug.Log("Testing file: " + unityLocalPath); // invoke an import AssetDatabase.ImportAsset(unityLocalPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); // double check we can load it up and validate it string fileContents = File.ReadAllText(fullPath); Assert.Greater(fileContents.Length, 0); var graphGuid = AssetDatabase.AssetPathToGUID(unityLocalPath); var messageManager = new MessageManager(); GraphData graphData = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, fileContents); graphData.OnEnable(); graphData.ValidateGraph(); string fileExtension = Path.GetExtension(fullPath).ToLower(); bool isSubgraph = (fileExtension == "shadersubgraph"); if (isSubgraph) { // check that the SubGraphAsset is the same after versioning twice // this is important to ensure we're not importing subgraphs non-deterministically when they are out-of-date on disk AssetDatabase.ImportAsset(unityLocalPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); var subGraph = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(unityLocalPath); var serialized = EditorJsonUtility.ToJson(subGraph); AssetDatabase.ImportAsset(unityLocalPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); var subGraph2 = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(unityLocalPath); var serialized2 = EditorJsonUtility.ToJson(subGraph2); Assert.AreEqual(serialized, serialized2, $"Importing the subgraph {unityLocalPath} twice resulted in different subgraph assets."); } else { // check that the generated shader is the same after versioning twice // this is important to ensure we're not importing shaders non-deterministically when they are out-of-date on disk string fileNameNoExtension = Path.GetFileNameWithoutExtension(fullPath); var generator = new Generator(graphData, graphData.outputNode, GenerationMode.ForReals, fileNameNoExtension, null); string shader = generator.generatedShader; // version again GraphData graphData2 = new GraphData() { assetGuid = graphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData2, fileContents); graphData2.OnEnable(); graphData2.ValidateGraph(); var generator2 = new Generator(graphData2, graphData2.outputNode, GenerationMode.ForReals, fileNameNoExtension, null); string shader2 = generator2.generatedShader; Assert.AreEqual(shader, shader2, $"Importing the graph {unityLocalPath} twice resulted in different generated shaders."); } }
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>(); 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 { var text = GetShaderText(path, out configuredTextures, sourceAssetDependencyPaths, 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>(); 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); } }
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); } }
public override void OnInspectorGUI() { GraphData GetGraphData(AssetImporter importer) { var textGraph = File.ReadAllText(importer.assetPath, Encoding.UTF8); var graphObject = CreateInstance <GraphObject>(); graphObject.hideFlags = HideFlags.HideAndDontSave; bool isSubGraph; var extension = Path.GetExtension(importer.assetPath).Replace(".", ""); switch (extension) { case ShaderGraphImporter.Extension: isSubGraph = false; break; case ShaderGraphImporter.LegacyExtension: isSubGraph = false; break; case ShaderSubGraphImporter.Extension: isSubGraph = true; break; default: throw new Exception($"Invalid file extension {extension}"); } var assetGuid = AssetDatabase.AssetPathToGUID(importer.assetPath); graphObject.graph = new GraphData { assetGuid = assetGuid, isSubGraph = isSubGraph, messageManager = null }; MultiJson.Deserialize(graphObject.graph, textGraph); graphObject.graph.OnEnable(); graphObject.graph.ValidateGraph(); return(graphObject.graph); } if (GUILayout.Button("Open Shader Editor")) { AssetImporter importer = target as AssetImporter; Debug.Assert(importer != null, "importer != null"); ShowGraphEditWindow(importer.assetPath); } using (var horizontalScope = new GUILayout.HorizontalScope("box")) { AssetImporter importer = target as AssetImporter; string assetName = Path.GetFileNameWithoutExtension(importer.assetPath); string path = String.Format("Temp/GeneratedFromGraph-{0}.shader", assetName.Replace(" ", "")); bool alreadyExists = File.Exists(path); bool update = false; bool open = false; if (GUILayout.Button("View Generated Shader")) { update = true; open = true; } if (alreadyExists && GUILayout.Button("Regenerate")) { update = true; } if (update) { var graphData = GetGraphData(importer); var generator = new Generator(graphData, null, GenerationMode.ForReals, assetName, null); if (!GraphUtil.WriteToFile(path, generator.generatedShader)) { open = false; } } if (open) { GraphUtil.OpenFile(path); } } if (Unsupported.IsDeveloperMode()) { if (GUILayout.Button("View Preview Shader")) { AssetImporter importer = target as AssetImporter; string assetName = Path.GetFileNameWithoutExtension(importer.assetPath); string path = String.Format("Temp/GeneratedFromGraph-{0}-Preview.shader", assetName.Replace(" ", "")); var graphData = GetGraphData(importer); var generator = new Generator(graphData, null, GenerationMode.Preview, $"{assetName}-Preview", null); if (GraphUtil.WriteToFile(path, generator.generatedShader)) { GraphUtil.OpenFile(path); } } } if (GUILayout.Button("Copy Shader")) { AssetImporter importer = target as AssetImporter; string assetName = Path.GetFileNameWithoutExtension(importer.assetPath); var graphData = GetGraphData(importer); var generator = new Generator(graphData, null, GenerationMode.ForReals, assetName, null); GUIUtility.systemCopyBuffer = generator.generatedShader; } ApplyRevertGUI(); }