791 lines
32 KiB
C#
791 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("Avoid body intersection", FormatOnOff(config.ModelDecimationAvoidBodyIntersection), GetOnOffColor(config.ModelDecimationAvoidBodyIntersection)),
|
|
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);
|
|
}
|