Add Texture Conversion IPC and use texture tasks.

This commit is contained in:
Ottermandias 2023-08-10 16:55:43 +02:00
parent af93c2aca9
commit 6e11b36401
9 changed files with 305 additions and 109 deletions

@ -1 +1 @@
Subproject commit 8d61845cd900fc0a3b58d475c43303b13c1165f4
Subproject commit 9dad955808831a5d154d778d1123acbe648c42ac

@ -1 +1 @@
Subproject commit 983c98f74e7cd052b21f6ca35ef0ceaa9b388964
Subproject commit 623e802bbc18496aab4030b444154a5b015093c2

View file

@ -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;

View file

@ -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);

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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()

View file

@ -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)
{