diff --git a/Dalamud/Interface/GameIconLookup.cs b/Dalamud/Interface/GameIconLookup.cs
deleted file mode 100644
index 001f519aa..000000000
--- a/Dalamud/Interface/GameIconLookup.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Dalamud.Interface;
-
-///
-/// Represents a lookup for a game icon.
-///
-/// The icon ID.
-/// Whether the HQ icon is requested, where HQ is in the context of items.
-/// Whether the high-resolution icon is requested.
-/// The language of the icon to load.
-[SuppressMessage(
- "StyleCop.CSharp.NamingRules",
- "SA1313:Parameter names should begin with lower-case letter",
- Justification = "no")]
-public record struct GameIconLookup(
- uint IconId,
- bool ItemHq = false,
- bool HiRes = true,
- ClientLanguage? Language = null);
diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs
index 00bef19af..aab4996bb 100644
--- a/Dalamud/Interface/Internal/DalamudInterface.cs
+++ b/Dalamud/Interface/Internal/DalamudInterface.cs
@@ -1,9 +1,12 @@
+using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
+using System.Threading.Tasks;
using CheapLoc;
using Dalamud.Configuration.Internal;
@@ -15,7 +18,9 @@ using Dalamud.Game.Internal;
using Dalamud.Hooking;
using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Colors;
+using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.ManagedAsserts;
+using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Internal.Windows;
using Dalamud.Interface.Internal.Windows.Data;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
@@ -24,6 +29,7 @@ using Dalamud.Interface.Internal.Windows.Settings;
using Dalamud.Interface.Internal.Windows.StyleEditor;
using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Style;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
@@ -40,6 +46,10 @@ using ImPlotNET;
using PInvoke;
using Serilog.Events;
+using TerraFX.Interop.Windows;
+
+using Task = System.Threading.Tasks.Task;
+
namespace Dalamud.Interface.Internal;
///
@@ -56,6 +66,8 @@ internal class DalamudInterface : IDisposable, IServiceType
private readonly DalamudConfiguration configuration;
private readonly InterfaceManager interfaceManager;
+ private readonly FileDialogManager fileDialogManager;
+
private readonly ChangelogWindow changelogWindow;
private readonly ColorDemoWindow colorDemoWindow;
private readonly ComponentDemoWindow componentDemoWindow;
@@ -109,6 +121,7 @@ internal class DalamudInterface : IDisposable, IServiceType
this.interfaceManager = interfaceManager;
this.WindowSystem = new WindowSystem("DalamudCore");
+ this.fileDialogManager = new();
this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
@@ -486,6 +499,119 @@ internal class DalamudInterface : IDisposable, IServiceType
this.creditsDarkeningAnimation.Restart();
}
+ /// Shows a context menu confirming texture save.
+ /// Name of the initiator.
+ /// Suggested name of the file being saved.
+ /// A task returning the texture to save.
+ /// A representing the asynchronous operation.
+ public async Task ShowTextureSaveMenuAsync(
+ string initiatorName,
+ string name,
+ Task texture)
+ {
+ try
+ {
+ var initiatorScreenOffset = ImGui.GetMousePos();
+ using var textureWrap = await texture;
+ var textureManager = await Service.GetAsync();
+ var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.ImGuiHandle:X}";
+
+ BitmapCodecInfo encoder;
+ {
+ var first = true;
+ var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList();
+ var tcs = new TaskCompletionSource();
+ Service.Get().Draw += DrawChoices;
+
+ encoder = await tcs.Task;
+
+ [SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "This shall not escape")]
+ void DrawChoices()
+ {
+ if (first)
+ {
+ ImGui.OpenPopup(popupName);
+ first = false;
+ }
+
+ ImGui.SetNextWindowPos(initiatorScreenOffset, ImGuiCond.Appearing);
+ if (!ImGui.BeginPopup(
+ popupName,
+ ImGuiWindowFlags.AlwaysAutoResize |
+ ImGuiWindowFlags.NoTitleBar |
+ ImGuiWindowFlags.NoSavedSettings))
+ {
+ Service.Get().Draw -= DrawChoices;
+ tcs.TrySetCanceled();
+ return;
+ }
+
+ foreach (var encoder2 in encoders)
+ {
+ if (ImGui.Selectable(encoder2.Name))
+ tcs.TrySetResult(encoder2);
+ }
+
+ const float previewImageWidth = 320;
+ var size = textureWrap.Size;
+ if (size.X > previewImageWidth)
+ size *= previewImageWidth / size.X;
+ if (size.Y > previewImageWidth)
+ size *= previewImageWidth / size.Y;
+ ImGui.Image(textureWrap.ImGuiHandle, size);
+
+ if (tcs.Task.IsCompleted)
+ ImGui.CloseCurrentPopup();
+
+ ImGui.EndPopup();
+ }
+ }
+
+ string path;
+ {
+ var tcs = new TaskCompletionSource();
+ this.fileDialogManager.SaveFileDialog(
+ "Save texture...",
+ $"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}",
+ name + encoder.Extensions.First(),
+ encoder.Extensions.First(),
+ (ok, path2) =>
+ {
+ if (!ok)
+ tcs.SetCanceled();
+ else
+ tcs.SetResult(path2);
+ });
+ path = await tcs.Task.ConfigureAwait(false);
+ }
+
+ var props = new Dictionary();
+ if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
+ props["CompressionQuality"] = 1.0f;
+ else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
+ encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
+ encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
+ props["ImageQuality"] = 1.0f;
+ await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props);
+
+ Service.Get().AddNotification(
+ $"File saved to: {path}",
+ initiatorName,
+ NotificationType.Success);
+ }
+ catch (Exception e)
+ {
+ if (e is OperationCanceledException)
+ return;
+
+ Log.Error(e, $"{nameof(DalamudInterface)}.{nameof(this.ShowTextureSaveMenuAsync)}({initiatorName}, {name})");
+ Service.Get().AddNotification(
+ $"Failed to save file: {e}",
+ initiatorName,
+ NotificationType.Error);
+ }
+ }
+
private void OnDraw()
{
this.FrameCount++;
@@ -537,6 +663,8 @@ internal class DalamudInterface : IDisposable, IServiceType
{
ImGui.SetWindowFocus(null);
}
+
+ this.fileDialogManager.Draw();
}
catch (Exception ex)
{
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 65c10d3a0..87bff1325 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -199,6 +199,10 @@ internal class InterfaceManager : IDisposable, IServiceType
///
public bool IsDispatchingEvents { get; set; } = true;
+ /// Gets a value indicating whether the main thread is executing .
+ /// This still will be true even when queried off the main thread.
+ public bool IsInPresent { get; private set; }
+
///
/// Gets a value indicating the native handle of the game main window.
///
@@ -595,8 +599,6 @@ internal class InterfaceManager : IDisposable, IServiceType
*/
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
{
- this.CumulativePresentCalls++;
-
Debug.Assert(this.presentHook is not null, "How did PresentDetour get called when presentHook is null?");
Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");
@@ -611,6 +613,9 @@ internal class InterfaceManager : IDisposable, IServiceType
if (!this.dalamudAtlas!.HasBuiltAtlas)
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
+ this.CumulativePresentCalls++;
+ this.IsInPresent = true;
+
while (this.runBeforeImGuiRender.TryDequeue(out var action))
action.InvokeSafely();
@@ -620,12 +625,14 @@ internal class InterfaceManager : IDisposable, IServiceType
RenderImGui(this.scene!);
this.PostImGuiRender();
+ this.IsInPresent = false;
return pRes;
}
RenderImGui(this.scene!);
this.PostImGuiRender();
+ this.IsInPresent = false;
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
}
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs
index b7b897e68..e10fca0e7 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs
@@ -142,7 +142,7 @@ public class IconBrowserWidget : IDataWindowWidget
var texm = Service.Get();
var cursor = ImGui.GetCursorScreenPos();
- if (texm.Shared.GetFromGameIcon(new((uint)iconId)).TryGetWrap(out var texture, out var exc))
+ if (texm.Shared.GetFromGameIcon(iconId).TryGetWrap(out var texture, out var exc))
{
ImGui.Image(texture.ImGuiHandle, this.iconSize);
@@ -168,6 +168,14 @@ public class IconBrowserWidget : IDataWindowWidget
ImGui.SetTooltip(iconId.ToString());
}
+ if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
+ {
+ _ = Service.Get().ShowTextureSaveMenuAsync(
+ this.DisplayName,
+ iconId.ToString(),
+ Task.FromResult(texture.CreateWrapSharingLowLevelResource()));
+ }
+
ImGui.GetWindowDrawList().AddRect(
cursor,
cursor + this.iconSize,
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
index 58a2b6b6e..4713022e6 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
@@ -8,10 +8,8 @@ using System.Threading.Tasks;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Components;
-using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Textures;
-using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services;
@@ -20,10 +18,7 @@ using Dalamud.Utility;
using ImGuiNET;
-using Serilog;
-
using TerraFX.Interop.DirectX;
-using TerraFX.Interop.Windows;
using TextureManager = Dalamud.Interface.Textures.Internal.TextureManager;
@@ -34,8 +29,22 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
///
internal class TexWidget : IDataWindowWidget
{
+ private static readonly Dictionary<
+ DrawBlameTableColumnUserId,
+ Func> DrawBlameTableColumnColumnComparers = new()
+ {
+ [DrawBlameTableColumnUserId.Plugins] = static x => string.Join(", ", x.OwnerPlugins.Select(y => y.Name)),
+ [DrawBlameTableColumnUserId.Name] = static x => x.Name,
+ [DrawBlameTableColumnUserId.Size] = static x => x.RawSpecs.EstimatedBytes,
+ [DrawBlameTableColumnUserId.Format] = static x => x.Format,
+ [DrawBlameTableColumnUserId.Width] = static x => x.Width,
+ [DrawBlameTableColumnUserId.Height] = static x => x.Height,
+ [DrawBlameTableColumnUserId.NativeAddress] = static x => x.ResourceAddress,
+ };
+
private readonly List addedTextures = new();
+ private string allLoadedTexturesTableName = "##table";
private string iconId = "18";
private bool hiRes = true;
private bool hq = false;
@@ -51,7 +60,6 @@ internal class TexWidget : IDataWindowWidget
private Vector4 inputTintCol = Vector4.One;
private Vector2 inputTexScale = Vector2.Zero;
private TextureManager textureManager = null!;
- private FileDialogManager fileDialogManager = null!;
private TextureModificationArgs textureModificationArgs;
private ImGuiViewportTextureArgs viewportTextureArgs;
@@ -60,6 +68,19 @@ internal class TexWidget : IDataWindowWidget
private DXGI_FORMAT[]? supportedRenderTargetFormats;
private int renderTargetChoiceInt;
+ private enum DrawBlameTableColumnUserId
+ {
+ NativeAddress,
+ Actions,
+ Name,
+ Width,
+ Height,
+ Format,
+ Size,
+ Plugins,
+ ColumnCount,
+ }
+
///
public string[]? CommandShortcuts { get; init; } = { "tex", "texture" };
@@ -74,6 +95,7 @@ internal class TexWidget : IDataWindowWidget
///
public void Load()
{
+ this.allLoadedTexturesTableName = "##table" + Environment.TickCount64;
this.addedTextures.AggregateToDisposable().Dispose();
this.addedTextures.Clear();
this.inputTexPath = "ui/loadingimage/-nowloading_base25_hr1.tex";
@@ -88,7 +110,6 @@ internal class TexWidget : IDataWindowWidget
this.supportedRenderTargetFormats = null;
this.supportedRenderTargetFormatNames = null;
this.renderTargetChoiceInt = 0;
- this.fileDialogManager = new();
this.textureModificationArgs = new()
{
Uv0 = new(0.25f),
@@ -105,30 +126,44 @@ internal class TexWidget : IDataWindowWidget
public void Draw()
{
this.textureManager = Service.Get();
+ var conf = Service.Get();
if (ImGui.Button("GC"))
GC.Collect();
- ImGui.PushID("blames");
- if (ImGui.CollapsingHeader($"All Loaded Textures: {this.textureManager.AllBlamesForDebug.Count:g}###header"))
- this.DrawBlame(this.textureManager.AllBlamesForDebug);
- ImGui.PopID();
+ var useTexturePluginTracking = conf.UseTexturePluginTracking;
+ if (ImGui.Checkbox("Enable Texture Tracking", ref useTexturePluginTracking))
+ {
+ conf.UseTexturePluginTracking = useTexturePluginTracking;
+ conf.QueueSave();
+ }
+
+ var allBlames = this.textureManager.BlameTracker;
+ lock (allBlames)
+ {
+ ImGui.PushID("blames");
+ var sizeSum = allBlames.Sum(static x => Math.Max(0, x.RawSpecs.EstimatedBytes));
+ if (ImGui.CollapsingHeader(
+ $"All Loaded Textures: {allBlames.Count:n0} ({Util.FormatBytes(sizeSum)})###header"))
+ this.DrawBlame(allBlames);
+ ImGui.PopID();
+ }
ImGui.PushID("loadedGameTextures");
if (ImGui.CollapsingHeader(
- $"Loaded Game Textures: {this.textureManager.Shared.ForDebugGamePathTextures.Count:g}###header"))
+ $"Loaded Game Textures: {this.textureManager.Shared.ForDebugGamePathTextures.Count:n0}###header"))
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugGamePathTextures);
ImGui.PopID();
ImGui.PushID("loadedFileTextures");
if (ImGui.CollapsingHeader(
- $"Loaded File Textures: {this.textureManager.Shared.ForDebugFileSystemTextures.Count:g}###header"))
+ $"Loaded File Textures: {this.textureManager.Shared.ForDebugFileSystemTextures.Count:n0}###header"))
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugFileSystemTextures);
ImGui.PopID();
ImGui.PushID("loadedManifestResourceTextures");
if (ImGui.CollapsingHeader(
- $"Loaded Manifest Resource Textures: {this.textureManager.Shared.ForDebugManifestResourceTextures.Count:g}###header"))
+ $"Loaded Manifest Resource Textures: {this.textureManager.Shared.ForDebugManifestResourceTextures.Count:n0}###header"))
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugManifestResourceTextures);
ImGui.PopID();
@@ -136,7 +171,7 @@ internal class TexWidget : IDataWindowWidget
{
ImGui.PushID("invalidatedTextures");
if (ImGui.CollapsingHeader(
- $"Invalidated: {this.textureManager.Shared.ForDebugInvalidatedTextures.Count:g}###header"))
+ $"Invalidated: {this.textureManager.Shared.ForDebugInvalidatedTextures.Count:n0}###header"))
{
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugInvalidatedTextures);
}
@@ -215,9 +250,10 @@ internal class TexWidget : IDataWindowWidget
ImGui.SameLine();
if (ImGui.Button("Save"))
{
- this.SaveTextureAsync(
+ _ = Service.Get().ShowTextureSaveMenuAsync(
+ this.DisplayName,
$"Texture {t.Id}",
- () => t.CreateNewTextureWrapReference(this.textureManager));
+ t.CreateNewTextureWrapReference(this.textureManager));
}
ImGui.SameLine();
@@ -262,10 +298,12 @@ internal class TexWidget : IDataWindowWidget
pres->Release();
ImGui.TextUnformatted($"RC: Resource({rcres})/View({rcsrv})");
+ ImGui.TextUnformatted(source.ToString());
}
else
{
ImGui.TextUnformatted("RC: -");
+ ImGui.TextUnformatted(" ");
}
}
@@ -294,22 +332,24 @@ internal class TexWidget : IDataWindowWidget
}
runLater?.Invoke();
-
- this.fileDialogManager.Draw();
}
- private unsafe void DrawBlame(IReadOnlyList allBlames)
+ private unsafe void DrawBlame(List allBlames)
{
- var conf = Service.Get();
var im = Service.Get();
- var blame = conf.UseTexturePluginTracking;
- if (ImGui.Checkbox("Enable", ref blame))
- {
- conf.UseTexturePluginTracking = blame;
- conf.QueueSave();
- }
- if (!ImGui.BeginTable("##table", 6))
+ var shouldSortAgain = ImGui.Button("Sort again");
+
+ ImGui.SameLine();
+ if (ImGui.Button("Reset Columns"))
+ this.allLoadedTexturesTableName = "##table" + Environment.TickCount64;
+
+ if (!ImGui.BeginTable(
+ this.allLoadedTexturesTableName,
+ (int)DrawBlameTableColumnUserId.ColumnCount,
+ ImGuiTableFlags.Sortable | ImGuiTableFlags.SortTristate | ImGuiTableFlags.SortMulti |
+ ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable | ImGuiTableFlags.NoBordersInBodyUntilResize |
+ ImGuiTableFlags.NoSavedSettings))
return;
const int numIcons = 1;
@@ -319,32 +359,88 @@ internal class TexWidget : IDataWindowWidget
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableSetupColumn(
- "Dimensions",
+ "Address",
ImGuiTableColumnFlags.WidthFixed,
- ImGui.CalcTextSize("00000x00000").X);
+ ImGui.CalcTextSize("0x7F0000000000").X,
+ (uint)DrawBlameTableColumnUserId.NativeAddress);
+ ImGui.TableSetupColumn(
+ "Actions",
+ ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoSort,
+ iconWidths +
+ (ImGui.GetStyle().FramePadding.X * 2 * numIcons) +
+ (ImGui.GetStyle().ItemSpacing.X * 1 * numIcons),
+ (uint)DrawBlameTableColumnUserId.Actions);
+ ImGui.TableSetupColumn(
+ "Name",
+ ImGuiTableColumnFlags.WidthStretch,
+ 0f,
+ (uint)DrawBlameTableColumnUserId.Name);
+ ImGui.TableSetupColumn(
+ "Width",
+ ImGuiTableColumnFlags.WidthFixed,
+ ImGui.CalcTextSize("000000").X,
+ (uint)DrawBlameTableColumnUserId.Width);
+ ImGui.TableSetupColumn(
+ "Height",
+ ImGuiTableColumnFlags.WidthFixed,
+ ImGui.CalcTextSize("000000").X,
+ (uint)DrawBlameTableColumnUserId.Height);
ImGui.TableSetupColumn(
"Format",
ImGuiTableColumnFlags.WidthFixed,
- ImGui.CalcTextSize("R32G32B32A32_TYPELESS").X);
+ ImGui.CalcTextSize("R32G32B32A32_TYPELESS").X,
+ (uint)DrawBlameTableColumnUserId.Format);
ImGui.TableSetupColumn(
"Size",
ImGuiTableColumnFlags.WidthFixed,
- ImGui.CalcTextSize("123.45 MB").X);
- ImGui.TableSetupColumn(
- "Name",
- ImGuiTableColumnFlags.WidthStretch);
- ImGui.TableSetupColumn(
- "Actions",
- ImGuiTableColumnFlags.WidthFixed,
- iconWidths +
- (ImGui.GetStyle().FramePadding.X * 2 * numIcons) +
- (ImGui.GetStyle().ItemSpacing.X * 1 * numIcons));
+ ImGui.CalcTextSize("123.45 MB").X,
+ (uint)DrawBlameTableColumnUserId.Size);
ImGui.TableSetupColumn(
"Plugins",
ImGuiTableColumnFlags.WidthFixed,
- ImGui.CalcTextSize("Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa").X);
+ ImGui.CalcTextSize("Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa").X,
+ (uint)DrawBlameTableColumnUserId.Plugins);
ImGui.TableHeadersRow();
+ var sortSpecs = ImGui.TableGetSortSpecs();
+ if (sortSpecs.NativePtr is not null && (sortSpecs.SpecsDirty || shouldSortAgain))
+ {
+ allBlames.Sort(
+ static (a, b) =>
+ {
+ var sortSpecs = ImGui.TableGetSortSpecs();
+ var specs = new Span(sortSpecs.NativePtr->Specs, sortSpecs.SpecsCount);
+ Span sorted = stackalloc bool[(int)DrawBlameTableColumnUserId.ColumnCount];
+ foreach (ref var spec in specs)
+ {
+ if (!DrawBlameTableColumnColumnComparers.TryGetValue(
+ (DrawBlameTableColumnUserId)spec.ColumnUserID,
+ out var comparableGetter))
+ continue;
+ sorted[(int)spec.ColumnUserID] = true;
+ var ac = comparableGetter(a);
+ var bc = comparableGetter(b);
+ var c = ac.CompareTo(bc);
+ if (c != 0)
+ return spec.SortDirection == ImGuiSortDirection.Ascending ? c : -c;
+ }
+
+ foreach (var (col, comparableGetter) in DrawBlameTableColumnColumnComparers)
+ {
+ if (sorted[(int)col])
+ continue;
+ var ac = comparableGetter(a);
+ var bc = comparableGetter(b);
+ var c = ac.CompareTo(bc);
+ if (c != 0)
+ return c;
+ }
+
+ return 0;
+ });
+ sortSpecs.SpecsDirty = false;
+ }
+
var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
clipper.Begin(allBlames.Count);
@@ -357,25 +453,16 @@ internal class TexWidget : IDataWindowWidget
ImGui.PushID(i);
ImGui.TableNextColumn();
- ImGui.TextUnformatted($"{wrap.Width}x{wrap.Height}");
-
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(Enum.GetName(wrap.Format)?[12..] ?? wrap.Format.ToString());
-
- ImGui.TableNextColumn();
- var rawSpec = new RawImageSpecification(wrap.Width, wrap.Height, (int)wrap.Format, 0);
- var bytes = rawSpec.EstimatedBytes;
- ImGui.TextUnformatted(bytes < 0 ? "-" : Util.FormatBytes(bytes));
-
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(wrap.Name);
+ ImGui.AlignTextToFramePadding();
+ this.TextCopiable($"0x{wrap.ResourceAddress:X}", true, true);
ImGui.TableNextColumn();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
{
- this.SaveTextureAsync(
+ _ = Service.Get().ShowTextureSaveMenuAsync(
+ this.DisplayName,
$"{wrap.ImGuiHandle:X16}",
- () => Task.FromResult(wrap.CreateWrapSharingLowLevelResource()));
+ Task.FromResult(wrap.CreateWrapSharingLowLevelResource()));
}
if (ImGui.IsItemHovered())
@@ -385,12 +472,25 @@ internal class TexWidget : IDataWindowWidget
ImGui.EndTooltip();
}
+ ImGui.TableNextColumn();
+ this.TextCopiable(wrap.Name, false, true);
+
+ ImGui.TableNextColumn();
+ this.TextCopiable($"{wrap.Width:n0}", true, true);
+
+ ImGui.TableNextColumn();
+ this.TextCopiable($"{wrap.Height:n0}", true, true);
+
+ ImGui.TableNextColumn();
+ this.TextCopiable(Enum.GetName(wrap.Format)?[12..] ?? wrap.Format.ToString(), false, true);
+
+ ImGui.TableNextColumn();
+ var bytes = wrap.RawSpecs.EstimatedBytes;
+ this.TextCopiable(bytes < 0 ? "?" : $"{bytes:n0}", true, true);
+
ImGui.TableNextColumn();
lock (wrap.OwnerPlugins)
- {
- foreach (var plugin in wrap.OwnerPlugins)
- ImGui.TextUnformatted(plugin.Name);
- }
+ this.TextCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true);
ImGui.PopID();
}
@@ -468,16 +568,16 @@ internal class TexWidget : IDataWindowWidget
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
- this.TextRightAlign($"{texture.InstanceIdForDebug:n0}");
+ this.TextCopiable($"{texture.InstanceIdForDebug:n0}", true, true);
ImGui.TableNextColumn();
- this.TextCopiable(texture.SourcePathForDebug, true);
+ this.TextCopiable(texture.SourcePathForDebug, false, true);
ImGui.TableNextColumn();
- this.TextRightAlign($"{texture.RefCountForDebug:n0}");
+ this.TextCopiable($"{texture.RefCountForDebug:n0}", true, true);
ImGui.TableNextColumn();
- this.TextRightAlign(remain <= 0 ? "-" : $"{remain:00.000}");
+ this.TextCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true);
ImGui.TableNextColumn();
ImGui.TextUnformatted(texture.HasRevivalPossibility ? "Yes" : "No");
@@ -486,7 +586,10 @@ internal class TexWidget : IDataWindowWidget
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
{
var name = Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), null);
- this.SaveTextureAsync(name, () => texture.RentAsync());
+ _ = Service.Get().ShowTextureSaveMenuAsync(
+ this.DisplayName,
+ name,
+ texture.RentAsync());
}
if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate)
@@ -783,119 +886,24 @@ internal class TexWidget : IDataWindowWidget
ImGuiHelpers.ScaledDummy(10);
}
- private async void SaveTextureAsync(string name, Func> textureGetter)
- {
- try
- {
- BitmapCodecInfo encoder;
- {
- var off = ImGui.GetCursorScreenPos();
- var first = true;
- var encoders = this.textureManager
- .Wic
- .GetSupportedEncoderInfos()
- .ToList();
- var tcs = new TaskCompletionSource();
- Service.Get().Draw += DrawChoices;
-
- encoder = await tcs.Task;
-
- void DrawChoices()
- {
- if (first)
- {
- ImGui.OpenPopup(nameof(this.SaveTextureAsync));
- first = false;
- }
-
- ImGui.SetNextWindowPos(off, ImGuiCond.Appearing);
- if (!ImGui.BeginPopup(
- nameof(this.SaveTextureAsync),
- ImGuiWindowFlags.AlwaysAutoResize |
- ImGuiWindowFlags.NoTitleBar |
- ImGuiWindowFlags.NoSavedSettings))
- {
- Service.Get().Draw -= DrawChoices;
- tcs.TrySetCanceled();
- return;
- }
-
- foreach (var encoder2 in encoders)
- {
- if (ImGui.Selectable(encoder2.Name))
- tcs.TrySetResult(encoder2);
- }
-
- if (tcs.Task.IsCompleted)
- ImGui.CloseCurrentPopup();
-
- ImGui.EndPopup();
- }
- }
-
- string path;
- {
- var tcs = new TaskCompletionSource();
- this.fileDialogManager.SaveFileDialog(
- "Save texture...",
- $"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}",
- name + encoder.Extensions.First(),
- encoder.Extensions.First(),
- (ok, path2) =>
- {
- if (!ok)
- tcs.SetCanceled();
- else
- tcs.SetResult(path2);
- });
- path = await tcs.Task.ConfigureAwait(false);
- }
-
- using var textureWrap = await textureGetter.Invoke();
- var props = new Dictionary();
- if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
- props["CompressionQuality"] = 1.0f;
- else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
- encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
- encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
- props["ImageQuality"] = 1.0f;
- await this.textureManager.SaveToFileAsync(
- textureWrap,
- encoder.ContainerGuid,
- path,
- props: props);
-
- Service.Get().AddNotification(
- $"File saved to: {path}",
- this.DisplayName,
- NotificationType.Success);
- }
- catch (Exception e)
- {
- if (e is OperationCanceledException)
- return;
-
- Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveTextureAsync)}");
- Service.Get().AddNotification(
- $"Failed to save file: {e}",
- this.DisplayName,
- NotificationType.Error);
- }
- }
-
- private void TextRightAlign(string s)
- {
- var width = ImGui.CalcTextSize(s).X;
- ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetColumnWidth() - width);
- ImGui.TextUnformatted(s);
- }
-
- private void TextCopiable(string s, bool framepad = false)
+ private void TextCopiable(string s, bool alignRight, bool framepad)
{
var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0);
if (framepad)
ImGui.AlignTextToFramePadding();
- ImGui.TextUnformatted(s);
+ if (alignRight)
+ {
+ var width = ImGui.CalcTextSize(s).X;
+ var xoff = ImGui.GetColumnWidth() - width;
+ offset.X += xoff;
+ ImGui.SetCursorPosX(ImGui.GetCursorPosX() + xoff);
+ ImGui.TextUnformatted(s);
+ }
+ else
+ {
+ ImGui.TextUnformatted(s);
+ }
+
if (ImGui.IsItemHovered())
{
ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding);
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
index f091e3164..d2e513c1b 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
@@ -591,7 +591,7 @@ internal sealed partial class FontAtlasFactory
{
ref var texture = ref textureSpan[i];
var name =
- $"FontAtlas.{ this.data.Owner?.Name ?? "(no owner or name)"}[0x{(long)this.data.Atlas.NativePtr:X}][{i}]";
+ $"{nameof(FontAtlasBuiltData)}[{this.data.Owner?.Name ?? "-"}][0x{(long)this.data.Atlas.NativePtr:X}][{i}]";
if (texture.TexID != 0)
{
// Nothing to do
@@ -602,6 +602,7 @@ internal sealed partial class FontAtlasFactory
RawImageSpecification.Rgba32(width, height),
new(texture.TexPixelsRGBA32, width * height * 4),
name);
+ this.factory.TextureManager.Blame(wrap, this.data.Owner?.OwnerPlugin);
this.data.AddExistingTexture(wrap);
texture.TexID = wrap.ImGuiHandle;
}
@@ -647,6 +648,7 @@ internal sealed partial class FontAtlasFactory
width * bpp),
buf,
name);
+ this.factory.TextureManager.Blame(wrap, this.data.Owner?.OwnerPlugin);
this.data.AddExistingTexture(wrap);
texture.TexID = wrap.ImGuiHandle;
continue;
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
index 883fcbbfc..9a6520e25 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
@@ -12,6 +12,7 @@ using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Logging.Internal;
+using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility;
using ImGuiNET;
@@ -256,12 +257,15 @@ internal sealed partial class FontAtlasFactory
/// Name of atlas, for debugging and logging purposes.
/// Specify how to auto rebuild.
/// Whether the fonts in the atlas are under the effect of global scale.
+ /// The owner plugin, if any.
public DalamudFontAtlas(
FontAtlasFactory factory,
string atlasName,
FontAtlasAutoRebuildMode autoRebuildMode,
- bool isGlobalScaled)
+ bool isGlobalScaled,
+ LocalPlugin? ownerPlugin)
{
+ this.OwnerPlugin = ownerPlugin;
this.IsGlobalScaled = isGlobalScaled;
try
{
@@ -355,6 +359,9 @@ internal sealed partial class FontAtlasFactory
///
public bool IsGlobalScaled { get; }
+ /// Gets the owner plugin, if any.
+ public LocalPlugin? OwnerPlugin { get; }
+
///
public void Dispose()
{
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
index 59df710d6..aaf031cba 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
@@ -12,6 +12,7 @@ using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
+using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
@@ -178,12 +179,14 @@ internal sealed partial class FontAtlasFactory
/// Name of atlas, for debugging and logging purposes.
/// Specify how to auto rebuild.
/// Whether the fonts in the atlas is global scaled.
+ /// The owner plugin, if any.
/// The new font atlas.
public IFontAtlas CreateFontAtlas(
string atlasName,
FontAtlasAutoRebuildMode autoRebuildMode,
- bool isGlobalScaled = true) =>
- new DalamudFontAtlas(this, atlasName, autoRebuildMode, isGlobalScaled);
+ bool isGlobalScaled = true,
+ LocalPlugin? ownerPlugin = null) =>
+ new DalamudFontAtlas(this, atlasName, autoRebuildMode, isGlobalScaled, ownerPlugin);
///
/// Adds the font from Dalamud Assets.
@@ -363,7 +366,7 @@ internal sealed partial class FontAtlasFactory
: DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM),
texFile.Header.Width * bpp),
buffer,
- $"{nameof(FontAtlasFactory)}:{texPathFormat.Format(fileIndex)}:{channelIndex}"));
+ $"{nameof(FontAtlasFactory)}[{texPathFormat.Format(fileIndex)}][{channelIndex}]"));
}
finally
{
diff --git a/Dalamud/Interface/Textures/GameIconLookup.cs b/Dalamud/Interface/Textures/GameIconLookup.cs
new file mode 100644
index 000000000..ccc999d56
--- /dev/null
+++ b/Dalamud/Interface/Textures/GameIconLookup.cs
@@ -0,0 +1,55 @@
+using System.Text;
+
+namespace Dalamud.Interface.Textures;
+
+/// Represents a lookup for a game icon.
+public readonly record struct GameIconLookup
+{
+ /// Initializes a new instance of the class.
+ /// The icon ID.
+ /// Whether the HQ icon is requested, where HQ is in the context of items.
+ /// Whether the high-resolution icon is requested.
+ /// The language of the icon to load.
+ public GameIconLookup(uint iconId, bool itemHq = false, bool hiRes = true, ClientLanguage? language = null)
+ {
+ this.IconId = iconId;
+ this.ItemHq = itemHq;
+ this.HiRes = hiRes;
+ this.Language = language;
+ }
+
+ public static implicit operator GameIconLookup(int iconId) => new(checked((uint)iconId));
+
+ public static implicit operator GameIconLookup(uint iconId) => new(iconId);
+
+ /// Gets the icon ID.
+ public uint IconId { get; init; }
+
+ /// Gets a value indicating whether the HQ icon is requested, where HQ is in the context of items.
+ public bool ItemHq { get; init; }
+
+ /// Gets a value indicating whether the high-resolution icon is requested.
+ public bool HiRes { get; init; }
+
+ /// Gets the language of the icon to load.
+ ///
+ /// null will use the active game language.
+ /// If the specified resource does not have variants per language, the language-neutral texture will be used.
+ ///
+ ///
+ public ClientLanguage? Language { get; init; }
+
+ ///
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append(nameof(GameIconLookup)).Append('(').Append(this.IconId);
+ if (this.ItemHq)
+ sb.Append(", HQ");
+ if (this.HiRes)
+ sb.Append(", HR1");
+ if (this.Language is not null)
+ sb.Append(", ").Append(Enum.GetName(this.Language.Value));
+ return sb.Append(')').ToString();
+ }
+}
diff --git a/Dalamud/Interface/Textures/ImGuiViewportTextureArgs.cs b/Dalamud/Interface/Textures/ImGuiViewportTextureArgs.cs
index 8193d65e6..1159f5dbf 100644
--- a/Dalamud/Interface/Textures/ImGuiViewportTextureArgs.cs
+++ b/Dalamud/Interface/Textures/ImGuiViewportTextureArgs.cs
@@ -1,9 +1,12 @@
using System.Numerics;
+using System.Text;
using Dalamud.Interface.Internal;
using ImGuiNET;
+using TerraFX.Interop.DirectX;
+
namespace Dalamud.Interface.Textures;
/// Describes how to take a texture of an existing ImGui viewport.
@@ -44,6 +47,30 @@ public record struct ImGuiViewportTextureArgs()
/// Gets the effective value of .
internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
+ ///
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append(nameof(ImGuiViewportTextureArgs)).Append('(');
+ sb.Append($"0x{this.ViewportId:X}");
+ if (this.AutoUpdate)
+ sb.Append($", {nameof(this.AutoUpdate)}");
+ if (this.TakeBeforeImGuiRender)
+ sb.Append($", {nameof(this.TakeBeforeImGuiRender)}");
+ if (this.KeepTransparency)
+ sb.Append($", {nameof(this.KeepTransparency)}");
+
+ if (this.Uv0 != Vector2.Zero || this.Uv1Effective != Vector2.One)
+ {
+ sb.Append(", ")
+ .Append(this.Uv0.ToString())
+ .Append('-')
+ .Append(this.Uv1.ToString());
+ }
+
+ return sb.Append(')').ToString();
+ }
+
/// Checks the properties and throws an exception if values are invalid.
internal void ThrowOnInvalidValues()
{
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
index 9e6af982d..69aca5c69 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
@@ -18,6 +18,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
/// Creates a new placeholder instance of .
/// The path.
/// The new instance.
+ /// Only to be used from .
public static SharedImmediateTexture CreatePlaceholder(string path) => new FileSystemSharedImmediateTexture(path);
///
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
index e33091127..8a1caacd6 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
@@ -22,6 +22,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
/// Creates a new placeholder instance of .
/// The path.
/// The new instance.
+ /// Only to be used from .
public static SharedImmediateTexture CreatePlaceholder(string path) => new GamePathSharedImmediateTexture(path);
///
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
index 525e25159..34ffbaf0e 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
@@ -27,6 +27,8 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
/// Creates a new placeholder instance of .
/// The arguments to pass to the constructor.
/// The new instance.
+ /// Only to be used from .
+ ///
public static SharedImmediateTexture CreatePlaceholder((Assembly Assembly, string Name) args) =>
new ManifestResourceSharedImmediateTexture(args.Assembly, args.Name);
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
index ed76223a3..a20736e0a 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
@@ -6,6 +6,8 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.TextureWraps;
+using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
@@ -22,6 +24,7 @@ internal abstract class SharedImmediateTexture
private static long instanceCounter;
private readonly object reviveLock = new();
+ private readonly List ownerPlugins = new();
private bool resourceReleased;
private int refCount;
@@ -43,10 +46,11 @@ internal abstract class SharedImmediateTexture
this.IsOpportunistic = true;
this.resourceReleased = true;
this.FirstRequestedTick = this.LatestRequestedTick = Environment.TickCount64;
+ this.PublicUseInstance = new(this);
}
- /// Gets the list of owner plugins.
- public List OwnerPlugins { get; } = new();
+ /// Gets a wrapper for this instance which disables resource reference management.
+ public PureImpl PublicUseInstance { get; }
/// Gets the instance ID. Debug use only.
public long InstanceIdForDebug { get; }
@@ -280,15 +284,15 @@ internal abstract class SharedImmediateTexture
return this.availableOnAccessWrapForApi9;
}
- /// Adds a plugin to , in a thread-safe way.
+ /// Adds a plugin to , in a thread-safe way.
/// The plugin to add.
public void AddOwnerPlugin(LocalPlugin plugin)
{
- lock (this.OwnerPlugins)
+ lock (this.ownerPlugins)
{
- if (!this.OwnerPlugins.Contains(plugin))
+ if (!this.ownerPlugins.Contains(plugin))
{
- this.OwnerPlugins.Add(plugin);
+ this.ownerPlugins.Add(plugin);
this.UnderlyingWrap?.ContinueWith(
r =>
{
@@ -314,14 +318,14 @@ internal abstract class SharedImmediateTexture
protected void LoadUnderlyingWrap()
{
int addLen;
- lock (this.OwnerPlugins)
+ lock (this.ownerPlugins)
{
this.UnderlyingWrap = Service.Get().DynamicPriorityTextureLoader.LoadAsync(
this,
this.CreateTextureAsync,
this.LoadCancellationToken);
- addLen = this.OwnerPlugins.Count;
+ addLen = this.ownerPlugins.Count;
}
if (addLen == 0)
@@ -331,8 +335,11 @@ internal abstract class SharedImmediateTexture
{
if (!r.IsCompletedSuccessfully)
return;
- foreach (var op in this.OwnerPlugins.Take(addLen))
- Service.Get().Blame(r.Result, op);
+ lock (this.ownerPlugins)
+ {
+ foreach (var op in this.ownerPlugins.Take(addLen))
+ Service.Get().Blame(r.Result, op);
+ }
},
default(CancellationToken));
}
@@ -427,6 +434,52 @@ internal abstract class SharedImmediateTexture
}
}
+ /// A wrapper around , to prevent external consumers from mistakenly
+ /// calling or .
+ internal sealed class PureImpl : ISharedImmediateTexture
+ {
+ private readonly SharedImmediateTexture inner;
+
+ /// Initializes a new instance of the class.
+ /// The actual instance.
+ public PureImpl(SharedImmediateTexture inner) => this.inner = inner;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IDalamudTextureWrap GetWrapOrEmpty() =>
+ this.inner.GetWrapOrEmpty();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(defaultWrap))]
+ public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap = null) =>
+ this.inner.GetWrapOrDefault(defaultWrap);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception) =>
+ this.inner.TryGetWrap(out texture, out exception);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task RentAsync(CancellationToken cancellationToken = default) =>
+ this.inner.RentAsync(cancellationToken);
+
+ ///
+ [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IDalamudTextureWrap? GetAvailableOnAccessWrapForApi9() =>
+ this.inner.GetAvailableOnAccessWrapForApi9();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddOwnerPlugin(LocalPlugin plugin) =>
+ this.inner.AddOwnerPlugin(plugin);
+
+ ///
+ public override string ToString() => $"{this.inner}({nameof(PureImpl)})";
+ }
+
/// Same with , but with a custom implementation of
/// .
private sealed class NotOwnedTextureWrap : DisposeSuppressingTextureWrap
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs b/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs
index 5e47d2c8e..a306b7c64 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -18,11 +17,12 @@ namespace Dalamud.Interface.Textures.Internal;
/// Service responsible for loading and disposing ImGui texture wraps.
internal sealed partial class TextureManager
{
- private readonly List blameTracker = new();
-
/// A wrapper for underlying texture2D resources.
public interface IBlameableDalamudTextureWrap : IDalamudTextureWrap
{
+ /// Gets the address of the native resource.
+ public nint ResourceAddress { get; }
+
/// Gets the name of the underlying resource of this texture wrap.
public string Name { get; }
@@ -31,13 +31,27 @@ internal sealed partial class TextureManager
/// Gets the list of owner plugins.
public List OwnerPlugins { get; }
+
+ /// Gets the raw image specification.
+ public RawImageSpecification RawSpecs { get; }
+
+ /// Tests whether the tag and the underlying resource are released or should be released.
+ /// true if there are no more remaining references to this instance.
+ bool TestIsReleasedOrShouldRelease();
}
- /// Gets all the loaded textures from plugins.
- /// The enumerable that goes through all textures and relevant plugins.
+ /// Gets the list containing all the loaded textures from plugins.
/// Returned value must be used inside a lock.
- [SuppressMessage("ReSharper", "InconsistentlySynchronizedField", Justification = "Caller locks the return value.")]
- public IReadOnlyList AllBlamesForDebug => this.blameTracker;
+ public List BlameTracker { get; } = new();
+
+ /// Gets the blame for a texture wrap.
+ /// The texture wrap.
+ /// The blame, if it exists.
+ public unsafe IBlameableDalamudTextureWrap? GetBlame(IDalamudTextureWrap textureWrap)
+ {
+ using var wrapAux = new WrapAux(textureWrap, true);
+ return BlameTag.Get(wrapAux.ResPtr);
+ }
/// Puts a plugin on blame for a texture.
/// The texture.
@@ -59,7 +73,7 @@ internal sealed partial class TextureManager
}
using var wrapAux = new WrapAux(textureWrap, true);
- var blame = BlameTag.From(wrapAux.ResPtr, out var isNew);
+ var blame = BlameTag.GetOrCreate(wrapAux.ResPtr, out var isNew);
if (ownerPlugin is not null)
{
@@ -69,8 +83,8 @@ internal sealed partial class TextureManager
if (isNew)
{
- lock (this.blameTracker)
- this.blameTracker.Add(blame);
+ lock (this.BlameTracker)
+ this.BlameTracker.Add(blame);
}
return textureWrap;
@@ -96,13 +110,13 @@ internal sealed partial class TextureManager
}
using var wrapAux = new WrapAux(textureWrap, true);
- var blame = BlameTag.From(wrapAux.ResPtr, out var isNew);
- blame.Name = name;
+ var blame = BlameTag.GetOrCreate(wrapAux.ResPtr, out var isNew);
+ blame.Name = name.Length <= 1024 ? name : $"{name[..1024]}...";
if (isNew)
{
- lock (this.blameTracker)
- this.blameTracker.Add(blame);
+ lock (this.BlameTracker)
+ this.BlameTracker.Add(blame);
}
return textureWrap;
@@ -110,15 +124,15 @@ internal sealed partial class TextureManager
private void BlameTrackerUpdate(IFramework unused)
{
- lock (this.blameTracker)
+ lock (this.BlameTracker)
{
- for (var i = 0; i < this.blameTracker.Count;)
+ for (var i = 0; i < this.BlameTracker.Count;)
{
- var entry = this.blameTracker[i];
+ var entry = this.BlameTracker[i];
if (entry.TestIsReleasedOrShouldRelease())
{
- this.blameTracker[i] = this.blameTracker[^1];
- this.blameTracker.RemoveAt(this.blameTracker.Count - 1);
+ this.BlameTracker[i] = this.BlameTracker[^1];
+ this.BlameTracker.RemoveAt(this.BlameTracker.Count - 1);
}
else
{
@@ -220,12 +234,22 @@ internal sealed partial class TextureManager
///
public List OwnerPlugins { get; } = new();
+ ///
+ public nint ResourceAddress => (nint)this.tex2D;
+
///
public string Name { get; set; } = "";
///
public DXGI_FORMAT Format => this.desc.Format;
+ ///
+ public RawImageSpecification RawSpecs => new(
+ (int)this.desc.Width,
+ (int)this.desc.Height,
+ (int)this.desc.Format,
+ 0);
+
///
public IntPtr ImGuiHandle
{
@@ -267,7 +291,23 @@ internal sealed partial class TextureManager
/// true if the tracker is new.
/// A COM object type.
/// A new instance of .
- public static BlameTag From(T* trackWhat, out bool isNew) where T : unmanaged, IUnknown.Interface
+ public static BlameTag GetOrCreate(T* trackWhat, out bool isNew) where T : unmanaged, IUnknown.Interface
+ {
+ if (Get(trackWhat) is { } v)
+ {
+ isNew = false;
+ return v;
+ }
+
+ isNew = true;
+ return new((IUnknown*)trackWhat);
+ }
+
+ /// Gets an existing instance of for the given resource.
+ /// The COM object to track.
+ /// A COM object type.
+ /// An existing instance of .
+ public static BlameTag? Get(T* trackWhat) where T : unmanaged, IUnknown.Interface
{
using var deviceChild = default(ComPtr);
fixed (Guid* piid = &IID.IID_ID3D11DeviceChild)
@@ -282,18 +322,15 @@ internal sealed partial class TextureManager
if (ToManagedObject(existingTag) is { } existingTagInstance)
{
existingTagInstance.Release();
- isNew = false;
return existingTagInstance;
}
}
}
- isNew = true;
- return new((IUnknown*)trackWhat);
+ return null;
}
- /// Tests whether the tag and the underlying resource are released or should be released.
- /// true if there are no more remaining references to this instance.
+ ///
public bool TestIsReleasedOrShouldRelease()
{
if (this.srvDebugPreviewExpiryTick <= Environment.TickCount64)
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
index 9d10457dc..7e9b209cf 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -82,7 +83,7 @@ internal sealed partial class TextureManager
this.BlameSetName(
outWrap,
debugName ??
- $"{nameof(this.CreateFromExistingTextureAsync)}({nameof(wrap)}, {nameof(args)}, {nameof(leaveWrapOpen)}, {nameof(cancellationToken)})");
+ $"{nameof(this.CreateFromExistingTextureAsync)}({wrap}, {args})");
return outWrap;
}
},
@@ -136,59 +137,57 @@ internal sealed partial class TextureManager
}
cancellationToken.ThrowIfCancellationRequested();
- return await this.interfaceManager.RunBeforeImGuiRender(
- () => ExtractMappedResource(wrapAux, tex2D, cancellationToken));
+
+ // ID3D11DeviceContext is not a threadsafe resource, and it must be used from the UI thread.
+ return await this.RunDuringPresent(() => ExtractMappedResource(tex2D, cancellationToken));
static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource(
- in WrapAux wrapAux,
ComPtr tex2D,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
- ID3D11Resource* mapWhat = null;
+ D3D11_TEXTURE2D_DESC desc;
+ tex2D.Get()->GetDesc(&desc);
+
+ using var device = default(ComPtr);
+ tex2D.Get()->GetDevice(device.GetAddressOf());
+ using var context = default(ComPtr);
+ device.Get()->GetImmediateContext(context.GetAddressOf());
+
+ using var tmpTex = default(ComPtr);
+ if ((desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0)
+ {
+ var tmpTexDesc = desc with
+ {
+ MipLevels = 1,
+ ArraySize = 1,
+ SampleDesc = new(1, 0),
+ Usage = D3D11_USAGE.D3D11_USAGE_STAGING,
+ BindFlags = 0u,
+ CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ,
+ MiscFlags = 0u,
+ };
+ device.Get()->CreateTexture2D(&tmpTexDesc, null, tmpTex.GetAddressOf()).ThrowOnError();
+ context.Get()->CopyResource((ID3D11Resource*)tmpTex.Get(), (ID3D11Resource*)tex2D.Get());
+
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+
+ var mapWhat = (ID3D11Resource*)(tmpTex.IsEmpty() ? tex2D.Get() : tmpTex.Get());
+
+ D3D11_MAPPED_SUBRESOURCE mapped;
+ context.Get()->Map(mapWhat, 0, D3D11_MAP.D3D11_MAP_READ, 0, &mapped).ThrowOnError();
+
try
{
- using var tmpTex = default(ComPtr);
- if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0)
- {
- var tmpTexDesc = wrapAux.Desc with
- {
- MipLevels = 1,
- ArraySize = 1,
- SampleDesc = new(1, 0),
- Usage = D3D11_USAGE.D3D11_USAGE_STAGING,
- BindFlags = 0u,
- CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ,
- MiscFlags = 0u,
- };
- wrapAux.DevPtr->CreateTexture2D(&tmpTexDesc, null, tmpTex.GetAddressOf()).ThrowOnError();
- wrapAux.CtxPtr->CopyResource((ID3D11Resource*)tmpTex.Get(), (ID3D11Resource*)tex2D.Get());
-
- cancellationToken.ThrowIfCancellationRequested();
- }
-
- D3D11_MAPPED_SUBRESOURCE mapped;
- mapWhat = (ID3D11Resource*)(tmpTex.IsEmpty() ? tex2D.Get() : tmpTex.Get());
- wrapAux.CtxPtr->Map(
- mapWhat,
- 0,
- D3D11_MAP.D3D11_MAP_READ,
- 0,
- &mapped).ThrowOnError();
-
- var specs = new RawImageSpecification(
- (int)wrapAux.Desc.Width,
- (int)wrapAux.Desc.Height,
- (int)wrapAux.Desc.Format,
- (int)mapped.RowPitch);
+ var specs = new RawImageSpecification(desc, mapped.RowPitch);
var bytes = new Span(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
return (specs, bytes);
}
finally
{
- if (mapWhat is not null)
- wrapAux.CtxPtr->Unmap(mapWhat, 0);
+ context.Get()->Unmap(mapWhat, 0);
}
}
}
@@ -226,35 +225,34 @@ internal sealed partial class TextureManager
this.device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
}
- await this.interfaceManager.RunBeforeImGuiRender(
- () =>
- {
- unsafe
- {
- using var rtvCopyTemp = default(ComPtr);
- var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
- tex2DCopyTemp,
- D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
- this.device.Get()->CreateRenderTargetView(
- (ID3D11Resource*)tex2DCopyTemp.Get(),
- &rtvCopyTempDesc,
- rtvCopyTemp.GetAddressOf()).ThrowOnError();
-
- wrapAux.CtxPtr->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
- this.SimpleDrawer.Draw(
- wrapAux.CtxPtr,
- wrapAux.SrvPtr,
- args.Uv0,
- args.Uv1Effective);
- if (args.MakeOpaque)
- this.SimpleDrawer.StripAlpha(wrapAux.CtxPtr);
-
- var dummy = default(ID3D11RenderTargetView*);
- wrapAux.CtxPtr->OMSetRenderTargets(1u, &dummy, null);
- }
- });
+ await this.RunDuringPresent(() => DrawSourceTextureToTarget(wrapAux, args, this.SimpleDrawer, tex2DCopyTemp));
return new(tex2DCopyTemp);
+
+ static unsafe void DrawSourceTextureToTarget(
+ WrapAux wrapAux,
+ TextureModificationArgs args,
+ SimpleDrawerImpl simpleDrawer,
+ ComPtr tex2DCopyTemp)
+ {
+ using var rtvCopyTemp = default(ComPtr);
+ var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
+ tex2DCopyTemp,
+ D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
+ wrapAux.DevPtr->CreateRenderTargetView(
+ (ID3D11Resource*)tex2DCopyTemp.Get(),
+ &rtvCopyTempDesc,
+ rtvCopyTemp.GetAddressOf())
+ .ThrowOnError();
+
+ wrapAux.CtxPtr->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
+ simpleDrawer.Draw(wrapAux.CtxPtr, wrapAux.SrvPtr, args.Uv0, args.Uv1Effective);
+ if (args.MakeOpaque)
+ simpleDrawer.StripAlpha(wrapAux.CtxPtr);
+
+ var dummy = default(ID3D11RenderTargetView*);
+ wrapAux.CtxPtr->OMSetRenderTargets(1u, &dummy, null);
+ }
}
/// Auxiliary data from .
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
index 71f300479..9a7d84deb 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
@@ -88,25 +88,28 @@ internal sealed partial class TextureManager
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) =>
+ public SharedImmediateTexture.PureImpl GetFromGameIcon(in GameIconLookup lookup) =>
this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue));
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromGame(string path) =>
- this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder);
+ public SharedImmediateTexture.PureImpl GetFromGame(string path) =>
+ this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder)
+ .PublicUseInstance;
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromFile(string path) =>
- this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder);
+ public SharedImmediateTexture.PureImpl GetFromFile(string path) =>
+ this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder)
+ .PublicUseInstance;
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromManifestResource(Assembly assembly, string name) =>
+ public SharedImmediateTexture.PureImpl GetFromManifestResource(Assembly assembly, string name) =>
this.manifestResourceDict.GetOrAdd(
(assembly, name),
- ManifestResourceSharedImmediateTexture.CreatePlaceholder);
+ ManifestResourceSharedImmediateTexture.CreatePlaceholder)
+ .PublicUseInstance;
/// Invalidates a cached item from and .
///
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
index bc10e5b77..40b9d3ee0 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
@@ -318,6 +318,7 @@ internal sealed partial class TextureManager
{
// See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
+ DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT => GUID.GUID_WICPixelFormat96bppRGBFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat64bppRGBAHalf,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppRGBA,
DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => GUID.GUID_WICPixelFormat32bppRGBA1010102XR,
@@ -497,10 +498,10 @@ internal sealed partial class TextureManager
var outPixelFormat = specs.Format switch
{
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
- DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT when !this.wicFactory2.IsEmpty() =>
- GUID.GUID_WICPixelFormat128bppRGBAFloat,
- DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat32bppBGRA,
+ DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT => GUID.GUID_WICPixelFormat96bppRGBFloat,
+ DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppBGRA,
+ DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => GUID.GUID_WICPixelFormat24bppBGR,
DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => GUID.GUID_WICPixelFormat16bppBGRA5551,
DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM => GUID.GUID_WICPixelFormat16bppBGR565,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => GUID.GUID_WICPixelFormat8bppGray,
@@ -508,9 +509,25 @@ internal sealed partial class TextureManager
DXGI_FORMAT.DXGI_FORMAT_R16_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
- _ => GUID.GUID_WICPixelFormat24bppBGR,
+ _ => GUID.GUID_WICPixelFormat32bppBGRA,
};
+ var accepted = false;
+ foreach (var pfi in new ComponentEnumerable(
+ this.wicFactory,
+ WICComponentType.WICPixelFormat))
+ {
+ Guid tmp;
+ if (pfi.Get()->GetFormatGUID(&tmp).FAILED)
+ continue;
+ accepted = tmp == outPixelFormat;
+ if (accepted)
+ break;
+ }
+
+ if (!accepted)
+ outPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
+
encoder.Get()->Initialize(stream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
.ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
@@ -613,8 +630,7 @@ internal sealed partial class TextureManager
private readonly WICComponentType componentType;
/// Initializes a new instance of the struct.
- /// The WIC factory. Ownership is not transferred.
- ///
+ /// The WIC factory. Ownership is not transferred.
/// The component type to enumerate.
public ComponentEnumerable(ComPtr factory, WICComponentType componentType)
{
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs
index f69f01877..7510ec7dc 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs
@@ -8,6 +8,7 @@ using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
+using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -127,7 +128,7 @@ internal sealed partial class TextureManager
this.BlameSetName(
this.NoThrottleCreateFromImage(bytes.ToArray(), ct),
debugName ??
- $"{nameof(this.CreateFromImageAsync)}({nameof(bytes)}, {nameof(cancellationToken)})"),
+ $"{nameof(this.CreateFromImageAsync)}({bytes.Length:n0}b)"),
ct),
cancellationToken);
@@ -146,7 +147,7 @@ internal sealed partial class TextureManager
return this.BlameSetName(
this.NoThrottleCreateFromImage(ms.GetBuffer(), ct),
debugName ??
- $"{nameof(this.CreateFromImageAsync)}({nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})");
+ $"{nameof(this.CreateFromImageAsync)}(stream)");
},
cancellationToken,
leaveOpen ? null : stream);
@@ -159,7 +160,7 @@ internal sealed partial class TextureManager
string? debugName = null) =>
this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, bytes),
- debugName ?? $"{nameof(this.CreateFromRaw)}({nameof(specs)}, {nameof(bytes)})");
+ debugName ?? $"{nameof(this.CreateFromRaw)}({specs}, {bytes.Length:n0})");
///
public Task CreateFromRawAsync(
@@ -173,7 +174,7 @@ internal sealed partial class TextureManager
this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, bytes.Span),
debugName ??
- $"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(bytes)}, {nameof(cancellationToken)})")),
+ $"{nameof(this.CreateFromRawAsync)}({specs}, {bytes.Length:n0})")),
cancellationToken);
///
@@ -192,7 +193,7 @@ internal sealed partial class TextureManager
return this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length)),
debugName ??
- $"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})");
+ $"{nameof(this.CreateFromRawAsync)}({specs}, stream)");
},
cancellationToken,
leaveOpen ? null : stream);
@@ -207,15 +208,19 @@ internal sealed partial class TextureManager
public Task CreateFromTexFileAsync(
TexFile file,
string? debugName = null,
- CancellationToken cancellationToken = default) =>
- this.DynamicPriorityTextureLoader.LoadAsync(
+ CancellationToken cancellationToken = default)
+ {
+ return this.DynamicPriorityTextureLoader.LoadAsync(
null,
_ => Task.FromResult(
this.BlameSetName(
this.NoThrottleCreateFromTexFile(file),
- debugName ?? $"{nameof(this.CreateFromTexFile)}({nameof(file)})")),
+ debugName ?? $"{nameof(this.CreateFromTexFile)}({ForceNullable(file.FilePath)?.Path})")),
cancellationToken);
+ static T? ForceNullable(T s) => s;
+ }
+
///
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
@@ -267,7 +272,7 @@ internal sealed partial class TextureManager
.ThrowOnError();
var wrap = new UnknownTextureWrap((IUnknown*)view.Get(), specs.Width, specs.Height, true);
- this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromRaw)}({nameof(specs)}, {nameof(bytes)})");
+ this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromRaw)}({specs}, {bytes.Length:n0})");
return wrap;
}
@@ -289,8 +294,10 @@ internal sealed partial class TextureManager
}
var wrap = this.NoThrottleCreateFromRaw(new(buffer.Width, buffer.Height, dxgiFormat), buffer.RawData);
- this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({nameof(file)})");
+ this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({ForceNullable(file.FilePath).Path})");
return wrap;
+
+ static T? ForceNullable(T s) => s;
}
/// Creates a texture from the given , trying to interpret it as a
@@ -315,9 +322,31 @@ internal sealed partial class TextureManager
// Note: FileInfo and FilePath are not used from TexFile; skip it.
var wrap = this.NoThrottleCreateFromTexFile(tf);
- this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({nameof(fileBytes)})");
+ this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({fileBytes.Length:n0})");
return wrap;
}
private void ReleaseUnmanagedResources() => this.device.Reset();
+
+ /// Runs the given action in IDXGISwapChain.Present immediately or waiting as needed.
+ /// The action to run.
+ // Not sure why this and the below can't be unconditional RunOnFrameworkThread
+ private async Task RunDuringPresent(Action action)
+ {
+ if (this.interfaceManager.IsInPresent && ThreadSafety.IsMainThread)
+ action();
+ else
+ await this.interfaceManager.RunBeforeImGuiRender(action);
+ }
+
+ /// Runs the given function in IDXGISwapChain.Present immediately or waiting as needed.
+ /// The type of the return value.
+ /// The function to run.
+ /// The return value from the function.
+ private async Task RunDuringPresent(Func func)
+ {
+ if (this.interfaceManager.IsInPresent && ThreadSafety.IsMainThread)
+ return func();
+ return await this.interfaceManager.RunBeforeImGuiRender(func);
+ }
}
diff --git a/Dalamud/Interface/Textures/RawImageSpecification.cs b/Dalamud/Interface/Textures/RawImageSpecification.cs
index 9ac649812..e2bad7e5f 100644
--- a/Dalamud/Interface/Textures/RawImageSpecification.cs
+++ b/Dalamud/Interface/Textures/RawImageSpecification.cs
@@ -12,7 +12,7 @@ public record struct RawImageSpecification
/// The height of the raw image.
/// The DXGI format of the raw image.
/// The pitch of the raw image in bytes.
- /// Specify -1 to calculate it from other parameters.
+ /// Specify -1 to calculate from other parameters.
public RawImageSpecification(int width, int height, int dxgiFormat, int pitch = -1)
{
if (pitch < 0)
@@ -31,6 +31,14 @@ public record struct RawImageSpecification
this.DxgiFormat = dxgiFormat;
}
+ /// Initializes a new instance of the class.
+ /// The source texture description.
+ /// The pitch of the raw image in bytes.
+ internal RawImageSpecification(in D3D11_TEXTURE2D_DESC desc, uint pitch)
+ : this((int)desc.Width, (int)desc.Height, (int)desc.Format, checked((int)pitch))
+ {
+ }
+
/// Gets or sets the width of the raw image.
public int Width { get; set; }
@@ -102,6 +110,10 @@ public record struct RawImageSpecification
public static RawImageSpecification A8(int width, int height) =>
new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM, width);
+ ///
+ public override string ToString() =>
+ $"{nameof(RawImageSpecification)}({this.Width}x{this.Height}, {this.Format}, {this.Pitch}b)";
+
private static bool GetFormatInfo(DXGI_FORMAT format, out int bitsPerPixel, out bool isBlockCompression)
{
switch (format)
@@ -246,7 +258,7 @@ public record struct RawImageSpecification
return true;
case DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM:
bitsPerPixel = 16;
- isBlockCompression = true;
+ isBlockCompression = false;
return true;
default:
bitsPerPixel = 0;
diff --git a/Dalamud/Interface/Textures/TextureModificationArgs.cs b/Dalamud/Interface/Textures/TextureModificationArgs.cs
index fac04189c..abccca6b5 100644
--- a/Dalamud/Interface/Textures/TextureModificationArgs.cs
+++ b/Dalamud/Interface/Textures/TextureModificationArgs.cs
@@ -1,4 +1,5 @@
using System.Numerics;
+using System.Text;
using Dalamud.Plugin.Services;
@@ -52,6 +53,36 @@ public record struct TextureModificationArgs()
/// Gets the effective value of .
internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
+ ///
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append(nameof(TextureModificationArgs)).Append('(');
+ if (this.MakeOpaque)
+ sb.Append($"{nameof(this.MakeOpaque)}, ");
+ if (this.Format != DXGI_FORMAT.DXGI_FORMAT_UNKNOWN)
+ sb.Append(Enum.GetName(this.Format) is { } name ? name[12..] : this.Format.ToString()).Append(", ");
+ if (this.NewWidth != 0 || this.NewHeight != 0)
+ {
+ sb.Append(this.NewWidth == 0 ? "?" : this.NewWidth.ToString())
+ .Append('x')
+ .Append(this.NewHeight == 0 ? "?" : this.NewHeight.ToString())
+ .Append(", ");
+ }
+
+ if (this.Uv0 != Vector2.Zero || this.Uv1Effective != Vector2.One)
+ {
+ sb.Append(this.Uv0.ToString())
+ .Append('-')
+ .Append(this.Uv1.ToString())
+ .Append(", ");
+ }
+
+ if (sb[^1] != '(')
+ sb.Remove(sb.Length - 2, 2);
+ return sb.Append(')').ToString();
+ }
+
/// Test if this instance of does not instruct to change the
/// underlying data of a texture.
/// The texture description to test against.
diff --git a/Dalamud/Interface/Textures/DalamudTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/DalamudTextureWrap.cs
similarity index 100%
rename from Dalamud/Interface/Textures/DalamudTextureWrap.cs
rename to Dalamud/Interface/Textures/TextureWraps/DalamudTextureWrap.cs
diff --git a/Dalamud/Interface/Textures/ForwardingTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs
similarity index 96%
rename from Dalamud/Interface/Textures/ForwardingTextureWrap.cs
rename to Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs
index c398fc55c..8b0516e03 100644
--- a/Dalamud/Interface/Textures/ForwardingTextureWrap.cs
+++ b/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs
@@ -3,11 +3,11 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Interface.Internal;
-using Dalamud.Interface.Textures.Internal;
+using Dalamud.Interface.Textures.TextureWraps.Internal;
using TerraFX.Interop.Windows;
-namespace Dalamud.Interface.Textures;
+namespace Dalamud.Interface.Textures.TextureWraps;
/// Base class for implementations of that forwards to another.
public abstract class ForwardingTextureWrap : IDalamudTextureWrap
diff --git a/Dalamud/Interface/Textures/IDalamudTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/IDalamudTextureWrap.cs
similarity index 96%
rename from Dalamud/Interface/Textures/IDalamudTextureWrap.cs
rename to Dalamud/Interface/Textures/TextureWraps/IDalamudTextureWrap.cs
index c51a69f06..09d64ad21 100644
--- a/Dalamud/Interface/Textures/IDalamudTextureWrap.cs
+++ b/Dalamud/Interface/Textures/TextureWraps/IDalamudTextureWrap.cs
@@ -2,6 +2,8 @@ using System.Numerics;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.Internal;
+using Dalamud.Interface.Textures.TextureWraps;
+using Dalamud.Interface.Textures.TextureWraps.Internal;
using TerraFX.Interop.Windows;
diff --git a/Dalamud/Interface/Textures/Internal/DisposeSuppressingTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs
similarity index 91%
rename from Dalamud/Interface/Textures/Internal/DisposeSuppressingTextureWrap.cs
rename to Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs
index 88d8d9ca0..0dd5c9f25 100644
--- a/Dalamud/Interface/Textures/Internal/DisposeSuppressingTextureWrap.cs
+++ b/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs
@@ -1,6 +1,6 @@
using Dalamud.Interface.Internal;
-namespace Dalamud.Interface.Textures.Internal;
+namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// A texture wrap that ignores calls.
internal class DisposeSuppressingTextureWrap : ForwardingTextureWrap
diff --git a/Dalamud/Interface/Textures/Internal/UnknownTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/Internal/UnknownTextureWrap.cs
similarity index 88%
rename from Dalamud/Interface/Textures/Internal/UnknownTextureWrap.cs
rename to Dalamud/Interface/Textures/TextureWraps/Internal/UnknownTextureWrap.cs
index c159f9b36..ec23d7d03 100644
--- a/Dalamud/Interface/Textures/Internal/UnknownTextureWrap.cs
+++ b/Dalamud/Interface/Textures/TextureWraps/Internal/UnknownTextureWrap.cs
@@ -1,11 +1,12 @@
using System.Threading;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Utility;
using TerraFX.Interop.Windows;
-namespace Dalamud.Interface.Textures.Internal;
+namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// A texture wrap that is created from an .
internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferredDisposable
@@ -50,6 +51,10 @@ internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferred
GC.SuppressFinalize(this);
}
+ ///
+ public override string ToString() =>
+ $"{nameof(UnknownTextureWrap)}({Service.GetNullable()?.GetBlame(this)?.Name ?? $"{this.imGuiHandle:X}"})";
+
/// Actually dispose the wrapped texture.
void IDeferredDisposable.RealDispose()
{
diff --git a/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/Internal/ViewportTextureWrap.cs
similarity index 99%
rename from Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs
rename to Dalamud/Interface/Textures/TextureWraps/Internal/ViewportTextureWrap.cs
index 77ddc2e34..ad3188925 100644
--- a/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs
+++ b/Dalamud/Interface/Textures/TextureWraps/Internal/ViewportTextureWrap.cs
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
@@ -15,7 +16,7 @@ using TerraFX.Interop.Windows;
using NotSupportedException = System.NotSupportedException;
-namespace Dalamud.Interface.Textures.Internal;
+namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// A texture wrap that takes its buffer from the frame buffer (of swap chain).
internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDisposable
diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs
index 3b6a754a9..4263fb1bb 100644
--- a/Dalamud/Interface/UiBuilder.cs
+++ b/Dalamud/Interface/UiBuilder.cs
@@ -32,6 +32,7 @@ namespace Dalamud.Interface;
///
public sealed class UiBuilder : IDisposable
{
+ private readonly LocalPlugin plugin;
private readonly Stopwatch stopwatch;
private readonly HitchDetector hitchDetector;
private readonly string namespaceName;
@@ -42,6 +43,8 @@ public sealed class UiBuilder : IDisposable
private readonly DalamudConfiguration configuration = Service.Get();
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
+
+ [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
private readonly TextureManagerPluginScoped scopedTextureProvider;
private bool hasErrorWindow = false;
@@ -64,6 +67,7 @@ public sealed class UiBuilder : IDisposable
this.stopwatch = new Stopwatch();
this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch);
this.namespaceName = namespaceName;
+ this.plugin = plugin;
this.interfaceManager.Draw += this.OnDraw;
this.scopedFinalizer.Add(() => this.interfaceManager.Draw -= this.OnDraw);
@@ -78,7 +82,10 @@ public sealed class UiBuilder : IDisposable
.Add(
Service
.Get()
- .CreateFontAtlas(namespaceName, FontAtlasAutoRebuildMode.Disable));
+ .CreateFontAtlas(
+ namespaceName,
+ FontAtlasAutoRebuildMode.Disable,
+ ownerPlugin: plugin));
this.FontAtlas.BuildStepChange += this.PrivateAtlasOnBuildStepChange;
this.FontAtlas.RebuildRecommend += this.RebuildFonts;
}
@@ -565,7 +572,8 @@ public sealed class UiBuilder : IDisposable
.CreateFontAtlas(
this.namespaceName + ":" + (debugName ?? "custom"),
autoRebuildMode,
- isGlobalScaled));
+ isGlobalScaled,
+ this.plugin));
///
/// Add a notification to the notification queue.
diff --git a/Dalamud/Plugin/Services/ITextureProvider.Api9.cs b/Dalamud/Plugin/Services/ITextureProvider.Api9.cs
index 71d1bf928..1e33cd371 100644
--- a/Dalamud/Plugin/Services/ITextureProvider.Api9.cs
+++ b/Dalamud/Plugin/Services/ITextureProvider.Api9.cs
@@ -2,6 +2,7 @@
using Dalamud.Interface;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures;
using Dalamud.Utility;
using Lumina.Data.Files;
diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs
index a8566d5eb..dfb785c92 100644
--- a/Dalamud/Storage/Assets/DalamudAssetManager.cs
+++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
+using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Networking.Http;
@@ -206,7 +207,7 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
this.cancellationTokenSource.Token);
}
- for (var j = RenameAttemptCount;; j--)
+ for (var j = RenameAttemptCount; ; j--)
{
try
{
@@ -311,17 +312,13 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
var length = checked((int)stream.Length);
buf = ArrayPool.Shared.Rent(length);
stream.ReadExactly(buf, 0, length);
+ var name = $"{nameof(DalamudAsset)}[{Enum.GetName(asset)}]";
var image = purpose switch
{
- DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync(
- buf,
- $"{nameof(DalamudAsset)}.{Enum.GetName(asset)}"),
+ DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync(buf, name),
DalamudAssetPurpose.TextureFromRaw =>
asset.GetAttribute() is { } raw
- ? await tm.CreateFromRawAsync(
- raw.Specification,
- buf,
- $"{nameof(DalamudAsset)}.{Enum.GetName(asset)}")
+ ? await tm.CreateFromRawAsync(raw.Specification, buf, name)
: throw new InvalidOperationException(
"TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."),
_ => null,