sigma update
This commit is contained in:
789
LightlessSync/UI/Components/OptimizationSummaryCard.cs
Normal file
789
LightlessSync/UI/Components/OptimizationSummaryCard.cs
Normal file
@@ -0,0 +1,789 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user