Files
LightlessClient/LightlessSync/UI/Components/OptimizationSummaryCard.cs
2026-01-16 11:00:58 +09:00

790 lines
32 KiB
C#

using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using LightlessSync.LightlessConfiguration;
using LightlessSync.LightlessConfiguration.Configurations;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Services;
using LightlessSync.UI.Style;
using LightlessSync.WebAPI.Files;
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;
namespace LightlessSync.UI.Components;
public sealed class OptimizationSummaryCard
{
private readonly UiSharedService _uiSharedService;
private readonly PairUiService _pairUiService;
private readonly PlayerPerformanceConfigService _playerPerformanceConfig;
private readonly FileUploadManager _fileTransferManager;
private readonly LightlessMediator _lightlessMediator;
private readonly OptimizationSettingsPanel _optimizationSettingsPanel;
private readonly SeluneBrush _optimizationBrush = new();
private const string OptimizationPopupId = "Optimization Settings##LightlessOptimization";
private bool _optimizationPopupOpen;
private bool _optimizationPopupRequest;
private OptimizationPanelSection _optimizationPopupSection = OptimizationPanelSection.Texture;
public OptimizationSummaryCard(
UiSharedService uiSharedService,
PairUiService pairUiService,
PlayerPerformanceConfigService playerPerformanceConfig,
FileUploadManager fileTransferManager,
LightlessMediator lightlessMediator)
{
_uiSharedService = uiSharedService;
_pairUiService = pairUiService;
_playerPerformanceConfig = playerPerformanceConfig;
_fileTransferManager = fileTransferManager;
_lightlessMediator = lightlessMediator;
_optimizationSettingsPanel = new OptimizationSettingsPanel(uiSharedService, playerPerformanceConfig, pairUiService);
}
public bool Draw(int activeDownloads)
{
var totals = GetPerformanceTotals();
var scale = ImGuiHelpers.GlobalScale;
var accent = UIColors.Get("LightlessPurple");
var accentBg = new Vector4(accent.X, accent.Y, accent.Z, 0.04f);
var accentBorder = new Vector4(accent.X, accent.Y, accent.Z, 0.16f);
var summaryPadding = new Vector2(12f * scale, 6f * scale);
var summaryItemSpacing = new Vector2(12f * scale, 4f * scale);
var cellPadding = new Vector2(6f * scale, 2f * scale);
var lineHeight = ImGui.GetFrameHeight();
var lineSpacing = summaryItemSpacing.Y;
var statsContentHeight = (lineHeight * 2f) + lineSpacing;
var summaryHeight = MathF.Max(56f * scale, statsContentHeight + (summaryPadding.Y * 2f) + (cellPadding.Y * 2f));
var activeUploads = _fileTransferManager.GetCurrentUploadsSnapshot().Count(upload => !upload.IsTransferred);
var textureButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Images);
var modelButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.ProjectDiagram);
var buttonWidth = MathF.Max(textureButtonSize.X, modelButtonSize.X);
var performanceConfig = _playerPerformanceConfig.Current;
var textureStatus = GetTextureOptimizationStatus(performanceConfig);
var modelStatus = GetModelOptimizationStatus(performanceConfig);
var textureStatusVisual = GetOptimizationStatusVisual(textureStatus);
var modelStatusVisual = GetOptimizationStatusVisual(modelStatus);
var textureStatusLines = BuildTextureOptimizationStatusLines(performanceConfig);
var modelStatusLines = BuildModelOptimizationStatusLines(performanceConfig);
var statusIconSpacing = 6f * scale;
var statusIconWidth = MathF.Max(GetIconWidth(textureStatusVisual.Icon), GetIconWidth(modelStatusVisual.Icon));
var buttonRowWidth = buttonWidth + statusIconWidth + statusIconSpacing;
var vramValue = totals.HasVramData
? UiSharedService.ByteToString(totals.DisplayVramBytes, addSuffix: true)
: "n/a";
var vramTooltip = BuildVramTooltipData(totals, UIColors.Get("LightlessBlue"));
var triangleValue = totals.HasTriangleData
? FormatTriangleCount(totals.DisplayTriangleCount)
: "n/a";
var triangleTooltip = BuildTriangleTooltipData(totals, UIColors.Get("LightlessPurple"));
var windowPos = ImGui.GetWindowPos();
var windowSize = ImGui.GetWindowSize();
var footerTop = ImGui.GetCursorScreenPos().Y;
var gradientTop = MathF.Max(windowPos.Y, footerTop - (12f * scale));
var gradientBottom = windowPos.Y + windowSize.Y;
var footerSettings = new SeluneGradientSettings
{
GradientColor = UIColors.Get("LightlessPurple"),
GradientPeakOpacity = 0.08f,
GradientPeakPosition = 0.18f,
BackgroundMode = SeluneGradientMode.Vertical,
};
using var footerSelune = Selune.Begin(_optimizationBrush, ImGui.GetWindowDrawList(), windowPos, windowSize, footerSettings);
footerSelune.DrawGradient(gradientTop, gradientBottom, ImGui.GetIO().DeltaTime);
using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 6f * scale))
using (ImRaii.PushStyle(ImGuiStyleVar.ChildBorderSize, MathF.Max(1f, ImGui.GetStyle().ChildBorderSize)))
using (ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, summaryPadding))
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, summaryItemSpacing))
using (ImRaii.PushColor(ImGuiCol.ChildBg, UiSharedService.Color(accentBg)))
using (ImRaii.PushColor(ImGuiCol.Border, UiSharedService.Color(accentBorder)))
using (var child = ImRaii.Child("optimizationSummary", new Vector2(-1f, summaryHeight), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
{
if (child)
{
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, cellPadding))
{
if (ImGui.BeginTable("optimizationSummaryTable", 2, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoBordersInBody))
{
ImGui.TableSetupColumn("Stats", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed, buttonRowWidth + 12f * scale);
ImGui.TableNextRow();
ImGui.TableNextColumn();
var availableHeight = summaryHeight - (summaryPadding.Y * 2f) - (cellPadding.Y * 2f);
var verticalPad = MathF.Max(0f, (availableHeight - statsContentHeight) * 0.5f);
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(summaryItemSpacing.X, 0f)))
{
if (verticalPad > 0f)
{
ImGui.Dummy(new Vector2(0f, verticalPad));
}
DrawOptimizationStatLine(FontAwesomeIcon.Memory, UIColors.Get("LightlessBlue"), "VRAM usage", vramValue, vramTooltip, scale);
if (lineSpacing > 0f)
{
ImGui.Dummy(new Vector2(0f, lineSpacing));
}
DrawOptimizationStatLine(FontAwesomeIcon.ProjectDiagram, UIColors.Get("LightlessPurple"), "Triangles", triangleValue, triangleTooltip, scale);
if (verticalPad > 0f)
{
ImGui.Dummy(new Vector2(0f, verticalPad));
}
}
ImGui.TableNextColumn();
var separatorX = ImGui.GetCursorScreenPos().X - cellPadding.X;
var separatorTop = ImGui.GetWindowPos().Y + summaryPadding.Y;
var separatorBottom = ImGui.GetWindowPos().Y + summaryHeight - summaryPadding.Y;
ImGui.GetWindowDrawList().AddLine(
new Vector2(separatorX, separatorTop),
new Vector2(separatorX, separatorBottom),
ImGui.ColorConvertFloat4ToU32(accentBorder),
MathF.Max(1f, 1f * scale));
float cellWidth = ImGui.GetContentRegionAvail().X;
float offsetX = MathF.Max(0f, cellWidth - buttonRowWidth);
float alignedX = ImGui.GetCursorPosX() + offsetX;
using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 6f * scale))
using (ImRaii.PushColor(ImGuiCol.Button, ImGui.ColorConvertFloat4ToU32(new Vector4(0f, 0f, 0f, 0f))))
{
var buttonBorderThickness = 10f * scale;
var buttonRounding = ImGui.GetStyle().FrameRounding;
DrawOptimizationStatusButtonRow(
"Texture Optimization",
textureStatusVisual.Icon,
textureStatusVisual.Color,
textureStatusVisual.Label,
textureStatusLines,
FontAwesomeIcon.Images,
textureButtonSize,
"Texture Optimization",
activeUploads,
activeDownloads,
() => OpenOptimizationPopup(OptimizationPanelSection.Texture),
alignedX,
statusIconSpacing,
buttonBorderThickness,
buttonRounding);
DrawOptimizationStatusButtonRow(
"Model Optimization",
modelStatusVisual.Icon,
modelStatusVisual.Color,
modelStatusVisual.Label,
modelStatusLines,
FontAwesomeIcon.ProjectDiagram,
modelButtonSize,
"Model Optimization",
activeUploads,
activeDownloads,
() => OpenOptimizationPopup(OptimizationPanelSection.Model),
alignedX,
statusIconSpacing,
buttonBorderThickness,
buttonRounding);
}
ImGui.EndTable();
}
}
}
}
footerSelune.DrawHighlightOnly(gradientTop, gradientBottom, ImGui.GetIO().DeltaTime);
DrawOptimizationPopup();
return true;
}
private PerformanceTotals GetPerformanceTotals()
{
HashSet<Pair> trackedPairs = new();
var snapshot = _pairUiService.GetSnapshot();
foreach (var pair in snapshot.DirectPairs)
{
trackedPairs.Add(pair);
}
foreach (var group in snapshot.GroupPairs.Values)
{
foreach (var pair in group)
{
trackedPairs.Add(pair);
}
}
long displayVramBytes = 0;
long originalVramBytes = 0;
long effectiveVramBytes = 0;
bool hasVramData = false;
bool hasOriginalVram = false;
bool hasEffectiveVram = false;
long displayTriangles = 0;
long originalTriangles = 0;
long effectiveTriangles = 0;
bool hasTriangleData = false;
bool hasOriginalTriangles = false;
bool hasEffectiveTriangles = false;
foreach (var pair in trackedPairs)
{
if (!pair.IsVisible)
{
continue;
}
var originalVram = pair.LastAppliedApproximateVRAMBytes;
var effectiveVram = pair.LastAppliedApproximateEffectiveVRAMBytes;
if (originalVram >= 0)
{
originalVramBytes += originalVram;
hasOriginalVram = true;
}
if (effectiveVram >= 0)
{
effectiveVramBytes += effectiveVram;
hasEffectiveVram = true;
}
if (effectiveVram >= 0)
{
displayVramBytes += effectiveVram;
hasVramData = true;
}
else if (originalVram >= 0)
{
displayVramBytes += originalVram;
hasVramData = true;
}
var originalTris = pair.LastAppliedDataTris;
var effectiveTris = pair.LastAppliedApproximateEffectiveTris;
if (originalTris >= 0)
{
originalTriangles += originalTris;
hasOriginalTriangles = true;
}
if (effectiveTris >= 0)
{
effectiveTriangles += effectiveTris;
hasEffectiveTriangles = true;
}
if (effectiveTris >= 0)
{
displayTriangles += effectiveTris;
hasTriangleData = true;
}
else if (originalTris >= 0)
{
displayTriangles += originalTris;
hasTriangleData = true;
}
}
return new PerformanceTotals(
displayVramBytes,
originalVramBytes,
effectiveVramBytes,
displayTriangles,
originalTriangles,
effectiveTriangles,
hasVramData,
hasOriginalVram,
hasEffectiveVram,
hasTriangleData,
hasOriginalTriangles,
hasEffectiveTriangles);
}
private void DrawOptimizationStatLine(FontAwesomeIcon icon, Vector4 iconColor, string label, string value, OptimizationStatTooltip? tooltip, float scale)
{
ImGui.AlignTextToFramePadding();
_uiSharedService.IconText(icon, iconColor);
var hovered = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
ImGui.SameLine(0f, 6f * scale);
ImGui.TextUnformatted($"{label}: {value}");
hovered |= ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
if (hovered && tooltip.HasValue)
{
DrawOptimizationStatTooltip(tooltip.Value);
}
}
private static OptimizationStatTooltip? BuildVramTooltipData(PerformanceTotals totals, Vector4 titleColor)
{
if (!totals.HasOriginalVram && !totals.HasEffectiveVram)
{
return null;
}
var lines = new List<OptimizationTooltipLine>();
if (totals.HasOriginalVram)
{
lines.Add(new OptimizationTooltipLine(
"Original",
UiSharedService.ByteToString(totals.OriginalVramBytes, addSuffix: true),
UIColors.Get("LightlessYellow")));
}
if (totals.HasEffectiveVram)
{
lines.Add(new OptimizationTooltipLine(
"Effective",
UiSharedService.ByteToString(totals.EffectiveVramBytes, addSuffix: true),
UIColors.Get("LightlessGreen")));
}
if (totals.HasOriginalVram && totals.HasEffectiveVram)
{
var savedBytes = Math.Max(0L, totals.OriginalVramBytes - totals.EffectiveVramBytes);
if (savedBytes > 0)
{
lines.Add(new OptimizationTooltipLine(
"Saved",
UiSharedService.ByteToString(savedBytes, addSuffix: true),
titleColor));
}
}
return new OptimizationStatTooltip(
"Total VRAM usage",
"Approximate texture memory across visible users.",
titleColor,
lines);
}
private static OptimizationStatTooltip? BuildTriangleTooltipData(PerformanceTotals totals, Vector4 titleColor)
{
if (!totals.HasOriginalTriangles && !totals.HasEffectiveTriangles)
{
return null;
}
var lines = new List<OptimizationTooltipLine>();
if (totals.HasOriginalTriangles)
{
lines.Add(new OptimizationTooltipLine(
"Original",
$"{FormatTriangleCount(totals.OriginalTriangleCount)} tris",
UIColors.Get("LightlessYellow")));
}
if (totals.HasEffectiveTriangles)
{
lines.Add(new OptimizationTooltipLine(
"Effective",
$"{FormatTriangleCount(totals.EffectiveTriangleCount)} tris",
UIColors.Get("LightlessGreen")));
}
if (totals.HasOriginalTriangles && totals.HasEffectiveTriangles)
{
var savedTris = Math.Max(0L, totals.OriginalTriangleCount - totals.EffectiveTriangleCount);
if (savedTris > 0)
{
lines.Add(new OptimizationTooltipLine(
"Saved",
$"{FormatTriangleCount(savedTris)} tris",
titleColor));
}
}
return new OptimizationStatTooltip(
"Total triangles",
"Approximate triangle count across visible users.",
titleColor,
lines);
}
private static string FormatTriangleCount(long triangleCount)
{
if (triangleCount < 0)
{
return "n/a";
}
if (triangleCount >= 1_000_000)
{
return FormattableString.Invariant($"{triangleCount / 1_000_000d:0.#}m");
}
if (triangleCount >= 1_000)
{
return FormattableString.Invariant($"{triangleCount / 1_000d:0.#}k");
}
return triangleCount.ToString(CultureInfo.InvariantCulture);
}
private enum OptimizationStatus
{
Off,
Partial,
On,
}
private static OptimizationStatus GetTextureOptimizationStatus(PlayerPerformanceConfig config)
{
bool trimEnabled = config.EnableNonIndexTextureMipTrim;
bool downscaleEnabled = config.EnableIndexTextureDownscale;
if (!trimEnabled && !downscaleEnabled)
{
return OptimizationStatus.Off;
}
return trimEnabled && downscaleEnabled
? OptimizationStatus.On
: OptimizationStatus.Partial;
}
private static OptimizationStatus GetModelOptimizationStatus(PlayerPerformanceConfig config)
{
if (!config.EnableModelDecimation)
{
return OptimizationStatus.Off;
}
bool hasTargets = config.ModelDecimationAllowBody
|| config.ModelDecimationAllowFaceHead
|| config.ModelDecimationAllowTail
|| config.ModelDecimationAllowClothing
|| config.ModelDecimationAllowAccessories;
return hasTargets
? OptimizationStatus.On
: OptimizationStatus.Partial;
}
private static (FontAwesomeIcon Icon, Vector4 Color, string Label) GetOptimizationStatusVisual(OptimizationStatus status)
{
return status switch
{
OptimizationStatus.On => (FontAwesomeIcon.Check, UIColors.Get("LightlessGreen"), "Enabled"),
OptimizationStatus.Partial => (FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"), "Partial"),
_ => (FontAwesomeIcon.Times, UIColors.Get("DimRed"), "Disabled"),
};
}
private static OptimizationTooltipLine[] BuildTextureOptimizationStatusLines(PlayerPerformanceConfig config)
{
return
[
new OptimizationTooltipLine("Trim mip levels", FormatOnOff(config.EnableNonIndexTextureMipTrim), GetOnOffColor(config.EnableNonIndexTextureMipTrim)),
new OptimizationTooltipLine("Downscale index textures", FormatOnOff(config.EnableIndexTextureDownscale), GetOnOffColor(config.EnableIndexTextureDownscale)),
new OptimizationTooltipLine("Max dimension", config.TextureDownscaleMaxDimension.ToString(CultureInfo.InvariantCulture)),
new OptimizationTooltipLine("Only downscale uncompressed", FormatOnOff(config.OnlyDownscaleUncompressedTextures), GetOnOffColor(config.OnlyDownscaleUncompressedTextures)),
new OptimizationTooltipLine("Compress uncompressed textures", FormatOnOff(config.EnableUncompressedTextureCompression), GetOnOffColor(config.EnableUncompressedTextureCompression)),
new OptimizationTooltipLine("Skip auto-compress mipmaps", FormatOnOff(config.SkipUncompressedTextureCompressionMipMaps), GetOnOffColor(config.SkipUncompressedTextureCompressionMipMaps)),
new OptimizationTooltipLine("Keep original textures", FormatOnOff(config.KeepOriginalTextureFiles), GetOnOffColor(config.KeepOriginalTextureFiles)),
new OptimizationTooltipLine("Skip preferred pairs", FormatOnOff(config.SkipTextureDownscaleForPreferredPairs), GetOnOffColor(config.SkipTextureDownscaleForPreferredPairs)),
];
}
private static OptimizationTooltipLine[] BuildModelOptimizationStatusLines(PlayerPerformanceConfig config)
{
var targets = new List<string>();
if (config.ModelDecimationAllowBody)
{
targets.Add("Body");
}
if (config.ModelDecimationAllowFaceHead)
{
targets.Add("Face/head");
}
if (config.ModelDecimationAllowTail)
{
targets.Add("Tails/Ears");
}
if (config.ModelDecimationAllowClothing)
{
targets.Add("Clothing");
}
if (config.ModelDecimationAllowAccessories)
{
targets.Add("Accessories");
}
var targetLabel = targets.Count > 0 ? string.Join(", ", targets) : "None";
var targetColor = targets.Count > 0 ? UIColors.Get("LightlessGreen") : UIColors.Get("DimRed");
var threshold = config.ModelDecimationTriangleThreshold.ToString("N0", CultureInfo.InvariantCulture);
var targetRatio = FormatPercent(config.ModelDecimationTargetRatio);
return
[
new OptimizationTooltipLine("Decimation enabled", FormatOnOff(config.EnableModelDecimation), GetOnOffColor(config.EnableModelDecimation)),
new OptimizationTooltipLine("Triangle threshold", threshold),
new OptimizationTooltipLine("Target ratio", targetRatio),
new OptimizationTooltipLine("Normalize tangents", FormatOnOff(config.ModelDecimationNormalizeTangents), GetOnOffColor(config.ModelDecimationNormalizeTangents)),
new OptimizationTooltipLine("Keep original models", FormatOnOff(config.KeepOriginalModelFiles), GetOnOffColor(config.KeepOriginalModelFiles)),
new OptimizationTooltipLine("Skip preferred pairs", FormatOnOff(config.SkipModelDecimationForPreferredPairs), GetOnOffColor(config.SkipModelDecimationForPreferredPairs)),
new OptimizationTooltipLine("Targets", targetLabel, targetColor),
];
}
private static string FormatOnOff(bool value)
=> value ? "On" : "Off";
private static string FormatPercent(double value)
=> FormattableString.Invariant($"{value * 100d:0.#}%");
private static Vector4 GetOnOffColor(bool value)
=> value ? UIColors.Get("LightlessGreen") : UIColors.Get("DimRed");
private static float GetIconWidth(FontAwesomeIcon icon)
{
using var iconFont = ImRaii.PushFont(UiBuilder.IconFont);
return ImGui.CalcTextSize(icon.ToIconString()).X;
}
private readonly record struct OptimizationStatTooltip(string Title, string Description, Vector4 TitleColor, IReadOnlyList<OptimizationTooltipLine> Lines);
private static void DrawOptimizationStatTooltip(OptimizationStatTooltip tooltip)
{
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 32f);
ImGui.TextColored(tooltip.TitleColor, tooltip.Title);
ImGui.TextColored(UIColors.Get("LightlessGrey"), tooltip.Description);
foreach (var line in tooltip.Lines)
{
ImGui.TextUnformatted($"{line.Label}:");
ImGui.SameLine();
if (line.ValueColor.HasValue)
{
ImGui.TextColored(line.ValueColor.Value, line.Value);
}
else
{
ImGui.TextUnformatted(line.Value);
}
}
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
private static void DrawOptimizationButtonTooltip(string title, int activeUploads, int activeDownloads)
{
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 32f);
ImGui.TextColored(UIColors.Get("LightlessPurple"), title);
ImGui.TextColored(UIColors.Get("LightlessGrey"), "Open optimization settings.");
if (activeUploads > 0 || activeDownloads > 0)
{
ImGui.Separator();
ImGui.TextUnformatted($"Active uploads: {activeUploads}");
ImGui.TextUnformatted($"Active downloads: {activeDownloads}");
}
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
private readonly record struct OptimizationTooltipLine(string Label, string Value, Vector4? ValueColor = null);
private static void DrawOptimizationStatusTooltip(string title, string statusLabel, Vector4 statusColor, IReadOnlyList<OptimizationTooltipLine> lines)
{
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 32f);
ImGui.TextColored(UIColors.Get("LightlessPurple"), title);
ImGui.TextUnformatted("Status:");
ImGui.SameLine();
ImGui.TextColored(statusColor, statusLabel);
foreach (var line in lines)
{
ImGui.TextUnformatted($"{line.Label}:");
ImGui.SameLine();
if (line.ValueColor.HasValue)
{
ImGui.TextColored(line.ValueColor.Value, line.Value);
}
else
{
ImGui.TextUnformatted(line.Value);
}
}
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
private void DrawOptimizationStatusButtonRow(
string statusTitle,
FontAwesomeIcon statusIcon,
Vector4 statusColor,
string statusLabel,
IReadOnlyList<OptimizationTooltipLine> statusLines,
FontAwesomeIcon buttonIcon,
Vector2 buttonSize,
string tooltipTitle,
int activeUploads,
int activeDownloads,
Action openPopup,
float alignedX,
float iconSpacing,
float buttonBorderThickness,
float buttonRounding)
{
ImGui.SetCursorPosX(alignedX);
ImGui.AlignTextToFramePadding();
_uiSharedService.IconText(statusIcon, statusColor);
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem))
{
DrawOptimizationStatusTooltip(statusTitle, statusLabel, statusColor, statusLines);
}
ImGui.SameLine(0f, iconSpacing);
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button(buttonIcon.ToIconString(), buttonSize))
{
openPopup();
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
{
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, buttonBorderThickness, exactSize: true, clipToElement: true, roundingOverride: buttonRounding);
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem))
{
DrawOptimizationButtonTooltip(tooltipTitle, activeUploads, activeDownloads);
}
}
private void OpenOptimizationPopup(OptimizationPanelSection section)
{
_optimizationPopupSection = section;
_optimizationPopupOpen = true;
_optimizationPopupRequest = true;
}
private void DrawOptimizationPopup()
{
if (!_optimizationPopupOpen)
{
return;
}
if (_optimizationPopupRequest)
{
ImGui.OpenPopup(OptimizationPopupId);
_optimizationPopupRequest = false;
}
var scale = ImGuiHelpers.GlobalScale;
ImGui.SetNextWindowSize(new Vector2(680f * scale, 640f * scale), ImGuiCond.Appearing);
if (ImGui.BeginPopupModal(OptimizationPopupId, ref _optimizationPopupOpen, UiSharedService.PopupWindowFlags))
{
DrawOptimizationPopupHeader();
ImGui.Separator();
ImGui.Dummy(new Vector2(0f, 4f * scale));
using (var child = ImRaii.Child("optimization-popup-body", new Vector2(0f, 0f), false, ImGuiWindowFlags.AlwaysVerticalScrollbar))
{
if (child)
{
_optimizationSettingsPanel.DrawPopup(_optimizationPopupSection);
}
}
ImGui.EndPopup();
}
}
private void DrawOptimizationPopupHeader()
{
var scale = ImGuiHelpers.GlobalScale;
var (title, icon, color, section) = GetPopupHeaderData(_optimizationPopupSection);
var settingsButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Cog);
using (var table = ImRaii.Table("optimization-popup-header", 2, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoBordersInBody))
{
if (!table)
{
return;
}
ImGui.TableSetupColumn("Title", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Settings", ImGuiTableColumnFlags.WidthFixed, settingsButtonSize.X);
ImGui.TableNextRow();
ImGui.TableNextColumn();
using (_uiSharedService.MediumFont.Push())
{
_uiSharedService.IconText(icon, color);
ImGui.SameLine(0f, 6f * scale);
ImGui.TextColored(color, title);
}
ImGui.TableNextColumn();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString(), settingsButtonSize))
{
OpenOptimizationSettings(section);
}
}
UiSharedService.AttachToolTip("Open this section in Settings.");
}
}
private void OpenOptimizationSettings(OptimizationPanelSection section)
{
var target = section == OptimizationPanelSection.Texture
? PerformanceSettingsSection.TextureOptimization
: PerformanceSettingsSection.ModelOptimization;
_lightlessMediator.Publish(new OpenPerformanceSettingsMessage(target));
_optimizationPopupOpen = false;
ImGui.CloseCurrentPopup();
}
private static (string Title, FontAwesomeIcon Icon, Vector4 Color, OptimizationPanelSection Section) GetPopupHeaderData(OptimizationPanelSection section)
{
return section == OptimizationPanelSection.Texture
? ("Texture Optimization", FontAwesomeIcon.Images, UIColors.Get("LightlessYellow"), OptimizationPanelSection.Texture)
: ("Model Optimization", FontAwesomeIcon.ProjectDiagram, UIColors.Get("LightlessOrange"), OptimizationPanelSection.Model);
}
[StructLayout(LayoutKind.Auto)]
private readonly record struct PerformanceTotals(
long DisplayVramBytes,
long OriginalVramBytes,
long EffectiveVramBytes,
long DisplayTriangleCount,
long OriginalTriangleCount,
long EffectiveTriangleCount,
bool HasVramData,
bool HasOriginalVram,
bool HasEffectiveVram,
bool HasTriangleData,
bool HasOriginalTriangles,
bool HasEffectiveTriangles);
}