Files
FlaxEngine/Source/Editor/Windows/Profiler/MemoryGPU.cs
Luke Schneider ad28a3fdbf Better light theme (Style) support, and a Default light theme (as a secondary option)
1) Added ForegroundViewport as a new color.  It is used in the main game viewport (ViewportWidgetButton), and the viewport for rendering of particles and materials.  It is needed because the default foreground in a Light theme is black, but black does not work well in a viewport.  A new color seemed appropriate.

2) Fixed the profiler window to use the Foreground color in multiple text elements, instead of Color.White (or no default TitleColor).  This includes  the Row class, Asset class, SingleChart class, Timeline Class, and more.

3) Added a second theme/Style (DefaultLight) to include with the engine.  It uses RGB float values because those were easier to transfer from the saved values that I had created (and they're easier for me to edit if necessary).  I tried to emulate how the Default theme is created/loaded/etc as closely as possible.
2023-09-27 21:54:34 -05:00

330 lines
12 KiB
C#

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows.Profiler
{
/// <summary>
/// The GPU Memory profiling mode.
/// </summary>
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
internal sealed class MemoryGPU : ProfilerMode
{
private class Resource
{
public string Name;
public string Tooltip;
public GPUResourceType Type;
public ulong MemoryUsage;
public Guid AssetId;
public bool IsAssetItem;
}
private readonly SingleChart _memoryUsageChart;
private readonly Table _table;
private SamplesBuffer<Resource[]> _resources;
private List<ClickableRow> _tableRowsCache;
private string[] _resourceTypesNames;
private Dictionary<string, Guid> _assetPathToId;
private Dictionary<Guid, Resource> _resourceCache;
private StringBuilder _stringBuilder;
public MemoryGPU()
: base("GPU Memory")
{
// Layout
var panel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = this,
};
var layout = new VerticalPanel
{
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero,
IsScrollable = true,
Parent = panel,
};
// Chart
_memoryUsageChart = new SingleChart
{
Title = "GPU Memory Usage",
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
Parent = layout,
};
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Table
var headerColor = Style.Current.LightBackground;
var textColor = Style.Current.Foreground;
_table = new Table
{
Columns = new[]
{
new ColumnDefinition
{
UseExpandCollapseMode = true,
CellAlignment = TextAlignment.Near,
Title = "Resource",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Type",
CellAlignment = TextAlignment.Center,
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Memory Usage",
TitleBackgroundColor = headerColor,
FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v),
TitleColor = textColor,
},
},
Parent = layout,
};
_table.Splits = new[]
{
0.6f,
0.2f,
0.2f,
};
}
/// <inheritdoc />
public override void Clear()
{
_memoryUsageChart.Clear();
_resources?.Clear();
_assetPathToId?.Clear();
_resourceCache?.Clear();
}
/// <inheritdoc />
public override void Update(ref SharedUpdateData sharedData)
{
_memoryUsageChart.AddSample(sharedData.Stats.MemoryGPU.Used);
if (_resourceCache == null)
_resourceCache = new Dictionary<Guid, Resource>();
if (_assetPathToId == null)
_assetPathToId = new Dictionary<string, Guid>();
if (_stringBuilder == null)
_stringBuilder = new StringBuilder();
// Capture current GPU resources usage info
var contentDatabase = Editor.Instance.ContentDatabase;
var gpuResources = GPUDevice.Instance.Resources;
var resources = new Resource[gpuResources.Length];
var sb = _stringBuilder;
for (int i = 0; i < resources.Length; i++)
{
var gpuResource = gpuResources[i];
ref var resource = ref resources[i];
// Try to reuse cached resource info
var gpuResourceId = gpuResource.ID;
if (!_resourceCache.TryGetValue(gpuResourceId, out resource))
{
resource = new Resource
{
#if !BUILD_RELEASE
Name = gpuResource.Name,
#endif
Type = gpuResource.ResourceType,
};
// Create tooltip
sb.Clear();
if (gpuResource is GPUTexture gpuTexture)
{
var desc = gpuTexture.Description;
sb.Append("Format: ").Append(desc.Format).AppendLine();
sb.Append("Size: ").Append(desc.Width).Append('x').Append(desc.Height);
if (desc.Depth != 1)
sb.Append('x').Append(desc.Depth);
if (desc.ArraySize != 1)
sb.Append('[').Append(desc.ArraySize).Append(']');
sb.AppendLine();
sb.Append("Mip Levels: ").Append(desc.MipLevels).AppendLine();
if (desc.IsMultiSample)
sb.Append("MSAA: ").Append('x').Append((int)desc.MultiSampleLevel).AppendLine();
sb.Append("Flags: ").Append(desc.Flags).AppendLine();
sb.Append("Usage: ").Append(desc.Usage);
}
else if (gpuResource is GPUBuffer gpuBuffer)
{
var desc = gpuBuffer.Description;
sb.Append("Format: ").Append(desc.Format).AppendLine();
sb.Append("Stride: ").Append(desc.Stride).AppendLine();
sb.Append("Elements: ").Append(desc.ElementsCount).AppendLine();
sb.Append("Flags: ").Append(desc.Flags).AppendLine();
sb.Append("Usage: ").Append(desc.Usage);
}
resource.Tooltip = _stringBuilder.ToString();
// Detect asset path in the resource name
int ext = resource.Name.LastIndexOf(".flax", StringComparison.OrdinalIgnoreCase);
if (ext != -1)
{
var assetPath = resource.Name.Substring(0, ext + 5);
if (!_assetPathToId.TryGetValue(assetPath, out resource.AssetId))
{
var asset = FlaxEngine.Content.GetAsset(assetPath);
if (asset != null)
resource.AssetId = asset.ID;
_assetPathToId.Add(assetPath, resource.AssetId);
}
var assetItem = contentDatabase.FindAsset(resource.AssetId);
if (assetItem != null)
{
resource.IsAssetItem = true;
resource.Name = assetItem.NamePath + resource.Name.Substring(ext + 5); // Use text after asset path to display (eg. subobject)
}
}
_resourceCache.Add(gpuResourceId, resource);
}
resource.MemoryUsage = gpuResource.MemoryUsage;
if (resource.MemoryUsage == 1)
resource.MemoryUsage = 0; // Sometimes GPU backend fakes memory usage as 1 to mark as allocated but not resided in actual GPU memory
}
if (_resources == null)
_resources = new SamplesBuffer<Resource[]>();
_resources.Add(resources);
}
/// <inheritdoc />
public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
{
_memoryUsageChart.SelectedSampleIndex = selectedFrame;
if (_resources == null)
return;
if (_tableRowsCache == null)
_tableRowsCache = new List<ClickableRow>();
if (_resourceTypesNames == null)
_resourceTypesNames = new string[(int)GPUResourceType.MAX]
{
"Render Target",
"Texture",
"Cube Texture",
"Volume Texture",
"Buffer",
"Shader",
"Pipeline State",
"Descriptor",
"Query",
"Sampler",
};
UpdateTable();
}
/// <inheritdoc />
public override void OnDestroy()
{
_resources?.Clear();
_resourceCache?.Clear();
_assetPathToId?.Clear();
_tableRowsCache?.Clear();
_stringBuilder?.Clear();
base.OnDestroy();
}
private void UpdateTable()
{
_table.IsLayoutLocked = true;
int idx = 0;
while (_table.Children.Count > idx)
{
var child = _table.Children[idx];
if (child is ClickableRow row)
{
_tableRowsCache.Add(row);
child.Parent = null;
}
else
{
idx++;
}
}
_table.LockChildrenRecursive();
UpdateTableInner();
_table.UnlockChildrenRecursive();
_table.PerformLayout();
}
private void UpdateTableInner()
{
if (_resources.Count == 0)
return;
var resources = _resources.Get(_memoryUsageChart.SelectedSampleIndex);
if (resources == null || resources.Length == 0)
return;
var resourcesOrdered = resources.OrderByDescending(x => x.MemoryUsage);
// Add rows
var rowColor2 = Style.Current.Background * 1.4f;
int rowIndex = 0;
foreach (var e in resourcesOrdered)
{
ClickableRow row;
if (_tableRowsCache.Count != 0)
{
// Reuse row
var last = _tableRowsCache.Count - 1;
row = _tableRowsCache[last];
_tableRowsCache.RemoveAt(last);
}
else
{
// Allocate new row
row = new ClickableRow { Values = new object[3] };
}
// Setup row data
row.Values[0] = e.Name;
row.Values[1] = _resourceTypesNames[(int)e.Type];
row.Values[2] = e.MemoryUsage;
// Setup row interactions
row.Tag = e;
row.TooltipText = e.Tooltip;
row.RowDoubleClick = null;
if (e.IsAssetItem)
{
row.RowDoubleClick = OnRowDoubleClickAsset;
}
// Add row to the table
row.Width = _table.Width;
row.BackgroundColor = rowIndex % 2 == 0 ? rowColor2 : Color.Transparent;
row.Parent = _table;
rowIndex++;
}
}
private void OnRowDoubleClickAsset(ClickableRow row)
{
var e = (Resource)row.Tag;
var assetItem = Editor.Instance.ContentDatabase.FindAsset(e.AssetId);
Editor.Instance.ContentEditing.Open(assetItem);
}
}
}