mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 20:54:16 +01:00
Add Texture Conversion IPC and use texture tasks.
This commit is contained in:
parent
af93c2aca9
commit
6e11b36401
9 changed files with 305 additions and 109 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 8d61845cd900fc0a3b58d475c43303b13c1165f4
|
||||
Subproject commit 9dad955808831a5d154d778d1123acbe648c42ac
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 983c98f74e7cd052b21f6ca35ef0ceaa9b388964
|
||||
Subproject commit 623e802bbc18496aab4030b444154a5b015093c2
|
||||
|
|
@ -9,6 +9,8 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Utility;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
|
|
@ -19,7 +21,6 @@ using Penumbra.Mods.Manager;
|
|||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ public class IpcTester : IDisposable
|
|||
private readonly Meta _meta;
|
||||
private readonly Mods _mods;
|
||||
private readonly ModSettings _modSettings;
|
||||
private readonly Editing _editing;
|
||||
private readonly Temporary _temporary;
|
||||
|
||||
public IpcTester(Configuration config, DalamudServices dalamud, PenumbraIpcProviders ipcProviders, ModManager modManager,
|
||||
|
|
@ -54,6 +56,7 @@ public class IpcTester : IDisposable
|
|||
_meta = new Meta(dalamud.PluginInterface);
|
||||
_mods = new Mods(dalamud.PluginInterface);
|
||||
_modSettings = new ModSettings(dalamud.PluginInterface);
|
||||
_editing = new Editing(dalamud.PluginInterface);
|
||||
_temporary = new Temporary(dalamud.PluginInterface, modManager, collections, tempMods, tempCollections, saveService, config);
|
||||
UnsubscribeEvents();
|
||||
}
|
||||
|
|
@ -74,6 +77,7 @@ public class IpcTester : IDisposable
|
|||
_meta.Draw();
|
||||
_mods.Draw();
|
||||
_modSettings.Draw();
|
||||
_editing.Draw();
|
||||
_temporary.Draw();
|
||||
_temporary.DrawCollections();
|
||||
_temporary.DrawMods();
|
||||
|
|
@ -402,9 +406,9 @@ public class IpcTester : IDisposable
|
|||
private string _lastRedrawnString = "None";
|
||||
|
||||
public Redrawing(DalamudServices dalamud)
|
||||
{
|
||||
{
|
||||
_dalamud = dalamud;
|
||||
Redrawn = Ipc.GameObjectRedrawn.Subscriber(_dalamud.PluginInterface, SetLastRedrawn);
|
||||
Redrawn = Ipc.GameObjectRedrawn.Subscriber(_dalamud.PluginInterface, SetLastRedrawn);
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -1149,6 +1153,72 @@ public class IpcTester : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private class Editing
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
|
||||
private string _inputPath = string.Empty;
|
||||
private string _inputPath2 = string.Empty;
|
||||
private string _outputPath = string.Empty;
|
||||
private string _outputPath2 = string.Empty;
|
||||
|
||||
private TextureType _typeSelector;
|
||||
private bool _mipMaps = true;
|
||||
|
||||
private Task? _task1;
|
||||
private Task? _task2;
|
||||
|
||||
public Editing(DalamudPluginInterface pi)
|
||||
=> _pi = pi;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Editing");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputTextWithHint("##inputPath", "Input Texture Path...", ref _inputPath, 256);
|
||||
ImGui.InputTextWithHint("##outputPath", "Output Texture Path...", ref _outputPath, 256);
|
||||
ImGui.InputTextWithHint("##inputPath2", "Input Texture Path 2...", ref _inputPath2, 256);
|
||||
ImGui.InputTextWithHint("##outputPath2", "Output Texture Path 2...", ref _outputPath2, 256);
|
||||
TypeCombo();
|
||||
ImGui.Checkbox("Add MipMaps", ref _mipMaps);
|
||||
|
||||
using var table = ImRaii.Table("...", 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
DrawIntro(Ipc.ConvertTextureFile.Label, "Convert Texture 1");
|
||||
if (ImGuiUtil.DrawDisabledButton("Save 1", Vector2.Zero, string.Empty, _task1 is { IsCompleted: false }))
|
||||
_task1 = Ipc.ConvertTextureFile.Subscriber(_pi).Invoke(_inputPath, _outputPath, _typeSelector, _mipMaps);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_task1 == null ? "Not Initiated" : _task1.Status.ToString());
|
||||
if (ImGui.IsItemHovered() && _task1?.Status == TaskStatus.Faulted)
|
||||
ImGui.SetTooltip(_task1.Exception?.ToString());
|
||||
|
||||
DrawIntro(Ipc.ConvertTextureFile.Label, "Convert Texture 2");
|
||||
if (ImGuiUtil.DrawDisabledButton("Save 2", Vector2.Zero, string.Empty, _task2 is { IsCompleted: false }))
|
||||
_task2 = Ipc.ConvertTextureFile.Subscriber(_pi).Invoke(_inputPath2, _outputPath2, _typeSelector, _mipMaps);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_task2 == null ? "Not Initiated" : _task2.Status.ToString());
|
||||
if (ImGui.IsItemHovered() && _task2?.Status == TaskStatus.Faulted)
|
||||
ImGui.SetTooltip(_task2.Exception?.ToString());
|
||||
}
|
||||
|
||||
private void TypeCombo()
|
||||
{
|
||||
using var combo = ImRaii.Combo("Convert To", _typeSelector.ToString());
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
foreach (var value in Enum.GetValues<TextureType>())
|
||||
{
|
||||
if (ImGui.Selectable(value.ToString(), _typeSelector == value))
|
||||
_typeSelector = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Temporary
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ using Penumbra.Interop.Structs;
|
|||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
|
@ -23,15 +25,17 @@ using Penumbra.String.Classes;
|
|||
using Penumbra.Services;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Import.Textures;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.UI;
|
||||
using TextureType = Penumbra.Api.Enums.TextureType;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
public class PenumbraApi : IDisposable, IPenumbraApi
|
||||
{
|
||||
public (int, int) ApiVersion
|
||||
=> (4, 20);
|
||||
=> (4, 21);
|
||||
|
||||
public event Action<string>? PreSettingsPanelDraw
|
||||
{
|
||||
|
|
@ -124,12 +128,13 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
private RedrawService _redrawService;
|
||||
private ModFileSystem _modFileSystem;
|
||||
private ConfigWindow _configWindow;
|
||||
private TextureManager _textureManager;
|
||||
|
||||
public unsafe PenumbraApi(CommunicatorService communicator, ModManager modManager, ResourceLoader resourceLoader,
|
||||
Configuration config, CollectionManager collectionManager, DalamudServices dalamud, TempCollectionManager tempCollections,
|
||||
TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService,
|
||||
ModImportManager modImportManager, CollectionEditor collectionEditor, RedrawService redrawService, ModFileSystem modFileSystem,
|
||||
ConfigWindow configWindow)
|
||||
ConfigWindow configWindow, TextureManager textureManager)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_modManager = modManager;
|
||||
|
|
@ -147,6 +152,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
_redrawService = redrawService;
|
||||
_modFileSystem = modFileSystem;
|
||||
_configWindow = configWindow;
|
||||
_textureManager = textureManager;
|
||||
_lumina = _dalamud.GameData.GameData;
|
||||
|
||||
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||
|
|
@ -179,6 +185,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
_redrawService = null!;
|
||||
_modFileSystem = null!;
|
||||
_configWindow = null!;
|
||||
_textureManager = null!;
|
||||
}
|
||||
|
||||
public event ChangedItemClick? ChangedItemClicked
|
||||
|
|
@ -992,6 +999,39 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion);
|
||||
}
|
||||
|
||||
public Task ConvertTextureFile(string inputFile, string outputFile, TextureType textureType, bool mipMaps)
|
||||
=> textureType switch
|
||||
{
|
||||
TextureType.Png => _textureManager.SavePng(inputFile, outputFile),
|
||||
TextureType.AsIsTex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.AsIsDds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, inputFile, outputFile),
|
||||
TextureType.RgbaTex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.RgbaDds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, inputFile, outputFile),
|
||||
TextureType.Bc3Tex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.Bc3Dds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, inputFile, outputFile),
|
||||
TextureType.Bc7Tex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.Bc7Dds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, inputFile, outputFile),
|
||||
_ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
|
||||
};
|
||||
|
||||
// @formatter:off
|
||||
public Task ConvertTextureData(byte[] rgbaData, int width, string outputFile, TextureType textureType, bool mipMaps)
|
||||
=> textureType switch
|
||||
{
|
||||
TextureType.Png => _textureManager.SavePng(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.AsIsTex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.AsIsDds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.RgbaTex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.RgbaDds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc3Tex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc3Dds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc7Tex => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc7Dds => _textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
_ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
|
||||
};
|
||||
// @formatter:on
|
||||
|
||||
|
||||
// TODO: cleanup when incrementing API
|
||||
public string GetMetaManipulations(string characterName)
|
||||
=> GetMetaManipulations(characterName, ushort.MaxValue);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Dalamud.Plugin;
|
|||
using Penumbra.GameData.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
|
@ -105,6 +106,10 @@ public class PenumbraIpcProviders : IDisposable
|
|||
internal readonly EventProvider<ModSettingChange, string, string, bool> ModSettingChanged;
|
||||
internal readonly FuncProvider<string, string, string, PenumbraApiEc> CopyModSettings;
|
||||
|
||||
// Editing
|
||||
internal readonly FuncProvider<string, string, TextureType, bool, Task> ConvertTextureFile;
|
||||
internal readonly FuncProvider<byte[], int, string, TextureType, bool, Task> ConvertTextureData;
|
||||
|
||||
// Temporary
|
||||
internal readonly FuncProvider<string, string, bool, (PenumbraApiEc, string)> CreateTemporaryCollection;
|
||||
internal readonly FuncProvider<string, PenumbraApiEc> RemoveTemporaryCollection;
|
||||
|
|
@ -219,6 +224,10 @@ public class PenumbraIpcProviders : IDisposable
|
|||
() => Api.ModSettingChanged -= ModSettingChangedEvent);
|
||||
CopyModSettings = Ipc.CopyModSettings.Provider(pi, Api.CopyModSettings);
|
||||
|
||||
// Editing
|
||||
ConvertTextureFile = Ipc.ConvertTextureFile.Provider(pi, Api.ConvertTextureFile);
|
||||
ConvertTextureData = Ipc.ConvertTextureData.Provider(pi, Api.ConvertTextureData);
|
||||
|
||||
// Temporary
|
||||
CreateTemporaryCollection = Ipc.CreateTemporaryCollection.Provider(pi, Api.CreateTemporaryCollection);
|
||||
RemoveTemporaryCollection = Ipc.RemoveTemporaryCollection.Provider(pi, Api.RemoveTemporaryCollection);
|
||||
|
|
@ -335,6 +344,10 @@ public class PenumbraIpcProviders : IDisposable
|
|||
RemoveTemporaryModAll.Dispose();
|
||||
RemoveTemporaryMod.Dispose();
|
||||
|
||||
// Editing
|
||||
ConvertTextureFile.Dispose();
|
||||
ConvertTextureData.Dispose();
|
||||
|
||||
Disposed.Invoke();
|
||||
Disposed.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Penumbra.Import.Textures;
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ public partial class CombinedTexture : IDisposable
|
|||
|
||||
private readonly Texture _centerStorage = new();
|
||||
|
||||
public Guid SaveGuid { get; private set; } = Guid.Empty;
|
||||
public Task SaveTask { get; private set; } = Task.CompletedTask;
|
||||
|
||||
public bool IsLoaded
|
||||
=> _mode != Mode.Empty;
|
||||
|
|
@ -55,7 +56,7 @@ public partial class CombinedTexture : IDisposable
|
|||
if (!IsLoaded || _current == null)
|
||||
return;
|
||||
|
||||
SaveGuid = textures.SavePng(_current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width, _current.TextureWrap!.Height);
|
||||
SaveTask = textures.SavePng(_current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width, _current.TextureWrap!.Height);
|
||||
}
|
||||
|
||||
private void SaveAs(TextureManager textures, string path, TextureSaveType type, bool mipMaps, bool writeTex)
|
||||
|
|
@ -63,7 +64,7 @@ public partial class CombinedTexture : IDisposable
|
|||
if (!IsLoaded || _current == null)
|
||||
return;
|
||||
|
||||
SaveGuid = textures.SaveAs(type, mipMaps, writeTex, _current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width,
|
||||
SaveTask = textures.SaveAs(type, mipMaps, writeTex, _current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width,
|
||||
_current.TextureWrap!.Height);
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +134,7 @@ public partial class CombinedTexture : IDisposable
|
|||
{
|
||||
_centerStorage.Dispose();
|
||||
_current = null;
|
||||
SaveGuid = Guid.Empty;
|
||||
SaveTask = Task.CompletedTask;
|
||||
_mode = Mode.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiScene;
|
||||
|
|
@ -11,100 +14,71 @@ using OtterGui.Log;
|
|||
using OtterGui.Tasks;
|
||||
using OtterTex;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Swan;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace Penumbra.Import.Textures;
|
||||
|
||||
public sealed class TextureManager : AsyncTaskManager
|
||||
public sealed class TextureManager : SingleTaskQueue, IDisposable
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly UiBuilder _uiBuilder;
|
||||
private readonly DataManager _gameData;
|
||||
|
||||
public TextureManager(Logger logger, UiBuilder uiBuilder, DataManager gameData)
|
||||
: base(logger)
|
||||
private readonly ConcurrentDictionary<IAction, (Task, CancellationTokenSource)> _tasks = new();
|
||||
private bool _disposed = false;
|
||||
|
||||
public TextureManager(UiBuilder uiBuilder, DataManager gameData, Logger logger)
|
||||
{
|
||||
_uiBuilder = uiBuilder;
|
||||
_gameData = gameData;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Guid SavePng(string input, string output)
|
||||
public IReadOnlyDictionary<IAction, (Task, CancellationTokenSource)> Tasks
|
||||
=> _tasks;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
foreach (var (_, cancel) in _tasks.Values.ToArray())
|
||||
cancel.Cancel();
|
||||
_tasks.Clear();
|
||||
}
|
||||
|
||||
public Task SavePng(string input, string output)
|
||||
=> Enqueue(new SavePngAction(this, input, output));
|
||||
|
||||
public Guid SavePng(BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0)
|
||||
public Task SavePng(BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0)
|
||||
=> Enqueue(new SavePngAction(this, image, path, rgba, width, height));
|
||||
|
||||
public Guid SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, string output)
|
||||
public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, string output)
|
||||
=> Enqueue(new SaveAsAction(this, type, mipMaps, asTex, input, output));
|
||||
|
||||
public Guid SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, string path, byte[]? rgba = null,
|
||||
public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, string path, byte[]? rgba = null,
|
||||
int width = 0, int height = 0)
|
||||
=> Enqueue(new SaveAsAction(this, type, mipMaps, asTex, image, path, rgba, width, height));
|
||||
|
||||
private readonly struct ImageInputData
|
||||
private Task Enqueue(IAction action)
|
||||
{
|
||||
private readonly string? _inputPath;
|
||||
if (_disposed)
|
||||
return Task.FromException(new ObjectDisposedException(nameof(TextureManager)));
|
||||
|
||||
private readonly BaseImage _image;
|
||||
private readonly byte[]? _rgba;
|
||||
private readonly int _width;
|
||||
private readonly int _height;
|
||||
|
||||
public ImageInputData(string inputPath)
|
||||
Task t;
|
||||
lock (_tasks)
|
||||
{
|
||||
_inputPath = inputPath;
|
||||
_image = new BaseImage();
|
||||
_rgba = null;
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
t = _tasks.GetOrAdd(action, a =>
|
||||
{
|
||||
var token = new CancellationTokenSource();
|
||||
var task = Enqueue(a, token.Token);
|
||||
task.ContinueWith(_ => _tasks.TryRemove(a, out var unused), CancellationToken.None);
|
||||
return (task, token);
|
||||
}).Item1;
|
||||
}
|
||||
|
||||
public ImageInputData(BaseImage image, byte[]? rgba = null, int width = 0, int height = 0)
|
||||
{
|
||||
_inputPath = null;
|
||||
_image = image.Width == 0 || image.Height == 0 ? new BaseImage() : image;
|
||||
_rgba = rgba?.ToArray();
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
public (BaseImage Image, byte[]? Rgba, int Width, int Height) GetData(TextureManager textures)
|
||||
{
|
||||
if (_inputPath == null)
|
||||
return (_image, _rgba, _width, _height);
|
||||
|
||||
if (!File.Exists(_inputPath))
|
||||
throw new FileNotFoundException($"Input texture file {_inputPath} not Found.", _inputPath);
|
||||
|
||||
var (image, _) = textures.Load(_inputPath);
|
||||
return (image, null, 0, 0);
|
||||
}
|
||||
|
||||
public bool Equals(ImageInputData rhs)
|
||||
{
|
||||
if (_inputPath != null)
|
||||
return string.Equals(_inputPath, rhs._inputPath, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (rhs._inputPath != null)
|
||||
return false;
|
||||
|
||||
if (_image.Image != null)
|
||||
return ReferenceEquals(_image.Image, rhs._image.Image);
|
||||
|
||||
return _width == rhs._width && _height == rhs._height && _rgba != null && rhs._rgba != null && _rgba.SequenceEqual(rhs._rgba);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> _inputPath
|
||||
?? _image.Type switch
|
||||
{
|
||||
TextureType.Unknown => $"Custom {_width} x {_height} RGBA Image",
|
||||
TextureType.Dds => $"Custom {_width} x {_height} {_image.Format} Image",
|
||||
TextureType.Tex => $"Custom {_width} x {_height} {_image.Format} Image",
|
||||
TextureType.Png => $"Custom {_width} x {_height} .png Image",
|
||||
TextureType.Bitmap => $"Custom {_width} x {_height} RGBA Image",
|
||||
_ => "Unknown Image",
|
||||
};
|
||||
return t;
|
||||
}
|
||||
|
||||
private class SavePngAction : IAction
|
||||
|
|
@ -129,7 +103,7 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
|
||||
public void Execute(CancellationToken cancel)
|
||||
{
|
||||
_textures.Logger.Information($"[{nameof(TextureManager)}] Saving {_input} as .png to {_outputPath}...");
|
||||
_textures._logger.Information($"[{nameof(TextureManager)}] Saving {_input} as .png to {_outputPath}...");
|
||||
var (image, rgba, width, height) = _input.GetData(_textures);
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
Image<Rgba32>? png = null;
|
||||
|
|
@ -144,9 +118,12 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
}
|
||||
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
png?.SaveAsync(_outputPath, cancel).Wait(cancel);
|
||||
png?.SaveAsync(_outputPath, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }, cancel).Wait(cancel);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{_input} to {_outputPath} PNG";
|
||||
|
||||
public bool Equals(IAction? other)
|
||||
{
|
||||
if (other is not SavePngAction rhs)
|
||||
|
|
@ -154,6 +131,9 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
|
||||
return string.Equals(_outputPath, rhs._outputPath, StringComparison.OrdinalIgnoreCase) && _input.Equals(rhs._input);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(_outputPath.ToLowerInvariant(), _input);
|
||||
}
|
||||
|
||||
private class SaveAsAction : IAction
|
||||
|
|
@ -177,8 +157,7 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
}
|
||||
|
||||
public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image,
|
||||
string path,
|
||||
byte[]? rgba = null, int width = 0, int height = 0)
|
||||
string path, byte[]? rgba = null, int width = 0, int height = 0)
|
||||
{
|
||||
_textures = textures;
|
||||
_input = new ImageInputData(image, rgba, width, height);
|
||||
|
|
@ -190,7 +169,7 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
|
||||
public void Execute(CancellationToken cancel)
|
||||
{
|
||||
_textures.Logger.Information(
|
||||
_textures._logger.Information(
|
||||
$"[{nameof(TextureManager)}] Saving {_input} as {_type} {(_asTex ? ".tex" : ".dds")} file{(_mipMaps ? " with mip maps" : string.Empty)} to {_outputPath}...");
|
||||
var (image, rgba, width, height) = _input.GetData(_textures);
|
||||
if (image.Type is TextureType.Unknown)
|
||||
|
|
@ -220,6 +199,9 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
dds.AsDds!.SaveDDS(_outputPath);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{_input} to {_outputPath} {_type} {(_asTex ? "TEX" : "DDS")}{(_mipMaps ? " with MipMaps" : string.Empty)}";
|
||||
|
||||
public bool Equals(IAction? other)
|
||||
{
|
||||
if (other is not SaveAsAction rhs)
|
||||
|
|
@ -231,6 +213,9 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
&& string.Equals(_outputPath, rhs._outputPath, StringComparison.OrdinalIgnoreCase)
|
||||
&& _input.Equals(rhs._input);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(_outputPath.ToLowerInvariant(), _type, _mipMaps, _asTex, _input);
|
||||
}
|
||||
|
||||
/// <summary> Load a texture wrap for a given image. </summary>
|
||||
|
|
@ -435,4 +420,73 @@ public sealed class TextureManager : AsyncTaskManager
|
|||
header.Write(w);
|
||||
w.Write(input.Pixels);
|
||||
}
|
||||
|
||||
private readonly struct ImageInputData
|
||||
{
|
||||
private readonly string? _inputPath;
|
||||
|
||||
private readonly BaseImage _image;
|
||||
private readonly byte[]? _rgba;
|
||||
private readonly int _width;
|
||||
private readonly int _height;
|
||||
|
||||
public ImageInputData(string inputPath)
|
||||
{
|
||||
_inputPath = inputPath;
|
||||
_image = new BaseImage();
|
||||
_rgba = null;
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
}
|
||||
|
||||
public ImageInputData(BaseImage image, byte[]? rgba = null, int width = 0, int height = 0)
|
||||
{
|
||||
_inputPath = null;
|
||||
_image = image.Width == 0 || image.Height == 0 ? new BaseImage() : image;
|
||||
_rgba = rgba?.ToArray();
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
public (BaseImage Image, byte[]? Rgba, int Width, int Height) GetData(TextureManager textures)
|
||||
{
|
||||
if (_inputPath == null)
|
||||
return (_image, _rgba, _width, _height);
|
||||
|
||||
if (!File.Exists(_inputPath))
|
||||
throw new FileNotFoundException($"Input texture file {_inputPath} not Found.", _inputPath);
|
||||
|
||||
var (image, _) = textures.Load(_inputPath);
|
||||
return (image, null, 0, 0);
|
||||
}
|
||||
|
||||
public bool Equals(ImageInputData rhs)
|
||||
{
|
||||
if (_inputPath != null)
|
||||
return string.Equals(_inputPath, rhs._inputPath, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (rhs._inputPath != null)
|
||||
return false;
|
||||
|
||||
if (_image.Image != null)
|
||||
return ReferenceEquals(_image.Image, rhs._image.Image);
|
||||
|
||||
return _width == rhs._width && _height == rhs._height && _rgba != null && rhs._rgba != null && _rgba.SequenceEqual(rhs._rgba);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> _inputPath
|
||||
?? _image.Type switch
|
||||
{
|
||||
TextureType.Unknown => $"Custom {_width} x {_height} RGBA Image",
|
||||
TextureType.Dds => $"Custom {_width} x {_height} {_image.Format} Image",
|
||||
TextureType.Tex => $"Custom {_width} x {_height} {_image.Format} Image",
|
||||
TextureType.Png => $"Custom {_width} x {_height} .png Image",
|
||||
TextureType.Bitmap => $"Custom {_width} x {_height} RGBA Image",
|
||||
_ => "Unknown Image",
|
||||
};
|
||||
|
||||
public override int GetHashCode()
|
||||
=> _inputPath != null ? _inputPath.ToLowerInvariant().GetHashCode() : HashCode.Combine(_width, _height);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -144,7 +145,7 @@ public partial class ModEditWindow
|
|||
_left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB))
|
||||
{
|
||||
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1);
|
||||
ReloadConvertedSubscribe(_left.Path, _center.SaveGuid);
|
||||
AddReloadTask(_left.Path);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -153,7 +154,7 @@ public partial class ModEditWindow
|
|||
_left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB))
|
||||
{
|
||||
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1);
|
||||
ReloadConvertedSubscribe(_left.Path, _center.SaveGuid);
|
||||
AddReloadTask(_left.Path);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -162,7 +163,7 @@ public partial class ModEditWindow
|
|||
_left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB))
|
||||
{
|
||||
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1);
|
||||
ReloadConvertedSubscribe(_left.Path, _center.SaveGuid);
|
||||
AddReloadTask(_left.Path);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -173,18 +174,20 @@ public partial class ModEditWindow
|
|||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
if (_center.SaveGuid != Guid.Empty)
|
||||
switch (_center.SaveTask.Status)
|
||||
{
|
||||
var state = _textures.GetState(_center.SaveGuid, out var saveException, out _, out _);
|
||||
if (saveException != null)
|
||||
case TaskStatus.WaitingForActivation:
|
||||
case TaskStatus.WaitingToRun:
|
||||
case TaskStatus.Running:
|
||||
ImGui.TextUnformatted("Computing...");
|
||||
break;
|
||||
case TaskStatus.Canceled:
|
||||
case TaskStatus.Faulted:
|
||||
{
|
||||
ImGui.TextUnformatted("Could not save file:");
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF);
|
||||
ImGuiUtil.TextWrapped(saveException.ToString());
|
||||
}
|
||||
else if (state == ActionState.Running)
|
||||
{
|
||||
ImGui.TextUnformatted("Computing...");
|
||||
ImGuiUtil.TextWrapped(_center.SaveTask.Exception?.ToString() ?? "Unknown Error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,23 +196,18 @@ public partial class ModEditWindow
|
|||
_center.Draw(_textures, imageSize);
|
||||
}
|
||||
|
||||
|
||||
private void ReloadConvertedSubscribe(string path, Guid guid)
|
||||
private void AddReloadTask(string path)
|
||||
{
|
||||
void Reload(Guid eventGuid, ActionState state, Exception? ex)
|
||||
_center.SaveTask.ContinueWith(t =>
|
||||
{
|
||||
if (guid != eventGuid)
|
||||
if (!t.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
if (_left.Path != path)
|
||||
return;
|
||||
|
||||
if (state is ActionState.Succeeded)
|
||||
_dalamud.Framework.RunOnFrameworkThread(() => _left.Reload(_textures));
|
||||
_textures.Finished -= Reload;
|
||||
}
|
||||
|
||||
_textures.Finished += Reload;
|
||||
_dalamud.Framework.RunOnFrameworkThread(() => _left.Reload(_textures));
|
||||
});
|
||||
}
|
||||
|
||||
private Vector2 GetChildWidth()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using Penumbra.Collections.Manager;
|
|||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Import.Textures;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
|
@ -61,13 +62,15 @@ public class DebugTab : Window, ITab
|
|||
private readonly ModImportManager _modImporter;
|
||||
private readonly ImportPopup _importPopup;
|
||||
private readonly FrameworkManager _framework;
|
||||
private readonly TextureManager _textureManager;
|
||||
|
||||
public DebugTab(StartTracker timer, PerformanceTracker performance, Configuration config, CollectionManager collectionManager,
|
||||
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorService actorService,
|
||||
DalamudServices dalamud, StainService stains, CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
||||
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
||||
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
||||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework)
|
||||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
||||
TextureManager textureManager)
|
||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse, false)
|
||||
{
|
||||
IsOpen = true;
|
||||
|
|
@ -99,6 +102,7 @@ public class DebugTab : Window, ITab
|
|||
_modImporter = modImporter;
|
||||
_importPopup = importPopup;
|
||||
_framework = framework;
|
||||
_textureManager = textureManager;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -147,14 +151,15 @@ public class DebugTab : Window, ITab
|
|||
|
||||
private void DrawCollectionCaches()
|
||||
{
|
||||
if (!ImGui.CollapsingHeader($"Collections ({_collectionManager.Caches.Count}/{_collectionManager.Storage.Count - 1} Caches)###Collections"))
|
||||
if (!ImGui.CollapsingHeader(
|
||||
$"Collections ({_collectionManager.Caches.Count}/{_collectionManager.Storage.Count - 1} Caches)###Collections"))
|
||||
return;
|
||||
|
||||
foreach (var collection in _collectionManager.Storage)
|
||||
{
|
||||
if (collection.HasCache)
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
|
||||
using var color = PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
|
||||
using var node = TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})");
|
||||
if (!node)
|
||||
continue;
|
||||
|
|
@ -177,8 +182,9 @@ public class DebugTab : Window, ITab
|
|||
}
|
||||
else
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.UndefinedMod.Value());
|
||||
TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})", ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
using var color = PushColor(ImGuiCol.Text, ColorId.UndefinedMod.Value());
|
||||
TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})",
|
||||
ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -293,6 +299,20 @@ public class DebugTab : Window, ITab
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var tree = TreeNode($"Texture Manager {_textureManager.Tasks.Count}###Texture Manager"))
|
||||
{
|
||||
if (tree)
|
||||
{
|
||||
using var table = Table("##Tasks", 2, ImGuiTableFlags.RowBg);
|
||||
if (table)
|
||||
foreach (var task in _textureManager.Tasks)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(task.Key.ToString()!);
|
||||
ImGuiUtil.DrawTableColumn(task.Value.Item1.Status.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPerformanceTab()
|
||||
|
|
@ -500,7 +520,7 @@ public class DebugTab : Window, ITab
|
|||
|
||||
if (agent->Data != null)
|
||||
{
|
||||
using var table = Table("###PBannerTable", 2, ImGuiTableFlags.SizingFixedFit);
|
||||
using var table = Table("###PBannerTable", 2, ImGuiTableFlags.SizingFixedFit);
|
||||
if (table)
|
||||
for (var i = 0; i < 8; ++i)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue