You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1345 lines
59 KiB
1345 lines
59 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using UnityEngine; |
|
using UnityEditor.Graphing; |
|
using UnityEditor.Graphing.Util; |
|
using UnityEngine.Assertions; |
|
using UnityEngine.Rendering; |
|
using UnityEditor.ShaderGraph.Internal; |
|
using Debug = UnityEngine.Debug; |
|
using Object = UnityEngine.Object; |
|
using Unity.Profiling; |
|
|
|
|
|
namespace UnityEditor.ShaderGraph.Drawing |
|
{ |
|
delegate void OnPrimaryMasterChanged(); |
|
|
|
class PreviewManager : IDisposable |
|
{ |
|
GraphData m_Graph; |
|
MessageManager m_Messenger; |
|
|
|
MaterialPropertyBlock m_SharedPreviewPropertyBlock; // stores preview properties (shared among ALL preview nodes) |
|
|
|
Dictionary<AbstractMaterialNode, PreviewRenderData> m_RenderDatas = new Dictionary<AbstractMaterialNode, PreviewRenderData>(); // stores all of the PreviewRendererData, mapped by node |
|
PreviewRenderData m_MasterRenderData; // ref to preview renderer data for the master node |
|
|
|
int m_MaxPreviewsCompiling = 2; // max preview shaders we want to async compile at once |
|
|
|
// state trackers |
|
HashSet<AbstractMaterialNode> m_NodesShaderChanged = new HashSet<AbstractMaterialNode>(); // nodes whose shader code has changed, this node and nodes that read from it are put into NeedRecompile |
|
HashSet<AbstractMaterialNode> m_NodesPropertyChanged = new HashSet<AbstractMaterialNode>(); // nodes whose property values have changed, the properties will need to be updated and all nodes that use that property re-rendered |
|
|
|
HashSet<PreviewRenderData> m_PreviewsNeedsRecompile = new HashSet<PreviewRenderData>(); // previews we need to recompile the preview shader |
|
HashSet<PreviewRenderData> m_PreviewsCompiling = new HashSet<PreviewRenderData>(); // previews currently being compiled |
|
HashSet<PreviewRenderData> m_PreviewsToDraw = new HashSet<PreviewRenderData>(); // previews to re-render the texture (either because shader compile changed or property changed) |
|
HashSet<PreviewRenderData> m_TimedPreviews = new HashSet<PreviewRenderData>(); // previews that are dependent on a time node -- i.e. animated / need to redraw every frame |
|
|
|
double m_LastTimedUpdateTime = 0.0f; |
|
|
|
bool m_TopologyDirty; // indicates topology changed, used to rebuild timed node list and preview type (2D/3D) inheritance. |
|
|
|
HashSet<BlockNode> m_MasterNodeTempBlocks = new HashSet<BlockNode>(); // temp blocks used by the most recent master node preview generation. |
|
|
|
PreviewSceneResources m_SceneResources; |
|
Texture2D m_ErrorTexture; |
|
Vector2? m_NewMasterPreviewSize; |
|
|
|
const AbstractMaterialNode kMasterProxyNode = null; |
|
|
|
public PreviewRenderData masterRenderData |
|
{ |
|
get { return m_MasterRenderData; } |
|
} |
|
|
|
public PreviewManager(GraphData graph, MessageManager messenger) |
|
{ |
|
m_SharedPreviewPropertyBlock = new MaterialPropertyBlock(); |
|
m_Graph = graph; |
|
m_Messenger = messenger; |
|
m_ErrorTexture = GenerateFourSquare(Color.magenta, Color.black); |
|
m_SceneResources = new PreviewSceneResources(); |
|
|
|
foreach (var node in m_Graph.GetNodes<AbstractMaterialNode>()) |
|
AddPreview(node); |
|
|
|
AddMasterPreview(); |
|
} |
|
|
|
static Texture2D GenerateFourSquare(Color c1, Color c2) |
|
{ |
|
var tex = new Texture2D(2, 2); |
|
tex.SetPixel(0, 0, c1); |
|
tex.SetPixel(0, 1, c2); |
|
tex.SetPixel(1, 0, c2); |
|
tex.SetPixel(1, 1, c1); |
|
tex.filterMode = FilterMode.Point; |
|
tex.Apply(); |
|
return tex; |
|
} |
|
|
|
public void ResizeMasterPreview(Vector2 newSize) |
|
{ |
|
m_NewMasterPreviewSize = newSize; |
|
} |
|
|
|
public PreviewRenderData GetPreviewRenderData(AbstractMaterialNode node) |
|
{ |
|
PreviewRenderData result = null; |
|
if (node == kMasterProxyNode || |
|
node is BlockNode || |
|
node == m_Graph.outputNode) // the outputNode, if it exists, is mapped to master |
|
{ |
|
result = m_MasterRenderData; |
|
} |
|
else |
|
{ |
|
m_RenderDatas.TryGetValue(node, out result); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
void AddMasterPreview() |
|
{ |
|
m_MasterRenderData = new PreviewRenderData |
|
{ |
|
previewName = "Master Preview", |
|
renderTexture = |
|
new RenderTexture(400, 400, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) |
|
{ |
|
hideFlags = HideFlags.HideAndDontSave |
|
}, |
|
previewMode = PreviewMode.Preview3D, |
|
}; |
|
|
|
m_MasterRenderData.renderTexture.Create(); |
|
|
|
var shaderData = new PreviewShaderData |
|
{ |
|
// 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, |
|
}; |
|
m_MasterRenderData.shaderData = shaderData; |
|
|
|
m_PreviewsNeedsRecompile.Add(m_MasterRenderData); |
|
m_PreviewsToDraw.Add(m_MasterRenderData); |
|
m_TopologyDirty = true; |
|
} |
|
|
|
public void UpdateMasterPreview(ModificationScope scope) |
|
{ |
|
if (scope == ModificationScope.Topological || |
|
scope == ModificationScope.Graph) |
|
{ |
|
// 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_TopologyDirty = true; |
|
} |
|
else if (scope == ModificationScope.Node) |
|
{ |
|
if (m_MasterRenderData != null) |
|
m_PreviewsToDraw.Add(m_MasterRenderData); |
|
} |
|
} |
|
|
|
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); |
|
UpdateMasterPreview(ModificationScope.Topological); |
|
m_NodesPropertyChanged.Add(node); |
|
return; |
|
} |
|
|
|
var renderData = new PreviewRenderData |
|
{ |
|
previewName = node.name ?? "UNNAMED NODE", |
|
renderTexture = |
|
new RenderTexture(200, 200, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) |
|
{ |
|
hideFlags = HideFlags.HideAndDontSave |
|
} |
|
}; |
|
|
|
renderData.renderTexture.Create(); |
|
|
|
var shaderData = new PreviewShaderData |
|
{ |
|
node = node, |
|
passesCompiling = 0, |
|
isOutOfDate = true, |
|
hasError = false, |
|
}; |
|
renderData.shaderData = shaderData; |
|
|
|
m_RenderDatas.Add(node, renderData); |
|
node.RegisterCallback(OnNodeModified); |
|
|
|
m_PreviewsNeedsRecompile.Add(renderData); |
|
m_NodesPropertyChanged.Add(node); |
|
m_TopologyDirty = true; |
|
} |
|
|
|
void OnNodeModified(AbstractMaterialNode node, ModificationScope scope) |
|
{ |
|
Assert.IsNotNull(node); |
|
|
|
if (scope == ModificationScope.Topological || |
|
scope == ModificationScope.Graph) |
|
{ |
|
m_NodesShaderChanged.Add(node); // shader code for this node changed, this will trigger m_PreviewsShaderChanged for all nodes downstream |
|
m_NodesPropertyChanged.Add(node); // properties could also have changed at the same time and need to be re-collected |
|
m_TopologyDirty = 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 |
|
// should instead flag m_NodesConstantChanged |
|
m_NodesPropertyChanged.Add(node); |
|
} |
|
} |
|
|
|
// temp structures that are kept around statically to avoid GC churn (not thread safe) |
|
static Stack<AbstractMaterialNode> m_TempNodeWave = new Stack<AbstractMaterialNode>(); |
|
static HashSet<AbstractMaterialNode> m_TempAddedToNodeWave = new HashSet<AbstractMaterialNode>(); |
|
|
|
// cache the Action to avoid GC |
|
static Action<AbstractMaterialNode> AddNextLevelNodesToWave = |
|
nextLevelNode => |
|
{ |
|
if (!m_TempAddedToNodeWave.Contains(nextLevelNode)) |
|
{ |
|
m_TempNodeWave.Push(nextLevelNode); |
|
m_TempAddedToNodeWave.Add(nextLevelNode); |
|
} |
|
}; |
|
|
|
internal enum PropagationDirection |
|
{ |
|
Upstream, |
|
Downstream |
|
} |
|
|
|
// ADDs all nodes in sources, and all nodes in the given direction relative to them, into result |
|
// sources and result can be the same HashSet |
|
private static readonly ProfilerMarker PropagateNodesMarker = new ProfilerMarker("PropagateNodes"); |
|
internal static void PropagateNodes(HashSet<AbstractMaterialNode> sources, PropagationDirection dir, HashSet<AbstractMaterialNode> 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(); |
|
m_TempAddedToNodeWave.Clear(); |
|
foreach (var node in sources) |
|
{ |
|
m_TempNodeWave.Push(node); |
|
m_TempAddedToNodeWave.Add(node); |
|
} |
|
|
|
while (m_TempNodeWave.Count > 0) |
|
{ |
|
var node = m_TempNodeWave.Pop(); |
|
if (node == null) |
|
continue; |
|
|
|
result.Add(node); |
|
|
|
// grab connected nodes in propagation direction, add them to the node wave |
|
ForeachConnectedNode(node, dir, AddNextLevelNodesToWave); |
|
} |
|
|
|
// clean up any temp data |
|
m_TempNodeWave.Clear(); |
|
m_TempAddedToNodeWave.Clear(); |
|
} |
|
} |
|
|
|
static void ForeachConnectedNode(AbstractMaterialNode node, PropagationDirection dir, Action<AbstractMaterialNode> action) |
|
{ |
|
using (var tempEdges = PooledList<IEdge>.Get()) |
|
using (var tempSlots = PooledList<MaterialSlot>.Get()) |
|
{ |
|
// Loop through all nodes that the node feeds into. |
|
if (dir == PropagationDirection.Downstream) |
|
node.GetOutputSlots(tempSlots); |
|
else |
|
node.GetInputSlots(tempSlots); |
|
|
|
foreach (var slot in tempSlots) |
|
{ |
|
// get the edges out of each slot |
|
tempEdges.Clear(); // and here we serialize another list, ouch! |
|
node.owner.GetEdges(slot.slotReference, tempEdges); |
|
foreach (var edge in tempEdges) |
|
{ |
|
// We look at each node we feed into. |
|
var connectedSlot = (dir == PropagationDirection.Downstream) ? edge.inputSlot : edge.outputSlot; |
|
var connectedNode = connectedSlot.node; |
|
|
|
action(connectedNode); |
|
} |
|
} |
|
} |
|
|
|
// Custom Interpolator Blocks have implied connections to their Custom Interpolator Nodes... |
|
if (dir == PropagationDirection.Downstream && node is BlockNode bnode && bnode.isCustomBlock) |
|
{ |
|
foreach (var cin in CustomInterpolatorUtils.GetCustomBlockNodeDependents(bnode)) |
|
{ |
|
action(cin); |
|
} |
|
} |
|
// ... Just as custom Interpolator Nodes have implied connections to their custom interpolator blocks |
|
if (dir == PropagationDirection.Upstream && node is CustomInterpolatorNode ciNode && ciNode.e_targetBlockNode != null) |
|
{ |
|
action(ciNode.e_targetBlockNode); |
|
} |
|
} |
|
|
|
public void HandleGraphChanges() |
|
{ |
|
foreach (var node in m_Graph.addedNodes) |
|
{ |
|
AddPreview(node); |
|
m_TopologyDirty = true; |
|
} |
|
|
|
foreach (var edge in m_Graph.addedEdges) |
|
{ |
|
var node = edge.inputSlot.node; |
|
if (node != null) |
|
{ |
|
if ((node is BlockNode) || (node is SubGraphOutputNode)) |
|
UpdateMasterPreview(ModificationScope.Topological); |
|
else |
|
m_NodesShaderChanged.Add(node); |
|
m_TopologyDirty = true; |
|
} |
|
} |
|
|
|
foreach (var node in m_Graph.removedNodes) |
|
{ |
|
DestroyPreview(node); |
|
m_TopologyDirty = true; |
|
} |
|
|
|
foreach (var edge in m_Graph.removedEdges) |
|
{ |
|
var node = edge.inputSlot.node; |
|
if ((node is BlockNode) || (node is SubGraphOutputNode)) |
|
{ |
|
UpdateMasterPreview(ModificationScope.Topological); |
|
} |
|
|
|
m_NodesShaderChanged.Add(node); |
|
//When an edge gets deleted, if the node had the edge on creation, the properties would get out of sync and no value would get set. |
|
//Fix for https://fogbugz.unity3d.com/f/cases/1284033/ |
|
m_NodesPropertyChanged.Add(node); |
|
|
|
m_TopologyDirty = true; |
|
} |
|
|
|
foreach (var edge in m_Graph.addedEdges) |
|
{ |
|
var node = edge.inputSlot.node; |
|
if (node != null) |
|
{ |
|
if ((node is BlockNode) || (node is SubGraphOutputNode)) |
|
{ |
|
UpdateMasterPreview(ModificationScope.Topological); |
|
} |
|
|
|
m_NodesShaderChanged.Add(node); |
|
m_TopologyDirty = true; |
|
} |
|
} |
|
|
|
// remove the nodes from the state trackers |
|
m_NodesShaderChanged.ExceptWith(m_Graph.removedNodes); |
|
m_NodesPropertyChanged.ExceptWith(m_Graph.removedNodes); |
|
|
|
m_Messenger.ClearNodesFromProvider(this, m_Graph.removedNodes); |
|
} |
|
|
|
private static readonly ProfilerMarker CollectPreviewPropertiesMarker = new ProfilerMarker("CollectPreviewProperties"); |
|
void CollectPreviewProperties(IEnumerable<AbstractMaterialNode> nodesToCollect, PooledList<PreviewProperty> perMaterialPreviewProperties) |
|
{ |
|
using (CollectPreviewPropertiesMarker.Auto()) |
|
using (var tempPreviewProps = PooledList<PreviewProperty>.Get()) |
|
{ |
|
// collect from all of the changed nodes |
|
foreach (var propNode in nodesToCollect) |
|
propNode.CollectPreviewMaterialProperties(tempPreviewProps); |
|
|
|
// also grab all graph properties (they are updated every frame) |
|
foreach (var prop in m_Graph.properties) |
|
tempPreviewProps.Add(prop.GetPreviewMaterialProperty()); |
|
|
|
foreach (var previewProperty in tempPreviewProps) |
|
{ |
|
previewProperty.SetValueOnMaterialPropertyBlock(m_SharedPreviewPropertyBlock); |
|
|
|
// virtual texture assignments must be pushed to the materials themselves (MaterialPropertyBlocks not supported) |
|
if ((previewProperty.propType == PropertyType.VirtualTexture) && |
|
(previewProperty.vtProperty?.value?.layers != null)) |
|
{ |
|
perMaterialPreviewProperties.Add(previewProperty); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void AssignPerMaterialPreviewProperties(Material mat, List<PreviewProperty> perMaterialPreviewProperties) |
|
{ |
|
foreach (var prop in perMaterialPreviewProperties) |
|
{ |
|
switch (prop.propType) |
|
{ |
|
case PropertyType.VirtualTexture: |
|
|
|
// setup the VT textures on the material |
|
bool setAnyTextures = false; |
|
var vt = prop.vtProperty.value; |
|
for (int layer = 0; layer < vt.layers.Count; layer++) |
|
{ |
|
var texture = vt.layers[layer].layerTexture?.texture; |
|
int propIndex = mat.shader.FindPropertyIndex(vt.layers[layer].layerRefName); |
|
if (propIndex != -1) |
|
{ |
|
mat.SetTexture(vt.layers[layer].layerRefName, texture); |
|
setAnyTextures = true; |
|
} |
|
} |
|
// also put in a request for the VT tiles, since preview rendering does not have feedback enabled |
|
if (setAnyTextures) |
|
{ |
|
#if ENABLE_VIRTUALTEXTURES |
|
int stackPropertyId = Shader.PropertyToID(prop.vtProperty.referenceName); |
|
try |
|
{ |
|
// Ensure we always request the mip sized 256x256 |
|
int width, height; |
|
UnityEngine.Rendering.VirtualTexturing.Streaming.GetTextureStackSize(mat, stackPropertyId, out width, out height); |
|
int textureMip = (int)Math.Max(Mathf.Log(width, 2f), Mathf.Log(height, 2f)); |
|
const int baseMip = 8; |
|
int mip = Math.Max(textureMip - baseMip, 0); |
|
UnityEngine.Rendering.VirtualTexturing.Streaming.RequestRegion(mat, stackPropertyId, new Rect(0.0f, 0.0f, 1.0f, 1.0f), mip, UnityEngine.Rendering.VirtualTexturing.System.AllMips); |
|
} |
|
catch (InvalidOperationException) |
|
{ |
|
// This gets thrown when the system is in an indeterminate state (like a material with no textures assigned which can obviously never have a texture stack streamed). |
|
// This is valid in this case as we're still authoring the material. |
|
} |
|
#endif // ENABLE_VIRTUALTEXTURES |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
bool TimedNodesShouldUpdate(EditorWindow editorWindow) |
|
{ |
|
// get current screen FPS, clamp to what we consider a valid range |
|
// this is probably not accurate for multi-monitor.. but should be relevant to at least one of the monitors |
|
double monitorFPS = Screen.currentResolution.refreshRate + 1.0; // +1 to round up, since it is an integer and rounded down |
|
if (Double.IsInfinity(monitorFPS) || Double.IsNaN(monitorFPS)) |
|
monitorFPS = 60.0f; |
|
monitorFPS = Math.Min(monitorFPS, 144.0); |
|
monitorFPS = Math.Max(monitorFPS, 30.0); |
|
|
|
var curTime = EditorApplication.timeSinceStartup; |
|
var deltaTime = curTime - m_LastTimedUpdateTime; |
|
bool isFocusedWindow = (EditorWindow.focusedWindow == editorWindow); |
|
|
|
// we throttle the update rate, based on whether the window is focused and if unity is active |
|
const double k_AnimatedFPS_WhenNotFocused = 10.0; |
|
const double k_AnimatedFPS_WhenInactive = 2.0; |
|
double maxAnimatedFPS = |
|
(UnityEditorInternal.InternalEditorUtility.isApplicationActive ? |
|
(isFocusedWindow ? monitorFPS : k_AnimatedFPS_WhenNotFocused) : |
|
k_AnimatedFPS_WhenInactive); |
|
|
|
bool update = (deltaTime > (1.0 / maxAnimatedFPS)); |
|
if (update) |
|
m_LastTimedUpdateTime = curTime; |
|
return update; |
|
} |
|
|
|
private static readonly ProfilerMarker RenderPreviewsMarker = new ProfilerMarker("RenderPreviews"); |
|
public void RenderPreviews(EditorWindow editorWindow, bool requestShaders = true) |
|
{ |
|
using (RenderPreviewsMarker.Auto()) |
|
using (var renderList2D = PooledList<PreviewRenderData>.Get()) |
|
using (var renderList3D = PooledList<PreviewRenderData>.Get()) |
|
using (var nodesToDraw = PooledHashSet<AbstractMaterialNode>.Get()) |
|
using (var perMaterialPreviewProperties = PooledList<PreviewProperty>.Get()) |
|
{ |
|
// update topology cached data |
|
// including list of time-dependent previews, and the preview mode (2d/3d) |
|
UpdateTopology(); |
|
|
|
if (requestShaders) |
|
UpdateShaders(); |
|
|
|
// Need to late capture custom interpolators because of how their type changes |
|
// can have downstream impacts on dynamic slots. |
|
HashSet<AbstractMaterialNode> customProps = new HashSet<AbstractMaterialNode>(); |
|
PropagateNodes( |
|
new HashSet<AbstractMaterialNode>(m_NodesPropertyChanged.OfType<BlockNode>().Where(b => b.isCustomBlock)), |
|
PropagationDirection.Downstream, |
|
customProps); |
|
|
|
m_NodesPropertyChanged.UnionWith(customProps); |
|
|
|
// all nodes downstream of a changed property must be redrawn (to display the updated the property value) |
|
PropagateNodes(m_NodesPropertyChanged, PropagationDirection.Downstream, nodesToDraw); |
|
|
|
// always update properties from temporary blocks created by master node preview generation |
|
m_NodesPropertyChanged.UnionWith(m_MasterNodeTempBlocks); |
|
|
|
CollectPreviewProperties(m_NodesPropertyChanged, perMaterialPreviewProperties); |
|
m_NodesPropertyChanged.Clear(); |
|
|
|
// timed nodes are animated, so they should be updated regularly (but not necessarily on every update) |
|
// (m_TimedPreviews has been pre-propagated downstream) |
|
// HOWEVER they do not need to collect properties. (the only property changing is time..) |
|
if (TimedNodesShouldUpdate(editorWindow)) |
|
m_PreviewsToDraw.UnionWith(m_TimedPreviews); |
|
|
|
ForEachNodesPreview(nodesToDraw, p => m_PreviewsToDraw.Add(p)); |
|
|
|
// 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; |
|
int drawPreviewCount = 0; |
|
foreach (var preview in m_PreviewsToDraw) |
|
{ |
|
Assert.IsNotNull(preview); |
|
|
|
{ // skip if the node doesn't have a preview expanded (unless it's master) |
|
var node = preview.shaderData.node; |
|
if ((node != kMasterProxyNode) && (!node.hasPreview || !node.previewExpanded)) |
|
continue; |
|
} |
|
|
|
// 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) |
|
{ |
|
preview.texture = null; |
|
preview.NotifyPreviewChanged(); |
|
} |
|
continue; |
|
} |
|
|
|
|
|
if (preview.shaderData.hasError) |
|
{ |
|
preview.texture = m_ErrorTexture; |
|
preview.NotifyPreviewChanged(); |
|
continue; |
|
} |
|
|
|
// skip rendering while a preview shader is being compiled |
|
if (m_PreviewsCompiling.Contains(preview)) |
|
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; |
|
m_SceneResources.light0.intensity = 1.0f; |
|
m_SceneResources.light0.transform.rotation = Quaternion.Euler(50f, 50f, 0); |
|
m_SceneResources.light1.enabled = true; |
|
m_SceneResources.light1.intensity = 1.0f; |
|
m_SceneResources.camera.clearFlags = CameraClearFlags.Color; |
|
|
|
// Render 2D previews |
|
m_SceneResources.camera.transform.position = -Vector3.forward * 2; |
|
m_SceneResources.camera.transform.rotation = Quaternion.identity; |
|
m_SceneResources.camera.orthographicSize = 0.5f; |
|
m_SceneResources.camera.orthographic = true; |
|
|
|
foreach (var renderData in renderList2D) |
|
RenderPreview(renderData, m_SceneResources.quad, Matrix4x4.identity, perMaterialPreviewProperties); |
|
|
|
// Render 3D previews |
|
m_SceneResources.camera.transform.position = -Vector3.forward * 5; |
|
m_SceneResources.camera.transform.rotation = Quaternion.identity; |
|
m_SceneResources.camera.orthographic = false; |
|
|
|
foreach (var renderData in renderList3D) |
|
RenderPreview(renderData, m_SceneResources.sphere, Matrix4x4.identity, perMaterialPreviewProperties); |
|
|
|
if (renderMasterPreview) |
|
{ |
|
if (m_NewMasterPreviewSize.HasValue) |
|
{ |
|
if (masterRenderData.renderTexture != null) |
|
Object.DestroyImmediate(masterRenderData.renderTexture, true); |
|
masterRenderData.renderTexture = new RenderTexture((int)m_NewMasterPreviewSize.Value.x, (int)m_NewMasterPreviewSize.Value.y, 16, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default) { hideFlags = HideFlags.HideAndDontSave }; |
|
masterRenderData.renderTexture.Create(); |
|
masterRenderData.texture = masterRenderData.renderTexture; |
|
m_NewMasterPreviewSize = null; |
|
} |
|
var mesh = m_Graph.previewData.serializedMesh.mesh; |
|
var preventRotation = m_Graph.previewData.preventRotation; |
|
if (!mesh) |
|
{ |
|
var useSpritePreview = |
|
m_Graph.activeTargets.LastOrDefault(t => t.IsActive())?.prefersSpritePreview ?? false; |
|
mesh = useSpritePreview ? m_SceneResources.quad : m_SceneResources.sphere; |
|
preventRotation = useSpritePreview; |
|
} |
|
|
|
var previewTransform = preventRotation ? Matrix4x4.identity : 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); |
|
previewTransform *= Matrix4x4.Translate(-mesh.bounds.center); |
|
|
|
RenderPreview(masterRenderData, mesh, previewTransform, perMaterialPreviewProperties); |
|
} |
|
|
|
m_SceneResources.light0.enabled = false; |
|
m_SceneResources.light1.enabled = false; |
|
|
|
foreach (var renderData in renderList2D) |
|
renderData.NotifyPreviewChanged(); |
|
foreach (var renderData in renderList3D) |
|
renderData.NotifyPreviewChanged(); |
|
if (renderMasterPreview) |
|
masterRenderData.NotifyPreviewChanged(); |
|
} |
|
} |
|
|
|
private static readonly ProfilerMarker ProcessCompletedShaderCompilationsMarker = new ProfilerMarker("ProcessCompletedShaderCompilations"); |
|
private int compileFailRekicks = 0; |
|
void ProcessCompletedShaderCompilations() |
|
{ |
|
// Check for shaders that finished compiling and set them to redraw |
|
using (ProcessCompletedShaderCompilationsMarker.Auto()) |
|
using (var previewsCompiled = PooledHashSet<PreviewRenderData>.Get()) |
|
{ |
|
foreach (var preview in m_PreviewsCompiling) |
|
{ |
|
{ |
|
var node = preview.shaderData.node; |
|
Assert.IsFalse(node is BlockNode); |
|
} |
|
|
|
PreviewRenderData renderData = preview; |
|
PreviewShaderData shaderData = renderData.shaderData; |
|
|
|
// 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) |
|
{ |
|
shaderData.passesCompiling = 0; |
|
previewsCompiled.Add(renderData); |
|
m_PreviewsNeedsRecompile.Add(renderData); |
|
continue; |
|
} |
|
else if (compileFailRekicks == 4) |
|
{ |
|
Debug.LogWarning("Unexpected error in compiling preview shaders: some previews might not update. You can try to re-open the Shader Graph window, or select <b>Help > Report a Bug</b> in the menu and report this bug."); |
|
} |
|
} |
|
|
|
// check that all passes have compiled |
|
var allPassesCompiled = true; |
|
for (var i = 0; i < renderData.shaderData.mat.passCount; i++) |
|
{ |
|
if (!ShaderUtil.IsPassCompiled(renderData.shaderData.mat, i)) |
|
{ |
|
allPassesCompiled = false; |
|
break; |
|
} |
|
} |
|
|
|
if (!allPassesCompiled) |
|
{ |
|
// keep waiting |
|
continue; |
|
} |
|
|
|
// Force the material to re-generate all it's shader properties, by reassigning the shader |
|
renderData.shaderData.mat.shader = renderData.shaderData.shader; |
|
renderData.shaderData.passesCompiling = 0; |
|
renderData.shaderData.isOutOfDate = false; |
|
CheckForErrors(renderData.shaderData); |
|
|
|
previewsCompiled.Add(renderData); |
|
} |
|
|
|
// removed compiled nodes from compiling list |
|
m_PreviewsCompiling.ExceptWith(previewsCompiled); |
|
|
|
// 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); |
|
} |
|
} |
|
|
|
private static readonly ProfilerMarker KickOffShaderCompilationsMarker = new ProfilerMarker("KickOffShaderCompilations"); |
|
void KickOffShaderCompilations() |
|
{ |
|
// Start compilation for nodes that need to recompile |
|
using (KickOffShaderCompilationsMarker.Auto()) |
|
using (var previewsToCompile = PooledHashSet<PreviewRenderData>.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) |
|
{ |
|
if (m_PreviewsNeedsRecompile.Contains(m_MasterRenderData) && |
|
!m_PreviewsCompiling.Contains(m_MasterRenderData)) |
|
{ |
|
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_PreviewsNeedsRecompile.Count(); i++) |
|
{ |
|
if (m_PreviewsCompiling.Count + previewsToCompile.Count >= m_MaxPreviewsCompiling) |
|
break; |
|
|
|
var preview = m_PreviewsNeedsRecompile.ElementAt(i); |
|
if (preview == m_MasterRenderData) // master preview is handled specially above |
|
continue; |
|
|
|
var node = preview.shaderData.node; |
|
Assert.IsNotNull(node); |
|
Assert.IsFalse(node is BlockNode); |
|
|
|
if (node.hasPreview && node.previewExpanded && !m_PreviewsCompiling.Contains(preview)) |
|
{ |
|
previewsToCompile.Add(preview); |
|
} |
|
} |
|
|
|
if (previewsToCompile.Count >= 0) |
|
using (var nodesToCompile = PooledHashSet<AbstractMaterialNode>.Get()) |
|
{ |
|
// remove the selected nodes from the recompile list |
|
m_PreviewsNeedsRecompile.ExceptWith(previewsToCompile); |
|
|
|
// Reset error states for the UI, the shader, and all render data for nodes we're recompiling |
|
nodesToCompile.UnionWith(previewsToCompile.Select(x => x.shaderData.node)); |
|
nodesToCompile.Remove(null); |
|
|
|
// 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; |
|
ShaderUtil.allowAsyncCompilation = true; |
|
|
|
// kick async compiles for all nodes in m_NodeToCompile |
|
foreach (var preview in previewsToCompile) |
|
{ |
|
if (preview == m_MasterRenderData) |
|
{ |
|
CompileMasterNodeShader(); |
|
continue; |
|
} |
|
|
|
var node = preview.shaderData.node; |
|
Assert.IsNotNull(node); // master preview is handled above |
|
|
|
// Get shader code and compile |
|
var generator = new Generator(node.owner, node, GenerationMode.Preview, $"hidden/preview/{node.GetVariableNameForNode()}", null); |
|
BeginCompile(preview, generator.generatedShader); |
|
} |
|
|
|
ShaderUtil.allowAsyncCompilation = wasAsyncAllowed; |
|
} |
|
} |
|
} |
|
|
|
private static readonly ProfilerMarker UpdateShadersMarker = new ProfilerMarker("UpdateShaders"); |
|
void UpdateShaders() |
|
{ |
|
using (UpdateShadersMarker.Auto()) |
|
{ |
|
ProcessCompletedShaderCompilations(); |
|
|
|
if (m_NodesShaderChanged.Count > 0) |
|
{ |
|
// nodes with shader changes cause all downstream nodes to need recompilation |
|
// (since they presumably include the code for these nodes) |
|
using (var nodesToRecompile = PooledHashSet<AbstractMaterialNode>.Get()) |
|
{ |
|
PropagateNodes(m_NodesShaderChanged, PropagationDirection.Downstream, nodesToRecompile); |
|
ForEachNodesPreview(nodesToRecompile, p => m_PreviewsNeedsRecompile.Add(p)); |
|
|
|
m_NodesShaderChanged.Clear(); |
|
} |
|
} |
|
|
|
// if there's nothing to update, or if too many nodes are still compiling, then just return |
|
if ((m_PreviewsNeedsRecompile.Count == 0) || (m_PreviewsCompiling.Count >= m_MaxPreviewsCompiling)) |
|
return; |
|
|
|
// flag all nodes in m_PreviewsNeedsRecompile as having out of date textures, and redraw them |
|
foreach (var preview in m_PreviewsNeedsRecompile) |
|
{ |
|
Assert.IsNotNull(preview); |
|
if (!preview.shaderData.isOutOfDate) |
|
{ |
|
preview.shaderData.isOutOfDate = true; |
|
preview.NotifyPreviewChanged(); |
|
} |
|
} |
|
|
|
InitializeSRPIfNeeded(); // SRP must be initialized to compile master node previews |
|
|
|
KickOffShaderCompilations(); |
|
} |
|
} |
|
|
|
private static readonly ProfilerMarker BeginCompileMarker = new ProfilerMarker("BeginCompile"); |
|
void BeginCompile(PreviewRenderData renderData, string shaderStr) |
|
{ |
|
using (BeginCompileMarker.Auto()) |
|
{ |
|
var shaderData = renderData.shaderData; |
|
|
|
// want to ensure this so we don't get confused with multiple compile versions in flight |
|
Assert.IsTrue(shaderData.passesCompiling == 0); |
|
|
|
if (shaderData.shader == null) |
|
{ |
|
shaderData.shader = ShaderUtil.CreateShaderAsset(shaderStr, false); |
|
shaderData.shader.hideFlags = HideFlags.HideAndDontSave; |
|
} |
|
else |
|
{ |
|
ShaderUtil.ClearCachedData(shaderData.shader); |
|
ShaderUtil.ClearShaderMessages(shaderData.shader); |
|
ShaderUtil.UpdateShaderAsset(shaderData.shader, shaderStr, false); |
|
} |
|
|
|
// Set up the material we use for the preview |
|
// Due to case 1259744, we have to re-create the material to update the preview material keywords |
|
Object.DestroyImmediate(shaderData.mat); |
|
{ |
|
shaderData.mat = new Material(shaderData.shader) { hideFlags = HideFlags.HideAndDontSave }; |
|
if (renderData == m_MasterRenderData) |
|
{ |
|
// apply active target settings to the Material |
|
foreach (var target in m_Graph.activeTargets) |
|
{ |
|
if (target.IsActive()) |
|
target.ProcessPreviewMaterial(renderData.shaderData.mat); |
|
} |
|
} |
|
} |
|
|
|
int materialPassCount = shaderData.mat.passCount; |
|
if (materialPassCount <= 0) |
|
Debug.Log("Zero Passes ON COMPILE: " + shaderData.node.name + " (" + shaderData.passesCompiling + " passes, " + renderData.shaderData.mat.passCount + " mat passes)"); |
|
else |
|
{ |
|
shaderData.passesCompiling = materialPassCount; |
|
for (var i = 0; i < materialPassCount; i++) |
|
{ |
|
ShaderUtil.CompilePass(shaderData.mat, i); |
|
} |
|
m_PreviewsCompiling.Add(renderData); |
|
} |
|
} |
|
} |
|
|
|
private void ForEachNodesPreview( |
|
IEnumerable<AbstractMaterialNode> nodes, |
|
Action<PreviewRenderData> action) |
|
{ |
|
foreach (var node in nodes) |
|
{ |
|
var preview = GetPreviewRenderData(node); |
|
if (preview != null) // some output nodes may have no preview |
|
action(preview); |
|
} |
|
} |
|
|
|
class NodeProcessor |
|
{ |
|
// parameters |
|
GraphData graphData; |
|
Action<AbstractMaterialNode, IEnumerable<AbstractMaterialNode>> process; |
|
|
|
// node tracking state |
|
HashSet<AbstractMaterialNode> processing = new HashSet<AbstractMaterialNode>(); |
|
HashSet<AbstractMaterialNode> processed = new HashSet<AbstractMaterialNode>(); |
|
|
|
// iteration state stack |
|
Stack<AbstractMaterialNode> nodeStack = new Stack<AbstractMaterialNode>(); |
|
Stack<int> childStartStack = new Stack<int>(); |
|
Stack<int> curChildStack = new Stack<int>(); |
|
Stack<int> stateStack = new Stack<int>(); |
|
|
|
List<AbstractMaterialNode> allChildren = new List<AbstractMaterialNode>(); |
|
|
|
public NodeProcessor(GraphData graphData, Action<AbstractMaterialNode, IEnumerable<AbstractMaterialNode>> 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<AbstractMaterialNode> rootNodes, // root nodes can share subtrees, but cannot themselves exist in any others subtree |
|
Action<AbstractMaterialNode, IEnumerable<AbstractMaterialNode>> 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 UpdateTopologyMarker = new ProfilerMarker("UpdateTopology"); |
|
void UpdateTopology() |
|
{ |
|
if (!m_TopologyDirty) |
|
return; |
|
|
|
using (UpdateTopologyMarker.Auto()) |
|
using (var timedNodes = PooledHashSet<AbstractMaterialNode>.Get()) |
|
{ |
|
timedNodes.UnionWith(m_Graph.GetNodes<AbstractMaterialNode>().Where(n => n.RequiresTime())); |
|
|
|
// 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(); |
|
ForEachNodesPreview(timedNodes, p => m_TimedPreviews.Add(p)); |
|
} |
|
|
|
// 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 preference |
|
preview.previewMode = node.previewMode; |
|
|
|
// Inherit becomes 2D or 3D based on child state |
|
if (preview.previewMode == PreviewMode.Inherit) |
|
{ |
|
if (children.Any(child => GetPreviewRenderData(child).previewMode == PreviewMode.Preview3D)) |
|
preview.previewMode = PreviewMode.Preview3D; |
|
else |
|
preview.previewMode = PreviewMode.Preview2D; |
|
} |
|
}); |
|
|
|
m_TopologyDirty = false; |
|
} |
|
|
|
private static readonly ProfilerMarker RenderPreviewMarker = new ProfilerMarker("RenderPreview"); |
|
void RenderPreview(PreviewRenderData renderData, Mesh mesh, Matrix4x4 transform, PooledList<PreviewProperty> perMaterialPreviewProperties) |
|
{ |
|
using (RenderPreviewMarker.Auto()) |
|
{ |
|
var wasAsyncAllowed = ShaderUtil.allowAsyncCompilation; |
|
ShaderUtil.allowAsyncCompilation = true; |
|
|
|
AssignPerMaterialPreviewProperties(renderData.shaderData.mat, perMaterialPreviewProperties); |
|
|
|
var previousRenderTexture = RenderTexture.active; |
|
|
|
//Temp workaround for alpha previews... |
|
var temp = RenderTexture.GetTemporary(renderData.renderTexture.descriptor); |
|
RenderTexture.active = temp; |
|
Graphics.Blit(Texture2D.whiteTexture, temp, m_SceneResources.checkerboardMaterial); |
|
|
|
// Mesh is invalid for VFXTarget |
|
// We should handle this more gracefully |
|
if (renderData != m_MasterRenderData || !m_Graph.isOnlyVFXTarget) |
|
{ |
|
m_SceneResources.camera.targetTexture = temp; |
|
Graphics.DrawMesh(mesh, transform, renderData.shaderData.mat, 1, m_SceneResources.camera, 0, m_SharedPreviewPropertyBlock, ShadowCastingMode.Off, false, null, false); |
|
} |
|
|
|
var previousUseSRP = Unsupported.useScriptableRenderPipeline; |
|
Unsupported.useScriptableRenderPipeline = (renderData == m_MasterRenderData); |
|
m_SceneResources.camera.Render(); |
|
Unsupported.useScriptableRenderPipeline = previousUseSRP; |
|
|
|
Graphics.Blit(temp, renderData.renderTexture, m_SceneResources.blitNoAlphaMaterial); |
|
RenderTexture.ReleaseTemporary(temp); |
|
|
|
RenderTexture.active = previousRenderTexture; |
|
renderData.texture = renderData.renderTexture; |
|
|
|
m_PreviewsToDraw.Remove(renderData); |
|
|
|
ShaderUtil.allowAsyncCompilation = wasAsyncAllowed; |
|
} |
|
} |
|
|
|
void InitializeSRPIfNeeded() |
|
{ |
|
if ((Shader.globalRenderPipeline != null) && (Shader.globalRenderPipeline.Length > 0)) |
|
{ |
|
return; |
|
} |
|
|
|
// issue a dummy SRP render to force SRP initialization, use the master node texture |
|
PreviewRenderData renderData = m_MasterRenderData; |
|
var previousRenderTexture = RenderTexture.active; |
|
|
|
//Temp workaround for alpha previews... |
|
var temp = RenderTexture.GetTemporary(renderData.renderTexture.descriptor); |
|
RenderTexture.active = temp; |
|
Graphics.Blit(Texture2D.whiteTexture, temp, m_SceneResources.checkerboardMaterial); |
|
|
|
m_SceneResources.camera.targetTexture = temp; |
|
|
|
var previousUseSRP = Unsupported.useScriptableRenderPipeline; |
|
Unsupported.useScriptableRenderPipeline = true; |
|
m_SceneResources.camera.Render(); |
|
Unsupported.useScriptableRenderPipeline = previousUseSRP; |
|
|
|
RenderTexture.ReleaseTemporary(temp); |
|
|
|
RenderTexture.active = previousRenderTexture; |
|
} |
|
|
|
void CheckForErrors(PreviewShaderData shaderData) |
|
{ |
|
shaderData.hasError = ShaderUtil.ShaderHasError(shaderData.shader); |
|
if (shaderData.hasError) |
|
{ |
|
var messages = ShaderUtil.GetShaderMessages(shaderData.shader); |
|
if (messages.Length > 0) |
|
{ |
|
// TODO: Where to add errors to the stack?? |
|
if (shaderData.node == null) |
|
return; |
|
|
|
m_Messenger.AddOrAppendError(this, shaderData.node.objectId, messages[0]); |
|
ShaderUtil.ClearShaderMessages(shaderData.shader); |
|
} |
|
} |
|
} |
|
|
|
void CompileMasterNodeShader() |
|
{ |
|
var shaderData = masterRenderData?.shaderData; |
|
|
|
// Skip generation for VFXTarget |
|
if (!m_Graph.isOnlyVFXTarget) |
|
{ |
|
var generator = new Generator(m_Graph, m_Graph.outputNode, GenerationMode.Preview, "Master", null); |
|
shaderData.shaderString = generator.generatedShader; |
|
|
|
// record the blocks temporarily created for missing stack blocks |
|
m_MasterNodeTempBlocks.Clear(); |
|
foreach (var block in generator.temporaryBlocks) |
|
{ |
|
m_MasterNodeTempBlocks.Add(block); |
|
} |
|
} |
|
|
|
if (string.IsNullOrEmpty(shaderData.shaderString)) |
|
{ |
|
if (shaderData.shader != null) |
|
{ |
|
ShaderUtil.ClearShaderMessages(shaderData.shader); |
|
Object.DestroyImmediate(shaderData.shader, true); |
|
shaderData.shader = null; |
|
} |
|
return; |
|
} |
|
|
|
BeginCompile(masterRenderData, shaderData.shaderString); |
|
} |
|
|
|
void DestroyRenderData(PreviewRenderData renderData) |
|
{ |
|
if (renderData.shaderData != null) |
|
{ |
|
if (renderData.shaderData.mat != null) |
|
{ |
|
Object.DestroyImmediate(renderData.shaderData.mat, true); |
|
} |
|
if (renderData.shaderData.shader != null) |
|
{ |
|
ShaderUtil.ClearShaderMessages(renderData.shaderData.shader); |
|
Object.DestroyImmediate(renderData.shaderData.shader, true); |
|
} |
|
} |
|
|
|
if (renderData.renderTexture != null) |
|
Object.DestroyImmediate(renderData.renderTexture, true); |
|
|
|
if (renderData.shaderData != null && renderData.shaderData.node != null) |
|
renderData.shaderData.node.UnregisterCallback(OnNodeModified); |
|
} |
|
|
|
void DestroyPreview(AbstractMaterialNode node) |
|
{ |
|
if (node is BlockNode) |
|
{ |
|
// block nodes don't have preview render data |
|
Assert.IsFalse(m_RenderDatas.ContainsKey(node)); |
|
node.UnregisterCallback(OnNodeModified); |
|
UpdateMasterPreview(ModificationScope.Topological); |
|
return; |
|
} |
|
|
|
if (!m_RenderDatas.TryGetValue(node, out var renderData)) |
|
{ |
|
return; |
|
} |
|
|
|
m_PreviewsNeedsRecompile.Remove(renderData); |
|
m_PreviewsCompiling.Remove(renderData); |
|
m_PreviewsToDraw.Remove(renderData); |
|
m_TimedPreviews.Remove(renderData); |
|
|
|
DestroyRenderData(renderData); |
|
m_RenderDatas.Remove(node); |
|
} |
|
|
|
void ReleaseUnmanagedResources() |
|
{ |
|
if (m_ErrorTexture != null) |
|
{ |
|
Object.DestroyImmediate(m_ErrorTexture); |
|
m_ErrorTexture = null; |
|
} |
|
if (m_SceneResources != null) |
|
{ |
|
m_SceneResources.Dispose(); |
|
m_SceneResources = null; |
|
} |
|
foreach (var renderData in m_RenderDatas.Values) |
|
DestroyRenderData(renderData); |
|
m_RenderDatas.Clear(); |
|
m_SharedPreviewPropertyBlock.Clear(); |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
ReleaseUnmanagedResources(); |
|
GC.SuppressFinalize(this); |
|
} |
|
|
|
~PreviewManager() |
|
{ |
|
throw new Exception("PreviewManager was not disposed of properly."); |
|
} |
|
} |
|
|
|
delegate void OnPreviewChanged(); |
|
|
|
class PreviewShaderData |
|
{ |
|
public AbstractMaterialNode node; |
|
public Shader shader; |
|
public Material mat; |
|
public string shaderString; |
|
public int passesCompiling; |
|
public bool isOutOfDate; |
|
public bool hasError; |
|
} |
|
|
|
class PreviewRenderData |
|
{ |
|
public string previewName; |
|
public PreviewShaderData shaderData; |
|
public RenderTexture renderTexture; |
|
public Texture texture; |
|
public PreviewMode previewMode; |
|
public OnPreviewChanged onPreviewChanged; |
|
|
|
public void NotifyPreviewChanged() |
|
{ |
|
if (onPreviewChanged != null) |
|
onPreviewChanged(); |
|
} |
|
} |
|
}
|
|
|