931 lines
40 KiB
C#
931 lines
40 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using LightlessSync.LightlessConfiguration;
|
|
using LightlessSync.LightlessConfiguration.Configurations;
|
|
using LightlessSync.PlayerData.Pairs;
|
|
using LightlessSync.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";
|
|
}
|
|
}
|