boom
This commit is contained in:
@@ -14,6 +14,7 @@ using LightlessSync.Services.TextureCompression;
|
||||
using LightlessSync.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OtterTex;
|
||||
using System.Buffers.Binary;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -49,6 +50,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
private readonly Dictionary<string, TextureCompressionTarget> _textureSelections = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly HashSet<string> _selectedTextureKeys = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, TexturePreviewState> _texturePreviews = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, TextureResolutionInfo?> _textureResolutionCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<ObjectKind, TextureWorkspaceTab> _textureWorkspaceTabs = new();
|
||||
private readonly List<string> _storedPathsToRemove = [];
|
||||
private readonly Dictionary<string, string> _filePathResolve = [];
|
||||
@@ -88,6 +90,9 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
private bool _showAlreadyAddedTransients = false;
|
||||
private bool _acknowledgeReview = false;
|
||||
|
||||
private Task<TextureRowBuildResult>? _textureRowsBuildTask;
|
||||
private CancellationTokenSource? _textureRowsBuildCts;
|
||||
|
||||
private ObjectKind _selectedObjectTab;
|
||||
|
||||
private TextureUsageCategory? _textureCategoryFilter = null;
|
||||
@@ -204,9 +209,9 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
return;
|
||||
}
|
||||
|
||||
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
|
||||
_cachedAnalysis = CloneAnalysis(_characterAnalyzer.LastAnalysis);
|
||||
_hasUpdate = false;
|
||||
_textureRowsDirty = true;
|
||||
InvalidateTextureRows();
|
||||
}
|
||||
|
||||
private void DrawContentTabs()
|
||||
@@ -750,7 +755,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
_selectedTextureKeys.Clear();
|
||||
_textureSelections.Clear();
|
||||
ResetTextureFilters();
|
||||
_textureRowsDirty = true;
|
||||
InvalidateTextureRows();
|
||||
_conversionFailed = false;
|
||||
}
|
||||
|
||||
@@ -762,6 +767,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
preview.Texture?.Dispose();
|
||||
}
|
||||
_texturePreviews.Clear();
|
||||
_textureRowsBuildCts?.Cancel();
|
||||
_textureRowsBuildCts?.Dispose();
|
||||
_conversionProgress.ProgressChanged -= ConversionProgress_ProgressChanged;
|
||||
}
|
||||
|
||||
@@ -775,18 +782,108 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
|
||||
private void EnsureTextureRows()
|
||||
{
|
||||
if (!_textureRowsDirty || _cachedAnalysis == null)
|
||||
if (_cachedAnalysis == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_textureRowsDirty && _textureRowsBuildTask == null)
|
||||
{
|
||||
_textureRowsBuildCts?.Dispose();
|
||||
_textureRowsBuildCts = new();
|
||||
var snapshot = _cachedAnalysis;
|
||||
_textureRowsBuildTask = Task.Run(() => BuildTextureRows(snapshot, _textureRowsBuildCts.Token), _textureRowsBuildCts.Token);
|
||||
}
|
||||
|
||||
if (_textureRowsBuildTask == null || !_textureRowsBuildTask.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var completedTask = _textureRowsBuildTask;
|
||||
_textureRowsBuildTask = null;
|
||||
_textureRowsBuildCts?.Dispose();
|
||||
_textureRowsBuildCts = null;
|
||||
|
||||
if (completedTask.IsCanceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (completedTask.IsFaulted)
|
||||
{
|
||||
_logger.LogWarning(completedTask.Exception, "Failed to build texture rows.");
|
||||
_textureRowsDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyTextureRowBuild(completedTask.Result);
|
||||
_textureRowsDirty = false;
|
||||
}
|
||||
|
||||
private void ApplyTextureRowBuild(TextureRowBuildResult result)
|
||||
{
|
||||
_textureRows.Clear();
|
||||
_textureRows.AddRange(result.Rows);
|
||||
|
||||
foreach (var row in _textureRows)
|
||||
{
|
||||
if (row.IsAlreadyCompressed)
|
||||
{
|
||||
_selectedTextureKeys.Remove(row.Key);
|
||||
_textureSelections.Remove(row.Key);
|
||||
}
|
||||
}
|
||||
|
||||
_selectedTextureKeys.RemoveWhere(key => !result.ValidKeys.Contains(key));
|
||||
|
||||
foreach (var key in _texturePreviews.Keys.ToArray())
|
||||
{
|
||||
if (!result.ValidKeys.Contains(key) && _texturePreviews.TryGetValue(key, out var preview))
|
||||
{
|
||||
preview.Texture?.Dispose();
|
||||
_texturePreviews.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in _textureResolutionCache.Keys.ToArray())
|
||||
{
|
||||
if (!result.ValidKeys.Contains(key))
|
||||
{
|
||||
_textureResolutionCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in _textureSelections.Keys.ToArray())
|
||||
{
|
||||
if (!result.ValidKeys.Contains(key))
|
||||
{
|
||||
_textureSelections.Remove(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
_textureSelections[key] = _textureCompressionService.NormalizeTarget(_textureSelections[key]);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_selectedTextureKey) && !result.ValidKeys.Contains(_selectedTextureKey))
|
||||
{
|
||||
_selectedTextureKey = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private TextureRowBuildResult BuildTextureRows(
|
||||
Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>> analysis,
|
||||
CancellationToken token)
|
||||
{
|
||||
var rows = new List<TextureRow>();
|
||||
HashSet<string> validKeys = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var (objectKind, entries) in _cachedAnalysis)
|
||||
foreach (var (objectKind, entries) in analysis)
|
||||
{
|
||||
foreach (var entry in entries.Values)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.Equals(entry.FileType, "tex", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
@@ -828,17 +925,11 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
suggestion?.Reason);
|
||||
|
||||
validKeys.Add(row.Key);
|
||||
_textureRows.Add(row);
|
||||
|
||||
if (row.IsAlreadyCompressed)
|
||||
{
|
||||
_selectedTextureKeys.Remove(row.Key);
|
||||
_textureSelections.Remove(row.Key);
|
||||
}
|
||||
rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
_textureRows.Sort((a, b) =>
|
||||
rows.Sort((a, b) =>
|
||||
{
|
||||
var comp = a.ObjectKind.CompareTo(b.ObjectKind);
|
||||
if (comp != 0)
|
||||
@@ -851,34 +942,14 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
return string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
_selectedTextureKeys.RemoveWhere(key => !validKeys.Contains(key));
|
||||
return new TextureRowBuildResult(rows, validKeys);
|
||||
}
|
||||
|
||||
foreach (var key in _texturePreviews.Keys.ToArray())
|
||||
{
|
||||
if (!validKeys.Contains(key) && _texturePreviews.TryGetValue(key, out var preview))
|
||||
{
|
||||
preview.Texture?.Dispose();
|
||||
_texturePreviews.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in _textureSelections.Keys.ToArray())
|
||||
{
|
||||
if (!validKeys.Contains(key))
|
||||
{
|
||||
_textureSelections.Remove(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
_textureSelections[key] = _textureCompressionService.NormalizeTarget(_textureSelections[key]);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_selectedTextureKey) && !validKeys.Contains(_selectedTextureKey))
|
||||
{
|
||||
_selectedTextureKey = string.Empty;
|
||||
}
|
||||
|
||||
_textureRowsDirty = false;
|
||||
private void InvalidateTextureRows()
|
||||
{
|
||||
_textureRowsDirty = true;
|
||||
_textureRowsBuildCts?.Cancel();
|
||||
_textureResolutionCache.Clear();
|
||||
}
|
||||
|
||||
private static string MakeTextureKey(ObjectKind objectKind, string primaryFilePath) =>
|
||||
@@ -893,6 +964,30 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
_textureSearch = string.Empty;
|
||||
}
|
||||
|
||||
private static Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>> CloneAnalysis(
|
||||
Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>> source)
|
||||
{
|
||||
var clone = new Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>(source.Count);
|
||||
foreach (var (objectKind, entries) in source)
|
||||
{
|
||||
var entryClone = new Dictionary<string, CharacterAnalyzer.FileDataEntry>(entries.Count, entries.Comparer);
|
||||
foreach (var (hash, entry) in entries)
|
||||
{
|
||||
entryClone[hash] = new CharacterAnalyzer.FileDataEntry(
|
||||
hash,
|
||||
entry.FileType,
|
||||
entry.GamePaths.ToList(),
|
||||
entry.FilePaths.ToList(),
|
||||
entry.OriginalSize,
|
||||
entry.CompressedSize,
|
||||
entry.Triangles);
|
||||
}
|
||||
clone[objectKind] = entryClone;
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
private void DrawAnalysisOverview(int totalFiles, long totalActualSize, long totalCompressedSize, long totalTriangles, string breakdownTooltip)
|
||||
{
|
||||
var scale = ImGuiHelpers.GlobalScale;
|
||||
@@ -1091,6 +1186,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
public bool IsAlreadyCompressed => CurrentTarget.HasValue;
|
||||
}
|
||||
|
||||
private sealed record TextureRowBuildResult(
|
||||
List<TextureRow> Rows,
|
||||
HashSet<string> ValidKeys);
|
||||
|
||||
private sealed class TexturePreviewState
|
||||
{
|
||||
public Task? LoadTask { get; set; }
|
||||
@@ -1099,6 +1198,22 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
public DateTime LastAccessUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private readonly struct TextureResolutionInfo
|
||||
{
|
||||
public TextureResolutionInfo(ushort width, ushort height, ushort depth, ushort mipLevels)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = depth;
|
||||
MipLevels = mipLevels;
|
||||
}
|
||||
|
||||
public ushort Width { get; }
|
||||
public ushort Height { get; }
|
||||
public ushort Depth { get; }
|
||||
public ushort MipLevels { get; }
|
||||
}
|
||||
|
||||
private void DrawTextureWorkspace(ObjectKind objectKind, IReadOnlyList<IGrouping<string, CharacterAnalyzer.FileDataEntry>> otherFileGroups)
|
||||
{
|
||||
if (!_textureWorkspaceTabs.ContainsKey(objectKind))
|
||||
@@ -1143,6 +1258,11 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
private void DrawTextureTabContent(ObjectKind objectKind)
|
||||
{
|
||||
var scale = ImGuiHelpers.GlobalScale;
|
||||
if (_textureRowsBuildTask != null && !_textureRowsBuildTask.IsCompleted && _textureRows.Count == 0)
|
||||
{
|
||||
UiSharedService.ColorText("Building texture list.", ImGuiColors.DalamudGrey);
|
||||
return;
|
||||
}
|
||||
var objectRows = _textureRows.Where(row => row.ObjectKind == objectKind).ToList();
|
||||
var hasAnyTextureRows = objectRows.Count > 0;
|
||||
var availableCategories = objectRows.Select(row => row.Category)
|
||||
@@ -1404,6 +1524,24 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
ResetTextureFilters();
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(6);
|
||||
ImGui.Separator();
|
||||
ImGuiHelpers.ScaledDummy(4);
|
||||
UiSharedService.ColorText("Texture row colors", UIColors.Get("LightlessPurple"));
|
||||
DrawTextureRowLegendItem("Selected", UIColors.Get("LightlessYellow"), "This row is selected in the texture table.");
|
||||
DrawTextureRowLegendItem("Already compressed", UIColors.Get("LightlessGreenDefault"), "Texture is already stored in a compressed format.");
|
||||
DrawTextureRowLegendItem("Missing analysis data", UIColors.Get("DimRed"), "File size data has not been computed yet.");
|
||||
}
|
||||
|
||||
private static void DrawTextureRowLegendItem(string label, Vector4 color, string description)
|
||||
{
|
||||
var scale = ImGuiHelpers.GlobalScale;
|
||||
var swatchSize = new Vector2(12f * scale, 12f * scale);
|
||||
ImGui.ColorButton($"##textureRowLegend{label}", color, ImGuiColorEditFlags.NoTooltip | ImGuiColorEditFlags.NoDragDrop, swatchSize);
|
||||
ImGui.SameLine(0f, 6f * scale);
|
||||
var wrapPos = ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X;
|
||||
UiSharedService.TextWrapped($"{label}: {description}", wrapPos);
|
||||
}
|
||||
|
||||
private static void DrawEnumFilterCombo<T>(
|
||||
@@ -1810,7 +1948,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.SameLine();
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, "Refresh", 130f * scale))
|
||||
{
|
||||
_textureRowsDirty = true;
|
||||
InvalidateTextureRows();
|
||||
}
|
||||
|
||||
TextureRow? lastSelected = null;
|
||||
@@ -1976,7 +2114,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
_selectedTextureKeys.Clear();
|
||||
_textureSelections.Clear();
|
||||
_textureRowsDirty = true;
|
||||
InvalidateTextureRows();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2197,6 +2335,68 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private TextureResolutionInfo? GetTextureResolution(TextureRow row)
|
||||
{
|
||||
if (_textureResolutionCache.TryGetValue(row.Key, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var info = TryReadTextureResolution(row.PrimaryFilePath, out var resolved)
|
||||
? resolved
|
||||
: (TextureResolutionInfo?)null;
|
||||
_textureResolutionCache[row.Key] = info;
|
||||
return info;
|
||||
}
|
||||
|
||||
private static bool TryReadTextureResolution(string path, out TextureResolutionInfo info)
|
||||
{
|
||||
info = default;
|
||||
|
||||
try
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[16];
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
|
||||
var read = stream.Read(buffer);
|
||||
if (read < buffer.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var width = BinaryPrimitives.ReadUInt16LittleEndian(buffer[8..10]);
|
||||
var height = BinaryPrimitives.ReadUInt16LittleEndian(buffer[10..12]);
|
||||
var depth = BinaryPrimitives.ReadUInt16LittleEndian(buffer[12..14]);
|
||||
var mipLevels = BinaryPrimitives.ReadUInt16LittleEndian(buffer[14..16]);
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (depth == 0)
|
||||
{
|
||||
depth = 1;
|
||||
}
|
||||
|
||||
if (mipLevels == 0)
|
||||
{
|
||||
mipLevels = 1;
|
||||
}
|
||||
|
||||
info = new TextureResolutionInfo(width, height, depth, mipLevels);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatTextureResolution(TextureResolutionInfo info)
|
||||
=> info.Depth > 1
|
||||
? $"{info.Width} x {info.Height} x {info.Depth}"
|
||||
: $"{info.Width} x {info.Height}";
|
||||
|
||||
private void DrawTextureRow(TextureRow row, IReadOnlyList<TextureCompressionTarget> targets, int index)
|
||||
{
|
||||
var key = row.Key;
|
||||
@@ -2465,6 +2665,9 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
MetaRow(FontAwesomeIcon.LayerGroup, "Map Type", row.MapKind.ToString());
|
||||
MetaRow(FontAwesomeIcon.Fingerprint, "Hash", row.Hash, UIColors.Get("LightlessBlue"));
|
||||
MetaRow(FontAwesomeIcon.InfoCircle, "Current Format", row.Format);
|
||||
var resolution = GetTextureResolution(row);
|
||||
var resolutionLabel = resolution.HasValue ? FormatTextureResolution(resolution.Value) : "Unknown";
|
||||
MetaRow(FontAwesomeIcon.Images, "Resolution", resolutionLabel);
|
||||
|
||||
var selectedLabel = hasSelectedInfo ? selectedInfo!.Title : selectedTarget.ToString();
|
||||
var selectionColor = hasSelectedInfo ? UIColors.Get("LightlessYellow") : UIColors.Get("LightlessGreen");
|
||||
|
||||
Reference in New Issue
Block a user