add bc7 texture conversion, add syncshell note in profile display

This commit is contained in:
rootdarkarchon
2023-08-19 00:21:51 +02:00
parent b279ac0d5e
commit d50d9cdf0f
5 changed files with 190 additions and 28 deletions

View File

@@ -57,6 +57,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
private readonly FuncSubscriber<string> _penumbraGetMetaManipulations; private readonly FuncSubscriber<string> _penumbraGetMetaManipulations;
private readonly EventSubscriber _penumbraInit; private readonly EventSubscriber _penumbraInit;
private readonly EventSubscriber<ModSettingChange, string, string, bool> _penumbraModSettingChanged; private readonly EventSubscriber<ModSettingChange, string, string, bool> _penumbraModSettingChanged;
private readonly FuncSubscriber<string, string, TextureType, bool, Task> _penumbraConvertTextureFile;
private readonly EventSubscriber<nint, int> _penumbraObjectIsRedrawn; private readonly EventSubscriber<nint, int> _penumbraObjectIsRedrawn;
private readonly ActionSubscriber<string, RedrawType> _penumbraRedraw; private readonly ActionSubscriber<string, RedrawType> _penumbraRedraw;
private readonly ActionSubscriber<GameObject, RedrawType> _penumbraRedrawObject; private readonly ActionSubscriber<GameObject, RedrawType> _penumbraRedrawObject;
@@ -101,6 +102,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
if (change == ModSettingChange.EnableState) if (change == ModSettingChange.EnableState)
Mediator.Publish(new PenumbraModSettingChangedMessage()); Mediator.Publish(new PenumbraModSettingChangedMessage());
}); });
_penumbraConvertTextureFile = Penumbra.Api.Ipc.ConvertTextureFile.Subscriber(pi);
_penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded); _penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded);
@@ -553,6 +555,43 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
public async Task PenumbraConvertTextureFiles(ILogger logger, Dictionary<string, string[]> textures, IProgress<(string, int)> progress, CancellationToken token)
{
if (!CheckPenumbraApi()) return;
Mediator.Publish(new HaltScanMessage("TextureConversion"));
int currentTexture = 0;
foreach (var texture in textures)
{
if (token.IsCancellationRequested) break;
progress.Report((texture.Key, ++currentTexture));
logger.LogInformation("Converting Texture {path} to {type}", texture.Key, TextureType.Bc7Tex);
var convertTask = _penumbraConvertTextureFile.Invoke(texture.Key, texture.Key, TextureType.Bc7Tex, true);
await convertTask.ConfigureAwait(false);
if (convertTask.IsCompletedSuccessfully && texture.Value.Any())
{
foreach (var duplicatedTexture in texture.Value)
{
logger.LogInformation("Migrating duplicate {dup}", duplicatedTexture);
try
{
File.Copy(texture.Key, duplicatedTexture, true);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to copy duplicate {dup}", duplicatedTexture);
}
}
}
}
Mediator.Publish(new ResumeScanMessage("TextureConversion"));
var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync());
_penumbraRedrawObject.Invoke(gameObject!, RedrawType.Redraw);
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
base.Dispose(disposing); base.Dispose(disposing);
@@ -668,7 +707,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
bool apiAvailable = false; bool apiAvailable = false;
try try
{ {
apiAvailable = _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 19 } && _penumbraEnabled.Invoke(); apiAvailable = _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 21 } && _penumbraEnabled.Invoke();
_shownPenumbraUnavailable = _shownPenumbraUnavailable && !apiAvailable; _shownPenumbraUnavailable = _shownPenumbraUnavailable && !apiAvailable;
return apiAvailable; return apiAvailable;
} }

View File

@@ -29,17 +29,17 @@
<PackageReference Include="Dalamud.ContextMenu" Version="1.2.3" /> <PackageReference Include="Dalamud.ContextMenu" Version="1.2.3" />
<PackageReference Include="DalamudPackager" Version="2.1.11" /> <PackageReference Include="DalamudPackager" Version="2.1.11" />
<PackageReference Include="lz4net" Version="1.0.15.93" /> <PackageReference Include="lz4net" Version="1.0.15.93" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.52"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.82">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.5" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.10" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.5" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.10" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Penumbra.Api" Version="1.0.8" /> <PackageReference Include="Penumbra.Api" Version="1.0.9" />
<PackageReference Include="Penumbra.String" Version="1.0.4" /> <PackageReference Include="Penumbra.String" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.0.0.68202"> <PackageReference Include="SonarAnalyzer.CSharp" Version="9.7.0.75501">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -37,7 +37,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
_analysisCts = null; _analysisCts = null;
} }
public async Task ComputeAnalysis(bool print = true) public async Task ComputeAnalysis(bool print = true, bool recalculate = false)
{ {
Logger.LogDebug("=== Calculating Character Analysis ==="); Logger.LogDebug("=== Calculating Character Analysis ===");
@@ -46,9 +46,9 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
var cancelToken = _analysisCts.Token; var cancelToken = _analysisCts.Token;
var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList(); var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList();
if (allFiles.Exists(c => !c.IsComputed)) if (allFiles.Exists(c => !c.IsComputed || recalculate))
{ {
var remaining = allFiles.Where(c => !c.IsComputed).ToList(); var remaining = allFiles.Where(c => !c.IsComputed || recalculate).ToList();
TotalFiles = remaining.Count; TotalFiles = remaining.Count;
CurrentFile = 1; CurrentFile = 1;
Logger.LogDebug("=== Computing {amount} remaining files ===", remaining.Count); Logger.LogDebug("=== Computing {amount} remaining files ===", remaining.Count);
@@ -57,6 +57,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
foreach (var file in remaining) foreach (var file in remaining)
{ {
Logger.LogDebug("Computing file {file}", file.FilePaths[0]);
await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false); await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false);
CurrentFile++; CurrentFile++;
} }
@@ -96,7 +97,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
{ {
data[fileEntry.Hash] = new FileDataEntry(fileEntry.Hash, ext, data[fileEntry.Hash] = new FileDataEntry(fileEntry.Hash, ext,
fileEntry.GamePaths.ToList(), fileEntry.GamePaths.ToList(),
fileCacheEntries.Select(c => c.ResolvedFilepath).ToList(), fileCacheEntries.Select(c => c.ResolvedFilepath).Distinct().ToList(),
entry.Size > 0 ? entry.Size.Value : 0, entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0); entry.Size > 0 ? entry.Size.Value : 0, entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0);
} }
} }

View File

@@ -3,6 +3,7 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Raii; using Dalamud.Interface.Raii;
using ImGuiNET; using ImGuiNET;
using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Enum;
using MareSynchronos.Interop;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils; using MareSynchronos.Utils;
@@ -14,16 +15,26 @@ namespace MareSynchronos.UI;
public class DataAnalysisUi : WindowMediatorSubscriberBase public class DataAnalysisUi : WindowMediatorSubscriberBase
{ {
private readonly CharacterAnalyzer _characterAnalyzer; private readonly CharacterAnalyzer _characterAnalyzer;
private readonly IpcManager _ipcManager;
private bool _hasUpdate = false; private bool _hasUpdate = false;
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis; private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis;
private string _selectedHash = string.Empty; private string _selectedHash = string.Empty;
private ObjectKind _selectedObjectTab; private ObjectKind _selectedObjectTab;
private string _selectedFileTypeTab = string.Empty; private string _selectedFileTypeTab = string.Empty;
private bool _enableBc7ConversionMode = false;
private readonly Dictionary<string, string[]> _texturesToConvert = new(StringComparer.Ordinal);
private Task? _conversionTask;
private CancellationTokenSource _conversionCancellationTokenSource = new();
private readonly Progress<(string, int)> _conversionProgress = new();
private string _conversionCurrentFileName = string.Empty;
private int _conversionCurrentFileProgress = 0;
private bool _modalOpen = false;
private bool _showModal = false;
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer) : base(logger, mediator, "Mare Character Data Analysis") public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer, IpcManager ipcManager) : base(logger, mediator, "Mare Character Data Analysis")
{ {
_characterAnalyzer = characterAnalyzer; _characterAnalyzer = characterAnalyzer;
_ipcManager = ipcManager;
Mediator.Subscribe<CharacterDataAnalyzedMessage>(this, (_) => Mediator.Subscribe<CharacterDataAnalyzedMessage>(this, (_) =>
{ {
_hasUpdate = true; _hasUpdate = true;
@@ -42,16 +53,66 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
Y = 2160 Y = 2160
} }
}; };
_conversionProgress.ProgressChanged += ConversionProgress_ProgressChanged;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_conversionProgress.ProgressChanged -= ConversionProgress_ProgressChanged;
}
private void ConversionProgress_ProgressChanged(object? sender, (string, int) e)
{
_conversionCurrentFileName = e.Item1;
_conversionCurrentFileProgress = e.Item2;
} }
public override void OnOpen() public override void OnOpen()
{ {
_hasUpdate = true; _hasUpdate = true;
_selectedHash = string.Empty; _selectedHash = string.Empty;
_enableBc7ConversionMode = false;
_texturesToConvert.Clear();
} }
public override void Draw() public override void Draw()
{ {
if (_conversionTask != null && !_conversionTask.IsCompleted)
{
_showModal = true;
if (ImGui.BeginPopupModal("BC7 Conversion in Progress"))
{
ImGui.Text("BC7 Conversion in progress: " + _conversionCurrentFileProgress + "/" + _texturesToConvert.Count);
UiSharedService.TextWrapped("Current file: " + _conversionCurrentFileName);
if (UiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel conversion"))
{
_conversionCancellationTokenSource.Cancel();
}
UiSharedService.SetScaledWindowSize(500);
ImGui.EndPopup();
}
else
{
_modalOpen = false;
}
}
else if (_conversionTask != null && _conversionTask.IsCompleted && _texturesToConvert.Count > 0)
{
_conversionTask = null;
_texturesToConvert.Clear();
_showModal = false;
_modalOpen = false;
_enableBc7ConversionMode = false;
}
if (_showModal && !_modalOpen)
{
ImGui.OpenPopup("BC7 Conversion in Progress");
_modalOpen = true;
}
if (_hasUpdate) if (_hasUpdate)
{ {
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone(); _cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
@@ -62,8 +123,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
if (_cachedAnalysis!.Count == 0) return; if (_cachedAnalysis!.Count == 0) return;
if (_cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed)))
{
bool isAnalyzing = _characterAnalyzer.IsAnalysisRunning; bool isAnalyzing = _characterAnalyzer.IsAnalysisRunning;
if (isAnalyzing) if (isAnalyzing)
{ {
@@ -75,14 +134,23 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
} }
} }
else else
{
if (_cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed)))
{ {
UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to analyze your current data", UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to analyze your current data",
ImGuiColors.DalamudYellow); ImGuiColors.DalamudYellow);
if (UiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis")) if (UiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (missing entries)"))
{ {
_ = _characterAnalyzer.ComputeAnalysis(false); _ = _characterAnalyzer.ComputeAnalysis(false);
} }
} }
else
{
if (UiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (recalculate all entries)"))
{
_ = _characterAnalyzer.ComputeAnalysis(false, true);
}
}
} }
ImGui.Separator(); ImGui.Separator();
@@ -154,6 +222,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
_selectedHash = string.Empty; _selectedHash = string.Empty;
_selectedObjectTab = kvp.Key; _selectedObjectTab = kvp.Key;
_selectedFileTypeTab = string.Empty; _selectedFileTypeTab = string.Empty;
_enableBc7ConversionMode = false;
_texturesToConvert.Clear();
} }
using var fileTabBar = ImRaii.TabBar("fileTabs"); using var fileTabBar = ImRaii.TabBar("fileTabs");
@@ -180,6 +250,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
{ {
_selectedFileTypeTab = fileGroup.Key; _selectedFileTypeTab = fileGroup.Key;
_selectedHash = string.Empty; _selectedHash = string.Empty;
_enableBc7ConversionMode = false;
_texturesToConvert.Clear();
} }
ImGui.TextUnformatted($"{fileGroup.Key} files"); ImGui.TextUnformatted($"{fileGroup.Key} files");
@@ -194,6 +266,28 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
if (_selectedFileTypeTab == "tex")
{
ImGui.Checkbox("Enable BC7 Conversion Mode", ref _enableBc7ConversionMode);
if (_enableBc7ConversionMode)
{
UiSharedService.ColorText("WARNING BC7 CONVERSION:", ImGuiColors.DalamudYellow);
ImGui.SameLine();
UiSharedService.ColorText("Converting textures to BC7 is irreversible!", ImGuiColors.DalamudRed);
UiSharedService.ColorTextWrapped("- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures." +
Environment.NewLine + "- Some textures, especially ones utilizing colorsets, might not be suited for BC7 conversion and might produce visual artifacts." +
Environment.NewLine + "- Before converting textures, make sure to have the original files of the mod you are converting so you can reimport it in case of issues." +
Environment.NewLine + "- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically." +
Environment.NewLine + "- Converting textures to BC7 is a very expensive operation and, depending on the amount of textures to convert, will take a while to complete."
, ImGuiColors.DalamudYellow);
if (_texturesToConvert.Count > 0 && UiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)"))
{
_conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate();
_conversionTask = _ipcManager.PenumbraConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token);
}
}
}
ImGui.Separator(); ImGui.Separator();
DrawTable(fileGroup); DrawTable(fileGroup);
@@ -240,7 +334,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
private void DrawTable(IGrouping<string, CharacterAnalyzer.FileDataEntry> fileGroup) private void DrawTable(IGrouping<string, CharacterAnalyzer.FileDataEntry> fileGroup)
{ {
using var table = ImRaii.Table("Analysis", fileGroup.Key == "tex" ? 6 : 5, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, using var table = ImRaii.Table("Analysis", fileGroup.Key == "tex" ? (_enableBc7ConversionMode ? 7 : 6) : 5, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
new Vector2(0, 300)); new Vector2(0, 300));
if (!table.Success) return; if (!table.Success) return;
ImGui.TableSetupColumn("Hash"); ImGui.TableSetupColumn("Hash");
@@ -248,7 +342,11 @@ new Vector2(0, 300));
ImGui.TableSetupColumn("Gamepaths"); ImGui.TableSetupColumn("Gamepaths");
ImGui.TableSetupColumn("Original Size"); ImGui.TableSetupColumn("Original Size");
ImGui.TableSetupColumn("Compressed Size"); ImGui.TableSetupColumn("Compressed Size");
if (fileGroup.Key == "tex") ImGui.TableSetupColumn("Format"); if (fileGroup.Key == "tex")
{
ImGui.TableSetupColumn("Format");
if (_enableBc7ConversionMode) ImGui.TableSetupColumn("Convert to BC7");
}
ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
@@ -319,6 +417,28 @@ new Vector2(0, 300));
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(item.Format.Value); ImGui.TextUnformatted(item.Format.Value);
if (ImGui.IsItemClicked()) _selectedHash = item.Hash; if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
if (_enableBc7ConversionMode)
{
ImGui.TableNextColumn();
if (item.Format.Value == "BC7")
{
ImGui.Text("");
continue;
}
var filePath = item.FilePaths[0];
bool toConvert = _texturesToConvert.ContainsKey(filePath);
if (ImGui.Checkbox("###convert" + item.Hash, ref toConvert))
{
if (toConvert && !_texturesToConvert.ContainsKey(filePath))
{
_texturesToConvert[filePath] = item.FilePaths.Skip(1).ToArray();
}
else if (!toConvert && _texturesToConvert.ContainsKey(filePath))
{
_texturesToConvert.Remove(filePath);
}
}
}
} }
} }
} }

View File

@@ -146,9 +146,11 @@ public class PopoutProfileUi : WindowMediatorSubscriberBase
if (_pair.GroupPair.Any()) if (_pair.GroupPair.Any())
{ {
ImGui.TextUnformatted("Paired through Syncshells:"); ImGui.TextUnformatted("Paired through Syncshells:");
foreach (var groupPair in _pair.GroupPair) foreach (var groupPair in _pair.GroupPair.Select(k => k.Key))
{ {
ImGui.TextUnformatted("- " + groupPair.Key.GroupAliasOrGID); var groupNote = _serverManager.GetNoteForGid(groupPair.GID);
var groupString = string.IsNullOrEmpty(groupNote) ? groupPair.GroupAliasOrGID : $"{groupNote} ({groupPair.GroupAliasOrGID})";
ImGui.TextUnformatted("- " + groupString);
} }
} }