sigma update
This commit is contained in:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user