A game about forced loneliness, made by TACStudios
at master 523 lines 26 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEditor.UIElements; 4using UnityEditorInternal; 5using UnityEngine; 6using UnityEngine.Rendering.RenderGraphModule; 7using UnityEngine.UIElements; 8 9namespace UnityEditor.Rendering 10{ 11 public partial class RenderGraphViewer 12 { 13 static readonly string[] k_PassTypeNames = 14 { 15 "Legacy Render Pass", 16 "Unsafe Render Pass", 17 "Raster Render Pass", 18 "Compute Pass" 19 }; 20 21 static partial class Names 22 { 23 public const string kPanelContainer = "panel-container"; 24 public const string kResourceListFoldout = "panel-resource-list"; 25 public const string kPassListFoldout = "panel-pass-list"; 26 public const string kResourceSearchField = "resource-search-field"; 27 public const string kPassSearchField = "pass-search-field"; 28 } 29 static partial class Classes 30 { 31 public const string kPanelListLineBreak = "panel-list__line-break"; 32 public const string kPanelListItem = "panel-list__item"; 33 public const string kPanelListItemSelectionAnimation = "panel-list__item--selection-animation"; 34 public const string kPanelResourceListItem = "panel-resource-list__item"; 35 public const string kPanelPassListItem = "panel-pass-list__item"; 36 public const string kSubHeaderText = "sub-header-text"; 37 public const string kInfoFoldout = "info-foldout"; 38 public const string kInfoFoldoutSecondaryText = "info-foldout__secondary-text"; 39 public const string kCustomFoldoutArrow = "custom-foldout-arrow"; 40 } 41 42 static readonly System.Text.RegularExpressions.Regex k_TagRegex = new ("<[^>]*>"); 43 const string k_SelectionColorBeginTag = "<mark=#3169ACAB>"; 44 const string k_SelectionColorEndTag = "</mark>"; 45 46 TwoPaneSplitView m_SidePanelSplitView; 47 bool m_ResourceListExpanded = true; 48 bool m_PassListExpanded = true; 49 float m_SidePanelVerticalAspectRatio = 0.5f; 50 float m_SidePanelFixedPaneHeight = 0; 51 float m_ContentSplitViewFixedPaneWidth = 280; 52 53 Dictionary<VisualElement, List<TextElement>> m_ResourceDescendantCache = new (); 54 Dictionary<VisualElement, List<TextElement>> m_PassDescendantCache = new (); 55 56 void InitializeSidePanel() 57 { 58 m_SidePanelSplitView = rootVisualElement.Q<TwoPaneSplitView>(Names.kPanelContainer); 59 rootVisualElement.RegisterCallback<GeometryChangedEvent>(_ => 60 { 61 SaveSplitViewFixedPaneHeight(); // Window resized - save the current pane height 62 UpdatePanelHeights(); 63 }); 64 65 var contentSplitView = rootVisualElement.Q<TwoPaneSplitView>(Names.kContentContainer); 66 contentSplitView.fixedPaneInitialDimension = m_ContentSplitViewFixedPaneWidth; 67 contentSplitView.fixedPaneIndex = 1; 68 contentSplitView.fixedPane?.RegisterCallback<GeometryChangedEvent>(_ => 69 { 70 float? w = contentSplitView.fixedPane?.resolvedStyle?.width; 71 if (w.HasValue) 72 m_ContentSplitViewFixedPaneWidth = w.Value; 73 }); 74 75 // Callbacks for dynamic height allocation between resource & pass lists 76 HeaderFoldout resourceListFoldout = rootVisualElement.Q<HeaderFoldout>(Names.kResourceListFoldout); 77 resourceListFoldout.value = m_ResourceListExpanded; 78 resourceListFoldout.RegisterValueChangedCallback(evt => 79 { 80 if (m_ResourceListExpanded) 81 SaveSplitViewFixedPaneHeight(); // Closing the foldout - save the current pane height 82 83 m_ResourceListExpanded = resourceListFoldout.value; 84 UpdatePanelHeights(); 85 }); 86 resourceListFoldout.icon = m_ResourceListIcon; 87 resourceListFoldout.contextMenuGenerator = () => CreateContextMenu(resourceListFoldout.Q<ScrollView>()); 88 89 HeaderFoldout passListFoldout = rootVisualElement.Q<HeaderFoldout>(Names.kPassListFoldout); 90 passListFoldout.value = m_PassListExpanded; 91 passListFoldout.RegisterValueChangedCallback(evt => 92 { 93 if (m_PassListExpanded) 94 SaveSplitViewFixedPaneHeight(); // Closing the foldout - save the current pane height 95 96 m_PassListExpanded = passListFoldout.value; 97 UpdatePanelHeights(); 98 }); 99 passListFoldout.icon = m_PassListIcon; 100 passListFoldout.contextMenuGenerator = () => CreateContextMenu(passListFoldout.Q<ScrollView>()); 101 102 // Search fields 103 var resourceSearchField = rootVisualElement.Q<ToolbarSearchField>(Names.kResourceSearchField); 104 resourceSearchField.placeholderText = "Search"; 105 resourceSearchField.RegisterValueChangedCallback(evt => OnSearchFilterChanged(m_ResourceDescendantCache, evt.newValue)); 106 107 var passSearchField = rootVisualElement.Q<ToolbarSearchField>(Names.kPassSearchField); 108 passSearchField.placeholderText = "Search"; 109 passSearchField.RegisterValueChangedCallback(evt => OnSearchFilterChanged(m_PassDescendantCache, evt.newValue)); 110 } 111 112 bool IsSearchFilterMatch(string str, string searchString, out int startIndex, out int endIndex) 113 { 114 startIndex = -1; 115 endIndex = -1; 116 117 startIndex = str.IndexOf(searchString, 0, StringComparison.CurrentCultureIgnoreCase); 118 if (startIndex == -1) 119 return false; 120 121 endIndex = startIndex + searchString.Length - 1; 122 return true; 123 } 124 125 void OnSearchFilterChanged(Dictionary<VisualElement, List<TextElement>> elementCache, string searchString) 126 { 127 // Display filter 128 foreach (var (foldout, descendants) in elementCache) 129 { 130 bool anyDescendantMatchesSearch = false; 131 foreach (var elem in descendants) 132 { 133 // Remove any existing highlight 134 var text = elem.text; 135 var hasHighlight = k_TagRegex.IsMatch(text); 136 text = k_TagRegex.Replace(text, string.Empty); 137 if (!IsSearchFilterMatch(text, searchString, out int startHighlight, out int endHighlight)) 138 { 139 if (hasHighlight) 140 elem.text = text; 141 continue; 142 } 143 144 145 text = text.Insert(startHighlight, k_SelectionColorBeginTag); 146 text = text.Insert(endHighlight + k_SelectionColorBeginTag.Length + 1, k_SelectionColorEndTag); 147 elem.text = text; 148 anyDescendantMatchesSearch = true; 149 } 150 foldout.style.display = anyDescendantMatchesSearch ? DisplayStyle.Flex : DisplayStyle.None; 151 } 152 } 153 154 void SetChildFoldoutsExpanded(VisualElement elem, bool expanded) 155 { 156 elem.Query<Foldout>().ForEach(f => f.value = expanded); 157 } 158 159 GenericMenu CreateContextMenu(VisualElement content) 160 { 161 var menu = new GenericMenu(); 162 menu.AddItem(new GUIContent("Collapse All"), false, () => SetChildFoldoutsExpanded(content, false)); 163 menu.AddItem(new GUIContent("Expand All"), false, () => SetChildFoldoutsExpanded(content, true)); 164 return menu; 165 } 166 167 void PopulateResourceList() 168 { 169 ScrollView content = rootVisualElement.Q<HeaderFoldout>(Names.kResourceListFoldout).Q<ScrollView>(); 170 content.Clear(); 171 172 UpdatePanelHeights(); 173 174 m_ResourceDescendantCache.Clear(); 175 176 int visibleResourceIndex = 0; 177 foreach (var visibleResourceElement in m_ResourceElementsInfo) 178 { 179 var resourceData = m_CurrentDebugData.resourceLists[(int)visibleResourceElement.type][visibleResourceElement.index]; 180 181 var resourceItem = new Foldout(); 182 resourceItem.text = resourceData.name; 183 resourceItem.value = false; 184 resourceItem.userData = visibleResourceIndex; 185 resourceItem.AddToClassList(Classes.kPanelListItem); 186 resourceItem.AddToClassList(Classes.kPanelResourceListItem); 187 resourceItem.AddToClassList(Classes.kCustomFoldoutArrow); 188 visibleResourceIndex++; 189 190 var iconContainer = new VisualElement(); 191 iconContainer.AddToClassList(Classes.kResourceIconContainer); 192 193 var importedIcon = new VisualElement(); 194 importedIcon.AddToClassList(Classes.kResourceIconImported); 195 importedIcon.tooltip = "Imported resource"; 196 importedIcon.style.display = resourceData.imported ? DisplayStyle.Flex : DisplayStyle.None; 197 iconContainer.Add(importedIcon); 198 199 var foldoutCheckmark = resourceItem.Q("unity-checkmark"); 200 // Add resource type icon before the label 201 foldoutCheckmark.parent.Insert(1, CreateResourceTypeIcon(visibleResourceElement.type)); 202 foldoutCheckmark.parent.Add(iconContainer); 203 foldoutCheckmark.BringToFront(); // Move foldout checkmark to the right 204 205 // Add imported icon to the right of the foldout checkmark 206 var toggleContainer = resourceItem.Q<Toggle>(); 207 toggleContainer.tooltip = resourceData.name; 208 209 RenderGraphResourceType type = visibleResourceElement.type; 210 if (type == RenderGraphResourceType.Texture && resourceData.textureData != null) 211 { 212 var lineBreak = new VisualElement(); 213 lineBreak.AddToClassList(Classes.kPanelListLineBreak); 214 resourceItem.Add(lineBreak); 215 resourceItem.Add(new Label($"Size: {resourceData.textureData.width}x{resourceData.textureData.height}x{resourceData.textureData.depth}")); 216 resourceItem.Add(new Label($"Format: {resourceData.textureData.format.ToString()}")); 217 resourceItem.Add(new Label($"Clear: {resourceData.textureData.clearBuffer}")); 218 resourceItem.Add(new Label($"BindMS: {resourceData.textureData.bindMS}")); 219 resourceItem.Add(new Label($"Samples: {resourceData.textureData.samples}")); 220 if (m_CurrentDebugData.isNRPCompiler) 221 resourceItem.Add(new Label($"Memoryless: {resourceData.memoryless}")); 222 } 223 else if (type == RenderGraphResourceType.Buffer && resourceData.bufferData != null) 224 { 225 var lineBreak = new VisualElement(); 226 lineBreak.AddToClassList(Classes.kPanelListLineBreak); 227 resourceItem.Add(lineBreak); 228 resourceItem.Add(new Label($"Count: {resourceData.bufferData.count}")); 229 resourceItem.Add(new Label($"Stride: {resourceData.bufferData.stride}")); 230 resourceItem.Add(new Label($"Target: {resourceData.bufferData.target.ToString()}")); 231 resourceItem.Add(new Label($"Usage: {resourceData.bufferData.usage.ToString()}")); 232 } 233 234 content.Add(resourceItem); 235 236 m_ResourceDescendantCache[resourceItem] = resourceItem.Query().Descendents<TextElement>().ToList(); 237 } 238 } 239 240 void PopulatePassList() 241 { 242 HeaderFoldout headerFoldout = rootVisualElement.Q<HeaderFoldout>(Names.kPassListFoldout); 243 if (!m_CurrentDebugData.isNRPCompiler) 244 { 245 headerFoldout.style.display = DisplayStyle.None; 246 return; 247 } 248 headerFoldout.style.display = DisplayStyle.Flex; 249 250 ScrollView content = headerFoldout.Q<ScrollView>(); 251 content.Clear(); 252 253 UpdatePanelHeights(); 254 255 m_PassDescendantCache.Clear(); 256 257 void CreateTextElement(VisualElement parent, string text, string className = null) 258 { 259 var textElement = new TextElement(); 260 textElement.text = text; 261 if (className != null) 262 textElement.AddToClassList(className); 263 parent.Add(textElement); 264 } 265 266 HashSet<int> addedPasses = new HashSet<int>(); 267 268 foreach (var visiblePassElement in m_PassElementsInfo) 269 { 270 if (addedPasses.Contains(visiblePassElement.passId)) 271 continue; // Add only one item per merged pass group 272 273 List<RenderGraph.DebugData.PassData> passDatas = new(); 274 List<string> passNames = new(); 275 var groupedPassIds = GetGroupedPassIds(visiblePassElement.passId); 276 foreach (int groupedId in groupedPassIds) { 277 addedPasses.Add(groupedId); 278 passDatas.Add(m_CurrentDebugData.passList[groupedId]); 279 passNames.Add(m_CurrentDebugData.passList[groupedId].name); 280 } 281 282 var passItem = new Foldout(); 283 var passesText = string.Join(", ", passNames); 284 passItem.text = $"<b>{passesText}</b>"; 285 passItem.Q<Toggle>().tooltip = passesText; 286 passItem.value = false; 287 passItem.userData = m_PassIdToVisiblePassIndex[visiblePassElement.passId]; 288 passItem.AddToClassList(Classes.kPanelListItem); 289 passItem.AddToClassList(Classes.kPanelPassListItem); 290 291 //Native pass info (duplicated for each pass group so just look at the first) 292 var firstPassData = passDatas[0]; 293 var nativePassInfo = firstPassData.nrpInfo?.nativePassInfo; 294 295 if (nativePassInfo != null) 296 { 297 if (nativePassInfo.mergedPassIds.Count == 1) 298 CreateTextElement(passItem, "Native Pass was created from Raster Render Pass."); 299 else if (nativePassInfo.mergedPassIds.Count > 1) 300 CreateTextElement(passItem, $"Native Pass was created by merging {nativePassInfo.mergedPassIds.Count} Raster Render Passes."); 301 302 CreateTextElement(passItem, "Pass break reasoning", Classes.kSubHeaderText); 303 CreateTextElement(passItem, nativePassInfo.passBreakReasoning); 304 } 305 else 306 { 307 CreateTextElement(passItem, "Pass break reasoning", Classes.kSubHeaderText); 308 var msg = $"This is a {k_PassTypeNames[(int) firstPassData.type]}. Only Raster Render Passes can be merged."; 309 msg = msg.Replace("a Unsafe", "an Unsafe"); 310 CreateTextElement(passItem, msg); 311 } 312 313 if (nativePassInfo != null) 314 { 315 CreateTextElement(passItem, "Render Graph Pass Info", Classes.kSubHeaderText); 316 foreach (int passId in groupedPassIds) 317 { 318 var pass = m_CurrentDebugData.passList[passId]; 319 Debug.Assert(pass.nrpInfo != null); // This overlay currently assumes NRP compiler 320 321 var passFoldout = new Foldout(); 322 passFoldout.text = $"<b>{pass.name}</b> ({k_PassTypeNames[(int) pass.type]})"; 323 324 var foldoutTextElement = passFoldout.Q<TextElement>(className: Foldout.textUssClassName); 325 foldoutTextElement.displayTooltipWhenElided = false; // no tooltip override when ellipsis is active 326 327 bool hasSubpassIndex = pass.nativeSubPassIndex != -1; 328 if (hasSubpassIndex) 329 { 330 // Abuse Foldout to allow two-line header: add line break <br> at the end of the actual foldout text to increase height, 331 // then inject a second label into the hierarchy starting with a line break to offset it to the second line. 332 passFoldout.text += "<br>"; 333 Label subpassIndexLabel = new Label($"<br>Subpass #{pass.nativeSubPassIndex}"); 334 subpassIndexLabel.AddToClassList(Classes.kInfoFoldoutSecondaryText); 335 foldoutTextElement.Add(subpassIndexLabel); 336 } 337 338 passFoldout.AddToClassList(Classes.kInfoFoldout); 339 passFoldout.AddToClassList(Classes.kCustomFoldoutArrow); 340 passFoldout.Q<Toggle>().tooltip = $"The {k_PassTypeNames[(int) pass.type]} <b>{pass.name}</b> belongs to native subpass {pass.nativeSubPassIndex}."; 341 342 var foldoutCheckmark = passFoldout.Q("unity-checkmark"); 343 foldoutCheckmark.BringToFront(); // Move foldout checkmark to the right 344 345 var lineBreak = new VisualElement(); 346 lineBreak.AddToClassList(Classes.kPanelListLineBreak); 347 passFoldout.Add(lineBreak); 348 349 CreateTextElement(passFoldout, 350 $"Attachment dimensions: {pass.nrpInfo.width}x{pass.nrpInfo.height}x{pass.nrpInfo.volumeDepth}"); 351 CreateTextElement(passFoldout, $"Has depth attachment: {pass.nrpInfo.hasDepth}"); 352 CreateTextElement(passFoldout, $"MSAA samples: {pass.nrpInfo.samples}"); 353 CreateTextElement(passFoldout, $"Async compute: {pass.async}"); 354 355 passItem.Add(passFoldout); 356 } 357 358 CreateTextElement(passItem, "Attachment Load/Store Actions", Classes.kSubHeaderText); 359 if (nativePassInfo != null && nativePassInfo.attachmentInfos.Count > 0) 360 { 361 foreach (var attachmentInfo in nativePassInfo.attachmentInfos) 362 { 363 var attachmentFoldout = new Foldout(); 364 365 // Abuse Foldout to allow two-line header (same as above) 366 attachmentFoldout.text = $"<b>{attachmentInfo.resourceName}</b><br>"; 367 Label attachmentIndexLabel = new Label($"<br>Attachment #{attachmentInfo.attachmentIndex}"); 368 attachmentIndexLabel.AddToClassList(Classes.kInfoFoldoutSecondaryText); 369 370 var foldoutTextElement = attachmentFoldout.Q<TextElement>(className: Foldout.textUssClassName); 371 foldoutTextElement.displayTooltipWhenElided = false; // no tooltip override when ellipsis is active 372 foldoutTextElement.Add(attachmentIndexLabel); 373 374 attachmentFoldout.AddToClassList(Classes.kInfoFoldout); 375 attachmentFoldout.AddToClassList(Classes.kCustomFoldoutArrow); 376 attachmentFoldout.Q<Toggle>().tooltip = $"Texture <b>{attachmentInfo.resourceName}</b> is bound at attachment index {attachmentInfo.attachmentIndex}."; 377 378 var foldoutCheckmark = attachmentFoldout.Q("unity-checkmark"); 379 foldoutCheckmark.BringToFront(); // Move foldout checkmark to the right 380 381 var lineBreak = new VisualElement(); 382 lineBreak.AddToClassList(Classes.kPanelListLineBreak); 383 attachmentFoldout.Add(lineBreak); 384 385 attachmentFoldout.Add(new TextElement 386 { 387 text = $"<b>Load action:</b> {attachmentInfo.loadAction}\n- {attachmentInfo.loadReason}" 388 }); 389 390 bool addMsaaInfo = !string.IsNullOrEmpty(attachmentInfo.storeMsaaReason); 391 string resolvedTexturePrefix = addMsaaInfo ? "Resolved surface: " : ""; 392 393 string storeActionText = $"<b>Store action:</b> {attachmentInfo.storeAction}" + 394 $"\n - {resolvedTexturePrefix}{attachmentInfo.storeReason}"; 395 396 if (addMsaaInfo) 397 { 398 string msaaTexturePrefix = "MSAA surface: "; 399 storeActionText += $"\n - {msaaTexturePrefix}{attachmentInfo.storeMsaaReason}"; 400 } 401 402 attachmentFoldout.Add(new TextElement { text = storeActionText }); 403 404 passItem.Add(attachmentFoldout); 405 } 406 } 407 else 408 { 409 CreateTextElement(passItem, "No attachments."); 410 } 411 } 412 413 content.Add(passItem); 414 415 m_PassDescendantCache[passItem] = passItem.Query().Descendents<TextElement>().ToList(); 416 } 417 } 418 419 void SaveSplitViewFixedPaneHeight() 420 { 421 m_SidePanelFixedPaneHeight = m_SidePanelSplitView.fixedPane?.resolvedStyle?.height ?? 0; 422 } 423 424 void UpdatePanelHeights() 425 { 426 bool passListExpanded = m_PassListExpanded && (m_CurrentDebugData != null && m_CurrentDebugData.isNRPCompiler); 427 const int kFoldoutHeaderHeightPx = 18; 428 const int kFoldoutHeaderExpandedMinHeightPx = 50; 429 const int kWindowExtraMarginPx = 6; 430 431 var resourceList = rootVisualElement.Q<HeaderFoldout>(Names.kResourceListFoldout); 432 var passList = rootVisualElement.Q<HeaderFoldout>(Names.kPassListFoldout); 433 434 resourceList.style.minHeight = kFoldoutHeaderHeightPx; 435 passList.style.minHeight = kFoldoutHeaderHeightPx; 436 437 float panelHeightPx = position.height - kHeaderContainerHeightPx - kWindowExtraMarginPx; 438 if (!m_ResourceListExpanded) 439 { 440 m_SidePanelSplitView.fixedPaneInitialDimension = kFoldoutHeaderHeightPx; 441 } 442 else if (!passListExpanded) 443 { 444 m_SidePanelSplitView.fixedPaneInitialDimension = panelHeightPx - kFoldoutHeaderHeightPx; 445 } 446 else 447 { 448 // Update aspect ratio in case user has dragged the split view 449 if (m_SidePanelFixedPaneHeight > kFoldoutHeaderHeightPx && m_SidePanelFixedPaneHeight < panelHeightPx - kFoldoutHeaderHeightPx) 450 { 451 m_SidePanelVerticalAspectRatio = m_SidePanelFixedPaneHeight / panelHeightPx; 452 } 453 m_SidePanelSplitView.fixedPaneInitialDimension = panelHeightPx * m_SidePanelVerticalAspectRatio; 454 455 resourceList.style.minHeight = kFoldoutHeaderExpandedMinHeightPx; 456 passList.style.minHeight = kFoldoutHeaderExpandedMinHeightPx; 457 } 458 459 // Ensure fixed pane initial dimension gets applied in case it has already been set 460 m_SidePanelSplitView.fixedPane.style.height = m_SidePanelSplitView.fixedPaneInitialDimension; 461 462 // Disable drag line when one of the foldouts is collapsed 463 var dragLine = m_SidePanelSplitView.Q("unity-dragline"); 464 var dragLineAnchor = m_SidePanelSplitView.Q("unity-dragline-anchor"); 465 if (!m_ResourceListExpanded || !passListExpanded) 466 { 467 dragLine.pickingMode = PickingMode.Ignore; 468 dragLineAnchor.pickingMode = PickingMode.Ignore; 469 } 470 else 471 { 472 dragLine.pickingMode = PickingMode.Position; 473 dragLineAnchor.pickingMode = PickingMode.Position; 474 } 475 } 476 477 void ScrollToPass(int visiblePassIndex) 478 { 479 var passFoldout = rootVisualElement.Q<HeaderFoldout>(Names.kPassListFoldout); 480 ScrollToFoldout(passFoldout, visiblePassIndex); 481 } 482 483 void ScrollToResource(int visibleResourceIndex) 484 { 485 var resourceFoldout = rootVisualElement.Q<HeaderFoldout>(Names.kResourceListFoldout); 486 ScrollToFoldout(resourceFoldout, visibleResourceIndex); 487 } 488 489 void ScrollToFoldout(VisualElement parent, int index) 490 { 491 ScrollView scrollView = parent.Q<ScrollView>(); 492 scrollView.Query<Foldout>(classes: Classes.kPanelListItem).ForEach(foldout => 493 { 494 if (index == (int) foldout.userData) 495 { 496 // Trigger animation 497 foldout.AddToClassList(Classes.kPanelListItemSelectionAnimation); 498 499 // This repaint hack is needed because transition animations have poor framerate. So we are hooking to editor update 500 // loop for the duration of the animation to force repaints and have a smooth highlight animation. 501 // See https://jira.unity3d.com/browse/UIE-1326 502 EditorApplication.update += Repaint; 503 504 foldout.RegisterCallbackOnce<TransitionEndEvent>(_ => 505 { 506 // "Highlight in" animation finished 507 foldout.RemoveFromClassList(Classes.kPanelListItemSelectionAnimation); 508 foldout.RegisterCallbackOnce<TransitionEndEvent>(_ => 509 { 510 // "Highlight out" animation finished 511 EditorApplication.update -= Repaint; 512 }); 513 }); 514 515 // Open foldout 516 foldout.value = true; 517 // Defer scrolling to allow foldout to be expanded first 518 scrollView.schedule.Execute(() => scrollView.ScrollTo(foldout)).StartingIn(50); 519 } 520 }); 521 } 522 } 523}