From 9f4d6adab97f926d06e1a18b1623ad6cf121a3e1 Mon Sep 17 00:00:00 2001 From: Chris Tchou Date: Thu, 7 May 2020 10:42:39 -0700 Subject: [PATCH 1/5] WIP : converted to tracking most things by preview instead of node --- .../Editor/Drawing/PreviewManager.cs | 318 +++++++++++------- 1 file changed, 190 insertions(+), 128 deletions(-) diff --git a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs index 45eadfa9671..27ce74c8a55 100644 --- a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs +++ b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs @@ -26,22 +26,23 @@ class PreviewManager : IDisposable Dictionary m_RenderDatas = new Dictionary(); // stores all of the PreviewRendererData, mapped by node object ID PreviewRenderData m_MasterRenderData; // cache ref to preview renderer data for the master node - int m_MaxNodesCompiling = 2; // max preview shaders we want to async compile at once + int m_MaxPreviewsCompiling = 2; // max preview shaders we want to async compile at once // state trackers HashSet m_NodesShaderChanged = new HashSet(); // nodes whose shader code has changed, this node and nodes that read from it are put into NeedRecompile - HashSet m_NodesNeedsRecompile = new HashSet(); // nodes we need to recompile the preview shader - HashSet m_NodesCompiling = new HashSet(); // nodes currently being compiled - HashSet m_NodesToDraw = new HashSet(); // nodes to rebuild the texture for + HashSet m_PreviewsNeedsRecompile = new HashSet(); // previews we need to recompile the preview shader + HashSet m_PreviewsCompiling = new HashSet(); // previews currently being compiled + HashSet m_PreviewsToDraw = new HashSet(); // nodes to rebuild the texture for (because shader compile changed or constant changed) HashSet m_TimedNodes = new HashSet(); // nodes that are dependent on a time node -- i.e. animated -- need to redraw every frame - HashSet m_Blocks = new HashSet(); // all blocks used for previous generation. this includes temporary blocks. bool m_RefreshTimedNodes; // flag to trigger rebuilding the list of timed nodes + HashSet m_MasterNodePreviewBlocks = new HashSet(); // all blocks used for the most recent master node preview generation. this includes temporary blocks. + PreviewSceneResources m_SceneResources; Texture2D m_ErrorTexture; Vector2? m_NewMasterPreviewSize; - Identifier m_MasterIdentifier; + const AbstractMaterialNode kMasterProxyNode = null; public PreviewRenderData masterRenderData { @@ -55,7 +56,6 @@ public PreviewManager(GraphData graph, MessageManager messenger) m_Messenger = messenger; m_ErrorTexture = GenerateFourSquare(Color.magenta, Color.black); m_SceneResources = new PreviewSceneResources(); - m_MasterIdentifier = new Identifier(0); foreach (var node in m_Graph.GetNodes()) AddPreview(node); @@ -83,7 +83,7 @@ public void ResizeMasterPreview(Vector2 newSize) public PreviewRenderData GetPreviewRenderData(AbstractMaterialNode node) { PreviewRenderData result = null; - if(node == null || node is SubGraphOutputNode) + if (node == kMasterProxyNode || node is BlockNode || node == m_Graph.outputNode) // TODO: node is SubGraphOutputNode ?? is there ever more than one?: Should be caught by m_Graph.outputNode... { result = m_MasterRenderData; } @@ -97,30 +97,29 @@ public PreviewRenderData GetPreviewRenderData(AbstractMaterialNode node) void AddMasterPreview() { - var renderData = new PreviewRenderData + m_MasterRenderData = new PreviewRenderData { + previewName = m_Graph.outputNode?.name ?? "Master", renderTexture = - new RenderTexture(200, 200, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) + new RenderTexture(400, 400, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) { hideFlags = HideFlags.HideAndDontSave }, previewMode = PreviewMode.Preview3D, }; - m_MasterRenderData = renderData; - renderData.renderTexture.width = renderData.renderTexture.height = 400; - renderData.renderTexture.Create(); + m_MasterRenderData.renderTexture.Create(); var shaderData = new PreviewShaderData { - node = m_Graph.outputNode, + node = m_Graph.outputNode, // can be null, which means to generate with active Target passesCompiling = 0, isOutOfDate = true, hasError = false, }; - renderData.shaderData = shaderData; + m_MasterRenderData.shaderData = shaderData; - m_NodesNeedsRecompile.Add(m_Graph.outputNode); + m_PreviewsNeedsRecompile.Add(m_MasterRenderData); m_RefreshTimedNodes = true; } @@ -129,20 +128,26 @@ public void UpdateMasterPreview(ModificationScope scope) if (scope == ModificationScope.Topological || scope == ModificationScope.Graph) { - m_NodesNeedsRecompile.Add(null); + // mark the master preview for recompile if it exists + // if not, no need to do it here, because it is always marked for recompile on creation + if (m_MasterRenderData != null) + m_PreviewsNeedsRecompile.Add(m_MasterRenderData); m_RefreshTimedNodes = true; } else if (scope == ModificationScope.Node) { - m_NodesToDraw.Add(null); + m_PreviewsToDraw.Add(m_MasterRenderData); } } void AddPreview(AbstractMaterialNode node) { - if(node is BlockNode) + Assert.IsNotNull(node); + + if (node is BlockNode) { node.RegisterCallback(OnNodeModified); + UpdateMasterPreview(ModificationScope.Topological); return; } @@ -153,6 +158,7 @@ void AddPreview(AbstractMaterialNode node) var renderData = new PreviewRenderData { + previewName = node.name ?? "UNNAMED NODE", renderTexture = new RenderTexture(200, 200, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) { @@ -162,6 +168,9 @@ void AddPreview(AbstractMaterialNode node) renderData.renderTexture.Create(); + if (node == null) + Debug.Log("node = null"); + var shaderData = new PreviewShaderData { node = node, @@ -178,22 +187,27 @@ void AddPreview(AbstractMaterialNode node) { m_RefreshTimedNodes = true; } - - m_NodesNeedsRecompile.Add(node); + + m_PreviewsNeedsRecompile.Add(renderData); } void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) { + Assert.IsFalse(node == null); + + PreviewRenderData preview = GetPreviewRenderData(node); + if (scope == ModificationScope.Topological || scope == ModificationScope.Graph) { - m_NodesShaderChanged.Add(node); + m_NodesShaderChanged.Add(node); // this will trigger m_PreviewsShaderChanged downstream m_RefreshTimedNodes = true; } else if (scope == ModificationScope.Node) { // if we only changed a constant on the node, we don't have to recompile the shader for it, just re-render it with the updated constant - m_NodesToDraw.Add(node); + // should instead flag m_NodesConstantChanged + m_PreviewsToDraw.Add(preview); } } @@ -284,35 +298,18 @@ public void HandleGraphChanges() { foreach (var node in m_Graph.removedNodes) { - if(node is BlockNode) - { - node.UnregisterCallback(OnNodeModified); - UpdateMasterPreview(ModificationScope.Topological); - continue; - } - - DestroyPreview(node.objectId); + DestroyPreview(node); m_RefreshTimedNodes = true; } // remove the nodes from the state trackers m_NodesShaderChanged.ExceptWith(m_Graph.removedNodes); - m_NodesNeedsRecompile.ExceptWith(m_Graph.removedNodes); - m_NodesCompiling.ExceptWith(m_Graph.removedNodes); - m_NodesToDraw.ExceptWith(m_Graph.removedNodes); m_TimedNodes.ExceptWith(m_Graph.removedNodes); m_Messenger.ClearNodesFromProvider(this, m_Graph.removedNodes); foreach (var node in m_Graph.addedNodes) { - if(node is BlockNode) - { - node.RegisterCallback(OnNodeModified); - UpdateMasterPreview(ModificationScope.Topological); - continue; - } - AddPreview(node); m_RefreshTimedNodes = true; } @@ -356,19 +353,25 @@ void CollectPreviewProperties() // we only collect properties from nodes upstream of something we want to draw // TODO: we could go a step farther and only collect properties from nodes we know have changed their value // but that's not something we currently track... - PropagateNodes(m_NodesToDraw, PropagationDirection.Upstream, tempCollectNodes); - if(m_NodesToDraw.Contains(null)) + // collect properties from all nodes we need to draw + tempCollectNodes.UnionWith(m_PreviewsToDraw.Select(p => p.shaderData.node)); + + // if we're collecting for master preview, also collect from all of the blocks and their upstream nodes + if (m_PreviewsToDraw.Contains(m_MasterRenderData) || tempCollectNodes.Contains(null)) { - foreach(var block in m_Blocks) - { + tempCollectNodes.Remove(null); + foreach (var block in m_MasterNodePreviewBlocks) tempCollectNodes.Add(block); - } } + // and also collect from all nodes upstream + PropagateNodes(tempCollectNodes, PropagationDirection.Upstream, tempCollectNodes); + foreach (var propNode in tempCollectNodes) propNode.CollectPreviewMaterialProperties(tempPreviewProps); + // also grab all graph properties foreach (var prop in m_Graph.properties) tempPreviewProps.Add(prop.GetPreviewMaterialProperty()); @@ -390,10 +393,20 @@ public void RenderPreviews(bool requestShaders = true) UpdateTimedNodeList(); - PropagateNodes(m_NodesToDraw, PropagationDirection.Downstream, m_NodesToDraw); - m_NodesToDraw.UnionWith(m_TimedNodes); + // TODO : I don't think we need to propagate draws downstream... ? + // We need to propagate constant changes downstream, which then invoke previews to redraw... + // PropagateNodes(m_PreviewsToDraw, PropagationDirection.Downstream, m_PreviewsToDraw); + { + var nodesToDraw = new HashSet(); + nodesToDraw.UnionWith(m_PreviewsToDraw.Select(p => p.shaderData.node)); + nodesToDraw.Remove(null); + PropagateNodes(nodesToDraw, PropagationDirection.Downstream, nodesToDraw); + + nodesToDraw.UnionWith(m_TimedNodes); // TODO: better to track timed previews + m_PreviewsToDraw.UnionWith(nodesToDraw.Select(n => GetPreviewRenderData(n))); + } - if (m_NodesToDraw.Count <= 0) + if (m_PreviewsToDraw.Count <= 0) return; CollectPreviewProperties(); @@ -404,21 +417,22 @@ public void RenderPreviews(bool requestShaders = true) using (PrepareNodesMarker.Auto()) { - foreach (var node in m_NodesToDraw) + foreach (var preview in m_PreviewsToDraw) { - if (node == null || node is BlockNode || node is SubGraphOutputNode) - { - renderMasterPreview = true; + var node = preview.shaderData.node; +// if (preview == null || node == m_Graph.outputNode || node is BlockNode || node is SubGraphOutputNode) +// { +// renderMasterPreview = true; +// continue; +// } + + if ((node != null) && (!node.hasPreview || !node.previewExpanded)) continue; - } - if (!node.hasPreview || !node.previewExpanded) + var renderData = preview; // GetPreviewRenderData(node); + if (renderData == null) // non-active output nodes can have NULL render data (no preview) continue; - var renderData = GetPreviewRenderData(node); - if (renderData == null) // non-active output nodes can have NULL render data (no preview) - continue; - if ((renderData.shaderData.shader == null) || (renderData.shaderData.mat == null)) { // avoid calling NotifyPreviewChanged repeatedly @@ -483,7 +497,7 @@ public void RenderPreviews(bool requestShaders = true) masterRenderData.texture = masterRenderData.renderTexture; m_NewMasterPreviewSize = null; } - var mesh = m_Graph.previewData.serializedMesh.mesh ? m_Graph.previewData.serializedMesh.mesh : m_SceneResources.sphere; + var mesh = m_Graph.previewData.serializedMesh.mesh ? m_Graph.previewData.serializedMesh.mesh : m_SceneResources.sphere; var previewTransform = Matrix4x4.Rotate(m_Graph.previewData.rotation); var scale = m_Graph.previewData.scale; previewTransform *= Matrix4x4.Scale(scale * Vector3.one * (Vector3.one).magnitude / mesh.bounds.size.magnitude); @@ -502,7 +516,7 @@ public void RenderPreviews(bool requestShaders = true) if (renderMasterPreview) masterRenderData.NotifyPreviewChanged(); - m_NodesToDraw.Clear(); + m_PreviewsToDraw.Clear(); } } @@ -512,23 +526,34 @@ void ProcessCompletedShaderCompilations() { // Check for shaders that finished compiling and set them to redraw using (ProcessCompletedShaderCompilationsMarker.Auto()) - using (var nodesCompiled = PooledHashSet.Get()) + using (var previewsCompiled = PooledHashSet.Get()) { - foreach (var node in m_NodesCompiling) + foreach (var preview in m_PreviewsCompiling) { - PreviewRenderData renderData = GetPreviewRenderData(node); + { + var node = preview.shaderData.node; + Assert.IsFalse(node is BlockNode); + } + + PreviewRenderData renderData = preview; PreviewShaderData shaderData = renderData.shaderData; - Assert.IsTrue(shaderData.passesCompiling > 0); + + // Assert.IsTrue(shaderData.passesCompiling > 0); + if (shaderData.passesCompiling <= 0) + { + Debug.Log("Zero Passes: " + preview.previewName + " (" + shaderData.passesCompiling + " passes, " + renderData.shaderData.mat.passCount + " mat passes)"); + } if (shaderData.passesCompiling != renderData.shaderData.mat.passCount) { // attempt to re-kick the compilation a few times + Debug.Log("Rekicking Compiling: " + preview.previewName + " (" + shaderData.passesCompiling + " passes, " + renderData.shaderData.mat.passCount + " mat passes)"); compileFailRekicks++; if (compileFailRekicks <= 3) { renderData.shaderData.passesCompiling = 0; - m_NodesNeedsRecompile.Add(node); - nodesCompiled.Add(node); + previewsCompiled.Add(renderData); + m_PreviewsNeedsRecompile.Add(renderData); continue; } else if (compileFailRekicks == 4) @@ -550,6 +575,7 @@ void ProcessCompletedShaderCompilations() if (!allPassesCompiled) { + // keep waiting return; } @@ -559,14 +585,14 @@ void ProcessCompletedShaderCompilations() renderData.shaderData.isOutOfDate = false; CheckForErrors(renderData.shaderData); - nodesCompiled.Add(renderData.shaderData.node); + previewsCompiled.Add(renderData); - if(renderData == m_MasterRenderData) + if (renderData == m_MasterRenderData) { // Process preview materials - foreach(var target in m_Graph.activeTargets) + foreach (var target in m_Graph.activeTargets) { - if(target.IsActive()) + if (target.IsActive()) { target.ProcessPreviewMaterial(renderData.shaderData.mat); } @@ -575,10 +601,10 @@ void ProcessCompletedShaderCompilations() } // removed compiled nodes from compiling list - m_NodesCompiling.ExceptWith(nodesCompiled); + m_PreviewsCompiling.ExceptWith(previewsCompiled); // and add them to the draw list - m_NodesToDraw.UnionWith(nodesCompiled); + m_PreviewsToDraw.UnionWith(previewsCompiled); } } @@ -587,67 +613,73 @@ void KickOffShaderCompilations() { // Start compilation for nodes that need to recompile using (KickOffShaderCompilationsMarker.Auto()) - using (var nodesToCompile = PooledHashSet.Get()) + using (var previewsToCompile = PooledHashSet.Get()) { // master node compile is first in the priority list, as it takes longer than the other previews - if ((m_NodesCompiling.Count + nodesToCompile.Count < m_MaxNodesCompiling) && - ((m_NodesNeedsRecompile.Contains(m_Graph.outputNode) && !m_NodesCompiling.Contains(m_Graph.outputNode)) || - (m_NodesNeedsRecompile.Any(x => x is BlockNode) && !m_NodesCompiling.Any(x => x is BlockNode))) && + if ((m_PreviewsCompiling.Count + previewsToCompile.Count < m_MaxPreviewsCompiling) && ((Shader.globalRenderPipeline != null) && (Shader.globalRenderPipeline.Length > 0))) // master node requires an SRP { - var renderData = GetPreviewRenderData(m_MasterRenderData.shaderData.node); - Assert.IsTrue(renderData != null); - nodesToCompile.Add(m_Graph.outputNode); + if (m_MasterRenderData.shaderData.node != m_Graph.outputNode) + Debug.Log("MasterRenderData mismatch! " + m_MasterRenderData.shaderData.node + " != " + m_Graph.outputNode); + + if (m_PreviewsNeedsRecompile.Contains(m_MasterRenderData) && + !m_PreviewsCompiling.Contains(m_MasterRenderData)) + { + Assert.IsTrue(m_MasterRenderData != null); + previewsToCompile.Add(m_MasterRenderData); + m_PreviewsNeedsRecompile.Remove(m_MasterRenderData); + } } // add each node to compile list if it needs a preview, is not already compiling, and we have room // (we don't want to double kick compiles, so wait for the first one to get back before kicking another) - for(int i = 0; i < m_NodesNeedsRecompile.Count(); i++) + for(int i = 0; i < m_PreviewsNeedsRecompile.Count(); i++) { - var node = m_NodesNeedsRecompile.ElementAt(i); + if (m_PreviewsCompiling.Count + previewsToCompile.Count >= m_MaxPreviewsCompiling) + break; - // Remove BlockNode instances as null gets added to nodesToCompile - if(node == null || node is BlockNode) - { - m_NodesNeedsRecompile.Remove(node); + var preview = m_PreviewsNeedsRecompile.ElementAt(i); + if (preview == m_MasterRenderData) // handled specially above continue; - } - if (m_NodesCompiling.Count + nodesToCompile.Count >= m_MaxNodesCompiling) - break; + var node = preview.shaderData.node; + Assert.IsFalse((node == null) || (node is BlockNode)); - if (node.hasPreview && node.previewExpanded && !m_NodesCompiling.Contains(node)) + if (node.hasPreview && node.previewExpanded && !m_PreviewsCompiling.Contains(preview)) { - var renderData = GetPreviewRenderData(node); - if (renderData == null) // non-active output nodes can have NULL render data (no preview) - continue; - - nodesToCompile.Add(node); + previewsToCompile.Add(preview); } } // remove the selected nodes from the recompile list - m_NodesNeedsRecompile.ExceptWith(nodesToCompile); + m_PreviewsNeedsRecompile.ExceptWith(previewsToCompile); // Reset error states for the UI, the shader, and all render data for nodes we're recompiling - m_Messenger.ClearNodesFromProvider(this, nodesToCompile); + var nodesToCompile = previewsToCompile.Select(x => x.shaderData.node); + m_Messenger.ClearNodesFromProvider(this, nodesToCompile); // not sure if it needs notification for BlockNodes when master rebuilds? // Force async compile on var wasAsyncAllowed = ShaderUtil.allowAsyncCompilation; ShaderUtil.allowAsyncCompilation = true; // kick async compiles for all nodes in m_NodeToCompile - foreach (var node in nodesToCompile) + foreach (var preview in previewsToCompile) { - if (node is BlockNode || node == null || node is SubGraphOutputNode) + if (preview == m_MasterRenderData) { UpdateMasterNodeShader(); continue; } + var node = preview.shaderData.node; + + + if (node == null) + Debug.Log("ERROR: null node preview compilation for node: " + preview.previewName); + Assert.IsFalse(!node.hasPreview && !(node is SubGraphOutputNode)); - var renderData = GetPreviewRenderData(node); + var renderData = preview; // GetPreviewRenderData(node); // Get shader code and compile var generator = new Generator(node.owner, node, GenerationMode.Preview, $"hidden/preview/{node.GetVariableNameForNode()}"); @@ -683,23 +715,32 @@ void UpdateShaders() if (m_NodesShaderChanged.Count > 0) { - // nodes with shader changes cause all downstream nodes to need recompilation - PropagateNodes(m_NodesShaderChanged, PropagationDirection.Downstream, m_NodesNeedsRecompile); - m_NodesShaderChanged.Clear(); + // nodes with shader changes cause all downstream nodes to need preview recompilation + using (var nodesToRecompile = PooledHashSet.Get()) + { + PropagateNodes(m_NodesShaderChanged, PropagationDirection.Downstream, nodesToRecompile); + foreach (var node in nodesToRecompile) + { + var preview = GetPreviewRenderData(node); + if (preview != null) // non-active output nodes can have NULL render data (no preview) + m_PreviewsNeedsRecompile.Add(preview); + } + m_NodesShaderChanged.Clear(); + } } // if there's nothing to update, or if too many nodes are still compiling, then just return - if ((m_NodesNeedsRecompile.Count == 0) || (m_NodesCompiling.Count >= m_MaxNodesCompiling)) + if ((m_PreviewsNeedsRecompile.Count == 0) || (m_PreviewsCompiling.Count >= m_MaxPreviewsCompiling)) return; - // flag all nodes in m_NodesNeedRecompile as having out of date textures, and redraw them - foreach (var node in m_NodesNeedsRecompile) + // flag all nodes in m_PreviewsNeedsRecompile as having out of date textures, and redraw them + foreach (var preview in m_PreviewsNeedsRecompile) { - PreviewRenderData previewRendererData = GetPreviewRenderData(node); - if ((previewRendererData != null) && !previewRendererData.shaderData.isOutOfDate) + Assert.IsNotNull(preview); + if (!preview.shaderData.isOutOfDate) { - previewRendererData.shaderData.isOutOfDate = true; - previewRendererData.NotifyPreviewChanged(); + preview.shaderData.isOutOfDate = true; + preview.NotifyPreviewChanged(); } } @@ -735,12 +776,17 @@ void BeginCompile(PreviewRenderData renderData, string shaderStr) shaderData.mat = new Material(shaderData.shader) { hideFlags = HideFlags.HideAndDontSave }; } - shaderData.passesCompiling = shaderData.mat.passCount; - for (var i = 0; i < shaderData.mat.passCount; i++) + if (shaderData.mat.passCount <= 0) + Debug.Log("WTF Zero Passes ON COMPILE: " + shaderData.node.name + " (" + shaderData.passesCompiling + " passes, " + renderData.shaderData.mat.passCount + " mat passes)"); + else { - ShaderUtil.CompilePass(shaderData.mat, i); + shaderData.passesCompiling = shaderData.mat.passCount; + for (var i = 0; i < shaderData.mat.passCount; i++) + { + ShaderUtil.CompilePass(shaderData.mat, i); + } + m_PreviewsCompiling.Add(renderData); } - m_NodesCompiling.Add(shaderData.node); } } @@ -863,10 +909,10 @@ void UpdateMasterNodeShader() // Blocks from the generation include those temporarily created for missing stack blocks // We need to hold on to these to set preview property values during CollectShaderProperties - m_Blocks.Clear(); + m_MasterNodePreviewBlocks.Clear(); foreach(var block in generator.blocks) { - m_Blocks.Add(block); + m_MasterNodePreviewBlocks.Add(block); } } @@ -905,13 +951,28 @@ void DestroyRenderData(PreviewRenderData renderData) renderData.shaderData.node.UnregisterCallback(OnNodeModified); } - void DestroyPreview(string nodeId) + void DestroyPreview(AbstractMaterialNode node) { + string nodeId = node.objectId; + + if (node is BlockNode) + { + // block nodes don't have preview render data + Assert.IsFalse(m_RenderDatas.ContainsKey(node.objectId)); + node.UnregisterCallback(OnNodeModified); + UpdateMasterPreview(ModificationScope.Topological); + return; + } + if (!m_RenderDatas.TryGetValue(nodeId, out var renderData)) { return; } + m_PreviewsNeedsRecompile.Remove(renderData); + m_PreviewsCompiling.Remove(renderData); + m_PreviewsToDraw.Remove(renderData); + DestroyRenderData(renderData); m_RenderDatas.Remove(nodeId); } @@ -950,21 +1011,22 @@ public void Dispose() class PreviewShaderData { - public AbstractMaterialNode node { get; set; } - public Shader shader { get; set; } - public Material mat { get; set; } - public string shaderString { get; set; } - public int passesCompiling { get; set; } - public bool isOutOfDate { get; set; } - public bool hasError { get; set; } + public AbstractMaterialNode node; + public Shader shader; + public Material mat; + public string shaderString; + public int passesCompiling; + public bool isOutOfDate; + public bool hasError; } class PreviewRenderData { - public PreviewShaderData shaderData { get; set; } - public RenderTexture renderTexture { get; set; } - public Texture texture { get; set; } - public PreviewMode previewMode { get; set; } + public string previewName; + public PreviewShaderData shaderData; + public RenderTexture renderTexture; + public Texture texture; + public PreviewMode previewMode; public OnPreviewChanged onPreviewChanged; public void NotifyPreviewChanged() From 710969800e803a5a1ba08f1812e7211c83654eb4 Mon Sep 17 00:00:00 2001 From: Chris Tchou Date: Thu, 7 May 2020 15:37:31 -0700 Subject: [PATCH 2/5] Fix for softlock when a non-compiling node exists on a graph --- com.unity.shadergraph/Editor/Drawing/PreviewManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs index 27ce74c8a55..546d543da0d 100644 --- a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs +++ b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs @@ -655,7 +655,9 @@ void KickOffShaderCompilations() m_PreviewsNeedsRecompile.ExceptWith(previewsToCompile); // Reset error states for the UI, the shader, and all render data for nodes we're recompiling - var nodesToCompile = previewsToCompile.Select(x => x.shaderData.node); + var nodesToCompile = new HashSet(); + nodesToCompile.UnionWith(previewsToCompile.Select(x => x.shaderData.node)); + nodesToCompile.Remove(null); m_Messenger.ClearNodesFromProvider(this, nodesToCompile); // not sure if it needs notification for BlockNodes when master rebuilds? // Force async compile on From e8d0ba9c733c99f1228e057497e698b220681748 Mon Sep 17 00:00:00 2001 From: Chris Tchou Date: Thu, 7 May 2020 18:25:43 -0700 Subject: [PATCH 3/5] WIP : Fixed issues with Master Preview transform Optimized property collection and timed node treatment --- .../Editor/Drawing/PreviewManager.cs | 133 ++++++++++-------- 1 file changed, 75 insertions(+), 58 deletions(-) diff --git a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs index 546d543da0d..7acc9597f59 100644 --- a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs +++ b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs @@ -30,11 +30,13 @@ class PreviewManager : IDisposable // state trackers HashSet m_NodesShaderChanged = new HashSet(); // nodes whose shader code has changed, this node and nodes that read from it are put into NeedRecompile + HashSet m_NodesPropertyChanged = new HashSet(); // nodes whose property values have changed, the properties will need to be updated and all nodes that use that property re-rendered + HashSet m_PreviewsNeedsRecompile = new HashSet(); // previews we need to recompile the preview shader HashSet m_PreviewsCompiling = new HashSet(); // previews currently being compiled - HashSet m_PreviewsToDraw = new HashSet(); // nodes to rebuild the texture for (because shader compile changed or constant changed) - HashSet m_TimedNodes = new HashSet(); // nodes that are dependent on a time node -- i.e. animated -- need to redraw every frame - bool m_RefreshTimedNodes; // flag to trigger rebuilding the list of timed nodes + HashSet m_PreviewsToDraw = new HashSet(); // previews to re-render the texture (either because shader compile changed or property changed) + HashSet m_TimedPreviews = new HashSet(); // previews that are dependent on a time node -- i.e. animated / need to redraw every frame + bool m_RefreshTimedNodes; // flag to trigger rebuilding the list of timed nodes. ANY topological change should trigger this HashSet m_MasterNodePreviewBlocks = new HashSet(); // all blocks used for the most recent master node preview generation. this includes temporary blocks. @@ -99,7 +101,7 @@ void AddMasterPreview() { m_MasterRenderData = new PreviewRenderData { - previewName = m_Graph.outputNode?.name ?? "Master", + previewName = "Master:" + (m_Graph.outputNode?.name ?? ""), renderTexture = new RenderTexture(400, 400, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) { @@ -151,10 +153,10 @@ void AddPreview(AbstractMaterialNode node) return; } - if (node is SubGraphOutputNode && masterRenderData != null) - { - return; - } +// if (node is SubGraphOutputNode && masterRenderData != null) +// { +// return; +// } var renderData = new PreviewRenderData { @@ -168,9 +170,6 @@ void AddPreview(AbstractMaterialNode node) renderData.renderTexture.Create(); - if (node == null) - Debug.Log("node = null"); - var shaderData = new PreviewShaderData { node = node, @@ -189,14 +188,13 @@ void AddPreview(AbstractMaterialNode node) } m_PreviewsNeedsRecompile.Add(renderData); + m_NodesPropertyChanged.Add(node); } void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) { Assert.IsFalse(node == null); - PreviewRenderData preview = GetPreviewRenderData(node); - if (scope == ModificationScope.Topological || scope == ModificationScope.Graph) { @@ -207,7 +205,7 @@ void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) { // if we only changed a constant on the node, we don't have to recompile the shader for it, just re-render it with the updated constant // should instead flag m_NodesConstantChanged - m_PreviewsToDraw.Add(preview); + m_NodesPropertyChanged.Add(node); } } @@ -304,7 +302,7 @@ public void HandleGraphChanges() // remove the nodes from the state trackers m_NodesShaderChanged.ExceptWith(m_Graph.removedNodes); - m_TimedNodes.ExceptWith(m_Graph.removedNodes); + m_NodesPropertyChanged.ExceptWith(m_Graph.removedNodes); m_Messenger.ClearNodesFromProvider(this, m_Graph.removedNodes); @@ -317,7 +315,7 @@ public void HandleGraphChanges() foreach (var edge in m_Graph.removedEdges) { var node = edge.inputSlot.node; - if(node is BlockNode) + if ((node is BlockNode) || (node is SubGraphOutputNode)) { UpdateMasterPreview(ModificationScope.Topological); continue; @@ -331,7 +329,7 @@ public void HandleGraphChanges() var node = edge.inputSlot.node; if(node != null) { - if(node is BlockNode) + if ((node is BlockNode) || (node is SubGraphOutputNode)) { UpdateMasterPreview(ModificationScope.Topological); continue; @@ -344,31 +342,35 @@ public void HandleGraphChanges() } private static readonly ProfilerMarker CollectPreviewPropertiesMarker = new ProfilerMarker("CollectPreviewProperties"); - void CollectPreviewProperties() + void CollectPreviewProperties(IEnumerable nodesToCollect) { using (CollectPreviewPropertiesMarker.Auto()) - using (var tempCollectNodes = PooledHashSet.Get()) + // using (var tempCollectNodes = PooledHashSet.Get()) using (var tempPreviewProps = PooledList.Get()) { - // we only collect properties from nodes upstream of something we want to draw - // TODO: we could go a step farther and only collect properties from nodes we know have changed their value - // but that's not something we currently track... + // we collect ALL properties from nodes upstream of something we want to draw + + // we could only bother to collect from nodes tagged as "m_NodesPropertyChanged" + // but we would also have to figure out how to fully populate Material properties on newly created nodes + // (shared MaterialPropertyBlock properties would be fine...) // collect properties from all nodes we need to draw - tempCollectNodes.UnionWith(m_PreviewsToDraw.Select(p => p.shaderData.node)); + // tempCollectNodes.UnionWith(m_PreviewsToDraw.Select(p => p.shaderData.node)); // if we're collecting for master preview, also collect from all of the blocks and their upstream nodes + /* if (m_PreviewsToDraw.Contains(m_MasterRenderData) || tempCollectNodes.Contains(null)) { tempCollectNodes.Remove(null); foreach (var block in m_MasterNodePreviewBlocks) tempCollectNodes.Add(block); } + */ // and also collect from all nodes upstream - PropagateNodes(tempCollectNodes, PropagationDirection.Upstream, tempCollectNodes); + // PropagateNodes(tempCollectNodes, PropagationDirection.Upstream, tempCollectNodes); - foreach (var propNode in tempCollectNodes) + foreach (var propNode in nodesToCollect) propNode.CollectPreviewMaterialProperties(tempPreviewProps); // also grab all graph properties @@ -387,52 +389,57 @@ public void RenderPreviews(bool requestShaders = true) using (RenderPreviewsMarker.Auto()) using (var renderList2D = PooledList.Get()) using (var renderList3D = PooledList.Get()) + using (var nodesToDraw = PooledHashSet.Get()) { if (requestShaders) UpdateShaders(); UpdateTimedNodeList(); - // TODO : I don't think we need to propagate draws downstream... ? - // We need to propagate constant changes downstream, which then invoke previews to redraw... - // PropagateNodes(m_PreviewsToDraw, PropagationDirection.Downstream, m_PreviewsToDraw); + if (m_NodesPropertyChanged.Count > 0) { - var nodesToDraw = new HashSet(); - nodesToDraw.UnionWith(m_PreviewsToDraw.Select(p => p.shaderData.node)); - nodesToDraw.Remove(null); - PropagateNodes(nodesToDraw, PropagationDirection.Downstream, nodesToDraw); + // all nodes downstream of a changed property must be redrawn (to display the updated the property value) + PropagateNodes(m_NodesPropertyChanged, PropagationDirection.Downstream, nodesToDraw); - nodesToDraw.UnionWith(m_TimedNodes); // TODO: better to track timed previews - m_PreviewsToDraw.UnionWith(nodesToDraw.Select(n => GetPreviewRenderData(n))); + // master node won't get picked up by the propagation + // but if any block nodes were picked up, flag master instead + if (nodesToDraw.RemoveWhere(n => n is BlockNode) > 0) + m_PreviewsToDraw.Add(m_MasterRenderData); } + CollectPreviewProperties(m_NodesPropertyChanged); + m_NodesPropertyChanged.Clear(); + + // timed nodes change every frame, so must be drawn + // (m_TimedNodes has been pre-propagated downstream) + // HOWEVER they do not need to collect properties. (the only property changing is time..) + m_PreviewsToDraw.UnionWith(m_TimedPreviews); + + m_PreviewsToDraw.UnionWith(nodesToDraw.Select(n => GetPreviewRenderData(n))); + m_PreviewsToDraw.Remove(null); + if (m_PreviewsToDraw.Count <= 0) return; - CollectPreviewProperties(); - var time = Time.realtimeSinceStartup; var timeParameters = new Vector4(time, Mathf.Sin(time), Mathf.Cos(time), 0.0f); - bool renderMasterPreview = false; + m_SharedPreviewPropertyBlock.SetVector("_TimeParameters", timeParameters); + bool renderMasterPreview = false; using (PrepareNodesMarker.Auto()) { foreach (var preview in m_PreviewsToDraw) { - var node = preview.shaderData.node; -// if (preview == null || node == m_Graph.outputNode || node is BlockNode || node is SubGraphOutputNode) -// { -// renderMasterPreview = true; -// continue; -// } - - if ((node != null) && (!node.hasPreview || !node.previewExpanded)) + if (preview == null) continue; - var renderData = preview; // GetPreviewRenderData(node); - if (renderData == null) // non-active output nodes can have NULL render data (no preview) + // early out if the node doesn't have a preview expanded + var node = preview.shaderData.node; + if ((node != null) && (!node.hasPreview || !node.previewExpanded)) continue; + // check that we've got shaders and materials generated + var renderData = preview; if ((renderData.shaderData.shader == null) || (renderData.shaderData.mat == null)) { // avoid calling NotifyPreviewChanged repeatedly @@ -444,8 +451,6 @@ public void RenderPreviews(bool requestShaders = true) continue; } - renderData.shaderData.mat.SetVector("_TimeParameters", timeParameters); - if (renderData.shaderData.hasError) { renderData.texture = m_ErrorTexture; @@ -453,7 +458,10 @@ public void RenderPreviews(bool requestShaders = true) continue; } - if (renderData.previewMode == PreviewMode.Preview2D) + // categorize what kind of render it is + if (node == kMasterProxyNode) + renderMasterPreview = true; + else if (renderData.previewMode == PreviewMode.Preview2D) renderList2D.Add(renderData); else renderList3D.Add(renderData); @@ -486,7 +494,7 @@ public void RenderPreviews(bool requestShaders = true) foreach (var renderData in renderList3D) RenderPreview(renderData, m_SceneResources.sphere, Matrix4x4.identity); - if (renderMasterPreview && masterRenderData != null && masterRenderData.shaderData.mat != null) + if (renderMasterPreview) { if (m_NewMasterPreviewSize.HasValue) { @@ -603,7 +611,7 @@ void ProcessCompletedShaderCompilations() // removed compiled nodes from compiling list m_PreviewsCompiling.ExceptWith(previewsCompiled); - // and add them to the draw list + // and add them to the draw list to display updated shader (note this will only redraw specifically this node, not any others) m_PreviewsToDraw.UnionWith(previewsCompiled); } } @@ -717,7 +725,8 @@ void UpdateShaders() if (m_NodesShaderChanged.Count > 0) { - // nodes with shader changes cause all downstream nodes to need preview recompilation + // nodes with shader changes cause all downstream nodes to need recompilation + // (since they presumably include the code for these nodes) using (var nodesToRecompile = PooledHashSet.Get()) { PropagateNodes(m_NodesShaderChanged, PropagationDirection.Downstream, nodesToRecompile); @@ -799,13 +808,20 @@ void UpdateTimedNodeList() return; using (UpdateTimedNodeListMarker.Auto()) + using (var timedNodes = PooledHashSet.Get()) { - m_TimedNodes.Clear(); - foreach (var timeNode in m_Graph.GetNodes().Where(node => node.RequiresTime())) + timedNodes.UnionWith(m_Graph.GetNodes().Where(n => n.RequiresTime())); + + // timed nodes are pre-propagated downstream, to reduce amount of propagation we have to do per frame + PropagateNodes(timedNodes, PropagationDirection.Downstream, timedNodes); + + m_TimedPreviews.Clear(); + foreach (var node in timedNodes) { - m_TimedNodes.Add(timeNode); + var preview = GetPreviewRenderData(node); + if (preview != null) + m_TimedPreviews.Add(preview); } - PropagateNodes(m_TimedNodes, PropagationDirection.Downstream, m_TimedNodes); m_RefreshTimedNodes = false; } @@ -957,7 +973,7 @@ void DestroyPreview(AbstractMaterialNode node) { string nodeId = node.objectId; - if (node is BlockNode) + if ((node is BlockNode) || (node is SubGraphOutputNode)) { // block nodes don't have preview render data Assert.IsFalse(m_RenderDatas.ContainsKey(node.objectId)); @@ -974,6 +990,7 @@ void DestroyPreview(AbstractMaterialNode node) m_PreviewsNeedsRecompile.Remove(renderData); m_PreviewsCompiling.Remove(renderData); m_PreviewsToDraw.Remove(renderData); + m_TimedPreviews.Remove(renderData); DestroyRenderData(renderData); m_RenderDatas.Remove(nodeId); From 0f7b76388d411bbed10ea5a548fa338102ba9f1e Mon Sep 17 00:00:00 2001 From: Chris Tchou Date: Fri, 8 May 2020 10:41:58 -0700 Subject: [PATCH 4/5] Subgraph Previews working Master node preview redraws when resized Better early out of render code --- .../Editor/Drawing/PreviewManager.cs | 221 ++++++++---------- 1 file changed, 96 insertions(+), 125 deletions(-) diff --git a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs index 7acc9597f59..232b5d4174b 100644 --- a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs +++ b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs @@ -85,7 +85,9 @@ public void ResizeMasterPreview(Vector2 newSize) public PreviewRenderData GetPreviewRenderData(AbstractMaterialNode node) { PreviewRenderData result = null; - if (node == kMasterProxyNode || node is BlockNode || node == m_Graph.outputNode) // TODO: node is SubGraphOutputNode ?? is there ever more than one?: Should be caught by m_Graph.outputNode... + if (node == kMasterProxyNode || + node is BlockNode || + node == m_Graph.outputNode) // the outputNode, if it exists, is mapped to master { result = m_MasterRenderData; } @@ -101,7 +103,7 @@ void AddMasterPreview() { m_MasterRenderData = new PreviewRenderData { - previewName = "Master:" + (m_Graph.outputNode?.name ?? ""), + previewName = "Master Preview", renderTexture = new RenderTexture(400, 400, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) { @@ -114,7 +116,10 @@ void AddMasterPreview() var shaderData = new PreviewShaderData { - node = m_Graph.outputNode, // can be null, which means to generate with active Target + // even though a SubGraphOutputNode can be directly mapped to master (via m_Graph.outputNode) + // we always keep master node associated with kMasterProxyNode instead + // just easier if the association is always dynamic + node = kMasterProxyNode, passesCompiling = 0, isOutOfDate = true, hasError = false, @@ -122,6 +127,7 @@ void AddMasterPreview() m_MasterRenderData.shaderData = shaderData; m_PreviewsNeedsRecompile.Add(m_MasterRenderData); + m_PreviewsToDraw.Add(m_MasterRenderData); m_RefreshTimedNodes = true; } @@ -138,7 +144,8 @@ public void UpdateMasterPreview(ModificationScope scope) } else if (scope == ModificationScope.Node) { - m_PreviewsToDraw.Add(m_MasterRenderData); + if (m_MasterRenderData != null) + m_PreviewsToDraw.Add(m_MasterRenderData); } } @@ -146,6 +153,8 @@ void AddPreview(AbstractMaterialNode node) { Assert.IsNotNull(node); + // BlockNodes have no preview for themselves, but are mapped to the "Master" preview + // SubGraphOutput nodes have their own previews, but will use the "Master" preview if they are the m_Graph.outputNode if (node is BlockNode) { node.RegisterCallback(OnNodeModified); @@ -153,11 +162,6 @@ void AddPreview(AbstractMaterialNode node) return; } -// if (node is SubGraphOutputNode && masterRenderData != null) -// { -// return; -// } - var renderData = new PreviewRenderData { previewName = node.name ?? "UNNAMED NODE", @@ -193,7 +197,7 @@ void AddPreview(AbstractMaterialNode node) void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) { - Assert.IsFalse(node == null); + Assert.IsNotNull(node); if (scope == ModificationScope.Topological || scope == ModificationScope.Graph) @@ -236,6 +240,7 @@ enum PropagationDirection void PropagateNodes(HashSet sources, PropagationDirection dir, HashSet result) { using (PropagateNodesMarker.Auto()) + if (sources.Count > 0) { // NodeWave represents the list of nodes we still have to process and add to result m_TempNodeWave.Clear(); @@ -316,26 +321,20 @@ public void HandleGraphChanges() { var node = edge.inputSlot.node; if ((node is BlockNode) || (node is SubGraphOutputNode)) - { UpdateMasterPreview(ModificationScope.Topological); - continue; - } - - m_NodesShaderChanged.Add(node); + else + m_NodesShaderChanged.Add(node); m_RefreshTimedNodes = true; } foreach (var edge in m_Graph.addedEdges) { var node = edge.inputSlot.node; - if(node != null) + if (node != null) { if ((node is BlockNode) || (node is SubGraphOutputNode)) - { UpdateMasterPreview(ModificationScope.Topological); - continue; - } - - m_NodesShaderChanged.Add(node); + else + m_NodesShaderChanged.Add(node); m_RefreshTimedNodes = true; } } @@ -345,35 +344,13 @@ public void HandleGraphChanges() void CollectPreviewProperties(IEnumerable nodesToCollect) { using (CollectPreviewPropertiesMarker.Auto()) - // using (var tempCollectNodes = PooledHashSet.Get()) using (var tempPreviewProps = PooledList.Get()) { - // we collect ALL properties from nodes upstream of something we want to draw - - // we could only bother to collect from nodes tagged as "m_NodesPropertyChanged" - // but we would also have to figure out how to fully populate Material properties on newly created nodes - // (shared MaterialPropertyBlock properties would be fine...) - - // collect properties from all nodes we need to draw - // tempCollectNodes.UnionWith(m_PreviewsToDraw.Select(p => p.shaderData.node)); - - // if we're collecting for master preview, also collect from all of the blocks and their upstream nodes - /* - if (m_PreviewsToDraw.Contains(m_MasterRenderData) || tempCollectNodes.Contains(null)) - { - tempCollectNodes.Remove(null); - foreach (var block in m_MasterNodePreviewBlocks) - tempCollectNodes.Add(block); - } - */ - - // and also collect from all nodes upstream - // PropagateNodes(tempCollectNodes, PropagationDirection.Upstream, tempCollectNodes); - + // collect from all of the changed nodes foreach (var propNode in nodesToCollect) propNode.CollectPreviewMaterialProperties(tempPreviewProps); - // also grab all graph properties + // also grab all graph properties (they are updated every frame) foreach (var prop in m_Graph.properties) tempPreviewProps.Add(prop.GetPreviewMaterialProperty()); @@ -383,7 +360,6 @@ void CollectPreviewProperties(IEnumerable nodesToCollect) } private static readonly ProfilerMarker RenderPreviewsMarker = new ProfilerMarker("RenderPreviews"); - private static readonly ProfilerMarker PrepareNodesMarker = new ProfilerMarker("PrepareNodesMarker"); public void RenderPreviews(bool requestShaders = true) { using (RenderPreviewsMarker.Auto()) @@ -396,78 +372,76 @@ public void RenderPreviews(bool requestShaders = true) UpdateTimedNodeList(); - if (m_NodesPropertyChanged.Count > 0) - { - // all nodes downstream of a changed property must be redrawn (to display the updated the property value) - PropagateNodes(m_NodesPropertyChanged, PropagationDirection.Downstream, nodesToDraw); - - // master node won't get picked up by the propagation - // but if any block nodes were picked up, flag master instead - if (nodesToDraw.RemoveWhere(n => n is BlockNode) > 0) - m_PreviewsToDraw.Add(m_MasterRenderData); - } + // all nodes downstream of a changed property must be redrawn (to display the updated the property value) + PropagateNodes(m_NodesPropertyChanged, PropagationDirection.Downstream, nodesToDraw); CollectPreviewProperties(m_NodesPropertyChanged); m_NodesPropertyChanged.Clear(); // timed nodes change every frame, so must be drawn - // (m_TimedNodes has been pre-propagated downstream) + // (m_TimedPreviews has been pre-propagated downstream) // HOWEVER they do not need to collect properties. (the only property changing is time..) m_PreviewsToDraw.UnionWith(m_TimedPreviews); - m_PreviewsToDraw.UnionWith(nodesToDraw.Select(n => GetPreviewRenderData(n))); - m_PreviewsToDraw.Remove(null); + ForEachNodesPreview(nodesToDraw, p => m_PreviewsToDraw.Add(p)); - if (m_PreviewsToDraw.Count <= 0) - return; - - var time = Time.realtimeSinceStartup; - var timeParameters = new Vector4(time, Mathf.Sin(time), Mathf.Cos(time), 0.0f); - m_SharedPreviewPropertyBlock.SetVector("_TimeParameters", timeParameters); + // redraw master when it is resized + if (m_NewMasterPreviewSize.HasValue) + m_PreviewsToDraw.Add(m_MasterRenderData); + // apply filtering to determine what nodes really get drawn bool renderMasterPreview = false; - using (PrepareNodesMarker.Auto()) + int drawPreviewCount = 0; + foreach (var preview in m_PreviewsToDraw) { - foreach (var preview in m_PreviewsToDraw) - { - if (preview == null) - continue; + Assert.IsNotNull(preview); - // early out if the node doesn't have a preview expanded + { // skip if the node doesn't have a preview expanded (unless it's master) var node = preview.shaderData.node; - if ((node != null) && (!node.hasPreview || !node.previewExpanded)) + if ((node != kMasterProxyNode) && (!node.hasPreview || !node.previewExpanded)) continue; + } - // check that we've got shaders and materials generated - var renderData = preview; - if ((renderData.shaderData.shader == null) || (renderData.shaderData.mat == null)) - { - // avoid calling NotifyPreviewChanged repeatedly - if (renderData.texture != null) - { - renderData.texture = null; - renderData.NotifyPreviewChanged(); - } - continue; - } - - if (renderData.shaderData.hasError) + // check that we've got shaders and materials generated + // if not ,replace the rendered texture with null + if ((preview.shaderData.shader == null) || + (preview.shaderData.mat == null)) + { + // avoid calling NotifyPreviewChanged repeatedly + if (preview.texture != null) { - renderData.texture = m_ErrorTexture; - renderData.NotifyPreviewChanged(); - continue; + preview.texture = null; + preview.NotifyPreviewChanged(); } + continue; + } - // categorize what kind of render it is - if (node == kMasterProxyNode) - renderMasterPreview = true; - else if (renderData.previewMode == PreviewMode.Preview2D) - renderList2D.Add(renderData); - else - renderList3D.Add(renderData); + // similarly with previews that have a shader error + if (preview.shaderData.hasError) + { + preview.texture = m_ErrorTexture; + preview.NotifyPreviewChanged(); + continue; } + + // we want to render this thing, now categorize what kind of render it is + if (preview == m_MasterRenderData) + renderMasterPreview = true; + else if (preview.previewMode == PreviewMode.Preview2D) + renderList2D.Add(preview); + else + renderList3D.Add(preview); + drawPreviewCount++; } + // if we actually don't want to render anything at all, early out here + if (drawPreviewCount <= 0) + return; + + var time = Time.realtimeSinceStartup; + var timeParameters = new Vector4(time, Mathf.Sin(time), Mathf.Cos(time), 0.0f); + m_SharedPreviewPropertyBlock.SetVector("_TimeParameters", timeParameters); + EditorUtility.SetCameraAnimateMaterialsTime(m_SceneResources.camera, time); m_SceneResources.light0.enabled = true; @@ -523,8 +497,6 @@ public void RenderPreviews(bool requestShaders = true) renderData.NotifyPreviewChanged(); if (renderMasterPreview) masterRenderData.NotifyPreviewChanged(); - - m_PreviewsToDraw.Clear(); } } @@ -627,13 +599,9 @@ void KickOffShaderCompilations() if ((m_PreviewsCompiling.Count + previewsToCompile.Count < m_MaxPreviewsCompiling) && ((Shader.globalRenderPipeline != null) && (Shader.globalRenderPipeline.Length > 0))) // master node requires an SRP { - if (m_MasterRenderData.shaderData.node != m_Graph.outputNode) - Debug.Log("MasterRenderData mismatch! " + m_MasterRenderData.shaderData.node + " != " + m_Graph.outputNode); - if (m_PreviewsNeedsRecompile.Contains(m_MasterRenderData) && !m_PreviewsCompiling.Contains(m_MasterRenderData)) { - Assert.IsTrue(m_MasterRenderData != null); previewsToCompile.Add(m_MasterRenderData); m_PreviewsNeedsRecompile.Remove(m_MasterRenderData); } @@ -689,7 +657,7 @@ void KickOffShaderCompilations() Assert.IsFalse(!node.hasPreview && !(node is SubGraphOutputNode)); - var renderData = preview; // GetPreviewRenderData(node); + var renderData = preview; // Get shader code and compile var generator = new Generator(node.owner, node, GenerationMode.Preview, $"hidden/preview/{node.GetVariableNameForNode()}"); @@ -730,12 +698,8 @@ void UpdateShaders() using (var nodesToRecompile = PooledHashSet.Get()) { PropagateNodes(m_NodesShaderChanged, PropagationDirection.Downstream, nodesToRecompile); - foreach (var node in nodesToRecompile) - { - var preview = GetPreviewRenderData(node); - if (preview != null) // non-active output nodes can have NULL render data (no preview) - m_PreviewsNeedsRecompile.Add(preview); - } + ForEachNodesPreview(nodesToRecompile, p => m_PreviewsNeedsRecompile.Add(p)); + m_NodesShaderChanged.Clear(); } } @@ -801,6 +765,18 @@ void BeginCompile(PreviewRenderData renderData, string shaderStr) } } + private void ForEachNodesPreview( + IEnumerable nodes, + Action action) + { + foreach (var node in nodes) + { + var preview = GetPreviewRenderData(node); + if (preview != null) // some output nodes may have no preview + action(preview); + } + } + private static readonly ProfilerMarker UpdateTimedNodeListMarker = new ProfilerMarker("RenderPreviews"); void UpdateTimedNodeList() { @@ -812,16 +788,11 @@ void UpdateTimedNodeList() { timedNodes.UnionWith(m_Graph.GetNodes().Where(n => n.RequiresTime())); - // timed nodes are pre-propagated downstream, to reduce amount of propagation we have to do per frame + // we pre-propagate timed nodes downstream, to reduce amount of propagation we have to do per frame PropagateNodes(timedNodes, PropagationDirection.Downstream, timedNodes); m_TimedPreviews.Clear(); - foreach (var node in timedNodes) - { - var preview = GetPreviewRenderData(node); - if (preview != null) - m_TimedPreviews.Add(preview); - } + ForEachNodesPreview(timedNodes, p => m_TimedPreviews.Add(p)); m_RefreshTimedNodes = false; } @@ -832,13 +803,11 @@ void RenderPreview(PreviewRenderData renderData, Mesh mesh, Matrix4x4 transform) { using (RenderPreviewMarker.Auto()) { - var node = renderData.shaderData.node; - Assert.IsTrue((node != null && node.hasPreview && node.previewExpanded) || node == masterRenderData?.shaderData?.node); - - if (renderData.shaderData.hasError) { - renderData.texture = m_ErrorTexture; - return; + var node = renderData.shaderData.node; + if (node != kMasterProxyNode) + if (!(node.hasPreview && node.previewExpanded && !renderData.shaderData.hasError)) + Assert.IsTrue(false); } var previousRenderTexture = RenderTexture.active; @@ -850,7 +819,7 @@ void RenderPreview(PreviewRenderData renderData, Mesh mesh, Matrix4x4 transform) // Mesh is invalid for VFXTarget // We should handle this more gracefully - if(renderData != m_MasterRenderData || !m_Graph.isVFXTarget) + if (renderData != m_MasterRenderData || !m_Graph.isVFXTarget) { m_SceneResources.camera.targetTexture = temp; Graphics.DrawMesh(mesh, transform, renderData.shaderData.mat, 1, m_SceneResources.camera, 0, m_SharedPreviewPropertyBlock, ShadowCastingMode.Off, false, null, false); @@ -866,6 +835,8 @@ void RenderPreview(PreviewRenderData renderData, Mesh mesh, Matrix4x4 transform) RenderTexture.active = previousRenderTexture; renderData.texture = renderData.renderTexture; + + m_PreviewsToDraw.Remove(renderData); } } @@ -922,7 +893,7 @@ void UpdateMasterNodeShader() // Skip generation for VFXTarget if(!m_Graph.isVFXTarget) { - var generator = new Generator(m_Graph, shaderData?.node, GenerationMode.Preview, "Master"); + var generator = new Generator(m_Graph, m_Graph.outputNode, GenerationMode.Preview, "Master"); shaderData.shaderString = generator.generatedShader; // Blocks from the generation include those temporarily created for missing stack blocks @@ -973,7 +944,7 @@ void DestroyPreview(AbstractMaterialNode node) { string nodeId = node.objectId; - if ((node is BlockNode) || (node is SubGraphOutputNode)) + if (node is BlockNode) { // block nodes don't have preview render data Assert.IsFalse(m_RenderDatas.ContainsKey(node.objectId)); From 292c9094a9c8ceee3b6632719ff656e99de565fb Mon Sep 17 00:00:00 2001 From: Chris Tchou Date: Fri, 8 May 2020 16:57:16 -0700 Subject: [PATCH 5/5] Moved preview mode computation to unified UpdateTopology() Cleaned up code --- .../Editor/Drawing/PreviewManager.cs | 284 ++++++++++++++---- 1 file changed, 230 insertions(+), 54 deletions(-) diff --git a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs index 232b5d4174b..68b21de9220 100644 --- a/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs +++ b/com.unity.shadergraph/Editor/Drawing/PreviewManager.cs @@ -16,6 +16,16 @@ namespace UnityEditor.ShaderGraph.Drawing { delegate void OnPrimaryMasterChanged(); + static class ListSliceUtility + { + // TODO: non-yield return, struct version of Slice + public static IEnumerable Slice(this List list, int start, int end) + { + for (int i = start; i < end; i++) + yield return list[i]; + } + } + class PreviewManager : IDisposable { GraphData m_Graph; @@ -36,7 +46,8 @@ class PreviewManager : IDisposable HashSet m_PreviewsCompiling = new HashSet(); // previews currently being compiled HashSet m_PreviewsToDraw = new HashSet(); // previews to re-render the texture (either because shader compile changed or property changed) HashSet m_TimedPreviews = new HashSet(); // previews that are dependent on a time node -- i.e. animated / need to redraw every frame - bool m_RefreshTimedNodes; // flag to trigger rebuilding the list of timed nodes. ANY topological change should trigger this + + bool m_TopologyDirty; // indicates topology changed, used to rebuild timed node list and preview type (2D/3D) inheritance. HashSet m_MasterNodePreviewBlocks = new HashSet(); // all blocks used for the most recent master node preview generation. this includes temporary blocks. @@ -128,7 +139,7 @@ void AddMasterPreview() m_PreviewsNeedsRecompile.Add(m_MasterRenderData); m_PreviewsToDraw.Add(m_MasterRenderData); - m_RefreshTimedNodes = true; + m_TopologyDirty = true; } public void UpdateMasterPreview(ModificationScope scope) @@ -140,7 +151,7 @@ public void UpdateMasterPreview(ModificationScope scope) // if not, no need to do it here, because it is always marked for recompile on creation if (m_MasterRenderData != null) m_PreviewsNeedsRecompile.Add(m_MasterRenderData); - m_RefreshTimedNodes = true; + m_TopologyDirty = true; } else if (scope == ModificationScope.Node) { @@ -186,13 +197,9 @@ void AddPreview(AbstractMaterialNode node) m_RenderDatas.Add(node.objectId, renderData); node.RegisterCallback(OnNodeModified); - if (node.RequiresTime()) - { - m_RefreshTimedNodes = true; - } - m_PreviewsNeedsRecompile.Add(renderData); m_NodesPropertyChanged.Add(node); + m_TopologyDirty = true; } void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) @@ -203,7 +210,7 @@ void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) scope == ModificationScope.Graph) { m_NodesShaderChanged.Add(node); // this will trigger m_PreviewsShaderChanged downstream - m_RefreshTimedNodes = true; + m_TopologyDirty = true; } else if (scope == ModificationScope.Node) { @@ -213,7 +220,7 @@ void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) } } - // temp structures that are kept around statically to avoid GC churn + // temp structures that are kept around statically to avoid GC churn (not thread safe) static Stack m_TempNodeWave = new Stack(); static HashSet m_TempAddedToNodeWave = new HashSet(); @@ -269,7 +276,7 @@ void PropagateNodes(HashSet sources, PropagationDirection } } - void ForeachConnectedNode(AbstractMaterialNode node, PropagationDirection dir, Action action) + static void ForeachConnectedNode(AbstractMaterialNode node, PropagationDirection dir, Action action) { using (var tempEdges = PooledList.Get()) using (var tempSlots = PooledList.Get()) @@ -284,7 +291,7 @@ void ForeachConnectedNode(AbstractMaterialNode node, PropagationDirection dir, A { // get the edges out of each slot tempEdges.Clear(); // and here we serialize another list, ouch! - m_Graph.GetEdges(slot.slotReference, tempEdges); + node.owner.GetEdges(slot.slotReference, tempEdges); foreach (var edge in tempEdges) { // We look at each node we feed into. @@ -302,7 +309,7 @@ public void HandleGraphChanges() foreach (var node in m_Graph.removedNodes) { DestroyPreview(node); - m_RefreshTimedNodes = true; + m_TopologyDirty = true; } // remove the nodes from the state trackers @@ -314,7 +321,7 @@ public void HandleGraphChanges() foreach (var node in m_Graph.addedNodes) { AddPreview(node); - m_RefreshTimedNodes = true; + m_TopologyDirty = true; } foreach (var edge in m_Graph.removedEdges) @@ -324,7 +331,7 @@ public void HandleGraphChanges() UpdateMasterPreview(ModificationScope.Topological); else m_NodesShaderChanged.Add(node); - m_RefreshTimedNodes = true; + m_TopologyDirty = true; } foreach (var edge in m_Graph.addedEdges) { @@ -335,7 +342,7 @@ public void HandleGraphChanges() UpdateMasterPreview(ModificationScope.Topological); else m_NodesShaderChanged.Add(node); - m_RefreshTimedNodes = true; + m_TopologyDirty = true; } } } @@ -367,11 +374,13 @@ public void RenderPreviews(bool requestShaders = true) using (var renderList3D = PooledList.Get()) using (var nodesToDraw = PooledHashSet.Get()) { + // update topology cached data + // including list of time-dependent previews, and the preview mode (2d/3d) + UpdateTopology(); + if (requestShaders) UpdateShaders(); - UpdateTimedNodeList(); - // all nodes downstream of a changed property must be redrawn (to display the updated the property value) PropagateNodes(m_NodesPropertyChanged, PropagationDirection.Downstream, nodesToDraw); @@ -594,6 +603,7 @@ void KickOffShaderCompilations() // Start compilation for nodes that need to recompile using (KickOffShaderCompilationsMarker.Auto()) using (var previewsToCompile = PooledHashSet.Get()) + using (var nodesToCompile = PooledHashSet.Get()) { // master node compile is first in the priority list, as it takes longer than the other previews if ((m_PreviewsCompiling.Count + previewsToCompile.Count < m_MaxPreviewsCompiling) && @@ -609,17 +619,18 @@ void KickOffShaderCompilations() // add each node to compile list if it needs a preview, is not already compiling, and we have room // (we don't want to double kick compiles, so wait for the first one to get back before kicking another) - for(int i = 0; i < m_PreviewsNeedsRecompile.Count(); i++) + for (int i = 0; i < m_PreviewsNeedsRecompile.Count(); i++) { if (m_PreviewsCompiling.Count + previewsToCompile.Count >= m_MaxPreviewsCompiling) break; var preview = m_PreviewsNeedsRecompile.ElementAt(i); - if (preview == m_MasterRenderData) // handled specially above + if (preview == m_MasterRenderData) // master preview is handled specially above continue; var node = preview.shaderData.node; - Assert.IsFalse((node == null) || (node is BlockNode)); + Assert.IsNotNull(node); + Assert.IsFalse(node is BlockNode); if (node.hasPreview && node.previewExpanded && !m_PreviewsCompiling.Contains(preview)) { @@ -631,10 +642,10 @@ void KickOffShaderCompilations() m_PreviewsNeedsRecompile.ExceptWith(previewsToCompile); // Reset error states for the UI, the shader, and all render data for nodes we're recompiling - var nodesToCompile = new HashSet(); nodesToCompile.UnionWith(previewsToCompile.Select(x => x.shaderData.node)); nodesToCompile.Remove(null); - m_Messenger.ClearNodesFromProvider(this, nodesToCompile); // not sure if it needs notification for BlockNodes when master rebuilds? + // TODO: not sure if we need to clear BlockNodes when master gets rebuilt? + m_Messenger.ClearNodesFromProvider(this, nodesToCompile); // Force async compile on var wasAsyncAllowed = ShaderUtil.allowAsyncCompilation; @@ -645,39 +656,16 @@ void KickOffShaderCompilations() { if (preview == m_MasterRenderData) { - UpdateMasterNodeShader(); + CompileMasterNodeShader(); continue; } var node = preview.shaderData.node; - - - if (node == null) - Debug.Log("ERROR: null node preview compilation for node: " + preview.previewName); - - Assert.IsFalse(!node.hasPreview && !(node is SubGraphOutputNode)); - - var renderData = preview; + Assert.IsNotNull(node); // master preview handled above // Get shader code and compile var generator = new Generator(node.owner, node, GenerationMode.Preview, $"hidden/preview/{node.GetVariableNameForNode()}"); - BeginCompile(renderData, generator.generatedShader); - - // Calculate the PreviewMode from upstream nodes - // If any upstream node is 3D that trickles downstream - // TODO: not sure why this code exists here - // it would make more sense in HandleGraphChanges and/or RenderPreview - List upstreamNodes = new List(); - NodeUtils.DepthFirstCollectNodesFromNode(upstreamNodes, node, NodeUtils.IncludeSelf.Include); - renderData.previewMode = PreviewMode.Preview2D; - foreach (var pNode in upstreamNodes) - { - if (pNode.previewMode == PreviewMode.Preview3D) - { - renderData.previewMode = PreviewMode.Preview3D; - break; - } - } + BeginCompile(preview, generator.generatedShader); } ShaderUtil.allowAsyncCompilation = wasAsyncAllowed; @@ -777,10 +765,173 @@ private void ForEachNodesPreview( } } + class NodeProcessor + { + // parameters + GraphData graphData; + Action> process; + + // node tracking state + HashSet processing = new HashSet(); + HashSet processed = new HashSet(); + + // iteration state stack + Stack nodeStack = new Stack(); + Stack childStartStack = new Stack(); + Stack curChildStack = new Stack(); + Stack stateStack = new Stack(); + + List allChildren = new List(); + + public NodeProcessor(GraphData graphData, Action> process) + { + this.graphData = graphData; + this.process = process; + } + + public void ProcessInDependencyOrder(AbstractMaterialNode root) + { + // early out to skip a bit of work + if (processed.Contains(root)) + return; + + // push root node in the initial state + stateStack.Push(0); + nodeStack.Push(root); + + while (nodeStack.Count > 0) + { + // check the state of the top of the stack + switch (stateStack.Pop()) + { + case 0: // node initial state (valid stacks: nodeStack) + { + var node = nodeStack.Peek(); + if (processed.Contains(node)) + { + // finished with this node, pop it off the stack + nodeStack.Pop(); + continue; + } + + if (processing.Contains(node)) + { + // not processed, but still processing.. means there was a circular dependency here + throw new ArgumentException("ERROR: graph contains circular wire connections"); + } + + processing.Add(node); + + int childStart = allChildren.Count; + childStartStack.Push(childStart); + + // add immediate children + ForeachConnectedNode(node, PropagationDirection.Upstream, n => allChildren.Add(n)); + + if (allChildren.Count == childStart) + { + // no children.. transition to state 2 (all children processed) + stateStack.Push(2); + } + else + { + // transition to state 1 (processing children) + stateStack.Push(1); + curChildStack.Push(childStart); + } + } + break; + case 1: // processing children (valid stacks: nodeStack, childStartStack, curChildStack) + { + int curChild = curChildStack.Pop(); + + // first update our state for when we return from the cur child + int nextChild = curChild + 1; + if (nextChild < allChildren.Count) + { + // we will process the next child + stateStack.Push(1); + curChildStack.Push(nextChild); + } + else + { + // we will be done iterating children, move to state 2 + stateStack.Push(2); + } + + // then push the current child in state 0 to process it + stateStack.Push(0); + nodeStack.Push(allChildren[curChild]); + } + break; + case 2: // all children processed (valid stacks: nodeStack, childStartStack) + { + // read state, popping all + var node = nodeStack.Pop(); + int childStart = childStartStack.Pop(); + + // process node + process(node, allChildren.Slice(childStart, allChildren.Count)); + processed.Add(node); + + // remove the children that were added in state 0 + allChildren.RemoveRange(childStart, allChildren.Count - childStart); + + // terminate node, stacks are popped to state of parent node + } + break; + } + } + } + + public void ProcessInDependencyOrderRecursive(AbstractMaterialNode node) + { + if (processed.Contains(node)) + return; // already processed + + if (processing.Contains(node)) + throw new ArgumentException("ERROR: graph contains circular wire connections"); + + processing.Add(node); + + int childStart = allChildren.Count; + + // add immediate children + ForeachConnectedNode(node, PropagationDirection.Upstream, n => allChildren.Add(n)); + + // process children + var children = allChildren.Slice(childStart, allChildren.Count); + foreach (var child in children) + ProcessInDependencyOrderRecursive(child); + + // process self + process(node, children); + processed.Add(node); + + // remove the children + allChildren.RemoveRange(childStart, allChildren.Count - childStart); + } + } + + // Processes all the nodes in the upstream trees of rootNodes + // Will only process each node once, even if the trees overlap + // Processes a node ONLY after processing all of the nodes in its upstream subtree + void ProcessUpstreamNodesInDependencyOrder( + IEnumerable rootNodes, // root nodes can share subtrees, but cannot themselves exist in any others subtree + Action> process) // process takes the node and it's list of immediate upstream children as parameters + { + if (rootNodes.Any()) + { + NodeProcessor processor = new NodeProcessor(rootNodes.First().owner, process); + foreach (var node in rootNodes) + processor.ProcessInDependencyOrderRecursive(node); + } + } + private static readonly ProfilerMarker UpdateTimedNodeListMarker = new ProfilerMarker("RenderPreviews"); - void UpdateTimedNodeList() + void UpdateTopology() { - if (!m_RefreshTimedNodes) + if (!m_TopologyDirty) return; using (UpdateTimedNodeListMarker.Auto()) @@ -793,9 +944,34 @@ void UpdateTimedNodeList() m_TimedPreviews.Clear(); ForEachNodesPreview(timedNodes, p => m_TimedPreviews.Add(p)); - - m_RefreshTimedNodes = false; } + + // Calculate the PreviewMode from upstream nodes + ProcessUpstreamNodesInDependencyOrder( + // we just pass all the nodes we care about as the roots + m_RenderDatas.Values.Select(p => p.shaderData.node).Where(n => n != null), + (node, children) => + { + var preview = GetPreviewRenderData(node); + + // set preview mode based on node + preview.previewMode = node.previewMode; + + // then 2d upgrades to 3d if any child is 3d + if (preview.previewMode == PreviewMode.Preview2D) + { + foreach (var child in children) + { + if (GetPreviewRenderData(child).previewMode == PreviewMode.Preview3D) + { + preview.previewMode = PreviewMode.Preview3D; + break; + } + } + } + }); + + m_TopologyDirty = false; } private static readonly ProfilerMarker RenderPreviewMarker = new ProfilerMarker("RenderPreview"); @@ -886,7 +1062,7 @@ void CheckForErrors(PreviewShaderData shaderData) } } - void UpdateMasterNodeShader() + void CompileMasterNodeShader() { var shaderData = masterRenderData?.shaderData;