sigma update
This commit is contained in:
@@ -28,7 +28,6 @@ using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LightlessSync.UI;
|
||||
|
||||
@@ -71,6 +70,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
private readonly SelectTagForSyncshellUi _selectTagForSyncshellUi;
|
||||
private readonly SeluneBrush _seluneBrush = new();
|
||||
private readonly TopTabMenu _tabMenu;
|
||||
private readonly OptimizationSummaryCard _optimizationSummaryCard;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -86,7 +86,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
private int _pendingFocusFrame = -1;
|
||||
private Pair? _pendingFocusPair;
|
||||
private bool _showModalForUserAddition;
|
||||
private float _transferPartHeight;
|
||||
private float _footerPartHeight;
|
||||
private bool _hasFooterPartHeight;
|
||||
private bool _wasOpen;
|
||||
private float _windowContentWidth;
|
||||
|
||||
@@ -177,6 +178,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
_characterAnalyzer = characterAnalyzer;
|
||||
_playerPerformanceConfig = playerPerformanceConfig;
|
||||
_lightlessMediator = mediator;
|
||||
_optimizationSummaryCard = new OptimizationSummaryCard(_uiSharedService, _pairUiService, _playerPerformanceConfig, _fileTransferManager, _lightlessMediator);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -262,12 +264,17 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
|
||||
using (ImRaii.PushId("global-topmenu")) _tabMenu.Draw(pairSnapshot);
|
||||
using (ImRaii.PushId("pairlist")) DrawPairs();
|
||||
var transfersTop = ImGui.GetCursorScreenPos().Y;
|
||||
var gradientBottom = MathF.Max(gradientTop, transfersTop - style.ItemSpacing.Y - gradientInset);
|
||||
var footerTop = ImGui.GetCursorScreenPos().Y;
|
||||
var gradientBottom = MathF.Max(gradientTop, footerTop - style.ItemSpacing.Y - gradientInset);
|
||||
selune.DrawGradient(gradientTop, gradientBottom, ImGui.GetIO().DeltaTime);
|
||||
float pairlistEnd = ImGui.GetCursorPosY();
|
||||
using (ImRaii.PushId("transfers")) DrawTransfers();
|
||||
_transferPartHeight = ImGui.GetCursorPosY() - pairlistEnd - ImGui.GetTextLineHeight();
|
||||
bool drewFooter;
|
||||
using (ImRaii.PushId("optimization-summary"))
|
||||
{
|
||||
drewFooter = _optimizationSummaryCard.Draw(_currentDownloads.Count);
|
||||
}
|
||||
_footerPartHeight = drewFooter ? ImGui.GetCursorPosY() - pairlistEnd : 0f;
|
||||
_hasFooterPartHeight = true;
|
||||
using (ImRaii.PushId("group-pair-popup")) _selectPairsForGroupUi.Draw(pairSnapshot.DirectPairs);
|
||||
using (ImRaii.PushId("group-syncshell-popup")) _selectSyncshellForTagUi.Draw(pairSnapshot.Groups);
|
||||
using (ImRaii.PushId("group-pair-edit")) _renamePairTagUi.Draw();
|
||||
@@ -330,10 +337,9 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
|
||||
private void DrawPairs()
|
||||
{
|
||||
float ySize = Math.Abs(_transferPartHeight) < 0.0001f
|
||||
float ySize = !_hasFooterPartHeight
|
||||
? 1
|
||||
: (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y
|
||||
+ ImGui.GetTextLineHeight() - ImGui.GetStyle().WindowPadding.Y - ImGui.GetStyle().WindowBorderSize) - _transferPartHeight - ImGui.GetCursorPosY();
|
||||
: MathF.Max(1f, ImGui.GetContentRegionAvail().Y - _footerPartHeight);
|
||||
|
||||
if (ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), border: false))
|
||||
{
|
||||
@@ -346,101 +352,6 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawTransfers()
|
||||
{
|
||||
var currentUploads = _fileTransferManager.GetCurrentUploadsSnapshot();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
_uiSharedService.IconText(FontAwesomeIcon.Upload);
|
||||
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
if (currentUploads.Count > 0)
|
||||
{
|
||||
int totalUploads = currentUploads.Count;
|
||||
int doneUploads = 0;
|
||||
long totalUploaded = 0;
|
||||
long totalToUpload = 0;
|
||||
|
||||
foreach (var upload in currentUploads)
|
||||
{
|
||||
if (upload.IsTransferred)
|
||||
{
|
||||
doneUploads++;
|
||||
}
|
||||
|
||||
totalUploaded += upload.Transferred;
|
||||
totalToUpload += upload.Total;
|
||||
}
|
||||
|
||||
int activeUploads = totalUploads - doneUploads;
|
||||
var uploadSlotLimit = Math.Clamp(_configService.Current.ParallelUploads, 1, 8);
|
||||
|
||||
ImGui.TextUnformatted($"{doneUploads}/{totalUploads} (slots {activeUploads}/{uploadSlotLimit})");
|
||||
var uploadText = $"({UiSharedService.ByteToString(totalUploaded)}/{UiSharedService.ByteToString(totalToUpload)})";
|
||||
var textSize = ImGui.CalcTextSize(uploadText);
|
||||
ImGui.SameLine(_windowContentWidth - textSize.X);
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(uploadText);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted("No uploads in progress");
|
||||
}
|
||||
|
||||
var downloadSummary = GetDownloadSummary();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
_uiSharedService.IconText(FontAwesomeIcon.Download);
|
||||
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
||||
|
||||
if (downloadSummary.HasDownloads)
|
||||
{
|
||||
var totalDownloads = downloadSummary.TotalFiles;
|
||||
var doneDownloads = downloadSummary.TransferredFiles;
|
||||
var totalDownloaded = downloadSummary.TransferredBytes;
|
||||
var totalToDownload = downloadSummary.TotalBytes;
|
||||
|
||||
ImGui.TextUnformatted($"{doneDownloads}/{totalDownloads}");
|
||||
var downloadText =
|
||||
$"({UiSharedService.ByteToString(totalDownloaded)}/{UiSharedService.ByteToString(totalToDownload)})";
|
||||
var textSize = ImGui.CalcTextSize(downloadText);
|
||||
ImGui.SameLine(_windowContentWidth - textSize.X);
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(downloadText);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted("No downloads in progress");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private DownloadSummary GetDownloadSummary()
|
||||
{
|
||||
long totalBytes = 0;
|
||||
long transferredBytes = 0;
|
||||
int totalFiles = 0;
|
||||
int transferredFiles = 0;
|
||||
|
||||
foreach (var kvp in _currentDownloads.ToArray())
|
||||
{
|
||||
if (kvp.Value is not { Count: > 0 } statuses)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var status in statuses.Values)
|
||||
{
|
||||
totalBytes += status.TotalBytes;
|
||||
transferredBytes += status.TransferredBytes;
|
||||
totalFiles += status.TotalFiles;
|
||||
transferredFiles += status.TransferredFiles;
|
||||
}
|
||||
}
|
||||
|
||||
return new DownloadSummary(totalFiles, transferredFiles, transferredBytes, totalBytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Header Drawing
|
||||
@@ -1147,13 +1058,4 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Types
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private readonly record struct DownloadSummary(int TotalFiles, int TransferredFiles, long TransferredBytes, long TotalBytes)
|
||||
{
|
||||
public bool HasDownloads => TotalFiles > 0 || TotalBytes > 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
|
||||
930
LightlessSync/UI/Components/OptimizationSettingsPanel.cs
Normal file
930
LightlessSync/UI/Components/OptimizationSettingsPanel.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -325,16 +325,13 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
|
||||
if (hasValidSize)
|
||||
{
|
||||
if (dlProg > 0)
|
||||
{
|
||||
fillPercent = transferredBytes / (double)totalBytes;
|
||||
showFill = true;
|
||||
}
|
||||
else if (dlDecomp > 0 || dlComplete > 0 || transferredBytes >= totalBytes)
|
||||
fillPercent = totalBytes > 0 ? transferredBytes / (double)totalBytes : 0.0;
|
||||
if (isAllComplete && totalBytes > 0)
|
||||
{
|
||||
fillPercent = 1.0;
|
||||
showFill = true;
|
||||
}
|
||||
|
||||
showFill = transferredBytes > 0 || isAllComplete;
|
||||
}
|
||||
|
||||
if (showFill)
|
||||
|
||||
@@ -25,6 +25,7 @@ using LightlessSync.Services.LightFinder;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.Services.PairProcessing;
|
||||
using LightlessSync.Services.ServerConfiguration;
|
||||
using LightlessSync.UI.Components;
|
||||
using LightlessSync.UI.Models;
|
||||
using LightlessSync.UI.Services;
|
||||
using LightlessSync.UI.Style;
|
||||
@@ -66,6 +67,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
private readonly PairUiService _pairUiService;
|
||||
private readonly PerformanceCollectorService _performanceCollector;
|
||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||
private readonly OptimizationSettingsPanel _optimizationSettingsPanel;
|
||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
@@ -133,6 +135,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
private readonly Dictionary<string, double> _generalTreeHighlights = new(StringComparer.Ordinal);
|
||||
private const float GeneralTreeHighlightDuration = 1.5f;
|
||||
private readonly SeluneBrush _generalSeluneBrush = new();
|
||||
private string? _performanceScrollTarget = null;
|
||||
private string? _performanceOpenTreeTarget = null;
|
||||
private const string PerformanceWarningsLabel = "Warnings";
|
||||
private const string PerformanceAutoPauseLabel = "Auto Pause";
|
||||
private const string PerformanceTextureOptimizationLabel = "Texture Optimization";
|
||||
private const string PerformanceModelOptimizationLabel = "Model Optimization";
|
||||
|
||||
private static readonly (string Label, SeIconChar Icon)[] LightfinderIconPresets = new[]
|
||||
{
|
||||
@@ -208,6 +216,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
_httpClient = httpClient;
|
||||
_fileCompactor = fileCompactor;
|
||||
_uiShared = uiShared;
|
||||
_optimizationSettingsPanel = new OptimizationSettingsPanel(_uiShared, _playerPerformanceConfigService, _pairUiService);
|
||||
_nameplateService = nameplateService;
|
||||
_actorObjectService = actorObjectService;
|
||||
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
|
||||
@@ -229,6 +238,11 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
_selectGeneralTabOnNextDraw = true;
|
||||
FocusGeneralTree("Lightfinder");
|
||||
});
|
||||
Mediator.Subscribe<OpenPerformanceSettingsMessage>(this, msg =>
|
||||
{
|
||||
IsOpen = true;
|
||||
FocusPerformanceSection(msg.Section);
|
||||
});
|
||||
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
||||
Mediator.Subscribe<CutsceneStartMessage>(this, (_) => UiSharedService_GposeStart());
|
||||
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
|
||||
@@ -516,162 +530,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThemeVectorRow(MainStyle.StyleVector2Option option)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
@@ -1593,6 +1451,24 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(statusColor, $"[{(pair.IsVisible ? "Visible" : pair.IsOnline ? "Online" : "Offline")}]");
|
||||
|
||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "Copy Pair Diagnostics##pairDebugCopy"))
|
||||
{
|
||||
ImGui.SetClipboardText(BuildPairDiagnosticsClipboard(pair, snapshot));
|
||||
}
|
||||
|
||||
UiSharedService.AttachToolTip("Copies the current pair diagnostics to the clipboard.");
|
||||
|
||||
ImGui.SameLine();
|
||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "Copy Last Data JSON##pairDebugCopyLastData"))
|
||||
{
|
||||
var lastDataForClipboard = pair.LastReceivedCharacterData;
|
||||
ImGui.SetClipboardText(lastDataForClipboard is null
|
||||
? "ERROR: No character data has been received for this pair."
|
||||
: JsonSerializer.Serialize(lastDataForClipboard, DebugJsonOptions));
|
||||
}
|
||||
|
||||
UiSharedService.AttachToolTip("Copies the last received character data JSON to the clipboard.");
|
||||
|
||||
if (ImGui.BeginTable("##pairDebugProperties", 2, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
DrawPairPropertyRow("UID", pair.UserData.UID);
|
||||
@@ -1722,6 +1598,141 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
DrawPairEventLog(pair);
|
||||
}
|
||||
|
||||
private string BuildPairDiagnosticsClipboard(Pair pair, PairUiSnapshot snapshot)
|
||||
{
|
||||
var debugInfo = pair.GetDebugInfo();
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine("Lightless Pair Diagnostics");
|
||||
sb.AppendLine($"Generated: {DateTime.Now.ToString("G", CultureInfo.CurrentCulture)}");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("Pair");
|
||||
sb.AppendLine($"Alias/UID: {pair.UserData.AliasOrUID}");
|
||||
sb.AppendLine($"UID: {pair.UserData.UID}");
|
||||
sb.AppendLine($"Alias: {(string.IsNullOrEmpty(pair.UserData.Alias) ? "(none)" : pair.UserData.Alias)}");
|
||||
sb.AppendLine($"Player Name: {pair.PlayerName ?? "(not cached)"}");
|
||||
sb.AppendLine($"Handler Ident: {(string.IsNullOrEmpty(pair.Ident) ? "(not bound)" : pair.Ident)}");
|
||||
sb.AppendLine($"Character Id: {FormatCharacterId(pair.PlayerCharacterId)}");
|
||||
sb.AppendLine($"Direct Pair: {FormatBool(pair.IsDirectlyPaired)}");
|
||||
sb.AppendLine($"Individual Status: {pair.IndividualPairStatus}");
|
||||
sb.AppendLine($"Any Connection: {FormatBool(pair.HasAnyConnection())}");
|
||||
sb.AppendLine($"Paused: {FormatBool(pair.IsPaused)}");
|
||||
sb.AppendLine($"Visible: {FormatBool(pair.IsVisible)}");
|
||||
sb.AppendLine($"Online: {FormatBool(pair.IsOnline)}");
|
||||
sb.AppendLine($"Has Handler: {FormatBool(debugInfo.HasHandler)}");
|
||||
sb.AppendLine($"Handler Initialized: {FormatBool(debugInfo.HandlerInitialized)}");
|
||||
sb.AppendLine($"Handler Visible: {FormatBool(debugInfo.HandlerVisible)}");
|
||||
sb.AppendLine($"Last Time person rendered in: {FormatTimestamp(debugInfo.InvisibleSinceUtc)}");
|
||||
sb.AppendLine($"Handler Timer Temp Collection removal: {FormatCountdown(debugInfo.VisibilityEvictionRemainingSeconds)}");
|
||||
sb.AppendLine($"Handler Scheduled For Deletion: {FormatBool(debugInfo.HandlerScheduledForDeletion)}");
|
||||
sb.AppendLine($"Note: {pair.GetNote() ?? "(none)"}");
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Applied Data");
|
||||
sb.AppendLine($"Last Data Size: {FormatBytes(pair.LastAppliedDataBytes)}");
|
||||
sb.AppendLine($"Approx. VRAM: {FormatBytes(pair.LastAppliedApproximateVRAMBytes)}");
|
||||
sb.AppendLine($"Effective VRAM: {FormatBytes(pair.LastAppliedApproximateEffectiveVRAMBytes)}");
|
||||
sb.AppendLine($"Last Triangles: {(pair.LastAppliedDataTris < 0 ? "n/a" : pair.LastAppliedDataTris.ToString(CultureInfo.InvariantCulture))}");
|
||||
sb.AppendLine($"Effective Triangles: {(pair.LastAppliedApproximateEffectiveTris < 0 ? "n/a" : pair.LastAppliedApproximateEffectiveTris.ToString(CultureInfo.InvariantCulture))}");
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Last Received Character Data");
|
||||
var lastData = pair.LastReceivedCharacterData;
|
||||
if (lastData is null)
|
||||
{
|
||||
sb.AppendLine("None");
|
||||
}
|
||||
else
|
||||
{
|
||||
var fileReplacementCount = lastData.FileReplacements.Values.Sum(list => list?.Count ?? 0);
|
||||
var totalGamePaths = lastData.FileReplacements.Values.Sum(list => list?.Sum(replacement => replacement.GamePaths.Length) ?? 0);
|
||||
sb.AppendLine($"File replacements: {fileReplacementCount} entries across {totalGamePaths} game paths.");
|
||||
sb.AppendLine($"Customize+: {lastData.CustomizePlusData.Count}, Glamourer entries: {lastData.GlamourerData.Count}");
|
||||
sb.AppendLine($"Manipulation length: {lastData.ManipulationData.Length}, Heels set: {FormatBool(!string.IsNullOrEmpty(lastData.HeelsData))}");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Application Timeline");
|
||||
sb.AppendLine($"Last Data Received: {FormatTimestamp(debugInfo.LastDataReceivedAt)}");
|
||||
sb.AppendLine($"Last Apply Attempt: {FormatTimestamp(debugInfo.LastApplyAttemptAt)}");
|
||||
sb.AppendLine($"Last Successful Apply: {FormatTimestamp(debugInfo.LastSuccessfulApplyAt)}");
|
||||
|
||||
if (!string.IsNullOrEmpty(debugInfo.LastFailureReason))
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"Last failure: {debugInfo.LastFailureReason}");
|
||||
if (debugInfo.BlockingConditions.Count > 0)
|
||||
{
|
||||
sb.AppendLine("Blocking conditions:");
|
||||
foreach (var condition in debugInfo.BlockingConditions)
|
||||
{
|
||||
sb.AppendLine($"- {condition}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Application & Download State");
|
||||
sb.AppendLine($"Applying Data: {FormatBool(debugInfo.IsApplying)}");
|
||||
sb.AppendLine($"Downloading: {FormatBool(debugInfo.IsDownloading)}");
|
||||
sb.AppendLine($"Pending Downloads: {debugInfo.PendingDownloadCount.ToString(CultureInfo.InvariantCulture)}");
|
||||
sb.AppendLine($"Forbidden Downloads: {debugInfo.ForbiddenDownloadCount.ToString(CultureInfo.InvariantCulture)}");
|
||||
sb.AppendLine($"Pending Mod Reapply: {FormatBool(debugInfo.PendingModReapply)}");
|
||||
sb.AppendLine($"Mod Apply Deferred: {FormatBool(debugInfo.ModApplyDeferred)}");
|
||||
sb.AppendLine($"Missing Critical Mods: {debugInfo.MissingCriticalMods.ToString(CultureInfo.InvariantCulture)}");
|
||||
sb.AppendLine($"Missing Non-Critical Mods: {debugInfo.MissingNonCriticalMods.ToString(CultureInfo.InvariantCulture)}");
|
||||
sb.AppendLine($"Missing Forbidden Mods: {debugInfo.MissingForbiddenMods.ToString(CultureInfo.InvariantCulture)}");
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Syncshell Memberships");
|
||||
if (snapshot.PairsWithGroups.TryGetValue(pair, out var groups) && groups.Count > 0)
|
||||
{
|
||||
foreach (var group in groups.OrderBy(g => g.Group.AliasOrGID, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var flags = group.GroupPairUserInfos.TryGetValue(pair.UserData.UID, out var info) ? info : GroupPairUserInfo.None;
|
||||
var flagLabel = flags switch
|
||||
{
|
||||
GroupPairUserInfo.None => string.Empty,
|
||||
_ => $" ({string.Join(", ", GetGroupInfoFlags(flags))})"
|
||||
};
|
||||
sb.AppendLine($"{group.Group.AliasOrGID} [{group.Group.GID}]{flagLabel}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("Not a member of any syncshells.");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Pair DTO Snapshot");
|
||||
if (pair.UserPair is null)
|
||||
{
|
||||
sb.AppendLine("(unavailable)");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(JsonSerializer.Serialize(pair.UserPair, DebugJsonOptions));
|
||||
}
|
||||
|
||||
var relevantEvents = GetRelevantPairEvents(pair, 40);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Recent Events");
|
||||
if (relevantEvents.Count == 0)
|
||||
{
|
||||
sb.AppendLine("No recent events were logged for this pair.");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var ev in relevantEvents)
|
||||
{
|
||||
var timestamp = ev.EventTime.ToString("T", CultureInfo.CurrentCulture);
|
||||
sb.AppendLine($"{timestamp} [{ev.EventSource}] {ev.EventSeverity}: {ev.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetGroupInfoFlags(GroupPairUserInfo info)
|
||||
{
|
||||
if (info.HasFlag(GroupPairUserInfo.IsModerator))
|
||||
@@ -1735,23 +1746,28 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPairEventLog(Pair pair)
|
||||
private List<Event> GetRelevantPairEvents(Pair pair, int maxEvents)
|
||||
{
|
||||
ImGui.TextUnformatted("Recent Events");
|
||||
var events = _eventAggregator.EventList.Value;
|
||||
var alias = pair.UserData.Alias;
|
||||
var aliasOrUid = pair.UserData.AliasOrUID;
|
||||
var rawUid = pair.UserData.UID;
|
||||
var playerName = pair.PlayerName;
|
||||
|
||||
var relevantEvents = events.Where(e =>
|
||||
return events.Where(e =>
|
||||
EventMatchesIdentifier(e, rawUid)
|
||||
|| EventMatchesIdentifier(e, aliasOrUid)
|
||||
|| EventMatchesIdentifier(e, alias)
|
||||
|| (!string.IsNullOrEmpty(playerName) && string.Equals(e.Character, playerName, StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderByDescending(e => e.EventTime)
|
||||
.Take(40)
|
||||
.Take(maxEvents)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void DrawPairEventLog(Pair pair)
|
||||
{
|
||||
ImGui.TextUnformatted("Recent Events");
|
||||
var relevantEvents = GetRelevantPairEvents(pair, 40);
|
||||
|
||||
if (relevantEvents.Count == 0)
|
||||
{
|
||||
@@ -2290,11 +2306,29 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
var syncshellOfflineSeparate = _configService.Current.ShowSyncshellOfflineUsersSeparately;
|
||||
var greenVisiblePair = _configService.Current.ShowVisiblePairsGreenEye;
|
||||
var enableParticleEffects = _configService.Current.EnableParticleEffects;
|
||||
var showUiWhenUiHidden = _configService.Current.ShowUiWhenUiHidden;
|
||||
var showUiInGpose = _configService.Current.ShowUiInGpose;
|
||||
|
||||
using (var behaviorTree = BeginGeneralTree("Behavior", UIColors.Get("LightlessPurple")))
|
||||
{
|
||||
if (behaviorTree.Visible)
|
||||
{
|
||||
if (ImGui.Checkbox("Show Lightless windows when game UI is hidden", ref showUiWhenUiHidden))
|
||||
{
|
||||
_configService.Current.ShowUiWhenUiHidden = showUiWhenUiHidden;
|
||||
_configService.Save();
|
||||
}
|
||||
|
||||
_uiShared.DrawHelpText("When disabled, Lightless windows (except chat) are hidden when the game UI is hidden.");
|
||||
|
||||
if (ImGui.Checkbox("Show Lightless windows in group pose", ref showUiInGpose))
|
||||
{
|
||||
_configService.Current.ShowUiInGpose = showUiInGpose;
|
||||
_configService.Save();
|
||||
}
|
||||
|
||||
_uiShared.DrawHelpText("When disabled, Lightless windows (except chat) are hidden while in group pose.");
|
||||
|
||||
if (ImGui.Checkbox("Enable Particle Effects", ref enableParticleEffects))
|
||||
{
|
||||
_configService.Current.EnableParticleEffects = enableParticleEffects;
|
||||
@@ -3401,6 +3435,43 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
_generalTreeHighlights[label] = ImGui.GetTime();
|
||||
}
|
||||
|
||||
private void FocusPerformanceSection(PerformanceSettingsSection section)
|
||||
{
|
||||
_selectGeneralTabOnNextDraw = false;
|
||||
_selectedMainTab = MainSettingsTab.Performance;
|
||||
var label = section switch
|
||||
{
|
||||
PerformanceSettingsSection.TextureOptimization => PerformanceTextureOptimizationLabel,
|
||||
PerformanceSettingsSection.ModelOptimization => PerformanceModelOptimizationLabel,
|
||||
_ => PerformanceTextureOptimizationLabel,
|
||||
};
|
||||
_performanceOpenTreeTarget = label;
|
||||
_performanceScrollTarget = label;
|
||||
}
|
||||
|
||||
private bool BeginPerformanceTree(string label, Vector4 color)
|
||||
{
|
||||
var shouldForceOpen = string.Equals(_performanceOpenTreeTarget, label, StringComparison.Ordinal);
|
||||
if (shouldForceOpen)
|
||||
{
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Always);
|
||||
}
|
||||
|
||||
var open = _uiShared.MediumTreeNode(label, color);
|
||||
if (shouldForceOpen)
|
||||
{
|
||||
_performanceOpenTreeTarget = null;
|
||||
}
|
||||
|
||||
if (open && string.Equals(_performanceScrollTarget, label, StringComparison.Ordinal))
|
||||
{
|
||||
ImGui.SetScrollHereY(0f);
|
||||
_performanceScrollTarget = null;
|
||||
}
|
||||
|
||||
return open;
|
||||
}
|
||||
|
||||
private float GetGeneralTreeHighlightAlpha(string label)
|
||||
{
|
||||
if (!_generalTreeHighlights.TryGetValue(label, out var startTime))
|
||||
@@ -3490,7 +3561,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
|
||||
bool showPerformanceIndicator = _playerPerformanceConfigService.Current.ShowPerformanceIndicator;
|
||||
|
||||
if (_uiShared.MediumTreeNode("Warnings", UIColors.Get("LightlessPurple")))
|
||||
if (BeginPerformanceTree(PerformanceWarningsLabel, UIColors.Get("LightlessPurple")))
|
||||
{
|
||||
if (ImGui.Checkbox("Show performance indicator", ref showPerformanceIndicator))
|
||||
{
|
||||
@@ -3586,7 +3657,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
bool autoPauseInCombat = _playerPerformanceConfigService.Current.PauseInCombat;
|
||||
bool autoPauseWhilePerforming = _playerPerformanceConfigService.Current.PauseWhilePerforming;
|
||||
|
||||
if (_uiShared.MediumTreeNode("Auto Pause", UIColors.Get("LightlessPurple")))
|
||||
if (BeginPerformanceTree(PerformanceAutoPauseLabel, UIColors.Get("LightlessPurple")))
|
||||
{
|
||||
if (ImGui.Checkbox("Auto pause sync while combat", ref autoPauseInCombat))
|
||||
{
|
||||
@@ -3683,261 +3754,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (_uiShared.MediumTreeNode("Texture Optimization", UIColors.Get("LightlessYellow")))
|
||||
{
|
||||
_uiShared.MediumText("Warning", UIColors.Get("DimRed"));
|
||||
_uiShared.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."));
|
||||
|
||||
_uiShared.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("."));
|
||||
|
||||
_uiShared.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."));
|
||||
|
||||
_uiShared.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
|
||||
new SeStringUtils.RichTextEntry("When enabled, we cannot provide support for appearance issues caused by this setting!", UIColors.Get("DimRed"), true));
|
||||
|
||||
var textureConfig = _playerPerformanceConfigService.Current;
|
||||
var trimNonIndex = textureConfig.EnableNonIndexTextureMipTrim;
|
||||
if (ImGui.Checkbox("Trim mip levels for textures", ref trimNonIndex))
|
||||
{
|
||||
textureConfig.EnableNonIndexTextureMipTrim = trimNonIndex;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("When enabled, Lightless will remove high-resolution mip levels from textures (not index) that exceed the size limit and are not compressed with any kind compression.");
|
||||
|
||||
var downscaleIndex = textureConfig.EnableIndexTextureDownscale;
|
||||
if (ImGui.Checkbox("Downscale index textures above limit", ref downscaleIndex))
|
||||
{
|
||||
textureConfig.EnableIndexTextureDownscale = downscaleIndex;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("Controls whether Lightless reduces index textures that exceed the size limit.");
|
||||
|
||||
var dimensionOptions = new[] { 512, 1024, 2048, 4096 };
|
||||
var optionLabels = dimensionOptions.Select(selector: 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(140 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.Combo("Maximum texture dimension", ref selectedIndex, optionLabels, optionLabels.Length))
|
||||
{
|
||||
textureConfig.TextureDownscaleMaxDimension = dimensionOptions[selectedIndex];
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText($"Textures above this size will be reduced until their largest dimension is at or below the limit. Block-compressed textures are skipped when \"Only downscale uncompressed\" is enabled.{UiSharedService.TooltipSeparator}Default: 2048");
|
||||
|
||||
var keepOriginalTextures = textureConfig.KeepOriginalTextureFiles;
|
||||
if (ImGui.Checkbox("Keep original texture files", ref keepOriginalTextures))
|
||||
{
|
||||
textureConfig.KeepOriginalTextureFiles = keepOriginalTextures;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("When disabled, Lightless removes the original texture after a downscaled copy is created.");
|
||||
ImGui.SameLine();
|
||||
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective VRAM usage information will not work.", UIColors.Get("LightlessYellow")));
|
||||
|
||||
var skipPreferredDownscale = textureConfig.SkipTextureDownscaleForPreferredPairs;
|
||||
if (ImGui.Checkbox("Skip downscale for preferred/direct pairs", ref skipPreferredDownscale))
|
||||
{
|
||||
textureConfig.SkipTextureDownscaleForPreferredPairs = skipPreferredDownscale;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("When enabled, textures for direct pairs with preferred permissions are left untouched.");
|
||||
|
||||
if (!textureConfig.EnableNonIndexTextureMipTrim && !textureConfig.EnableIndexTextureDownscale)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Both trimming and downscale are disabled. Lightless will keep original textures regardless of size.", UIColors.Get("DimRed"));
|
||||
}
|
||||
|
||||
ImGui.Dummy(new Vector2(5));
|
||||
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 3f);
|
||||
var onlyUncompressed = textureConfig.OnlyDownscaleUncompressedTextures;
|
||||
if (ImGui.Checkbox("Only downscale uncompressed textures", ref onlyUncompressed))
|
||||
{
|
||||
textureConfig.OnlyDownscaleUncompressedTextures = onlyUncompressed;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("If disabled, compressed textures will be targeted for downscaling too.");
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 3f);
|
||||
|
||||
ImGui.Dummy(new Vector2(5));
|
||||
|
||||
DrawTextureDownscaleCounters();
|
||||
|
||||
ImGui.Dummy(new Vector2(5));
|
||||
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (_uiShared.MediumTreeNode("Model Optimization", UIColors.Get("DimRed")))
|
||||
{
|
||||
_uiShared.MediumText("Warning", UIColors.Get("DimRed"));
|
||||
_uiShared.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."));
|
||||
|
||||
|
||||
_uiShared.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("."));
|
||||
|
||||
_uiShared.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."));
|
||||
|
||||
_uiShared.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(15));
|
||||
|
||||
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessGreen"),
|
||||
new SeStringUtils.RichTextEntry("If a mesh exceeds the "),
|
||||
new SeStringUtils.RichTextEntry("triangle threshold", UIColors.Get("LightlessGreen"), true),
|
||||
new SeStringUtils.RichTextEntry(", it will be decimated automatically to the set "),
|
||||
new SeStringUtils.RichTextEntry("target triangle ratio", UIColors.Get("LightlessGreen"), true),
|
||||
new SeStringUtils.RichTextEntry(". This will reduce quality of the mesh or may break it's intended structure."));
|
||||
|
||||
|
||||
var performanceConfig = _playerPerformanceConfigService.Current;
|
||||
var enableDecimation = performanceConfig.EnableModelDecimation;
|
||||
if (ImGui.Checkbox("Enable model decimation", ref enableDecimation))
|
||||
{
|
||||
performanceConfig.EnableModelDecimation = enableDecimation;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("When enabled, Lightless generates a decimated copy of given model after download.");
|
||||
|
||||
var keepOriginalModels = performanceConfig.KeepOriginalModelFiles;
|
||||
if (ImGui.Checkbox("Keep original model files", ref keepOriginalModels))
|
||||
{
|
||||
performanceConfig.KeepOriginalModelFiles = keepOriginalModels;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("When disabled, Lightless removes the original model after a decimated copy is created.");
|
||||
ImGui.SameLine();
|
||||
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective triangle usage information will not work.", UIColors.Get("LightlessYellow")));
|
||||
|
||||
var skipPreferredDecimation = performanceConfig.SkipModelDecimationForPreferredPairs;
|
||||
if (ImGui.Checkbox("Skip decimation for preferred/direct pairs", ref skipPreferredDecimation))
|
||||
{
|
||||
performanceConfig.SkipModelDecimationForPreferredPairs = skipPreferredDecimation;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("When enabled, models for direct pairs with preferred permissions are left untouched.");
|
||||
|
||||
var triangleThreshold = performanceConfig.ModelDecimationTriangleThreshold;
|
||||
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.SliderInt("Decimate models above", ref triangleThreshold, 8_000, 100_000))
|
||||
{
|
||||
performanceConfig.ModelDecimationTriangleThreshold = Math.Clamp(triangleThreshold, 8_000, 100_000);
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("triangles");
|
||||
_uiShared.DrawHelpText($"Models below this triangle count are left untouched.{UiSharedService.TooltipSeparator}Default: 50,000");
|
||||
|
||||
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;
|
||||
_playerPerformanceConfigService.Save();
|
||||
targetPercent = clampedPercent;
|
||||
}
|
||||
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.SliderFloat("Target triangle ratio", ref targetPercent, 60f, 99f, "%.0f%%"))
|
||||
{
|
||||
performanceConfig.ModelDecimationTargetRatio = Math.Clamp(targetPercent / 100f, 0.6f, 0.99f);
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText($"Target ratio relative to original triangle count (80% keeps 80% of triangles).{UiSharedService.TooltipSeparator}Default: 80%");
|
||||
|
||||
ImGui.Dummy(new Vector2(15));
|
||||
ImGui.TextUnformatted("Decimation targets");
|
||||
_uiShared.DrawHelpText("Hair mods are always excluded from decimation.");
|
||||
|
||||
_uiShared.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("."));
|
||||
|
||||
_uiShared.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("."));
|
||||
|
||||
_uiShared.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
|
||||
new SeStringUtils.RichTextEntry("Remember, automatic decimation is not perfect and can cause meshes to be ruined, especially hair mods.", UIColors.Get("DimRed"), true));
|
||||
|
||||
var allowBody = performanceConfig.ModelDecimationAllowBody;
|
||||
if (ImGui.Checkbox("Body", ref allowBody))
|
||||
{
|
||||
performanceConfig.ModelDecimationAllowBody = allowBody;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
|
||||
var allowFaceHead = performanceConfig.ModelDecimationAllowFaceHead;
|
||||
if (ImGui.Checkbox("Face/head", ref allowFaceHead))
|
||||
{
|
||||
performanceConfig.ModelDecimationAllowFaceHead = allowFaceHead;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
|
||||
var allowTail = performanceConfig.ModelDecimationAllowTail;
|
||||
if (ImGui.Checkbox("Tails/Ears", ref allowTail))
|
||||
{
|
||||
performanceConfig.ModelDecimationAllowTail = allowTail;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
|
||||
var allowClothing = performanceConfig.ModelDecimationAllowClothing;
|
||||
if (ImGui.Checkbox("Clothing (body/legs/shoes/gloves/hats)", ref allowClothing))
|
||||
{
|
||||
performanceConfig.ModelDecimationAllowClothing = allowClothing;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
|
||||
var allowAccessories = performanceConfig.ModelDecimationAllowAccessories;
|
||||
if (ImGui.Checkbox("Accessories (earring/rings/bracelet/necklace)", ref allowAccessories))
|
||||
{
|
||||
performanceConfig.ModelDecimationAllowAccessories = allowAccessories;
|
||||
_playerPerformanceConfigService.Save();
|
||||
}
|
||||
|
||||
ImGui.Dummy(new Vector2(5));
|
||||
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessGrey"), 3f);
|
||||
|
||||
ImGui.Dummy(new Vector2(5));
|
||||
DrawTriangleDecimationCounters();
|
||||
ImGui.Dummy(new Vector2(5));
|
||||
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.5f);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
_optimizationSettingsPanel.DrawSettingsTrees(
|
||||
PerformanceTextureOptimizationLabel,
|
||||
UIColors.Get("LightlessYellow"),
|
||||
PerformanceModelOptimizationLabel,
|
||||
UIColors.Get("LightlessOrange"),
|
||||
BeginPerformanceTree);
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Dummy(new Vector2(10));
|
||||
|
||||
@@ -40,10 +40,10 @@ internal static class MainStyle
|
||||
new("color.frameBg", "Frame Background", () => Rgba(40, 40, 40, 255), ImGuiCol.FrameBg),
|
||||
new("color.frameBgHovered", "Frame Background (Hover)", () => Rgba(50, 50, 50, 100), ImGuiCol.FrameBgHovered),
|
||||
new("color.frameBgActive", "Frame Background (Active)", () => Rgba(30, 30, 30, 255), ImGuiCol.FrameBgActive),
|
||||
new("color.titleBg", "Title Background", () => Rgba(22, 14, 41, 255), ImGuiCol.TitleBg),
|
||||
new("color.titleBgActive", "Title Background (Active)", () => Rgba(22, 14, 41, 255), ImGuiCol.TitleBgActive),
|
||||
new("color.titleBgCollapsed", "Title Background (Collapsed)", () => Rgba(22, 14, 41, 255), ImGuiCol.TitleBgCollapsed),
|
||||
|
||||
new("color.titleBg", "Title Background", () => Rgba(24, 24, 24, 232), ImGuiCol.TitleBg),
|
||||
new("color.titleBgActive", "Title Background (Active)", () => Rgba(30, 30, 30, 255), ImGuiCol.TitleBgActive),
|
||||
new("color.titleBgCollapsed", "Title Background (Collapsed)", () => Rgba(27, 27, 27, 255), ImGuiCol.TitleBgCollapsed),
|
||||
|
||||
new("color.menuBarBg", "Menu Bar Background", () => Rgba(36, 36, 36, 255), ImGuiCol.MenuBarBg),
|
||||
new("color.scrollbarBg", "Scrollbar Background", () => Rgba(0, 0, 0, 0), ImGuiCol.ScrollbarBg),
|
||||
new("color.scrollbarGrab", "Scrollbar Grab", () => Rgba(62, 62, 62, 255), ImGuiCol.ScrollbarGrab),
|
||||
|
||||
@@ -29,6 +29,7 @@ public sealed class SeluneGradientSettings
|
||||
public Vector4 GradientColor { get; init; } = UIColors.Get("LightlessPurple");
|
||||
public Vector4? HighlightColor { get; init; }
|
||||
public float GradientPeakOpacity { get; init; } = 0.07f;
|
||||
public float GradientPeakPosition { get; init; } = 0.035f;
|
||||
public float HighlightPeakAlpha { get; init; } = 0.13f;
|
||||
public float HighlightEdgeAlpha { get; init; } = 0f;
|
||||
public float HighlightMidpoint { get; init; } = 0.45f;
|
||||
@@ -378,6 +379,7 @@ internal static class SeluneRenderer
|
||||
topColorVec,
|
||||
midColorVec,
|
||||
bottomColorVec,
|
||||
settings,
|
||||
settings.BackgroundMode);
|
||||
}
|
||||
|
||||
@@ -403,19 +405,21 @@ internal static class SeluneRenderer
|
||||
Vector4 topColorVec,
|
||||
Vector4 midColorVec,
|
||||
Vector4 bottomColorVec,
|
||||
SeluneGradientSettings settings,
|
||||
SeluneGradientMode mode)
|
||||
{
|
||||
var peakPosition = Math.Clamp(settings.GradientPeakPosition, 0.01f, 0.99f);
|
||||
switch (mode)
|
||||
{
|
||||
case SeluneGradientMode.Vertical:
|
||||
DrawVerticalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
|
||||
DrawVerticalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec, peakPosition);
|
||||
break;
|
||||
case SeluneGradientMode.Horizontal:
|
||||
DrawHorizontalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
|
||||
DrawHorizontalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec, peakPosition);
|
||||
break;
|
||||
case SeluneGradientMode.Both:
|
||||
DrawVerticalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
|
||||
DrawHorizontalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
|
||||
DrawVerticalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec, peakPosition);
|
||||
DrawHorizontalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec, peakPosition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -428,13 +432,14 @@ internal static class SeluneRenderer
|
||||
float clampedBottomY,
|
||||
Vector4 topColorVec,
|
||||
Vector4 midColorVec,
|
||||
Vector4 bottomColorVec)
|
||||
Vector4 bottomColorVec,
|
||||
float peakPosition)
|
||||
{
|
||||
var topColor = ImGui.ColorConvertFloat4ToU32(topColorVec);
|
||||
var midColor = ImGui.ColorConvertFloat4ToU32(midColorVec);
|
||||
var bottomColor = ImGui.ColorConvertFloat4ToU32(bottomColorVec);
|
||||
|
||||
var midY = clampedTopY + (clampedBottomY - clampedTopY) * 0.035f;
|
||||
var midY = clampedTopY + (clampedBottomY - clampedTopY) * peakPosition;
|
||||
drawList.AddRectFilledMultiColor(
|
||||
new Vector2(gradientLeft, clampedTopY),
|
||||
new Vector2(gradientRight, midY),
|
||||
@@ -460,13 +465,14 @@ internal static class SeluneRenderer
|
||||
float clampedBottomY,
|
||||
Vector4 leftColorVec,
|
||||
Vector4 midColorVec,
|
||||
Vector4 rightColorVec)
|
||||
Vector4 rightColorVec,
|
||||
float peakPosition)
|
||||
{
|
||||
var leftColor = ImGui.ColorConvertFloat4ToU32(leftColorVec);
|
||||
var midColor = ImGui.ColorConvertFloat4ToU32(midColorVec);
|
||||
var rightColor = ImGui.ColorConvertFloat4ToU32(rightColorVec);
|
||||
|
||||
var midX = gradientLeft + (gradientRight - gradientLeft) * 0.035f;
|
||||
var midX = gradientLeft + (gradientRight - gradientLeft) * peakPosition;
|
||||
drawList.AddRectFilledMultiColor(
|
||||
new Vector2(gradientLeft, clampedTopY),
|
||||
new Vector2(midX, clampedBottomY),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user