sigma update

This commit is contained in:
2026-01-16 11:00:58 +09:00
parent 59ed03a825
commit 96123d00a2
51 changed files with 6640 additions and 1382 deletions

View File

@@ -39,7 +39,8 @@ public abstract class DrawFolderBase : IDrawFolder
public void Draw()
{
if (!RenderIfEmpty && !DrawPairs.Any()) return;
var drawPairCount = DrawPairs.Count;
if (!RenderIfEmpty && drawPairCount == 0) return;
_suppressNextRowToggle = false;
@@ -111,9 +112,9 @@ public abstract class DrawFolderBase : IDrawFolder
if (_tagHandler.IsTagOpen(_id))
{
using var indent = ImRaii.PushIndent(_uiSharedService.GetIconSize(FontAwesomeIcon.EllipsisV).X + ImGui.GetStyle().ItemSpacing.X, false);
if (DrawPairs.Any())
if (drawPairCount > 0)
{
using var clipper = ImUtf8.ListClipper(DrawPairs.Count, ImGui.GetFrameHeightWithSpacing());
using var clipper = ImUtf8.ListClipper(drawPairCount, ImGui.GetFrameHeightWithSpacing());
while (clipper.Step())
{
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)

View File

@@ -22,13 +22,16 @@ public class DrawGroupedGroupFolder : IDrawFolder
private readonly ApiController _apiController;
private readonly SelectSyncshellForTagUi _selectSyncshellForTagUi;
private readonly RenameSyncshellTagUi _renameSyncshellTagUi;
private readonly HashSet<string> _onlinePairBuffer = new(StringComparer.Ordinal);
private IImmutableList<DrawUserPair>? _drawPairsCache;
private int? _totalPairsCache;
private bool _wasHovered = false;
private float _menuWidth;
private bool _rowClickArmed;
public IImmutableList<DrawUserPair> DrawPairs => _groups.SelectMany(g => g.GroupDrawFolder.DrawPairs).ToImmutableList();
public int OnlinePairs => _groups.SelectMany(g => g.GroupDrawFolder.DrawPairs).Where(g => g.Pair.IsOnline).DistinctBy(g => g.Pair.UserData.UID).Count();
public int TotalPairs => _groups.Sum(g => g.GroupDrawFolder.TotalPairs);
public IImmutableList<DrawUserPair> DrawPairs => _drawPairsCache ??= _groups.SelectMany(g => g.GroupDrawFolder.DrawPairs).ToImmutableList();
public int OnlinePairs => CountOnlinePairs(DrawPairs);
public int TotalPairs => _totalPairsCache ??= _groups.Sum(g => g.GroupDrawFolder.TotalPairs);
public DrawGroupedGroupFolder(IEnumerable<GroupFolder> groups, TagHandler tagHandler, ApiController apiController, UiSharedService uiSharedService, SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameSyncshellTagUi, string tag)
{
@@ -50,6 +53,10 @@ public class DrawGroupedGroupFolder : IDrawFolder
}
using var id = ImRaii.PushId(_id);
var drawPairs = DrawPairs;
var onlinePairs = CountOnlinePairs(drawPairs);
var totalPairs = TotalPairs;
var hasPairs = drawPairs.Count > 0;
var color = ImRaii.PushColor(ImGuiCol.ChildBg, ImGui.GetColorU32(ImGuiCol.FrameBgHovered), _wasHovered);
var allowRowClick = string.IsNullOrEmpty(_tag);
var suppressRowToggle = false;
@@ -85,10 +92,10 @@ public class DrawGroupedGroupFolder : IDrawFolder
{
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("[" + OnlinePairs.ToString() + "]");
ImGui.TextUnformatted("[" + onlinePairs.ToString() + "]");
}
UiSharedService.AttachToolTip(OnlinePairs + " online in all of your joined syncshells" + Environment.NewLine +
TotalPairs + " pairs combined in all of your joined syncshells");
UiSharedService.AttachToolTip(onlinePairs + " online in all of your joined syncshells" + Environment.NewLine +
totalPairs + " pairs combined in all of your joined syncshells");
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
if (_tag != "")
@@ -96,7 +103,7 @@ public class DrawGroupedGroupFolder : IDrawFolder
ImGui.TextUnformatted(_tag);
ImGui.SameLine();
DrawPauseButton();
DrawPauseButton(hasPairs);
ImGui.SameLine();
DrawMenu(ref suppressRowToggle);
} else
@@ -104,7 +111,7 @@ public class DrawGroupedGroupFolder : IDrawFolder
ImGui.TextUnformatted("All Syncshells");
ImGui.SameLine();
DrawPauseButton();
DrawPauseButton(hasPairs);
}
}
color.Dispose();
@@ -151,9 +158,9 @@ public class DrawGroupedGroupFolder : IDrawFolder
}
}
protected void DrawPauseButton()
protected void DrawPauseButton(bool hasPairs)
{
if (DrawPairs.Count > 0)
if (hasPairs)
{
var isPaused = _groups.Select(g => g.GroupFullInfo).All(g => g.GroupUserPermissions.IsPaused());
FontAwesomeIcon pauseIcon = isPaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
@@ -179,6 +186,27 @@ public class DrawGroupedGroupFolder : IDrawFolder
}
}
private int CountOnlinePairs(IImmutableList<DrawUserPair> drawPairs)
{
if (drawPairs.Count == 0)
{
return 0;
}
_onlinePairBuffer.Clear();
foreach (var pair in drawPairs)
{
if (!pair.Pair.IsOnline)
{
continue;
}
_onlinePairBuffer.Add(pair.Pair.UserData.UID);
}
return _onlinePairBuffer.Count;
}
protected void ChangePauseStateGroups()
{
foreach(var group in _groups)

View File

@@ -340,7 +340,10 @@ public class DrawUserPair
? FontAwesomeIcon.User : FontAwesomeIcon.Users);
}
UiSharedService.AttachToolTip(GetUserTooltip());
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
UiSharedService.AttachToolTip(GetUserTooltip());
}
if (_performanceConfigService.Current.ShowPerformanceIndicator
&& !_performanceConfigService.Current.UIDsToIgnore
@@ -354,22 +357,25 @@ public class DrawUserPair
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
string userWarningText = "WARNING: This user exceeds one or more of your defined thresholds:" + UiSharedService.TooltipSeparator;
bool shownVram = false;
if (_performanceConfigService.Current.VRAMSizeWarningThresholdMiB > 0
&& _performanceConfigService.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < _pair.LastAppliedApproximateVRAMBytes)
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
shownVram = true;
userWarningText += $"Approx. VRAM Usage: Used: {UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes)}, Threshold: {_performanceConfigService.Current.VRAMSizeWarningThresholdMiB} MiB";
}
if (_performanceConfigService.Current.TrisWarningThresholdThousands > 0
&& _performanceConfigService.Current.TrisWarningThresholdThousands * 1024 < _pair.LastAppliedDataTris)
{
if (shownVram) userWarningText += Environment.NewLine;
userWarningText += $"Approx. Triangle count: Used: {_pair.LastAppliedDataTris}, Threshold: {_performanceConfigService.Current.TrisWarningThresholdThousands * 1000}";
}
string userWarningText = "WARNING: This user exceeds one or more of your defined thresholds:" + UiSharedService.TooltipSeparator;
bool shownVram = false;
if (_performanceConfigService.Current.VRAMSizeWarningThresholdMiB > 0
&& _performanceConfigService.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < _pair.LastAppliedApproximateVRAMBytes)
{
shownVram = true;
userWarningText += $"Approx. VRAM Usage: Used: {UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes)}, Threshold: {_performanceConfigService.Current.VRAMSizeWarningThresholdMiB} MiB";
}
if (_performanceConfigService.Current.TrisWarningThresholdThousands > 0
&& _performanceConfigService.Current.TrisWarningThresholdThousands * 1024 < _pair.LastAppliedDataTris)
{
if (shownVram) userWarningText += Environment.NewLine;
userWarningText += $"Approx. Triangle count: Used: {_pair.LastAppliedDataTris}, Threshold: {_performanceConfigService.Current.TrisWarningThresholdThousands * 1000}";
}
UiSharedService.AttachToolTip(userWarningText);
UiSharedService.AttachToolTip(userWarningText);
}
}
ImGui.SameLine();
@@ -613,12 +619,15 @@ public class DrawUserPair
perm.SetPaused(!perm.IsPaused());
_ = _apiController.UserSetPairPermissions(new(_pair.UserData, perm));
}
UiSharedService.AttachToolTip(!_pair.UserPair!.OwnPermissions.IsPaused()
? ("Pause pairing with " + _pair.UserData.AliasOrUID
+ (_pair.UserPair!.OwnPermissions.IsSticky()
? string.Empty
: UiSharedService.TooltipSeparator + "Hold CTRL to enable preferred permissions while pausing." + Environment.NewLine + "This will leave this pair paused even if unpausing syncshells including this pair."))
: "Resume pairing with " + _pair.UserData.AliasOrUID);
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
UiSharedService.AttachToolTip(!_pair.UserPair!.OwnPermissions.IsPaused()
? ("Pause pairing with " + _pair.UserData.AliasOrUID
+ (_pair.UserPair!.OwnPermissions.IsSticky()
? string.Empty
: UiSharedService.TooltipSeparator + "Hold CTRL to enable preferred permissions while pausing." + Environment.NewLine + "This will leave this pair paused even if unpausing syncshells including this pair."))
: "Resume pairing with " + _pair.UserData.AliasOrUID);
}
if (_pair.IsPaired)
{
@@ -781,8 +790,11 @@ public class DrawUserPair
currentRightSide -= (_uiSharedService.GetIconSize(FontAwesomeIcon.Running).X + (spacingX / 2f));
ImGui.SameLine(currentRightSide);
_uiSharedService.IconText(FontAwesomeIcon.Running);
UiSharedService.AttachToolTip($"This user has shared {sharedData.Count} Character Data Sets with you." + UiSharedService.TooltipSeparator
+ "Click to open the Character Data Hub and show the entries.");
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
UiSharedService.AttachToolTip($"This user has shared {sharedData.Count} Character Data Sets with you." + UiSharedService.TooltipSeparator
+ "Click to open the Character Data Hub and show the entries.");
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
_mediator.Publish(new OpenCharaDataHubWithFilterMessage(_pair.UserData));

View File

@@ -0,0 +1,930 @@
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.UI.Services;
using LightlessSync.Utils;
using System.Numerics;
namespace LightlessSync.UI.Components;
public enum OptimizationPanelSection
{
Texture,
Model,
}
public sealed class OptimizationSettingsPanel
{
private readonly UiSharedService _uiSharedService;
private readonly PlayerPerformanceConfigService _performanceConfigService;
private readonly PairUiService _pairUiService;
private const ImGuiTableFlags SettingsTableFlags = ImGuiTableFlags.SizingStretchProp
| ImGuiTableFlags.NoBordersInBody
| ImGuiTableFlags.PadOuterX;
public OptimizationSettingsPanel(
UiSharedService uiSharedService,
PlayerPerformanceConfigService performanceConfigService,
PairUiService pairUiService)
{
_uiSharedService = uiSharedService;
_performanceConfigService = performanceConfigService;
_pairUiService = pairUiService;
}
public void DrawSettingsTrees(
string textureLabel,
Vector4 textureColor,
string modelLabel,
Vector4 modelColor,
Func<string, Vector4, bool> beginTree)
{
if (beginTree(textureLabel, textureColor))
{
DrawTextureSection(showTitle: false);
UiSharedService.ColoredSeparator(textureColor, 1.5f);
ImGui.TreePop();
}
ImGui.Separator();
if (beginTree(modelLabel, modelColor))
{
DrawModelSection(showTitle: false);
UiSharedService.ColoredSeparator(modelColor, 1.5f);
ImGui.TreePop();
}
}
public void DrawPopup(OptimizationPanelSection section)
{
switch (section)
{
case OptimizationPanelSection.Texture:
DrawTextureSection(showTitle: false);
break;
case OptimizationPanelSection.Model:
DrawModelSection(showTitle: false);
break;
}
}
private void DrawTextureSection(bool showTitle)
{
var scale = ImGuiHelpers.GlobalScale;
DrawSectionIntro(
FontAwesomeIcon.Images,
UIColors.Get("LightlessYellow"),
"Texture Optimization",
"Reduce texture memory by trimming mip levels and downscaling oversized textures.",
showTitle);
DrawCallout("texture-opt-warning", UIColors.Get("DimRed"), () =>
{
_uiSharedService.MediumText("Warning", UIColors.Get("DimRed"));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Texture compression and downscaling is potentially a "),
new SeStringUtils.RichTextEntry("destructive", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" process and may cause broken or incorrect character appearances."));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("This feature is encouraged to help "),
new SeStringUtils.RichTextEntry("lower-end systems with limited VRAM", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry(" and for use in "),
new SeStringUtils.RichTextEntry("performance-critical scenarios", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry("."));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Runtime downscaling "),
new SeStringUtils.RichTextEntry("MAY", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" cause higher load on the system when processing downloads."));
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("When enabled, we cannot provide support for appearance issues caused by this setting!", UIColors.Get("DimRed"), true));
});
ImGui.Dummy(new Vector2(0f, 2f * scale));
DrawGroupHeader("Core Controls", UIColors.Get("LightlessYellow"));
var textureConfig = _performanceConfigService.Current;
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(6f * scale, 2f * scale)))
using (var table = ImRaii.Table("texture-opt-core", 3, SettingsTableFlags))
{
if (table)
{
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 220f * scale);
ImGui.TableSetupColumn("Control", ImGuiTableColumnFlags.WidthFixed, 180f * scale);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
DrawControlRow("Trim mip levels", () =>
{
var trimNonIndex = textureConfig.EnableNonIndexTextureMipTrim;
var accent = UIColors.Get("LightlessYellow");
if (DrawAccentCheckbox("##texture-trim-mips", ref trimNonIndex, accent))
{
textureConfig.EnableNonIndexTextureMipTrim = trimNonIndex;
_performanceConfigService.Save();
}
}, "Removes high-resolution mip levels from oversized non-index textures.", UIColors.Get("LightlessYellow"), UIColors.Get("LightlessYellow"));
DrawControlRow("Downscale index textures", () =>
{
var downscaleIndex = textureConfig.EnableIndexTextureDownscale;
var accent = UIColors.Get("LightlessYellow");
if (DrawAccentCheckbox("##texture-downscale-index", ref downscaleIndex, accent))
{
textureConfig.EnableIndexTextureDownscale = downscaleIndex;
_performanceConfigService.Save();
}
}, "Downscales oversized index textures to the configured dimension.", UIColors.Get("LightlessYellow"), UIColors.Get("LightlessYellow"));
DrawControlRow("Max texture dimension", () =>
{
var dimensionOptions = new[] { 512, 1024, 2048, 4096 };
var optionLabels = dimensionOptions.Select(static value => value.ToString()).ToArray();
var currentDimension = textureConfig.TextureDownscaleMaxDimension;
var selectedIndex = Array.IndexOf(dimensionOptions, currentDimension);
if (selectedIndex < 0)
{
selectedIndex = Array.IndexOf(dimensionOptions, 2048);
}
ImGui.SetNextItemWidth(-1f);
if (ImGui.Combo("##texture-max-dimension", ref selectedIndex, optionLabels, optionLabels.Length))
{
textureConfig.TextureDownscaleMaxDimension = dimensionOptions[selectedIndex];
_performanceConfigService.Save();
}
}, "Textures above this size are reduced to the limit. Default: 2048.");
}
}
if (!textureConfig.EnableNonIndexTextureMipTrim
&& !textureConfig.EnableIndexTextureDownscale
&& !textureConfig.EnableUncompressedTextureCompression)
{
UiSharedService.ColorTextWrapped(
"Texture trimming, downscale, and compression are disabled. Lightless will keep original textures regardless of size.",
UIColors.Get("DimRed"));
}
ImGui.Dummy(new Vector2(0f, 4f * scale));
DrawGroupHeader("Behavior & Exceptions", UIColors.Get("LightlessYellow"));
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(6f * scale, 2f * scale)))
using (var table = ImRaii.Table("texture-opt-behavior", 3, SettingsTableFlags))
{
if (table)
{
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 220f * scale);
ImGui.TableSetupColumn("Control", ImGuiTableColumnFlags.WidthFixed, 180f * scale);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
DrawControlRow("Only downscale uncompressed", () =>
{
var onlyUncompressed = textureConfig.OnlyDownscaleUncompressedTextures;
if (ImGui.Checkbox("##texture-only-uncompressed", ref onlyUncompressed))
{
textureConfig.OnlyDownscaleUncompressedTextures = onlyUncompressed;
_performanceConfigService.Save();
}
}, "When disabled, block-compressed textures can be downscaled too.");
}
}
ImGui.Dummy(new Vector2(0f, 2f * scale));
DrawTextureCompressionCard(textureConfig);
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(6f * scale, 2f * scale)))
using (var table = ImRaii.Table("texture-opt-behavior-extra", 3, SettingsTableFlags))
{
if (table)
{
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 220f * scale);
ImGui.TableSetupColumn("Control", ImGuiTableColumnFlags.WidthFixed, 180f * scale);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
DrawControlRow("Keep original texture files", () =>
{
var keepOriginalTextures = textureConfig.KeepOriginalTextureFiles;
if (ImGui.Checkbox("##texture-keep-original", ref keepOriginalTextures))
{
textureConfig.KeepOriginalTextureFiles = keepOriginalTextures;
_performanceConfigService.Save();
}
}, "Keeps the original texture alongside the downscaled copy.");
DrawControlRow("Skip preferred/direct pairs", () =>
{
var skipPreferredDownscale = textureConfig.SkipTextureDownscaleForPreferredPairs;
if (ImGui.Checkbox("##texture-skip-preferred", ref skipPreferredDownscale))
{
textureConfig.SkipTextureDownscaleForPreferredPairs = skipPreferredDownscale;
_performanceConfigService.Save();
}
}, "Leaves textures untouched for preferred/direct pairs.");
}
}
UiSharedService.ColorTextWrapped(
"Note: Disabling \"Keep original texture files\" prevents saved/effective VRAM usage information.",
UIColors.Get("LightlessYellow"));
ImGui.Dummy(new Vector2(0f, 4f * scale));
DrawSummaryPanel("Usage Summary", UIColors.Get("LightlessPurple"), DrawTextureDownscaleCounters);
}
private void DrawModelSection(bool showTitle)
{
var scale = ImGuiHelpers.GlobalScale;
DrawSectionIntro(
FontAwesomeIcon.ProjectDiagram,
UIColors.Get("LightlessOrange"),
"Model Optimization",
"Reduce triangle counts by decimating models above a threshold.",
showTitle);
DrawCallout("model-opt-warning", UIColors.Get("DimRed"), () =>
{
_uiSharedService.MediumText("Warning", UIColors.Get("DimRed"));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Model decimation is a "),
new SeStringUtils.RichTextEntry("destructive", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" process and may cause broken or incorrect character appearances."));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("This feature is encouraged to help "),
new SeStringUtils.RichTextEntry("lower-end systems with limited VRAM", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry(" and for use in "),
new SeStringUtils.RichTextEntry("performance-critical scenarios", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry("."));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Runtime decimation "),
new SeStringUtils.RichTextEntry("MAY", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" cause higher load on the system when processing downloads."));
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("When enabled, we cannot provide support for appearance issues caused by this setting!", UIColors.Get("DimRed"), true));
});
ImGui.Dummy(new Vector2(0f, 2f * scale));
DrawCallout("model-opt-behavior", UIColors.Get("LightlessGreen"), () =>
{
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessGreen"),
new SeStringUtils.RichTextEntry("Meshes above the "),
new SeStringUtils.RichTextEntry("triangle threshold", UIColors.Get("LightlessGreen"), true),
new SeStringUtils.RichTextEntry(" will be decimated to the "),
new SeStringUtils.RichTextEntry("target ratio", UIColors.Get("LightlessGreen"), true),
new SeStringUtils.RichTextEntry(". This can reduce quality or alter intended structure."));
});
DrawGroupHeader("Core Controls", UIColors.Get("LightlessOrange"));
var performanceConfig = _performanceConfigService.Current;
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(6f * scale, 2f * scale)))
using (var table = ImRaii.Table("model-opt-core", 3, SettingsTableFlags))
{
if (table)
{
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 220f * scale);
ImGui.TableSetupColumn("Control", ImGuiTableColumnFlags.WidthFixed, 180f * scale);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
DrawControlRow("Enable model decimation", () =>
{
var enableDecimation = performanceConfig.EnableModelDecimation;
var accent = UIColors.Get("LightlessOrange");
if (DrawAccentCheckbox("##enable-model-decimation", ref enableDecimation, accent))
{
performanceConfig.EnableModelDecimation = enableDecimation;
_performanceConfigService.Save();
}
}, "Generates a decimated copy of models after download.", UIColors.Get("LightlessOrange"), UIColors.Get("LightlessOrange"));
DrawControlRow("Decimate above (triangles)", () =>
{
var triangleThreshold = performanceConfig.ModelDecimationTriangleThreshold;
ImGui.SetNextItemWidth(-1f);
if (ImGui.SliderInt("##model-decimation-threshold", ref triangleThreshold, 1_000, 100_000))
{
performanceConfig.ModelDecimationTriangleThreshold = Math.Clamp(triangleThreshold, 1_000, 100_000);
_performanceConfigService.Save();
}
}, "Models below this triangle count are left untouched. Default: 15,000.");
DrawControlRow("Target triangle ratio", () =>
{
var targetPercent = (float)(performanceConfig.ModelDecimationTargetRatio * 100.0);
var clampedPercent = Math.Clamp(targetPercent, 60f, 99f);
if (Math.Abs(clampedPercent - targetPercent) > float.Epsilon)
{
performanceConfig.ModelDecimationTargetRatio = clampedPercent / 100.0;
_performanceConfigService.Save();
targetPercent = clampedPercent;
}
ImGui.SetNextItemWidth(-1f);
if (ImGui.SliderFloat("##model-decimation-target", ref targetPercent, 60f, 99f, "%.0f%%"))
{
performanceConfig.ModelDecimationTargetRatio = Math.Clamp(targetPercent / 100f, 0.6f, 0.99f);
_performanceConfigService.Save();
}
}, "Ratio relative to original triangle count (80% keeps 80%). Default: 80%.");
}
}
ImGui.Dummy(new Vector2(0f, 2f * scale));
DrawGroupHeader("Behavior & Exceptions", UIColors.Get("LightlessOrange"));
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(6f * scale, 2f * scale)))
using (var table = ImRaii.Table("model-opt-behavior-table", 3, SettingsTableFlags))
{
if (table)
{
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 220f * scale);
ImGui.TableSetupColumn("Control", ImGuiTableColumnFlags.WidthFixed, 180f * scale);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
DrawControlRow("Normalize tangents", () =>
{
var normalizeTangents = performanceConfig.ModelDecimationNormalizeTangents;
if (ImGui.Checkbox("##model-normalize-tangents", ref normalizeTangents))
{
performanceConfig.ModelDecimationNormalizeTangents = normalizeTangents;
_performanceConfigService.Save();
}
}, "Normalizes tangents to reduce shading artifacts.");
DrawControlRow("Keep original model files", () =>
{
var keepOriginalModels = performanceConfig.KeepOriginalModelFiles;
if (ImGui.Checkbox("##model-keep-original", ref keepOriginalModels))
{
performanceConfig.KeepOriginalModelFiles = keepOriginalModels;
_performanceConfigService.Save();
}
}, "Keeps the original model alongside the decimated copy.");
DrawControlRow("Skip preferred/direct pairs", () =>
{
var skipPreferredDecimation = performanceConfig.SkipModelDecimationForPreferredPairs;
if (ImGui.Checkbox("##model-skip-preferred", ref skipPreferredDecimation))
{
performanceConfig.SkipModelDecimationForPreferredPairs = skipPreferredDecimation;
_performanceConfigService.Save();
}
}, "Leaves models untouched for preferred/direct pairs.");
}
}
UiSharedService.ColorTextWrapped(
"Note: Disabling \"Keep original model files\" prevents saved/effective triangle usage information.",
UIColors.Get("LightlessYellow"));
ImGui.Dummy(new Vector2(0f, 2f * scale));
DrawGroupHeader("Decimation Targets", UIColors.Get("LightlessGrey"), "Hair mods are always excluded from decimation.");
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessGreen"),
new SeStringUtils.RichTextEntry("Automatic decimation will only target the selected "),
new SeStringUtils.RichTextEntry("decimation targets", UIColors.Get("LightlessGreen"), true),
new SeStringUtils.RichTextEntry("."));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"),
new SeStringUtils.RichTextEntry("It is advised to not decimate any body related meshes which includes: "),
new SeStringUtils.RichTextEntry("facial mods + sculpts, chest, legs, hands and feet", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry("."));
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Automatic decimation is not perfect and can cause meshes with bad topology to be worse.", UIColors.Get("DimRed"), true));
DrawTargetGrid(performanceConfig);
ImGui.Dummy(new Vector2(0f, 4f * scale));
DrawSummaryPanel("Usage Summary", UIColors.Get("LightlessPurple"), DrawTriangleDecimationCounters);
}
private void DrawTargetGrid(PlayerPerformanceConfig config)
{
var scale = ImGuiHelpers.GlobalScale;
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(6f * scale, 2f * scale)))
using (var table = ImRaii.Table("model-opt-targets", 3, SettingsTableFlags))
{
if (!table)
{
return;
}
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 220f * scale);
ImGui.TableSetupColumn("Control", ImGuiTableColumnFlags.WidthFixed, 180f * scale);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
DrawControlRow("Body", () =>
{
var allowBody = config.ModelDecimationAllowBody;
if (ImGui.Checkbox("##model-target-body", ref allowBody))
{
config.ModelDecimationAllowBody = allowBody;
_performanceConfigService.Save();
}
}, "Body meshes (torso, limbs).");
DrawControlRow("Face/head", () =>
{
var allowFaceHead = config.ModelDecimationAllowFaceHead;
if (ImGui.Checkbox("##model-target-facehead", ref allowFaceHead))
{
config.ModelDecimationAllowFaceHead = allowFaceHead;
_performanceConfigService.Save();
}
}, "Face and head meshes.");
DrawControlRow("Tails/Ears", () =>
{
var allowTail = config.ModelDecimationAllowTail;
if (ImGui.Checkbox("##model-target-tail", ref allowTail))
{
config.ModelDecimationAllowTail = allowTail;
_performanceConfigService.Save();
}
}, "Tail, ear, and similar appendages.");
DrawControlRow("Clothing", () =>
{
var allowClothing = config.ModelDecimationAllowClothing;
if (ImGui.Checkbox("##model-target-clothing", ref allowClothing))
{
config.ModelDecimationAllowClothing = allowClothing;
_performanceConfigService.Save();
}
}, "Outfits, shoes, gloves, hats.");
DrawControlRow("Accessories", () =>
{
var allowAccessories = config.ModelDecimationAllowAccessories;
if (ImGui.Checkbox("##model-target-accessories", ref allowAccessories))
{
config.ModelDecimationAllowAccessories = allowAccessories;
_performanceConfigService.Save();
}
}, "Jewelry and small add-ons.");
}
}
private void DrawSectionIntro(FontAwesomeIcon icon, Vector4 color, string title, string subtitle, bool showTitle)
{
var scale = ImGuiHelpers.GlobalScale;
if (showTitle)
{
using (_uiSharedService.MediumFont.Push())
{
_uiSharedService.IconText(icon, color);
ImGui.SameLine(0f, 6f * scale);
ImGui.TextColored(color, title);
}
ImGui.TextColored(UIColors.Get("LightlessGrey"), subtitle);
}
else
{
_uiSharedService.IconText(icon, color);
ImGui.SameLine(0f, 6f * scale);
ImGui.TextColored(UIColors.Get("LightlessGrey"), subtitle);
}
ImGui.Dummy(new Vector2(0f, 2f * scale));
}
private void DrawGroupHeader(string title, Vector4 color, string? helpText = null)
{
using var font = _uiSharedService.MediumFont.Push();
ImGui.TextColored(color, title);
if (!string.IsNullOrWhiteSpace(helpText))
{
_uiSharedService.DrawHelpText(helpText);
}
UiSharedService.ColoredSeparator(color, 1.2f);
}
private void DrawCallout(string id, Vector4 color, Action content)
{
var scale = ImGuiHelpers.GlobalScale;
var bg = new Vector4(color.X, color.Y, color.Z, 0.08f);
var border = new Vector4(color.X, color.Y, color.Z, 0.25f);
DrawPanelBox(id, bg, border, 6f * scale, new Vector2(10f * scale, 6f * scale), content);
}
private void DrawSummaryPanel(string title, Vector4 accent, Action content)
{
var scale = ImGuiHelpers.GlobalScale;
var bg = new Vector4(accent.X, accent.Y, accent.Z, 0.06f);
var border = new Vector4(accent.X, accent.Y, accent.Z, 0.2f);
DrawPanelBox($"summary-{title}", bg, border, 6f * scale, new Vector2(10f * scale, 6f * scale), () =>
{
_uiSharedService.MediumText(title, accent);
content();
});
}
private void DrawTextureCompressionCard(PlayerPerformanceConfig textureConfig)
{
var scale = ImGuiHelpers.GlobalScale;
var baseColor = UIColors.Get("LightlessGrey");
var bg = new Vector4(baseColor.X, baseColor.Y, baseColor.Z, 0.12f);
var border = new Vector4(baseColor.X, baseColor.Y, baseColor.Z, 0.32f);
DrawPanelBox("texture-compression-card", bg, border, 6f * scale, new Vector2(10f * scale, 6f * scale), () =>
{
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(6f * scale, 2f * scale)))
using (var table = ImRaii.Table("texture-opt-compress-card", 2, SettingsTableFlags))
{
if (!table)
{
return;
}
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 220f * scale);
ImGui.TableSetupColumn("Control", ImGuiTableColumnFlags.WidthStretch);
DrawInlineDescriptionRow("Compress uncompressed textures", () =>
{
var autoCompress = textureConfig.EnableUncompressedTextureCompression;
if (UiSharedService.CheckboxWithBorder("##texture-auto-compress", ref autoCompress, baseColor))
{
textureConfig.EnableUncompressedTextureCompression = autoCompress;
_performanceConfigService.Save();
}
}, "Converts uncompressed textures to BC formats based on map type (heavy). Runs after downscale/mip trim.",
drawLabelSuffix: () =>
{
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
UiSharedService.AttachToolTip("This feature can be demanding and will increase character load times.");
});
DrawInlineDescriptionRow("Skip mipmaps for auto-compress", () =>
{
var skipMipMaps = textureConfig.SkipUncompressedTextureCompressionMipMaps;
if (UiSharedService.CheckboxWithBorder("##texture-auto-compress-skip-mips", ref skipMipMaps, baseColor))
{
textureConfig.SkipUncompressedTextureCompressionMipMaps = skipMipMaps;
_performanceConfigService.Save();
}
}, "Skips mipmap generation to speed up compression, but can cause shimmering.",
disableControl: !textureConfig.EnableUncompressedTextureCompression);
}
});
}
private void DrawInlineDescriptionRow(
string label,
Action drawControl,
string description,
Action? drawLabelSuffix = null,
bool disableControl = false)
{
var scale = ImGuiHelpers.GlobalScale;
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(label);
if (drawLabelSuffix != null)
{
ImGui.SameLine(0f, 4f * scale);
drawLabelSuffix();
}
ImGui.TableSetColumnIndex(1);
using (ImRaii.Disabled(disableControl))
{
drawControl();
}
ImGui.SameLine(0f, 8f * scale);
using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessGrey")))
{
ImGui.PushTextWrapPos(ImGui.GetCursorPos().X + ImGui.GetContentRegionAvail().X);
ImGui.TextUnformatted(description);
ImGui.PopTextWrapPos();
}
}
private void DrawControlRow(string label, Action drawControl, string description, Vector4? labelColor = null, Vector4? cardAccent = null, Action? drawLabelSuffix = null)
{
var scale = ImGuiHelpers.GlobalScale;
if (!cardAccent.HasValue)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
using var labelTint = ImRaii.PushColor(ImGuiCol.Text, labelColor ?? Vector4.Zero, labelColor.HasValue);
ImGui.TextUnformatted(label);
if (drawLabelSuffix != null)
{
ImGui.SameLine(0f, 4f * scale);
drawLabelSuffix();
}
ImGui.TableSetColumnIndex(1);
drawControl();
ImGui.TableSetColumnIndex(2);
using var color = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessGrey"));
ImGui.TextWrapped(description);
return;
}
var padX = 6f * scale;
var padY = 3f * scale;
var rowGap = 4f * scale;
var accent = cardAccent.Value;
var drawList = ImGui.GetWindowDrawList();
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
var col0Start = ImGui.GetCursorScreenPos();
ImGui.TableSetColumnIndex(1);
var col1Start = ImGui.GetCursorScreenPos();
ImGui.TableSetColumnIndex(2);
var col2Start = ImGui.GetCursorScreenPos();
var col2Width = ImGui.GetContentRegionAvail().X;
float descriptionHeight;
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0f, 0f, 0f, 0f)))
{
ImGui.SetCursorScreenPos(col2Start);
ImGui.PushTextWrapPos(ImGui.GetCursorPos().X + col2Width);
ImGui.TextUnformatted(description);
ImGui.PopTextWrapPos();
descriptionHeight = ImGui.GetItemRectSize().Y;
}
var lineHeight = ImGui.GetTextLineHeight();
var labelHeight = lineHeight;
var controlHeight = ImGui.GetFrameHeight();
var contentHeight = MathF.Max(labelHeight, MathF.Max(controlHeight, descriptionHeight));
var lineCount = Math.Max(1, (int)MathF.Round(descriptionHeight / MathF.Max(1f, lineHeight)));
var descOffset = lineCount > 1 ? lineHeight * 0.18f : 0f;
var cardTop = col0Start.Y;
var contentTop = cardTop + padY;
var cardHeight = contentHeight + (padY * 2f);
var labelY = contentTop + (contentHeight - labelHeight) * 0.5f;
var controlY = contentTop + (contentHeight - controlHeight) * 0.5f;
var descY = contentTop + (contentHeight - descriptionHeight) * 0.5f - descOffset;
drawList.ChannelsSplit(2);
drawList.ChannelsSetCurrent(1);
ImGui.TableSetColumnIndex(0);
ImGui.SetCursorScreenPos(new Vector2(col0Start.X, labelY));
using (ImRaii.PushColor(ImGuiCol.Text, labelColor ?? Vector4.Zero, labelColor.HasValue))
{
ImGui.TextUnformatted(label);
if (drawLabelSuffix != null)
{
ImGui.SameLine(0f, 4f * scale);
drawLabelSuffix();
}
}
ImGui.TableSetColumnIndex(1);
ImGui.SetCursorScreenPos(new Vector2(col1Start.X, controlY));
drawControl();
ImGui.TableSetColumnIndex(2);
ImGui.SetCursorScreenPos(new Vector2(col2Start.X, descY));
using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessGrey")))
{
ImGui.PushTextWrapPos(ImGui.GetCursorPos().X + col2Width);
ImGui.TextUnformatted(description);
ImGui.PopTextWrapPos();
}
var rectMin = new Vector2(col0Start.X - padX, cardTop);
var rectMax = new Vector2(col2Start.X + col2Width + padX, cardTop + cardHeight);
var fill = new Vector4(accent.X, accent.Y, accent.Z, 0.07f);
var border = new Vector4(accent.X, accent.Y, accent.Z, 0.35f);
var rounding = MathF.Max(5f, ImGui.GetStyle().FrameRounding) * scale;
var borderThickness = MathF.Max(1f, ImGui.GetStyle().ChildBorderSize);
var clipMin = drawList.GetClipRectMin();
var clipMax = drawList.GetClipRectMax();
clipMin.X = MathF.Min(clipMin.X, rectMin.X);
clipMax.X = MathF.Max(clipMax.X, rectMax.X);
drawList.ChannelsSetCurrent(0);
drawList.PushClipRect(clipMin, clipMax, false);
drawList.AddRectFilled(rectMin, rectMax, UiSharedService.Color(fill), rounding);
drawList.AddRect(rectMin, rectMax, UiSharedService.Color(border), rounding, ImDrawFlags.None, borderThickness);
drawList.PopClipRect();
drawList.ChannelsMerge();
ImGui.TableSetColumnIndex(2);
ImGui.SetCursorScreenPos(new Vector2(col2Start.X, cardTop + cardHeight));
ImGui.Dummy(new Vector2(0f, rowGap));
}
private static bool DrawAccentCheckbox(string id, ref bool value, Vector4 accent)
{
var frame = new Vector4(accent.X, accent.Y, accent.Z, 0.14f);
var frameHovered = new Vector4(accent.X, accent.Y, accent.Z, 0.22f);
var frameActive = new Vector4(accent.X, accent.Y, accent.Z, 0.3f);
bool changed;
using (ImRaii.PushColor(ImGuiCol.CheckMark, accent))
using (ImRaii.PushColor(ImGuiCol.FrameBg, frame))
using (ImRaii.PushColor(ImGuiCol.FrameBgHovered, frameHovered))
using (ImRaii.PushColor(ImGuiCol.FrameBgActive, frameActive))
{
changed = ImGui.Checkbox(id, ref value);
}
return changed;
}
private static void DrawPanelBox(string id, Vector4 background, Vector4 border, float rounding, Vector2 padding, Action content)
{
using (ImRaii.PushId(id))
{
var startPos = ImGui.GetCursorScreenPos();
var availableWidth = ImGui.GetContentRegionAvail().X;
var drawList = ImGui.GetWindowDrawList();
drawList.ChannelsSplit(2);
drawList.ChannelsSetCurrent(1);
using (ImRaii.Group())
{
ImGui.Dummy(new Vector2(0f, padding.Y));
ImGui.Indent(padding.X);
content();
ImGui.Unindent(padding.X);
ImGui.Dummy(new Vector2(0f, padding.Y));
}
var rectMin = startPos;
var rectMax = new Vector2(startPos.X + availableWidth, ImGui.GetItemRectMax().Y);
var borderThickness = MathF.Max(1f, ImGui.GetStyle().ChildBorderSize);
drawList.ChannelsSetCurrent(0);
drawList.AddRectFilled(rectMin, rectMax, UiSharedService.Color(background), rounding);
drawList.AddRect(rectMin, rectMax, UiSharedService.Color(border), rounding, ImDrawFlags.None, borderThickness);
drawList.ChannelsMerge();
}
}
private void DrawTextureDownscaleCounters()
{
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 totalOriginalBytes = 0;
long totalEffectiveBytes = 0;
var hasData = false;
foreach (var pair in trackedPairs)
{
if (!pair.IsVisible)
continue;
var original = pair.LastAppliedApproximateVRAMBytes;
var effective = pair.LastAppliedApproximateEffectiveVRAMBytes;
if (original >= 0)
{
hasData = true;
totalOriginalBytes += original;
}
if (effective >= 0)
{
hasData = true;
totalEffectiveBytes += effective;
}
}
if (!hasData)
{
ImGui.TextDisabled("VRAM usage has not been calculated yet.");
return;
}
var savedBytes = Math.Max(0L, totalOriginalBytes - totalEffectiveBytes);
var originalText = UiSharedService.ByteToString(totalOriginalBytes, addSuffix: true);
var effectiveText = UiSharedService.ByteToString(totalEffectiveBytes, addSuffix: true);
var savedText = UiSharedService.ByteToString(savedBytes, addSuffix: true);
ImGui.TextUnformatted($"Total VRAM usage (original): {originalText}");
ImGui.TextUnformatted($"Total VRAM usage (effective): {effectiveText}");
if (savedBytes > 0)
{
UiSharedService.ColorText($"VRAM saved by downscaling: {savedText}", UIColors.Get("LightlessGreen"));
}
else
{
ImGui.TextUnformatted($"VRAM saved by downscaling: {savedText}");
}
}
private void DrawTriangleDecimationCounters()
{
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 totalOriginalTris = 0;
long totalEffectiveTris = 0;
var hasData = false;
foreach (var pair in trackedPairs)
{
if (!pair.IsVisible)
continue;
var original = pair.LastAppliedDataTris;
var effective = pair.LastAppliedApproximateEffectiveTris;
if (original >= 0)
{
hasData = true;
totalOriginalTris += original;
}
if (effective >= 0)
{
hasData = true;
totalEffectiveTris += effective;
}
}
if (!hasData)
{
ImGui.TextDisabled("Triangle usage has not been calculated yet.");
return;
}
var savedTris = Math.Max(0L, totalOriginalTris - totalEffectiveTris);
var originalText = FormatTriangleCount(totalOriginalTris);
var effectiveText = FormatTriangleCount(totalEffectiveTris);
var savedText = FormatTriangleCount(savedTris);
ImGui.TextUnformatted($"Total triangle usage (original): {originalText}");
ImGui.TextUnformatted($"Total triangle usage (effective): {effectiveText}");
if (savedTris > 0)
{
UiSharedService.ColorText($"Triangles saved by decimation: {savedText}", UIColors.Get("LightlessGreen"));
}
else
{
ImGui.TextUnformatted($"Triangles saved by decimation: {savedText}");
}
}
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 tris");
}
if (triangleCount >= 1_000)
{
return FormattableString.Invariant($"{triangleCount / 1_000d:0.#}k tris");
}
return $"{triangleCount} tris";
}
}

View 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);
}