diff --git a/Packages/com.unity.render-pipelines.core/Editor/Analytics/RenderGraphViewerSessionAnalytic.cs b/Packages/com.unity.render-pipelines.core/Editor/Analytics/RenderGraphViewerSessionAnalytic.cs new file mode 100644 index 00000000000..4a1bddd4fcf --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Analytics/RenderGraphViewerSessionAnalytic.cs @@ -0,0 +1,70 @@ +using System; +using UnityEngine; +using UnityEngine.Analytics; +using UnityEngine.Rendering; +using UnityEngine.Rendering.RenderGraphModule; + +namespace UnityEditor.Rendering.Analytics +{ + // schema = com.unity3d.data.schemas.editor.analytics.uRenderGraphViewerSessionCreated_v1 + // taxonomy = editor.analytics.uRenderGraphViewerSessionCreated.v1 + internal class RenderGraphViewerSessionCreatedAnalytic + { + const int k_MaxEventsPerHour = 1000; + const int k_MaxNumberOfElements = 1000; + const string k_VendorKey = "unity.srp"; + const string k_EventName = "uRenderGraphViewerSessionCreated"; + + public enum SessionType + { + Local = 0, + Remote = 1 + } + + [AnalyticInfo(eventName: k_EventName, vendorKey: k_VendorKey, maxEventsPerHour: k_MaxEventsPerHour, maxNumberOfElements: k_MaxNumberOfElements)] + class Analytic : IAnalytic + { + public Analytic(SessionType sessionType, DebugMessageHandler.AnalyticsPayload payload) + { + using (GenericPool.Get(out var data)) + { + data.session_type = sessionType.ToString(); + data.graphics_device_type = payload.graphicsDeviceType.ToString(); + data.device_type = payload.deviceType.ToString(); + data.device_model = payload.deviceModel; + data.gpu_vendor = payload.gpuVendor; + data.gpu_name = payload.gpuName; + + m_Data = data; + } + } + + [Serializable] + class Data : IAnalytic.IData + { + // Naming convention for analytics data + public string session_type; + public string graphics_device_type; + public string device_type; + public string device_model; + public string gpu_vendor; + public string gpu_name; + } + + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + data = m_Data; + error = null; + return true; + } + + Data m_Data; + }; + + public static void Send(SessionType sessionType, DebugMessageHandler.AnalyticsPayload payload) + { + Analytic analytic = new Analytic(sessionType, payload); + AnalyticsUtils.SendData(analytic); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/Analytics/RenderGraphViewerSessionAnalytic.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/Analytics/RenderGraphViewerSessionAnalytic.cs.meta new file mode 100644 index 00000000000..65b17a34a6d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Analytics/RenderGraphViewerSessionAnalytic.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0a22b1f2645743e0b78bd9fd651a38c8 +timeCreated: 1746442904 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/CommandBuffers/CommandBufferGenerator/CommandBufferGenerator.cs b/Packages/com.unity.render-pipelines.core/Editor/CommandBuffers/CommandBufferGenerator/CommandBufferGenerator.cs index 74075bafa61..3dbab021559 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/CommandBuffers/CommandBufferGenerator/CommandBufferGenerator.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/CommandBuffers/CommandBufferGenerator/CommandBufferGenerator.cs @@ -58,6 +58,7 @@ class CommandBufferGenerator // Functions for compute only static List computeFunctions = new List { + "SetComputeParamsFromMaterial", "SetComputeFloatParam", "SetComputeIntParam", "SetComputeVectorArrayParam", @@ -68,11 +69,7 @@ class CommandBufferGenerator new FunctionInfo("SetComputeTextureParam", textureArg: "rt"), "SetComputeBufferParam", "SetComputeConstantBufferParam", - "SetComputeFloatParam", - "SetComputeIntParam", "SetComputeVectorParam", - "SetComputeVectorArrayParam", - "SetComputeMatrixParam", "DispatchCompute", "BuildRayTracingAccelerationStructure", "SetRayTracingAccelerationStructure", @@ -87,6 +84,7 @@ class CommandBufferGenerator "SetRayTracingVectorArrayParam", "SetRayTracingMatrixParam", "SetRayTracingMatrixArrayParam", + "SetRayTracingShaderPass", "DispatchRays", "SetBufferData", "SetBufferCounterValue", @@ -119,6 +117,17 @@ class CommandBufferGenerator "SetRenderTarget", "Clear", "RequestAsyncReadbackIntoNativeArray", + "RequestAsyncReadback", + "ClearRandomWriteTargets", + "SetRandomWriteTarget", + "CopyTexture", + "GenerateMips", + // Next APIs are already available in UnsafeCommandBuffer through compute or base functions lists, + // but the added overloads below relax safety rules to support texture parameters without texture handle usage. + new FunctionInfo("SetGlobalTexture", textureArg: "", modifiesGlobalState: true), + "SetRayTracingTextureParam", + "SetComputeTextureParam", + // End of added unsafe overloads }; // Generated file header static string preamble = @@ -129,6 +138,7 @@ class CommandBufferGenerator using UnityEngine.Profiling; using Unity.Profiling; using UnityEngine.Rendering.RenderGraphModule; +using UnityEngine.Experimental.Rendering; // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE // @@ -255,21 +265,21 @@ static void GenerateCommandBufferType(string className, string docString, string BindingFlags flags = BindingFlags.Public | BindingFlags.Instance; var methods = commandBufferType.GetMethods(flags); + var allowedFuncInfoMethodList = new List>(); + + // Filtering function list to only keep the ones existing in CommandBuffer foreach (var method in methods) { - bool allowed = false; - FunctionInfo info = new FunctionInfo(); foreach (var fn in functionList) { if (fn.name == method.Name) - { - allowed = true; - info = fn; - break; - } + allowedFuncInfoMethodList.Add(new Tuple(fn, method)); } - if (!allowed) continue; + } + // Generating function list, we can have duplicates due to overloads in methods and different support in the different functionLists (TextureHandle overload or not) + foreach (var allowedFuncInfoMethod in allowedFuncInfoMethodList) + { StringBuilder argList = new StringBuilder(); StringBuilder typedArgList = new StringBuilder(); StringBuilder argDocList = new StringBuilder(); @@ -277,6 +287,9 @@ static void GenerateCommandBufferType(string className, string docString, string StringBuilder genericArgList = new StringBuilder(); StringBuilder genericConstraints = new StringBuilder(); + var info = allowedFuncInfoMethod.Item1; + var method = allowedFuncInfoMethod.Item2; + if (info.modifiesGlobalState) { validationCode.Append("ThrowIfGlobalStateNotAllowed(); "); @@ -326,7 +339,7 @@ static void GenerateCommandBufferType(string className, string docString, string if (method.ContainsGenericParameters) { - // Hack: The current command buffer only inclues very simple single generic functions. We just hard code these but in the future we might need reflection on the generic arguments if needed. + // Hack: The current command buffer only includes very simple single generic functions. We just hard code these but in the future we might need reflection on the generic arguments if needed. genericArgList.Append(""); genericConstraints.Append("where T : struct"); argDocList.Append("\n"); diff --git a/Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs b/Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs index 625821b9870..67ae557bf26 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs @@ -537,7 +537,7 @@ void OnGUI() if (dragging) { splitterPos += Event.current.delta.x; - splitterPos = Mathf.Clamp(splitterPos, minSideBarWidth, Screen.width - minContentWidth); + splitterPos = Mathf.Clamp(splitterPos, minSideBarWidth, position.width - minContentWidth); Repaint(); } break; diff --git a/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/Refresh@2x.png b/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/Refresh@2x.png deleted file mode 100644 index 0d31a75c91e..00000000000 Binary files a/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/Refresh@2x.png and /dev/null differ diff --git a/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/d_Refresh@2x.png b/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/d_Refresh@2x.png deleted file mode 100644 index dd4f0ff48bb..00000000000 Binary files a/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/d_Refresh@2x.png and /dev/null differ diff --git a/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/d_Refresh@2x.png.meta b/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/d_Refresh@2x.png.meta deleted file mode 100644 index a521bcbca4e..00000000000 --- a/Packages/com.unity.render-pipelines.core/Editor/Icons/RenderGraphViewer/d_Refresh@2x.png.meta +++ /dev/null @@ -1,166 +0,0 @@ -fileFormatVersion: 2 -guid: d285b5fd5692f2546884cd28a928e1da -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 13 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 0 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 4 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: CloudRendering - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Win - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Switch - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Win64 - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.cs b/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.cs index 83289b393d2..de20efb7d8e 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.cs @@ -647,7 +647,12 @@ public void Dispose() static bool m_IsInit = false; static BakingBatch m_BakingBatch; - static ProbeVolumeBakingSet m_BakingSet = null; + static ProbeVolumeBakingSetWeakReference m_BakingSetReference = new(); + static ProbeVolumeBakingSet m_BakingSet + { + get => m_BakingSetReference.Get(); + set => m_BakingSetReference.Set(value); + } static TouchupVolumeWithBoundsList s_AdjustmentVolumes; static Bounds globalBounds = new Bounds(); diff --git a/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeLightingTab.cs b/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeLightingTab.cs index fccb3e25701..4e087fe443d 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeLightingTab.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeLightingTab.cs @@ -112,18 +112,18 @@ static ProbeVolumeBakingSet defaultSet bool m_TempBakingSet = false; bool m_Initialized = false; - ProbeVolumeBakingSet m_ActiveSet; + ProbeVolumeBakingSetWeakReference m_ActiveSet = new(); ProbeVolumeBakingSet activeSet { - get => m_ActiveSet; + get => m_ActiveSet.Get(); set { - if (ReferenceEquals(m_ActiveSet, value)) return; - if (m_TempBakingSet) Object.DestroyImmediate(m_ActiveSet); - m_ActiveSet = value; + if (ReferenceEquals(m_ActiveSet.Get(), value)) return; + if (m_TempBakingSet) Object.DestroyImmediate(m_ActiveSet.Get()); + m_ActiveSet.Set(value); m_TempBakingSet = false; - if (m_ActiveSet == null) return; - m_SingleSceneMode = m_ActiveSet.singleSceneMode; + if (m_ActiveSet.Get() == null) return; + m_SingleSceneMode = m_ActiveSet.Get().singleSceneMode; InitializeSceneList(); } } @@ -169,14 +169,14 @@ public override void OnEnable() bool FindActiveSet() { - if (m_ActiveSet == null) + if (m_ActiveSet.Get() == null) { activeSet = ProbeVolumeBakingSet.GetBakingSetForScene(SceneManager.GetActiveScene()); for (int i = 0; activeSet == null && i < SceneManager.sceneCount; i++) activeSet = ProbeVolumeBakingSet.GetBakingSetForScene(SceneManager.GetSceneAt(i)); } - return m_ActiveSet != null; + return m_ActiveSet.Get() != null; } void Initialize() diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorLocalDebugSession.cs b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorLocalDebugSession.cs new file mode 100644 index 00000000000..79070230a4b --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorLocalDebugSession.cs @@ -0,0 +1,17 @@ +using UnityEditor.Rendering.Analytics; + +namespace UnityEngine.Rendering.RenderGraphModule +{ + internal sealed class RenderGraphEditorLocalDebugSession : RenderGraphDebugSession + { + public override bool isActive => true; + + public RenderGraphEditorLocalDebugSession() : base() + { + RegisterAllLocallyKnownGraphsAndExecutions(); + + var analyticsPayload = new DebugMessageHandler.AnalyticsPayload(); + RenderGraphViewerSessionCreatedAnalytic.Send(RenderGraphViewerSessionCreatedAnalytic.SessionType.Local, analyticsPayload); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorLocalDebugSession.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorLocalDebugSession.cs.meta new file mode 100644 index 00000000000..574b16f52f4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorLocalDebugSession.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a1fab0dc8c5b451b9f19f59612b24987 +timeCreated: 1746602442 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorRemoteDebugSession.cs b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorRemoteDebugSession.cs new file mode 100644 index 00000000000..ed8e790425f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorRemoteDebugSession.cs @@ -0,0 +1,69 @@ +using UnityEditor.Rendering.Analytics; +using static UnityEngine.Rendering.RenderGraphModule.RenderGraph; + +namespace UnityEngine.Rendering.RenderGraphModule +{ + internal sealed class RenderGraphEditorRemoteDebugSession : RenderGraphDebugSession + { + public override bool isActive => false; + + DebugMessageHandler m_DebugMessageHandler = ScriptableObject.CreateInstance(); + + public RenderGraphEditorRemoteDebugSession() : base() + { + m_DebugMessageHandler.Register(OnMessageFromPlayer); + + // In the case of auto-connect profiler option, the player doesn't receive the OnEditorConnected callback. + // Therefore send an explicit activation message to ensure the player becomes aware of the editor. + m_DebugMessageHandler.Send(DebugMessageHandler.MessageType.Activate); + } + + public override void Dispose() + { + base.Dispose(); + + m_DebugMessageHandler.UnregisterAll(); + CoreUtils.Destroy(m_DebugMessageHandler); + } + + void OnMessageFromPlayer(DebugMessageHandler.MessageType messageType, DebugMessageHandler.IPayload payload) + { + if (messageType == DebugMessageHandler.MessageType.Activate) + { + // In the event that the player starts after the editor, it will request activation from the editor. + m_DebugMessageHandler.Send(DebugMessageHandler.MessageType.Activate); + } + else if (messageType == DebugMessageHandler.MessageType.DebugData) + { + var debugDataPayload = payload as DebugMessageHandler.DebugDataPayload; + if (!debugDataPayload.isCompatible) + { + string errorStr = ""; + RegisterAndUpdateDebugData(errorStr, EntityId.None, errorStr, null); + } + else + { + RegisterAndUpdateDebugData( + debugDataPayload.graphName, + debugDataPayload.executionId, + debugDataPayload.debugData.executionName, + debugDataPayload.debugData); + } + } + else if (messageType == DebugMessageHandler.MessageType.AnalyticsData) + { + if (payload is DebugMessageHandler.AnalyticsPayload { isCompatible: true } analyticsPayload) + { + RenderGraphViewerSessionCreatedAnalytic.Send(RenderGraphViewerSessionCreatedAnalytic.SessionType.Remote, analyticsPayload); + } + } + } + + void RegisterAndUpdateDebugData(string graphName, EntityId executionId, string executionName, DebugData debugData) + { + RegisterGraph(graphName); + RegisterExecution(graphName, executionId, debugData.executionName); + SetDebugData(graphName, executionId, debugData); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorRemoteDebugSession.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorRemoteDebugSession.cs.meta new file mode 100644 index 00000000000..6a92d4d0be2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphEditorRemoteDebugSession.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 135f7e1e9c01406b8ea46f1bfcfb55cd +timeCreated: 1746446346 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.PlayerConnection.cs b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.PlayerConnection.cs new file mode 100644 index 00000000000..1c608b40cf8 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.PlayerConnection.cs @@ -0,0 +1,61 @@ +using System; +using UnityEditor.Networking.PlayerConnection; +using UnityEngine.Networking.PlayerConnection; + +namespace UnityEditor.Rendering +{ + public partial class RenderGraphViewer + { + class PlayerConnection : IDisposable + { + IConnectionState m_ConnectionState; + + bool m_EditorQuitting; + + readonly UnityEngine.Events.UnityAction m_OnPlayerConnected; + readonly UnityEngine.Events.UnityAction m_OnPlayerDisconnected; + + public PlayerConnection(IConnectionState connectionState, UnityEngine.Events.UnityAction onPlayerConnected, UnityEngine.Events.UnityAction onPlayerDisconnected) + { + m_ConnectionState = connectionState; + m_OnPlayerConnected = onPlayerConnected; + m_OnPlayerDisconnected = onPlayerDisconnected; + + EditorConnection.instance.Initialize(); + EditorConnection.instance.RegisterConnection(m_OnPlayerConnected); + EditorConnection.instance.RegisterDisconnection(m_OnPlayerDisconnected); + + EditorApplication.quitting += OnEditorQuitting; + } + + public void Dispose() + { + if (m_ConnectionState != null) + { + EditorConnection.instance.UnregisterConnection(m_OnPlayerConnected); + EditorConnection.instance.UnregisterDisconnection(m_OnPlayerDisconnected); + + // NOTE: There is a bug where editor crashes if we call DisconnectAll during shutdown flow. In this case + // it's fine to skip the disconnect as the player will get notified of it anyway. + if (!m_EditorQuitting) + EditorConnection.instance.DisconnectAll(); + + m_ConnectionState.Dispose(); + m_ConnectionState = null; + + EditorApplication.quitting -= OnEditorQuitting; + } + } + + public void OnConnectionDropdownIMGUI() + { + PlayerConnectionGUILayout.ConnectionTargetSelectionDropdown(m_ConnectionState, EditorStyles.toolbarDropDown, 250); + } + + void OnEditorQuitting() + { + m_EditorQuitting = true; + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.PlayerConnection.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.PlayerConnection.cs.meta new file mode 100644 index 00000000000..a9922918bcf --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.PlayerConnection.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3d79fadf0079447b8a64ec414906d549 +timeCreated: 1744181299 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.SidePanel.cs b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.SidePanel.cs index c670fbd4610..15ba41fd6b7 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.SidePanel.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.SidePanel.cs @@ -31,8 +31,7 @@ static partial class Names public const string kPanelContainer = "panel-container"; public const string kResourceListFoldout = "panel-resource-list"; public const string kPassListFoldout = "panel-pass-list"; - public const string kResourceSearchField = "resource-search-field"; - public const string kPassSearchField = "pass-search-field"; + public const string kSearchField = "search-field"; } static partial class Classes { @@ -57,8 +56,11 @@ static partial class Classes float m_SidePanelFixedPaneHeight = 0; float m_ContentSplitViewFixedPaneWidth = 280; - Dictionary> m_ResourceDescendantCache = new (); - Dictionary> m_PassDescendantCache = new (); + // Lists of text elements that the search filters are able to highlight + Dictionary> m_SidePanelResourceTexts = new (); + Dictionary> m_SidePanelPassTexts = new (); + Dictionary> m_GridResourceListTexts = new (); + Dictionary> m_GridPassListTexts = new (); void InitializeSidePanel() { @@ -110,14 +112,9 @@ void InitializeSidePanel() passListFoldout.icon = m_PassListIcon; passListFoldout.contextMenuGenerator = () => CreateContextMenu(passListFoldout.Q()); - // Search fields - var resourceSearchField = rootVisualElement.Q(Names.kResourceSearchField); - resourceSearchField.placeholderText = "Search"; - resourceSearchField.RegisterValueChangedCallback(evt => OnSearchFilterChanged(m_ResourceDescendantCache, evt.newValue)); - - var passSearchField = rootVisualElement.Q(Names.kPassSearchField); - passSearchField.placeholderText = "Search"; - passSearchField.RegisterValueChangedCallback(evt => OnSearchFilterChanged(m_PassDescendantCache, evt.newValue)); + var searchField = rootVisualElement.Q(Names.kSearchField); + searchField.placeholderText = L10n.Tr("Search"); + searchField.RegisterValueChangedCallback(evt => OnSearchFilterChanged(evt.newValue)); } static bool IsInsideTag(string input, int index) @@ -161,7 +158,7 @@ static bool IsSearchFilterMatch(string str, string searchString, out int startIn private IVisualElementScheduledItem m_PreviousSearch; private string m_PendingSearchString = string.Empty; private const int k_SearchStringLimit = 15; - void OnSearchFilterChanged(Dictionary> elementCache, string searchString) + void OnSearchFilterChanged(string searchString) { // Ensure the search string is within the allowed length limit (15 chars max) if (searchString.Length > k_SearchStringLimit) @@ -186,18 +183,21 @@ void OnSearchFilterChanged(Dictionary> elementC .schedule .Execute(() => { - PerformSearch(elementCache, searchString); + PerformSearch(m_SidePanelResourceTexts, searchString, hideRootElementIfNoMatch: true); + PerformSearch(m_SidePanelPassTexts, searchString, hideRootElementIfNoMatch: true); + PerformSearch(m_GridResourceListTexts, searchString); + PerformSearch(m_GridPassListTexts, searchString); }) .StartingIn(5); // Avoid spamming multiple search if the user types really fast } - internal static void PerformSearch(Dictionary> elementCache, string searchString) + internal static void PerformSearch(Dictionary> elementCache, string searchString, bool hideRootElementIfNoMatch = false) { // Display filter - foreach (var (foldout, descendants) in elementCache) + foreach (var (rootElement, textElements) in elementCache) { bool anyDescendantMatchesSearch = false; - foreach (var elem in descendants) + foreach (var elem in textElements) { var text = elem.text; @@ -224,7 +224,9 @@ internal static void PerformSearch(Dictionary> elem.text = text; anyDescendantMatchesSearch = true; } - foldout.style.display = anyDescendantMatchesSearch ? DisplayStyle.Flex : DisplayStyle.None; + + if (hideRootElementIfNoMatch) + rootElement.style.display = anyDescendantMatchesSearch ? DisplayStyle.Flex : DisplayStyle.None; } } @@ -248,7 +250,7 @@ void PopulateResourceList() UpdatePanelHeights(); - m_ResourceDescendantCache.Clear(); + m_SidePanelResourceTexts.Clear(); int visibleResourceIndex = 0; foreach (var visibleResourceElement in m_ResourceElementsInfo) @@ -310,7 +312,7 @@ void PopulateResourceList() content.Add(resourceItem); - m_ResourceDescendantCache[resourceItem] = resourceItem.Query().Descendents().ToList(); + m_SidePanelResourceTexts[resourceItem] = resourceItem.Query().Descendents().ToList(); } } @@ -329,7 +331,7 @@ void PopulatePassList() UpdatePanelHeights(); - m_PassDescendantCache.Clear(); + m_SidePanelPassTexts.Clear(); void CreateTextElement(VisualElement parent, string text, string className = null) { @@ -492,7 +494,7 @@ void CreateTextElement(VisualElement parent, string text, string className = nul content.Add(passItem); - m_PassDescendantCache[passItem] = passItem.Query().Descendents().ToList(); + m_SidePanelPassTexts[passItem] = passItem.Query().Descendents().ToList(); } } @@ -503,7 +505,7 @@ void SaveSplitViewFixedPaneHeight() void UpdatePanelHeights() { - bool passListExpanded = m_PassListExpanded && (m_CurrentDebugData != null && m_CurrentDebugData.isNRPCompiler); + bool passListExpanded = m_PassListExpanded && HasValidDebugData && m_CurrentDebugData.isNRPCompiler; const int kFoldoutHeaderHeightPx = 18; const int kFoldoutHeaderExpandedMinHeightPx = 50; const int kWindowExtraMarginPx = 6; diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.cs b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.cs index bdd225a41ca..e0b09fd1654 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphViewer.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using UnityEditor.Networking.PlayerConnection; using UnityEditor.Rendering.Analytics; +using UnityEditor.Toolbars; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.Rendering; @@ -17,11 +19,12 @@ namespace UnityEditor.Rendering [CoreRPHelpURL(packageName: "com.unity.render-pipelines.universal", pageName: "render-graph-view")] public partial class RenderGraphViewer : EditorWindowWithHelpButton { - static partial class Names + internal static partial class Names { - public const string kCaptureButton = "capture-button"; + public const string kAutoPauseToggle = "auto-pause-toggle"; public const string kCurrentGraphDropdown = "current-graph-dropdown"; - public const string kCurrentExecutionDropdown = "current-execution-dropdown"; + public const string kConnectionDropdown = "connection-dropdown"; + public const string kCurrentExecutionToolbarMenu = "current-execution-toolbar-menu"; public const string kPassFilterField = "pass-filter-field"; public const string kResourceFilterField = "resource-filter-field"; public const string kViewOptionsField = "view-options-field"; @@ -38,7 +41,7 @@ static partial class Names public const string kPassListCornerOccluder = "pass-list-corner-occluder"; } - static partial class Classes + internal static partial class Classes { public const string kPassListItem = "pass-list__item"; public const string kPassTitle = "pass-title"; @@ -113,8 +116,15 @@ static partial class Classes static readonly Color kReadWriteBlockFillColorDark = new Color32(0xA9, 0xD1, 0x36, 255); static readonly Color kReadWriteBlockFillColorLight = new Color32(0x67, 0x9C, 0x33, 255); - readonly Dictionary> m_RegisteredGraphs = new(); - RenderGraph.DebugData m_CurrentDebugData; + internal RenderGraph.DebugData m_CurrentDebugData; + + PlayerConnection m_PlayerConnection; + + bool m_Paused = false; + + static int s_EditorWindowInstanceId; + + bool HasValidDebugData => m_CurrentDebugData != null && m_CurrentDebugData.valid; Foldout m_ResourcesList; Foldout m_PassList; @@ -137,6 +147,9 @@ static partial class Classes const string kSelectedExecutionEditorPrefsKey = "RenderGraphViewer.SelectedExecution"; const string kViewOptionsEditorPrefsKey = "RenderGraphViewer.ViewOptions"; + IVisualElementScheduledItem m_StoreSelectedExecutionDelayed; + IVisualElementScheduledItem m_RefreshUIDelayed; + PassFilter m_PassFilter = PassFilter.CulledPasses | PassFilter.RasterPasses | PassFilter.UnsafePasses | PassFilter.ComputePasses; PassFilterLegacy m_PassFilterLegacy = PassFilterLegacy.CulledPasses; @@ -150,22 +163,20 @@ enum EmptyStateReason { None = 0, NoGraphRegistered, - NoExecutionRegistered, - NoDataAvailable, WaitingForCameraRender, EmptyPassFilterResult, - EmptyResourceFilterResult + EmptyResourceFilterResult, + IncompatibleDataReceived }; static readonly string[] kEmptyStateMessages = { "", L10n.Tr("No Render Graph has been registered. The Render Graph Viewer is only functional when Render Graph API is in use."), - L10n.Tr("The selected camera has not rendered anything yet using a Render Graph API. Interact with the selected camera to display data in the Render Graph Viewer. Make sure your current SRP is using the Render Graph API."), - L10n.Tr("No data to display. Click refresh to capture data."), - L10n.Tr("Waiting for the selected camera to render. Depending on the camera, you may need to trigger rendering by selecting the Scene or Game view."), + L10n.Tr("Waiting for the selected camera to render. Depending on the camera, you may need to trigger rendering by selecting the Scene or Game view.\n\nEnsure Render Graph is not disabled in Project Settings > Graphics."), L10n.Tr("No passes to display. Select a different Pass Filter to display contents."), - L10n.Tr("No resources to display. Select a different Resource Filter to display contents.") + L10n.Tr("No resources to display. Select a different Resource Filter to display contents."), + L10n.Tr("Editor received incompatible data. Rebuild the player with this version of the editor, or switch to an older version of the editor."), }; private static readonly string[] kLoadActionNames = @@ -859,44 +870,6 @@ void KeyPressed(KeyUpEvent evt) DeselectPass(); } - void RequestCaptureSelectedExecution() - { - if (!CaptureEnabled()) - return; - - selectedRenderGraph.RequestCaptureDebugData(selectedExecutionName); - - ClearGraphViewerUI(); - SetEmptyStateMessage(EmptyStateReason.WaitingForCameraRender); - } - - void SelectedRenderGraphChanged(string newRenderGraphName) - { - foreach (var rg in m_RegisteredGraphs.Keys) - { - if (rg.name == newRenderGraphName) - { - selectedRenderGraph = rg; - return; - } - } - selectedRenderGraph = null; - - if (m_CurrentDebugData != null) - RequestCaptureSelectedExecution(); - } - - void SelectedExecutionChanged(string newExecutionName) - { - if (newExecutionName == selectedExecutionName) - return; - - selectedExecutionName = newExecutionName; - - if (m_CurrentDebugData != null) - RequestCaptureSelectedExecution(); - } - void ClearEmptyStateMessage() { rootVisualElement.Q(Names.kContentContainer).style.display = DisplayStyle.Flex; @@ -913,169 +886,240 @@ void SetEmptyStateMessage(EmptyStateReason reason) emptyStateText.text = $"{kEmptyStateMessages[(int) reason]}"; } - void RebuildRenderGraphPopup() + void OnAutoPlayStatusChanged(ChangeEvent evt) { - var renderGraphDropdownField = rootVisualElement.Q(Names.kCurrentGraphDropdown); - if (m_RegisteredGraphs.Count == 0 || renderGraphDropdownField == null) - { - selectedRenderGraph = null; - return; - } - - var choices = new List(); - foreach (var rg in m_RegisteredGraphs.Keys) - choices.Add(rg.name); + var autoPlayToggle = rootVisualElement.Q(Names.kAutoPauseToggle); + autoPlayToggle.text = evt.newValue ? L10n.Tr("Auto Update") : L10n.Tr("Pause"); + m_Paused = evt.newValue; - renderGraphDropdownField.choices = choices; - renderGraphDropdownField.style.display = DisplayStyle.Flex; - renderGraphDropdownField.value = choices[0]; - SelectedRenderGraphChanged(choices[0]); + // Force update when unpausing + if (!m_Paused) + UpdateCurrentDebugData(); } - void RebuildExecutionPopup() + // Generic helper function to iterate through enum values and add them to a ToolbarMenu. + void BuildEnumFlagsMenu(ToolbarMenu menu, T currentValue, string prefsKey, Action setValue, bool includeNoneAllOptions = true) where T : Enum { - var executionDropdownField = rootVisualElement.Q(Names.kCurrentExecutionDropdown); - List choices = new List(); - if (selectedRenderGraph != null) - { - m_RegisteredGraphs.TryGetValue(selectedRenderGraph, out var executionSet); - choices.AddRange(executionSet); - } - - if (choices.Count == 0 || executionDropdownField == null) + if (!HasValidDebugData) { - selectedExecutionName = null; + menu.style.display = DisplayStyle.None; return; } + menu.style.display = DisplayStyle.Flex; + menu.menu.ClearItems(); - executionDropdownField.choices = choices; - executionDropdownField.RegisterValueChangedCallback(evt => selectedExecutionName = evt.newValue); - - int selectedIndex = 0; - if (EditorPrefs.HasKey(kSelectedExecutionEditorPrefsKey)) + Array enumValues = Enum.GetValues(typeof(T)); + List values = new List(); + int allFlagsValue = 0; + for (int i = 0; i < enumValues.Length; i++) { - string previousSelectedExecution = EditorPrefs.GetString(kSelectedExecutionEditorPrefsKey); - int previousSelectedIndex = choices.IndexOf(previousSelectedExecution); - if (previousSelectedIndex != -1) - selectedIndex = previousSelectedIndex; + T value = (T)enumValues.GetValue(i); + int intValue = Convert.ToInt32(value); + if (intValue != 0) + { + values.Add(value); + allFlagsValue |= intValue; + } + } + T allFlags = (T)Enum.ToObject(typeof(T), allFlagsValue); + if (includeNoneAllOptions) + { + menu.menu.AppendAction(L10n.Tr("Nothing"), _ => { + var newValue = (T)Enum.ToObject(typeof(T), 0); + setValue(newValue); + EditorPrefs.SetInt(prefsKey, 0); + RebuildGraphViewerUI(); + }, _ => { + return Convert.ToInt32(currentValue) == 0 ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal; + }); + menu.menu.AppendAction(L10n.Tr("Everything"), _ => { + setValue(allFlags); + EditorPrefs.SetInt(prefsKey, allFlagsValue); + RebuildGraphViewerUI(); + }, _ => { + return Convert.ToInt32(currentValue) == allFlagsValue ? + DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal; + }); } - // Set value without triggering serialization of the editorpref - executionDropdownField.SetValueWithoutNotify(choices[selectedIndex]); - SelectedExecutionChanged(choices[selectedIndex]); - } - - void OnPassFilterChanged(ChangeEvent evt) - { - m_PassFilter = (PassFilter) evt.newValue; - EditorPrefs.SetInt(kPassFilterEditorPrefsKey, (int)m_PassFilter); - RebuildGraphViewerUI(); + for (int i = 0; i < values.Count; i++) + { + T flag = values[i]; + string name = ObjectNames.NicifyVariableName(flag.ToString()); + menu.menu.AppendAction(name, _ => { + int newValueInt = Convert.ToInt32(currentValue) ^ Convert.ToInt32(flag); + var newValue = (T)Enum.ToObject(typeof(T), newValueInt); + setValue(newValue); + EditorPrefs.SetInt(prefsKey, newValueInt); + RebuildGraphViewerUI(); + }, _ => { + return HasFlag(currentValue, flag) ? + DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal; + }); + } } - void OnPassFilterLegacyChanged(ChangeEvent evt) + // Helper method to check if an enum value has a specific flag. + bool HasFlag(T value, T flag) where T : Enum { - m_PassFilterLegacy = (PassFilterLegacy) evt.newValue; - EditorPrefs.SetInt(kPassFilterLegacyEditorPrefsKey, (int)m_PassFilterLegacy); - RebuildGraphViewerUI(); + return (Convert.ToInt32(value) & Convert.ToInt32(flag)) == Convert.ToInt32(flag); } - void OnResourceFilterChanged(ChangeEvent evt) + void RebuildViewOptionsUI() { - m_ResourceFilter = (ResourceFilter) evt.newValue; - EditorPrefs.SetInt(kResourceFilterEditorPrefsKey, (int)m_ResourceFilter); - RebuildGraphViewerUI(); + var viewOptions = rootVisualElement.Q(Names.kViewOptionsField); + BuildEnumFlagsMenu(viewOptions, m_ViewOptions, kViewOptionsEditorPrefsKey, val => m_ViewOptions = val, false); + viewOptions.text = L10n.Tr("View Options"); } - void OnViewOptionsChanged(ChangeEvent evt) + void RebuildResourceFilterUI() { - m_ViewOptions = (ViewOptions) evt.newValue; - EditorPrefs.SetInt(kViewOptionsEditorPrefsKey, (int)m_ViewOptions); - RebuildGraphViewerUI(); + var resourceFilter = rootVisualElement.Q(Names.kResourceFilterField); + BuildEnumFlagsMenu(resourceFilter, m_ResourceFilter, kResourceFilterEditorPrefsKey, val => m_ResourceFilter = val); + resourceFilter.text = L10n.Tr("Resource Filter"); } void RebuildPassFilterUI() { - var passFilter = rootVisualElement.Q(Names.kPassFilterField); - passFilter.style.display = DisplayStyle.Flex; - // We don't know which callback was registered before, so unregister both. - passFilter.UnregisterCallback>(OnPassFilterChanged); - passFilter.UnregisterCallback>(OnPassFilterLegacyChanged); - if (m_CurrentDebugData.isNRPCompiler) + var passFilter = rootVisualElement.Q(Names.kPassFilterField); + if (m_CurrentDebugData?.isNRPCompiler ?? false) { - passFilter.Init(m_PassFilter); - passFilter.RegisterCallback>(OnPassFilterChanged); + BuildEnumFlagsMenu(passFilter, m_PassFilter, kPassFilterEditorPrefsKey, val => m_PassFilter = val); } else { - passFilter.Init(m_PassFilterLegacy); - passFilter.RegisterCallback>(OnPassFilterLegacyChanged); + BuildEnumFlagsMenu(passFilter, m_PassFilterLegacy, kPassFilterLegacyEditorPrefsKey, + val => m_PassFilterLegacy = val, false); } + passFilter.text = L10n.Tr("Pass Filter"); } - void RebuildResourceFilterUI() + void RebuildAutoPlayUI() { - var resourceFilter = rootVisualElement.Q(Names.kResourceFilterField); - resourceFilter.style.display = DisplayStyle.Flex; - resourceFilter.UnregisterCallback>(OnResourceFilterChanged); - resourceFilter.Init(m_ResourceFilter); - resourceFilter.RegisterCallback>(OnResourceFilterChanged); + var autoPlayToggle = rootVisualElement.Q(Names.kAutoPauseToggle); + autoPlayToggle.UnregisterCallback>(OnAutoPlayStatusChanged); + autoPlayToggle.RegisterCallback>(OnAutoPlayStatusChanged); + autoPlayToggle.value = m_Paused; + autoPlayToggle.text = m_Paused ? L10n.Tr("Auto Update") : L10n.Tr("Pause"); } - void RebuildViewOptionsUI() + void UpdateSelectedGraphAndExecution() { - var viewOptions = rootVisualElement.Q(Names.kViewOptionsField); - viewOptions.style.display = DisplayStyle.Flex; - viewOptions.UnregisterCallback>(OnViewOptionsChanged); - viewOptions.Init(m_ViewOptions); - viewOptions.RegisterCallback>(OnViewOptionsChanged); - } + m_ExecutionItems = RenderGraphDebugSession.GetExecutions(m_SelectedRenderGraph); - void RebuildHeaderUI() - { - RebuildRenderGraphPopup(); - RebuildExecutionPopup(); - } + var renderGraphDropdownField = rootVisualElement.Q(Names.kCurrentGraphDropdown); - RenderGraph m_SelectedRenderGraph; + // Update selected render graph + var graphs = RenderGraphDebugSession.GetRegisteredGraphs(); + if (graphs.Count == 0 || renderGraphDropdownField == null) + { + m_SelectedRenderGraph = null; + SetSelectedExecutionIndex(-1); + return; + } - RenderGraph selectedRenderGraph - { - get => m_SelectedRenderGraph; - set + m_SelectedRenderGraph = graphs[0]; + renderGraphDropdownField.choices = graphs; + renderGraphDropdownField.value = m_SelectedRenderGraph; + + //Hide the dropdown since we currently only allow one render graph anyway! We potentially want to reconsider + //so we keep the code here for now. + renderGraphDropdownField.style.display = DisplayStyle.None; + + // Update selected execution + int newExecutionIndex = m_SelectedExecutionIndex; + if (m_ExecutionItems.Count > 0) + { + string previousSelectedExecutionName = EditorPrefs.GetString(kSelectedExecutionEditorPrefsKey); + + int previousSelectedExecutionFoundIndex = -1; + int previousSelectedExecutionFoundCount = 0; + for (int i = 0; i < m_ExecutionItems.Count; i++) + { + if (m_ExecutionItems[i].name == previousSelectedExecutionName) + { + previousSelectedExecutionFoundIndex = i; + previousSelectedExecutionFoundCount++; + } + } + + // If nothing is selected, try to select a previously selected execution. Note that because we allow + // duplicate camera names, if the camera name saved in EditorPrefs appears multiple times in the list, + // we cannot know which one needs to be activated and therefore must ignore it. + if (newExecutionIndex == -1 && previousSelectedExecutionFoundIndex != -1 && previousSelectedExecutionFoundCount == 1) + newExecutionIndex = previousSelectedExecutionFoundIndex; + + if (newExecutionIndex == -1) + newExecutionIndex = 0; + } + else { - m_SelectedRenderGraph = value; - UpdateCaptureEnabledUIState(); + newExecutionIndex = -1; } + + SetSelectedExecutionIndex(newExecutionIndex); } - string m_SelectedExecutionName; + string m_SelectedRenderGraph; + + List m_ExecutionItems; - string selectedExecutionName + int m_SelectedExecutionIndex = -1; + + void SetSelectedExecutionIndex(int executionIndex) { - get => m_SelectedExecutionName; - set + if (m_SelectedExecutionIndex != executionIndex) { - m_SelectedExecutionName = value; - UpdateCaptureEnabledUIState(); + m_SelectedExecutionIndex = executionIndex; + UpdateCurrentDebugData(); } - } - bool CaptureEnabled() => selectedExecutionName != null && selectedRenderGraph != null; + // Using a custom toolbar menu instead of default Dropdown in order to get access to allowDuplicateNames, + // as well as adding custom items and separators. Update the toolbar here to ensure it reacts well to + // deleted/renamed cameras etc. + var toolbarMenu = rootVisualElement.Q(Names.kCurrentExecutionToolbarMenu); + toolbarMenu.style.display = DisplayStyle.Flex; + toolbarMenu.text = selectedExecutionItem?.name ?? "Camera"; + toolbarMenu.menu.ClearItems(); + toolbarMenu.menu.allowDuplicateNames = true; - void UpdateCaptureEnabledUIState() - { - if (rootVisualElement?.childCount == 0) - return; + for (int i = 0; i < m_ExecutionItems.Count; i++) + { + var executionId = m_ExecutionItems[i]; + toolbarMenu.menu.AppendAction(executionId.name, OnExecutionMenuItemClicked, GetExecutionMenuItemStatus, userData: i); + } - bool enabled = CaptureEnabled(); - var captureButton = rootVisualElement.Q