This commit is contained in:
Soreepeong 2024-03-05 17:41:27 +09:00
parent 9ba0a297c9
commit 70eecdaaef
30 changed files with 767 additions and 345 deletions

View file

@ -1,20 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Interface;
/// <summary>
/// Represents a lookup for a game icon.
/// </summary>
/// <param name="IconId">The icon ID.</param>
/// <param name="ItemHq">Whether the HQ icon is requested, where HQ is in the context of items.</param>
/// <param name="HiRes">Whether the high-resolution icon is requested.</param>
/// <param name="Language">The language of the icon to load.</param>
[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);

View file

@ -1,9 +1,12 @@
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using CheapLoc; using CheapLoc;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
@ -15,7 +18,9 @@ using Dalamud.Game.Internal;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Interface.Animation.EasingFunctions; using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Internal.Windows; using Dalamud.Interface.Internal.Windows;
using Dalamud.Interface.Internal.Windows.Data; using Dalamud.Interface.Internal.Windows.Data;
using Dalamud.Interface.Internal.Windows.PluginInstaller; 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.Internal.Windows.StyleEditor;
using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Style; using Dalamud.Interface.Style;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
@ -40,6 +46,10 @@ using ImPlotNET;
using PInvoke; using PInvoke;
using Serilog.Events; using Serilog.Events;
using TerraFX.Interop.Windows;
using Task = System.Threading.Tasks.Task;
namespace Dalamud.Interface.Internal; namespace Dalamud.Interface.Internal;
/// <summary> /// <summary>
@ -56,6 +66,8 @@ internal class DalamudInterface : IDisposable, IServiceType
private readonly DalamudConfiguration configuration; private readonly DalamudConfiguration configuration;
private readonly InterfaceManager interfaceManager; private readonly InterfaceManager interfaceManager;
private readonly FileDialogManager fileDialogManager;
private readonly ChangelogWindow changelogWindow; private readonly ChangelogWindow changelogWindow;
private readonly ColorDemoWindow colorDemoWindow; private readonly ColorDemoWindow colorDemoWindow;
private readonly ComponentDemoWindow componentDemoWindow; private readonly ComponentDemoWindow componentDemoWindow;
@ -109,6 +121,7 @@ internal class DalamudInterface : IDisposable, IServiceType
this.interfaceManager = interfaceManager; this.interfaceManager = interfaceManager;
this.WindowSystem = new WindowSystem("DalamudCore"); this.WindowSystem = new WindowSystem("DalamudCore");
this.fileDialogManager = new();
this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
@ -486,6 +499,119 @@ internal class DalamudInterface : IDisposable, IServiceType
this.creditsDarkeningAnimation.Restart(); this.creditsDarkeningAnimation.Restart();
} }
/// <summary>Shows a context menu confirming texture save.</summary>
/// <param name="initiatorName">Name of the initiator.</param>
/// <param name="name">Suggested name of the file being saved.</param>
/// <param name="texture">A task returning the texture to save.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task ShowTextureSaveMenuAsync(
string initiatorName,
string name,
Task<IDalamudTextureWrap> texture)
{
try
{
var initiatorScreenOffset = ImGui.GetMousePos();
using var textureWrap = await texture;
var textureManager = await Service<TextureManager>.GetAsync();
var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.ImGuiHandle:X}";
BitmapCodecInfo encoder;
{
var first = true;
var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList();
var tcs = new TaskCompletionSource<BitmapCodecInfo>();
Service<InterfaceManager>.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<InterfaceManager>.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<string>();
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<string, object>();
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<NotificationManager>.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<NotificationManager>.Get().AddNotification(
$"Failed to save file: {e}",
initiatorName,
NotificationType.Error);
}
}
private void OnDraw() private void OnDraw()
{ {
this.FrameCount++; this.FrameCount++;
@ -537,6 +663,8 @@ internal class DalamudInterface : IDisposable, IServiceType
{ {
ImGui.SetWindowFocus(null); ImGui.SetWindowFocus(null);
} }
this.fileDialogManager.Draw();
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -199,6 +199,10 @@ internal class InterfaceManager : IDisposable, IServiceType
/// </summary> /// </summary>
public bool IsDispatchingEvents { get; set; } = true; public bool IsDispatchingEvents { get; set; } = true;
/// <summary>Gets a value indicating whether the main thread is executing <see cref="PresentDetour"/>.</summary>
/// <remarks>This still will be <c>true</c> even when queried off the main thread.</remarks>
public bool IsInPresent { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating the native handle of the game main window. /// Gets a value indicating the native handle of the game main window.
/// </summary> /// </summary>
@ -595,8 +599,6 @@ internal class InterfaceManager : IDisposable, IServiceType
*/ */
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) 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.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"); 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) if (!this.dalamudAtlas!.HasBuiltAtlas)
return this.presentHook!.Original(swapChain, syncInterval, presentFlags); return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
this.CumulativePresentCalls++;
this.IsInPresent = true;
while (this.runBeforeImGuiRender.TryDequeue(out var action)) while (this.runBeforeImGuiRender.TryDequeue(out var action))
action.InvokeSafely(); action.InvokeSafely();
@ -620,12 +625,14 @@ internal class InterfaceManager : IDisposable, IServiceType
RenderImGui(this.scene!); RenderImGui(this.scene!);
this.PostImGuiRender(); this.PostImGuiRender();
this.IsInPresent = false;
return pRes; return pRes;
} }
RenderImGui(this.scene!); RenderImGui(this.scene!);
this.PostImGuiRender(); this.PostImGuiRender();
this.IsInPresent = false;
return this.presentHook!.Original(swapChain, syncInterval, presentFlags); return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
} }

View file

@ -142,7 +142,7 @@ public class IconBrowserWidget : IDataWindowWidget
var texm = Service<TextureManager>.Get(); var texm = Service<TextureManager>.Get();
var cursor = ImGui.GetCursorScreenPos(); 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); ImGui.Image(texture.ImGuiHandle, this.iconSize);
@ -168,6 +168,14 @@ public class IconBrowserWidget : IDataWindowWidget
ImGui.SetTooltip(iconId.ToString()); ImGui.SetTooltip(iconId.ToString());
} }
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
_ = Service<DalamudInterface>.Get().ShowTextureSaveMenuAsync(
this.DisplayName,
iconId.ToString(),
Task.FromResult(texture.CreateWrapSharingLowLevelResource()));
}
ImGui.GetWindowDrawList().AddRect( ImGui.GetWindowDrawList().AddRect(
cursor, cursor,
cursor + this.iconSize, cursor + this.iconSize,

View file

@ -8,10 +8,8 @@ using System.Threading.Tasks;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures; using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@ -20,10 +18,7 @@ using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
using Serilog;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using TextureManager = Dalamud.Interface.Textures.Internal.TextureManager; using TextureManager = Dalamud.Interface.Textures.Internal.TextureManager;
@ -34,8 +29,22 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
/// </summary> /// </summary>
internal class TexWidget : IDataWindowWidget internal class TexWidget : IDataWindowWidget
{ {
private static readonly Dictionary<
DrawBlameTableColumnUserId,
Func<TextureManager.IBlameableDalamudTextureWrap, IComparable>> 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<TextureEntry> addedTextures = new(); private readonly List<TextureEntry> addedTextures = new();
private string allLoadedTexturesTableName = "##table";
private string iconId = "18"; private string iconId = "18";
private bool hiRes = true; private bool hiRes = true;
private bool hq = false; private bool hq = false;
@ -51,7 +60,6 @@ internal class TexWidget : IDataWindowWidget
private Vector4 inputTintCol = Vector4.One; private Vector4 inputTintCol = Vector4.One;
private Vector2 inputTexScale = Vector2.Zero; private Vector2 inputTexScale = Vector2.Zero;
private TextureManager textureManager = null!; private TextureManager textureManager = null!;
private FileDialogManager fileDialogManager = null!;
private TextureModificationArgs textureModificationArgs; private TextureModificationArgs textureModificationArgs;
private ImGuiViewportTextureArgs viewportTextureArgs; private ImGuiViewportTextureArgs viewportTextureArgs;
@ -60,6 +68,19 @@ internal class TexWidget : IDataWindowWidget
private DXGI_FORMAT[]? supportedRenderTargetFormats; private DXGI_FORMAT[]? supportedRenderTargetFormats;
private int renderTargetChoiceInt; private int renderTargetChoiceInt;
private enum DrawBlameTableColumnUserId
{
NativeAddress,
Actions,
Name,
Width,
Height,
Format,
Size,
Plugins,
ColumnCount,
}
/// <inheritdoc/> /// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "tex", "texture" }; public string[]? CommandShortcuts { get; init; } = { "tex", "texture" };
@ -74,6 +95,7 @@ internal class TexWidget : IDataWindowWidget
/// <inheritdoc/> /// <inheritdoc/>
public void Load() public void Load()
{ {
this.allLoadedTexturesTableName = "##table" + Environment.TickCount64;
this.addedTextures.AggregateToDisposable().Dispose(); this.addedTextures.AggregateToDisposable().Dispose();
this.addedTextures.Clear(); this.addedTextures.Clear();
this.inputTexPath = "ui/loadingimage/-nowloading_base25_hr1.tex"; this.inputTexPath = "ui/loadingimage/-nowloading_base25_hr1.tex";
@ -88,7 +110,6 @@ internal class TexWidget : IDataWindowWidget
this.supportedRenderTargetFormats = null; this.supportedRenderTargetFormats = null;
this.supportedRenderTargetFormatNames = null; this.supportedRenderTargetFormatNames = null;
this.renderTargetChoiceInt = 0; this.renderTargetChoiceInt = 0;
this.fileDialogManager = new();
this.textureModificationArgs = new() this.textureModificationArgs = new()
{ {
Uv0 = new(0.25f), Uv0 = new(0.25f),
@ -105,30 +126,44 @@ internal class TexWidget : IDataWindowWidget
public void Draw() public void Draw()
{ {
this.textureManager = Service<TextureManager>.Get(); this.textureManager = Service<TextureManager>.Get();
var conf = Service<DalamudConfiguration>.Get();
if (ImGui.Button("GC")) if (ImGui.Button("GC"))
GC.Collect(); GC.Collect();
ImGui.PushID("blames"); var useTexturePluginTracking = conf.UseTexturePluginTracking;
if (ImGui.CollapsingHeader($"All Loaded Textures: {this.textureManager.AllBlamesForDebug.Count:g}###header")) if (ImGui.Checkbox("Enable Texture Tracking", ref useTexturePluginTracking))
this.DrawBlame(this.textureManager.AllBlamesForDebug); {
ImGui.PopID(); 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"); ImGui.PushID("loadedGameTextures");
if (ImGui.CollapsingHeader( 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); this.DrawLoadedTextures(this.textureManager.Shared.ForDebugGamePathTextures);
ImGui.PopID(); ImGui.PopID();
ImGui.PushID("loadedFileTextures"); ImGui.PushID("loadedFileTextures");
if (ImGui.CollapsingHeader( 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); this.DrawLoadedTextures(this.textureManager.Shared.ForDebugFileSystemTextures);
ImGui.PopID(); ImGui.PopID();
ImGui.PushID("loadedManifestResourceTextures"); ImGui.PushID("loadedManifestResourceTextures");
if (ImGui.CollapsingHeader( 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); this.DrawLoadedTextures(this.textureManager.Shared.ForDebugManifestResourceTextures);
ImGui.PopID(); ImGui.PopID();
@ -136,7 +171,7 @@ internal class TexWidget : IDataWindowWidget
{ {
ImGui.PushID("invalidatedTextures"); ImGui.PushID("invalidatedTextures");
if (ImGui.CollapsingHeader( 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); this.DrawLoadedTextures(this.textureManager.Shared.ForDebugInvalidatedTextures);
} }
@ -215,9 +250,10 @@ internal class TexWidget : IDataWindowWidget
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Save")) if (ImGui.Button("Save"))
{ {
this.SaveTextureAsync( _ = Service<DalamudInterface>.Get().ShowTextureSaveMenuAsync(
this.DisplayName,
$"Texture {t.Id}", $"Texture {t.Id}",
() => t.CreateNewTextureWrapReference(this.textureManager)); t.CreateNewTextureWrapReference(this.textureManager));
} }
ImGui.SameLine(); ImGui.SameLine();
@ -262,10 +298,12 @@ internal class TexWidget : IDataWindowWidget
pres->Release(); pres->Release();
ImGui.TextUnformatted($"RC: Resource({rcres})/View({rcsrv})"); ImGui.TextUnformatted($"RC: Resource({rcres})/View({rcsrv})");
ImGui.TextUnformatted(source.ToString());
} }
else else
{ {
ImGui.TextUnformatted("RC: -"); ImGui.TextUnformatted("RC: -");
ImGui.TextUnformatted(" ");
} }
} }
@ -294,22 +332,24 @@ internal class TexWidget : IDataWindowWidget
} }
runLater?.Invoke(); runLater?.Invoke();
this.fileDialogManager.Draw();
} }
private unsafe void DrawBlame(IReadOnlyList<TextureManager.IBlameableDalamudTextureWrap> allBlames) private unsafe void DrawBlame(List<TextureManager.IBlameableDalamudTextureWrap> allBlames)
{ {
var conf = Service<DalamudConfiguration>.Get();
var im = Service<InterfaceManager>.Get(); var im = Service<InterfaceManager>.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; return;
const int numIcons = 1; const int numIcons = 1;
@ -319,32 +359,88 @@ internal class TexWidget : IDataWindowWidget
ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableSetupColumn( ImGui.TableSetupColumn(
"Dimensions", "Address",
ImGuiTableColumnFlags.WidthFixed, 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( ImGui.TableSetupColumn(
"Format", "Format",
ImGuiTableColumnFlags.WidthFixed, ImGuiTableColumnFlags.WidthFixed,
ImGui.CalcTextSize("R32G32B32A32_TYPELESS").X); ImGui.CalcTextSize("R32G32B32A32_TYPELESS").X,
(uint)DrawBlameTableColumnUserId.Format);
ImGui.TableSetupColumn( ImGui.TableSetupColumn(
"Size", "Size",
ImGuiTableColumnFlags.WidthFixed, ImGuiTableColumnFlags.WidthFixed,
ImGui.CalcTextSize("123.45 MB").X); ImGui.CalcTextSize("123.45 MB").X,
ImGui.TableSetupColumn( (uint)DrawBlameTableColumnUserId.Size);
"Name",
ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn(
"Actions",
ImGuiTableColumnFlags.WidthFixed,
iconWidths +
(ImGui.GetStyle().FramePadding.X * 2 * numIcons) +
(ImGui.GetStyle().ItemSpacing.X * 1 * numIcons));
ImGui.TableSetupColumn( ImGui.TableSetupColumn(
"Plugins", "Plugins",
ImGuiTableColumnFlags.WidthFixed, ImGuiTableColumnFlags.WidthFixed,
ImGui.CalcTextSize("Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa").X); ImGui.CalcTextSize("Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa").X,
(uint)DrawBlameTableColumnUserId.Plugins);
ImGui.TableHeadersRow(); 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<ImGuiTableColumnSortSpecs>(sortSpecs.NativePtr->Specs, sortSpecs.SpecsCount);
Span<bool> 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()); var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
clipper.Begin(allBlames.Count); clipper.Begin(allBlames.Count);
@ -357,25 +453,16 @@ internal class TexWidget : IDataWindowWidget
ImGui.PushID(i); ImGui.PushID(i);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted($"{wrap.Width}x{wrap.Height}"); ImGui.AlignTextToFramePadding();
this.TextCopiable($"0x{wrap.ResourceAddress:X}", true, true);
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.TableNextColumn(); ImGui.TableNextColumn();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
{ {
this.SaveTextureAsync( _ = Service<DalamudInterface>.Get().ShowTextureSaveMenuAsync(
this.DisplayName,
$"{wrap.ImGuiHandle:X16}", $"{wrap.ImGuiHandle:X16}",
() => Task.FromResult(wrap.CreateWrapSharingLowLevelResource())); Task.FromResult(wrap.CreateWrapSharingLowLevelResource()));
} }
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
@ -385,12 +472,25 @@ internal class TexWidget : IDataWindowWidget
ImGui.EndTooltip(); 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(); ImGui.TableNextColumn();
lock (wrap.OwnerPlugins) lock (wrap.OwnerPlugins)
{ this.TextCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true);
foreach (var plugin in wrap.OwnerPlugins)
ImGui.TextUnformatted(plugin.Name);
}
ImGui.PopID(); ImGui.PopID();
} }
@ -468,16 +568,16 @@ internal class TexWidget : IDataWindowWidget
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
this.TextRightAlign($"{texture.InstanceIdForDebug:n0}"); this.TextCopiable($"{texture.InstanceIdForDebug:n0}", true, true);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
this.TextCopiable(texture.SourcePathForDebug, true); this.TextCopiable(texture.SourcePathForDebug, false, true);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
this.TextRightAlign($"{texture.RefCountForDebug:n0}"); this.TextCopiable($"{texture.RefCountForDebug:n0}", true, true);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
this.TextRightAlign(remain <= 0 ? "-" : $"{remain:00.000}"); this.TextCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(texture.HasRevivalPossibility ? "Yes" : "No"); ImGui.TextUnformatted(texture.HasRevivalPossibility ? "Yes" : "No");
@ -486,7 +586,10 @@ internal class TexWidget : IDataWindowWidget
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
{ {
var name = Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), null); var name = Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), null);
this.SaveTextureAsync(name, () => texture.RentAsync()); _ = Service<DalamudInterface>.Get().ShowTextureSaveMenuAsync(
this.DisplayName,
name,
texture.RentAsync());
} }
if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate) if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate)
@ -783,119 +886,24 @@ internal class TexWidget : IDataWindowWidget
ImGuiHelpers.ScaledDummy(10); ImGuiHelpers.ScaledDummy(10);
} }
private async void SaveTextureAsync(string name, Func<Task<IDalamudTextureWrap>> textureGetter) private void TextCopiable(string s, bool alignRight, bool framepad)
{
try
{
BitmapCodecInfo encoder;
{
var off = ImGui.GetCursorScreenPos();
var first = true;
var encoders = this.textureManager
.Wic
.GetSupportedEncoderInfos()
.ToList();
var tcs = new TaskCompletionSource<BitmapCodecInfo>();
Service<InterfaceManager>.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<InterfaceManager>.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<string>();
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<string, object>();
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<NotificationManager>.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<NotificationManager>.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)
{ {
var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0); var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0);
if (framepad) if (framepad)
ImGui.AlignTextToFramePadding(); 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()) if (ImGui.IsItemHovered())
{ {
ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding); ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding);

View file

@ -591,7 +591,7 @@ internal sealed partial class FontAtlasFactory
{ {
ref var texture = ref textureSpan[i]; ref var texture = ref textureSpan[i];
var name = 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) if (texture.TexID != 0)
{ {
// Nothing to do // Nothing to do
@ -602,6 +602,7 @@ internal sealed partial class FontAtlasFactory
RawImageSpecification.Rgba32(width, height), RawImageSpecification.Rgba32(width, height),
new(texture.TexPixelsRGBA32, width * height * 4), new(texture.TexPixelsRGBA32, width * height * 4),
name); name);
this.factory.TextureManager.Blame(wrap, this.data.Owner?.OwnerPlugin);
this.data.AddExistingTexture(wrap); this.data.AddExistingTexture(wrap);
texture.TexID = wrap.ImGuiHandle; texture.TexID = wrap.ImGuiHandle;
} }
@ -647,6 +648,7 @@ internal sealed partial class FontAtlasFactory
width * bpp), width * bpp),
buf, buf,
name); name);
this.factory.TextureManager.Blame(wrap, this.data.Owner?.OwnerPlugin);
this.data.AddExistingTexture(wrap); this.data.AddExistingTexture(wrap);
texture.TexID = wrap.ImGuiHandle; texture.TexID = wrap.ImGuiHandle;
continue; continue;

View file

@ -12,6 +12,7 @@ using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility; using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
@ -256,12 +257,15 @@ internal sealed partial class FontAtlasFactory
/// <param name="atlasName">Name of atlas, for debugging and logging purposes.</param> /// <param name="atlasName">Name of atlas, for debugging and logging purposes.</param>
/// <param name="autoRebuildMode">Specify how to auto rebuild.</param> /// <param name="autoRebuildMode">Specify how to auto rebuild.</param>
/// <param name="isGlobalScaled">Whether the fonts in the atlas are under the effect of global scale.</param> /// <param name="isGlobalScaled">Whether the fonts in the atlas are under the effect of global scale.</param>
/// <param name="ownerPlugin">The owner plugin, if any.</param>
public DalamudFontAtlas( public DalamudFontAtlas(
FontAtlasFactory factory, FontAtlasFactory factory,
string atlasName, string atlasName,
FontAtlasAutoRebuildMode autoRebuildMode, FontAtlasAutoRebuildMode autoRebuildMode,
bool isGlobalScaled) bool isGlobalScaled,
LocalPlugin? ownerPlugin)
{ {
this.OwnerPlugin = ownerPlugin;
this.IsGlobalScaled = isGlobalScaled; this.IsGlobalScaled = isGlobalScaled;
try try
{ {
@ -355,6 +359,9 @@ internal sealed partial class FontAtlasFactory
/// <inheritdoc/> /// <inheritdoc/>
public bool IsGlobalScaled { get; } public bool IsGlobalScaled { get; }
/// <summary>Gets the owner plugin, if any.</summary>
public LocalPlugin? OwnerPlugin { get; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {

View file

@ -12,6 +12,7 @@ using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.GameFonts; using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets; using Dalamud.Storage.Assets;
using Dalamud.Utility; using Dalamud.Utility;
@ -178,12 +179,14 @@ internal sealed partial class FontAtlasFactory
/// <param name="atlasName">Name of atlas, for debugging and logging purposes.</param> /// <param name="atlasName">Name of atlas, for debugging and logging purposes.</param>
/// <param name="autoRebuildMode">Specify how to auto rebuild.</param> /// <param name="autoRebuildMode">Specify how to auto rebuild.</param>
/// <param name="isGlobalScaled">Whether the fonts in the atlas is global scaled.</param> /// <param name="isGlobalScaled">Whether the fonts in the atlas is global scaled.</param>
/// <param name="ownerPlugin">The owner plugin, if any.</param>
/// <returns>The new font atlas.</returns> /// <returns>The new font atlas.</returns>
public IFontAtlas CreateFontAtlas( public IFontAtlas CreateFontAtlas(
string atlasName, string atlasName,
FontAtlasAutoRebuildMode autoRebuildMode, FontAtlasAutoRebuildMode autoRebuildMode,
bool isGlobalScaled = true) => bool isGlobalScaled = true,
new DalamudFontAtlas(this, atlasName, autoRebuildMode, isGlobalScaled); LocalPlugin? ownerPlugin = null) =>
new DalamudFontAtlas(this, atlasName, autoRebuildMode, isGlobalScaled, ownerPlugin);
/// <summary> /// <summary>
/// Adds the font from Dalamud Assets. /// Adds the font from Dalamud Assets.
@ -363,7 +366,7 @@ internal sealed partial class FontAtlasFactory
: DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM), : DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM),
texFile.Header.Width * bpp), texFile.Header.Width * bpp),
buffer, buffer,
$"{nameof(FontAtlasFactory)}:{texPathFormat.Format(fileIndex)}:{channelIndex}")); $"{nameof(FontAtlasFactory)}[{texPathFormat.Format(fileIndex)}][{channelIndex}]"));
} }
finally finally
{ {

View file

@ -0,0 +1,55 @@
using System.Text;
namespace Dalamud.Interface.Textures;
/// <summary>Represents a lookup for a game icon.</summary>
public readonly record struct GameIconLookup
{
/// <summary>Initializes a new instance of the <see cref="GameIconLookup"/> class.</summary>
/// <param name="iconId">The icon ID.</param>
/// <param name="itemHq">Whether the HQ icon is requested, where HQ is in the context of items.</param>
/// <param name="hiRes">Whether the high-resolution icon is requested.</param>
/// <param name="language">The language of the icon to load.</param>
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);
/// <summary>Gets the icon ID.</summary>
public uint IconId { get; init; }
/// <summary>Gets a value indicating whether the HQ icon is requested, where HQ is in the context of items.</summary>
public bool ItemHq { get; init; }
/// <summary>Gets a value indicating whether the high-resolution icon is requested.</summary>
public bool HiRes { get; init; }
/// <summary>Gets the language of the icon to load.</summary>
/// <remarks>
/// <para><c>null</c> will use the active game language.</para>
/// <para>If the specified resource does not have variants per language, the language-neutral texture will be used.
/// </para>
/// </remarks>
public ClientLanguage? Language { get; init; }
/// <inheritdoc/>
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();
}
}

View file

@ -1,9 +1,12 @@
using System.Numerics; using System.Numerics;
using System.Text;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using ImGuiNET; using ImGuiNET;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.Textures; namespace Dalamud.Interface.Textures;
/// <summary>Describes how to take a texture of an existing ImGui viewport.</summary> /// <summary>Describes how to take a texture of an existing ImGui viewport.</summary>
@ -44,6 +47,30 @@ public record struct ImGuiViewportTextureArgs()
/// <summary>Gets the effective value of <see cref="Uv1"/>.</summary> /// <summary>Gets the effective value of <see cref="Uv1"/>.</summary>
internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1; internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
/// <inheritdoc/>
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();
}
/// <summary>Checks the properties and throws an exception if values are invalid.</summary> /// <summary>Checks the properties and throws an exception if values are invalid.</summary>
internal void ThrowOnInvalidValues() internal void ThrowOnInvalidValues()
{ {

View file

@ -18,6 +18,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary> /// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>The new instance.</returns> /// <returns>The new instance.</returns>
/// <remarks>Only to be used from <see cref="TextureManager.SharedTextureManager.GetFromFile"/>.</remarks>
public static SharedImmediateTexture CreatePlaceholder(string path) => new FileSystemSharedImmediateTexture(path); public static SharedImmediateTexture CreatePlaceholder(string path) => new FileSystemSharedImmediateTexture(path);
/// <inheritdoc/> /// <inheritdoc/>

View file

@ -22,6 +22,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary> /// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>The new instance.</returns> /// <returns>The new instance.</returns>
/// <remarks>Only to be used from <see cref="TextureManager.SharedTextureManager.GetFromGame"/>.</remarks>
public static SharedImmediateTexture CreatePlaceholder(string path) => new GamePathSharedImmediateTexture(path); public static SharedImmediateTexture CreatePlaceholder(string path) => new GamePathSharedImmediateTexture(path);
/// <inheritdoc/> /// <inheritdoc/>

View file

@ -27,6 +27,8 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
/// <summary>Creates a new placeholder instance of <see cref="ManifestResourceSharedImmediateTexture"/>.</summary> /// <summary>Creates a new placeholder instance of <see cref="ManifestResourceSharedImmediateTexture"/>.</summary>
/// <param name="args">The arguments to pass to the constructor.</param> /// <param name="args">The arguments to pass to the constructor.</param>
/// <returns>The new instance.</returns> /// <returns>The new instance.</returns>
/// <remarks>Only to be used from <see cref="TextureManager.SharedTextureManager.GetFromManifestResource"/>.
/// </remarks>
public static SharedImmediateTexture CreatePlaceholder((Assembly Assembly, string Name) args) => public static SharedImmediateTexture CreatePlaceholder((Assembly Assembly, string Name) args) =>
new ManifestResourceSharedImmediateTexture(args.Assembly, args.Name); new ManifestResourceSharedImmediateTexture(args.Assembly, args.Name);

View file

@ -6,6 +6,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets; using Dalamud.Storage.Assets;
using Dalamud.Utility; using Dalamud.Utility;
@ -22,6 +24,7 @@ internal abstract class SharedImmediateTexture
private static long instanceCounter; private static long instanceCounter;
private readonly object reviveLock = new(); private readonly object reviveLock = new();
private readonly List<LocalPlugin> ownerPlugins = new();
private bool resourceReleased; private bool resourceReleased;
private int refCount; private int refCount;
@ -43,10 +46,11 @@ internal abstract class SharedImmediateTexture
this.IsOpportunistic = true; this.IsOpportunistic = true;
this.resourceReleased = true; this.resourceReleased = true;
this.FirstRequestedTick = this.LatestRequestedTick = Environment.TickCount64; this.FirstRequestedTick = this.LatestRequestedTick = Environment.TickCount64;
this.PublicUseInstance = new(this);
} }
/// <summary>Gets the list of owner plugins.</summary> /// <summary>Gets a wrapper for this instance which disables resource reference management.</summary>
public List<LocalPlugin> OwnerPlugins { get; } = new(); public PureImpl PublicUseInstance { get; }
/// <summary>Gets the instance ID. Debug use only.</summary> /// <summary>Gets the instance ID. Debug use only.</summary>
public long InstanceIdForDebug { get; } public long InstanceIdForDebug { get; }
@ -280,15 +284,15 @@ internal abstract class SharedImmediateTexture
return this.availableOnAccessWrapForApi9; return this.availableOnAccessWrapForApi9;
} }
/// <summary>Adds a plugin to <see cref="OwnerPlugins"/>, in a thread-safe way.</summary> /// <summary>Adds a plugin to <see cref="ownerPlugins"/>, in a thread-safe way.</summary>
/// <param name="plugin">The plugin to add.</param> /// <param name="plugin">The plugin to add.</param>
public void AddOwnerPlugin(LocalPlugin plugin) 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( this.UnderlyingWrap?.ContinueWith(
r => r =>
{ {
@ -314,14 +318,14 @@ internal abstract class SharedImmediateTexture
protected void LoadUnderlyingWrap() protected void LoadUnderlyingWrap()
{ {
int addLen; int addLen;
lock (this.OwnerPlugins) lock (this.ownerPlugins)
{ {
this.UnderlyingWrap = Service<TextureManager>.Get().DynamicPriorityTextureLoader.LoadAsync( this.UnderlyingWrap = Service<TextureManager>.Get().DynamicPriorityTextureLoader.LoadAsync(
this, this,
this.CreateTextureAsync, this.CreateTextureAsync,
this.LoadCancellationToken); this.LoadCancellationToken);
addLen = this.OwnerPlugins.Count; addLen = this.ownerPlugins.Count;
} }
if (addLen == 0) if (addLen == 0)
@ -331,8 +335,11 @@ internal abstract class SharedImmediateTexture
{ {
if (!r.IsCompletedSuccessfully) if (!r.IsCompletedSuccessfully)
return; return;
foreach (var op in this.OwnerPlugins.Take(addLen)) lock (this.ownerPlugins)
Service<TextureManager>.Get().Blame(r.Result, op); {
foreach (var op in this.ownerPlugins.Take(addLen))
Service<TextureManager>.Get().Blame(r.Result, op);
}
}, },
default(CancellationToken)); default(CancellationToken));
} }
@ -427,6 +434,52 @@ internal abstract class SharedImmediateTexture
} }
} }
/// <summary>A wrapper around <see cref="SharedImmediateTexture"/>, to prevent external consumers from mistakenly
/// calling <see cref="IDisposable.Dispose"/> or <see cref="IRefCountable.Release"/>.</summary>
internal sealed class PureImpl : ISharedImmediateTexture
{
private readonly SharedImmediateTexture inner;
/// <summary>Initializes a new instance of the <see cref="PureImpl"/> class.</summary>
/// <param name="inner">The actual instance.</param>
public PureImpl(SharedImmediateTexture inner) => this.inner = inner;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDalamudTextureWrap GetWrapOrEmpty() =>
this.inner.GetWrapOrEmpty();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull(nameof(defaultWrap))]
public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap = null) =>
this.inner.GetWrapOrDefault(defaultWrap);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception) =>
this.inner.TryGetWrap(out texture, out exception);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Task<IDalamudTextureWrap> RentAsync(CancellationToken cancellationToken = default) =>
this.inner.RentAsync(cancellationToken);
/// <inheritdoc cref="SharedImmediateTexture.GetAvailableOnAccessWrapForApi9"/>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDalamudTextureWrap? GetAvailableOnAccessWrapForApi9() =>
this.inner.GetAvailableOnAccessWrapForApi9();
/// <inheritdoc cref="SharedImmediateTexture.AddOwnerPlugin"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddOwnerPlugin(LocalPlugin plugin) =>
this.inner.AddOwnerPlugin(plugin);
/// <inheritdoc/>
public override string ToString() => $"{this.inner}({nameof(PureImpl)})";
}
/// <summary>Same with <see cref="DisposeSuppressingTextureWrap"/>, but with a custom implementation of /// <summary>Same with <see cref="DisposeSuppressingTextureWrap"/>, but with a custom implementation of
/// <see cref="CreateWrapSharingLowLevelResource"/>.</summary> /// <see cref="CreateWrapSharingLowLevelResource"/>.</summary>
private sealed class NotOwnedTextureWrap : DisposeSuppressingTextureWrap private sealed class NotOwnedTextureWrap : DisposeSuppressingTextureWrap

View file

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -18,11 +17,12 @@ namespace Dalamud.Interface.Textures.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary> /// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
internal sealed partial class TextureManager internal sealed partial class TextureManager
{ {
private readonly List<BlameTag> blameTracker = new();
/// <summary>A wrapper for underlying texture2D resources.</summary> /// <summary>A wrapper for underlying texture2D resources.</summary>
public interface IBlameableDalamudTextureWrap : IDalamudTextureWrap public interface IBlameableDalamudTextureWrap : IDalamudTextureWrap
{ {
/// <summary>Gets the address of the native resource.</summary>
public nint ResourceAddress { get; }
/// <summary>Gets the name of the underlying resource of this texture wrap.</summary> /// <summary>Gets the name of the underlying resource of this texture wrap.</summary>
public string Name { get; } public string Name { get; }
@ -31,13 +31,27 @@ internal sealed partial class TextureManager
/// <summary>Gets the list of owner plugins.</summary> /// <summary>Gets the list of owner plugins.</summary>
public List<LocalPlugin> OwnerPlugins { get; } public List<LocalPlugin> OwnerPlugins { get; }
/// <summary>Gets the raw image specification.</summary>
public RawImageSpecification RawSpecs { get; }
/// <summary>Tests whether the tag and the underlying resource are released or should be released.</summary>
/// <returns><c>true</c> if there are no more remaining references to this instance.</returns>
bool TestIsReleasedOrShouldRelease();
} }
/// <summary>Gets all the loaded textures from plugins.</summary> /// <summary>Gets the list containing all the loaded textures from plugins.</summary>
/// <returns>The enumerable that goes through all textures and relevant plugins.</returns>
/// <remarks>Returned value must be used inside a lock.</remarks> /// <remarks>Returned value must be used inside a lock.</remarks>
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField", Justification = "Caller locks the return value.")] public List<IBlameableDalamudTextureWrap> BlameTracker { get; } = new();
public IReadOnlyList<IBlameableDalamudTextureWrap> AllBlamesForDebug => this.blameTracker;
/// <summary>Gets the blame for a texture wrap.</summary>
/// <param name="textureWrap">The texture wrap.</param>
/// <returns>The blame, if it exists.</returns>
public unsafe IBlameableDalamudTextureWrap? GetBlame(IDalamudTextureWrap textureWrap)
{
using var wrapAux = new WrapAux(textureWrap, true);
return BlameTag.Get(wrapAux.ResPtr);
}
/// <summary>Puts a plugin on blame for a texture.</summary> /// <summary>Puts a plugin on blame for a texture.</summary>
/// <param name="textureWrap">The texture.</param> /// <param name="textureWrap">The texture.</param>
@ -59,7 +73,7 @@ internal sealed partial class TextureManager
} }
using var wrapAux = new WrapAux(textureWrap, true); 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) if (ownerPlugin is not null)
{ {
@ -69,8 +83,8 @@ internal sealed partial class TextureManager
if (isNew) if (isNew)
{ {
lock (this.blameTracker) lock (this.BlameTracker)
this.blameTracker.Add(blame); this.BlameTracker.Add(blame);
} }
return textureWrap; return textureWrap;
@ -96,13 +110,13 @@ internal sealed partial class TextureManager
} }
using var wrapAux = new WrapAux(textureWrap, true); 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);
blame.Name = name; blame.Name = name.Length <= 1024 ? name : $"{name[..1024]}...";
if (isNew) if (isNew)
{ {
lock (this.blameTracker) lock (this.BlameTracker)
this.blameTracker.Add(blame); this.BlameTracker.Add(blame);
} }
return textureWrap; return textureWrap;
@ -110,15 +124,15 @@ internal sealed partial class TextureManager
private void BlameTrackerUpdate(IFramework unused) 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()) if (entry.TestIsReleasedOrShouldRelease())
{ {
this.blameTracker[i] = this.blameTracker[^1]; this.BlameTracker[i] = this.BlameTracker[^1];
this.blameTracker.RemoveAt(this.blameTracker.Count - 1); this.BlameTracker.RemoveAt(this.BlameTracker.Count - 1);
} }
else else
{ {
@ -220,12 +234,22 @@ internal sealed partial class TextureManager
/// <inheritdoc/> /// <inheritdoc/>
public List<LocalPlugin> OwnerPlugins { get; } = new(); public List<LocalPlugin> OwnerPlugins { get; } = new();
/// <inheritdoc/>
public nint ResourceAddress => (nint)this.tex2D;
/// <inheritdoc/> /// <inheritdoc/>
public string Name { get; set; } = "<unnamed>"; public string Name { get; set; } = "<unnamed>";
/// <inheritdoc/> /// <inheritdoc/>
public DXGI_FORMAT Format => this.desc.Format; public DXGI_FORMAT Format => this.desc.Format;
/// <inheritdoc/>
public RawImageSpecification RawSpecs => new(
(int)this.desc.Width,
(int)this.desc.Height,
(int)this.desc.Format,
0);
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr ImGuiHandle public IntPtr ImGuiHandle
{ {
@ -267,7 +291,23 @@ internal sealed partial class TextureManager
/// <param name="isNew"><c>true</c> if the tracker is new.</param> /// <param name="isNew"><c>true</c> if the tracker is new.</param>
/// <typeparam name="T">A COM object type.</typeparam> /// <typeparam name="T">A COM object type.</typeparam>
/// <returns>A new instance of <see cref="BlameTag"/>.</returns> /// <returns>A new instance of <see cref="BlameTag"/>.</returns>
public static BlameTag From<T>(T* trackWhat, out bool isNew) where T : unmanaged, IUnknown.Interface public static BlameTag GetOrCreate<T>(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);
}
/// <summary>Gets an existing instance of <see cref="BlameTag"/> for the given resource.</summary>
/// <param name="trackWhat">The COM object to track.</param>
/// <typeparam name="T">A COM object type.</typeparam>
/// <returns>An existing instance of <see cref="BlameTag"/>.</returns>
public static BlameTag? Get<T>(T* trackWhat) where T : unmanaged, IUnknown.Interface
{ {
using var deviceChild = default(ComPtr<ID3D11DeviceChild>); using var deviceChild = default(ComPtr<ID3D11DeviceChild>);
fixed (Guid* piid = &IID.IID_ID3D11DeviceChild) fixed (Guid* piid = &IID.IID_ID3D11DeviceChild)
@ -282,18 +322,15 @@ internal sealed partial class TextureManager
if (ToManagedObject(existingTag) is { } existingTagInstance) if (ToManagedObject(existingTag) is { } existingTagInstance)
{ {
existingTagInstance.Release(); existingTagInstance.Release();
isNew = false;
return existingTagInstance; return existingTagInstance;
} }
} }
} }
isNew = true; return null;
return new((IUnknown*)trackWhat);
} }
/// <summary>Tests whether the tag and the underlying resource are released or should be released.</summary> /// <inheritdoc/>
/// <returns><c>true</c> if there are no more remaining references to this instance.</returns>
public bool TestIsReleasedOrShouldRelease() public bool TestIsReleasedOrShouldRelease()
{ {
if (this.srvDebugPreviewExpiryTick <= Environment.TickCount64) if (this.srvDebugPreviewExpiryTick <= Environment.TickCount64)

View file

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
@ -82,7 +83,7 @@ internal sealed partial class TextureManager
this.BlameSetName( this.BlameSetName(
outWrap, outWrap,
debugName ?? debugName ??
$"{nameof(this.CreateFromExistingTextureAsync)}({nameof(wrap)}, {nameof(args)}, {nameof(leaveWrapOpen)}, {nameof(cancellationToken)})"); $"{nameof(this.CreateFromExistingTextureAsync)}({wrap}, {args})");
return outWrap; return outWrap;
} }
}, },
@ -136,59 +137,57 @@ internal sealed partial class TextureManager
} }
cancellationToken.ThrowIfCancellationRequested(); 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( static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource(
in WrapAux wrapAux,
ComPtr<ID3D11Texture2D> tex2D, ComPtr<ID3D11Texture2D> tex2D,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
ID3D11Resource* mapWhat = null; D3D11_TEXTURE2D_DESC desc;
tex2D.Get()->GetDesc(&desc);
using var device = default(ComPtr<ID3D11Device>);
tex2D.Get()->GetDevice(device.GetAddressOf());
using var context = default(ComPtr<ID3D11DeviceContext>);
device.Get()->GetImmediateContext(context.GetAddressOf());
using var tmpTex = default(ComPtr<ID3D11Texture2D>);
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 try
{ {
using var tmpTex = default(ComPtr<ID3D11Texture2D>); var specs = new RawImageSpecification(desc, mapped.RowPitch);
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 bytes = new Span<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray(); var bytes = new Span<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
return (specs, bytes); return (specs, bytes);
} }
finally finally
{ {
if (mapWhat is not null) context.Get()->Unmap(mapWhat, 0);
wrapAux.CtxPtr->Unmap(mapWhat, 0);
} }
} }
} }
@ -226,35 +225,34 @@ internal sealed partial class TextureManager
this.device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError(); this.device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
} }
await this.interfaceManager.RunBeforeImGuiRender( await this.RunDuringPresent(() => DrawSourceTextureToTarget(wrapAux, args, this.SimpleDrawer, tex2DCopyTemp));
() =>
{
unsafe
{
using var rtvCopyTemp = default(ComPtr<ID3D11RenderTargetView>);
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);
}
});
return new(tex2DCopyTemp); return new(tex2DCopyTemp);
static unsafe void DrawSourceTextureToTarget(
WrapAux wrapAux,
TextureModificationArgs args,
SimpleDrawerImpl simpleDrawer,
ComPtr<ID3D11Texture2D> tex2DCopyTemp)
{
using var rtvCopyTemp = default(ComPtr<ID3D11RenderTargetView>);
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);
}
} }
/// <summary>Auxiliary data from <see cref="IDalamudTextureWrap"/>.</summary> /// <summary>Auxiliary data from <see cref="IDalamudTextureWrap"/>.</summary>

View file

@ -88,25 +88,28 @@ internal sealed partial class TextureManager
/// <inheritdoc cref="ITextureProvider.GetFromGameIcon"/> /// <inheritdoc cref="ITextureProvider.GetFromGameIcon"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) => public SharedImmediateTexture.PureImpl GetFromGameIcon(in GameIconLookup lookup) =>
this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue)); this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue));
/// <inheritdoc cref="ITextureProvider.GetFromGame"/> /// <inheritdoc cref="ITextureProvider.GetFromGame"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromGame(string path) => public SharedImmediateTexture.PureImpl GetFromGame(string path) =>
this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder); this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder)
.PublicUseInstance;
/// <inheritdoc cref="ITextureProvider.GetFromFile"/> /// <inheritdoc cref="ITextureProvider.GetFromFile"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromFile(string path) => public SharedImmediateTexture.PureImpl GetFromFile(string path) =>
this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder); this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder)
.PublicUseInstance;
/// <inheritdoc cref="ITextureProvider.GetFromFile"/> /// <inheritdoc cref="ITextureProvider.GetFromFile"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromManifestResource(Assembly assembly, string name) => public SharedImmediateTexture.PureImpl GetFromManifestResource(Assembly assembly, string name) =>
this.manifestResourceDict.GetOrAdd( this.manifestResourceDict.GetOrAdd(
(assembly, name), (assembly, name),
ManifestResourceSharedImmediateTexture.CreatePlaceholder); ManifestResourceSharedImmediateTexture.CreatePlaceholder)
.PublicUseInstance;
/// <summary>Invalidates a cached item from <see cref="GetFromGame"/> and <see cref="GetFromGameIcon"/>. /// <summary>Invalidates a cached item from <see cref="GetFromGame"/> and <see cref="GetFromGameIcon"/>.
/// </summary> /// </summary>

View file

@ -318,6 +318,7 @@ internal sealed partial class TextureManager
{ {
// See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile // 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_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_FLOAT => GUID.GUID_WICPixelFormat64bppRGBAHalf,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppRGBA, DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppRGBA,
DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => GUID.GUID_WICPixelFormat32bppRGBA1010102XR, 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 var outPixelFormat = specs.Format switch
{ {
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat, DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT when !this.wicFactory2.IsEmpty() => DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT => GUID.GUID_WICPixelFormat96bppRGBFloat,
GUID.GUID_WICPixelFormat128bppRGBAFloat, DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat32bppBGRA,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppBGRA, 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_B5G5R5A1_UNORM => GUID.GUID_WICPixelFormat16bppBGRA5551,
DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM => GUID.GUID_WICPixelFormat16bppBGR565, DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM => GUID.GUID_WICPixelFormat16bppBGR565,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => GUID.GUID_WICPixelFormat8bppGray, 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_R16_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray, DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_A8_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<IWICPixelFormatInfo>(
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) encoder.Get()->Initialize(stream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
.ThrowOnError(); .ThrowOnError();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -613,8 +630,7 @@ internal sealed partial class TextureManager
private readonly WICComponentType componentType; private readonly WICComponentType componentType;
/// <summary>Initializes a new instance of the <see cref="ComponentEnumerable{T}"/> struct.</summary> /// <summary>Initializes a new instance of the <see cref="ComponentEnumerable{T}"/> struct.</summary>
/// <param name="factory">The WIC factory. Ownership is not transferred. /// <param name="factory">The WIC factory. Ownership is not transferred.</param>
/// </param>
/// <param name="componentType">The component type to enumerate.</param> /// <param name="componentType">The component type to enumerate.</param>
public ComponentEnumerable(ComPtr<IWICImagingFactory> factory, WICComponentType componentType) public ComponentEnumerable(ComPtr<IWICImagingFactory> factory, WICComponentType componentType)
{ {

View file

@ -8,6 +8,7 @@ using Dalamud.Data;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures; using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
@ -127,7 +128,7 @@ internal sealed partial class TextureManager
this.BlameSetName( this.BlameSetName(
this.NoThrottleCreateFromImage(bytes.ToArray(), ct), this.NoThrottleCreateFromImage(bytes.ToArray(), ct),
debugName ?? debugName ??
$"{nameof(this.CreateFromImageAsync)}({nameof(bytes)}, {nameof(cancellationToken)})"), $"{nameof(this.CreateFromImageAsync)}({bytes.Length:n0}b)"),
ct), ct),
cancellationToken); cancellationToken);
@ -146,7 +147,7 @@ internal sealed partial class TextureManager
return this.BlameSetName( return this.BlameSetName(
this.NoThrottleCreateFromImage(ms.GetBuffer(), ct), this.NoThrottleCreateFromImage(ms.GetBuffer(), ct),
debugName ?? debugName ??
$"{nameof(this.CreateFromImageAsync)}({nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})"); $"{nameof(this.CreateFromImageAsync)}(stream)");
}, },
cancellationToken, cancellationToken,
leaveOpen ? null : stream); leaveOpen ? null : stream);
@ -159,7 +160,7 @@ internal sealed partial class TextureManager
string? debugName = null) => string? debugName = null) =>
this.BlameSetName( this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, bytes), this.NoThrottleCreateFromRaw(specs, bytes),
debugName ?? $"{nameof(this.CreateFromRaw)}({nameof(specs)}, {nameof(bytes)})"); debugName ?? $"{nameof(this.CreateFromRaw)}({specs}, {bytes.Length:n0})");
/// <inheritdoc/> /// <inheritdoc/>
public Task<IDalamudTextureWrap> CreateFromRawAsync( public Task<IDalamudTextureWrap> CreateFromRawAsync(
@ -173,7 +174,7 @@ internal sealed partial class TextureManager
this.BlameSetName( this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, bytes.Span), this.NoThrottleCreateFromRaw(specs, bytes.Span),
debugName ?? debugName ??
$"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(bytes)}, {nameof(cancellationToken)})")), $"{nameof(this.CreateFromRawAsync)}({specs}, {bytes.Length:n0})")),
cancellationToken); cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
@ -192,7 +193,7 @@ internal sealed partial class TextureManager
return this.BlameSetName( return this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length)), this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length)),
debugName ?? debugName ??
$"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})"); $"{nameof(this.CreateFromRawAsync)}({specs}, stream)");
}, },
cancellationToken, cancellationToken,
leaveOpen ? null : stream); leaveOpen ? null : stream);
@ -207,15 +208,19 @@ internal sealed partial class TextureManager
public Task<IDalamudTextureWrap> CreateFromTexFileAsync( public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
TexFile file, TexFile file,
string? debugName = null, string? debugName = null,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default)
this.DynamicPriorityTextureLoader.LoadAsync( {
return this.DynamicPriorityTextureLoader.LoadAsync(
null, null,
_ => Task.FromResult( _ => Task.FromResult(
this.BlameSetName( this.BlameSetName(
this.NoThrottleCreateFromTexFile(file), this.NoThrottleCreateFromTexFile(file),
debugName ?? $"{nameof(this.CreateFromTexFile)}({nameof(file)})")), debugName ?? $"{nameof(this.CreateFromTexFile)}({ForceNullable(file.FilePath)?.Path})")),
cancellationToken); cancellationToken);
static T? ForceNullable<T>(T s) => s;
}
/// <inheritdoc/> /// <inheritdoc/>
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) => bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat); this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
@ -267,7 +272,7 @@ internal sealed partial class TextureManager
.ThrowOnError(); .ThrowOnError();
var wrap = new UnknownTextureWrap((IUnknown*)view.Get(), specs.Width, specs.Height, true); 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; return wrap;
} }
@ -289,8 +294,10 @@ internal sealed partial class TextureManager
} }
var wrap = this.NoThrottleCreateFromRaw(new(buffer.Width, buffer.Height, dxgiFormat), buffer.RawData); 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; return wrap;
static T? ForceNullable<T>(T s) => s;
} }
/// <summary>Creates a texture from the given <paramref name="fileBytes"/>, trying to interpret it as a /// <summary>Creates a texture from the given <paramref name="fileBytes"/>, 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. // Note: FileInfo and FilePath are not used from TexFile; skip it.
var wrap = this.NoThrottleCreateFromTexFile(tf); var wrap = this.NoThrottleCreateFromTexFile(tf);
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({nameof(fileBytes)})"); this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({fileBytes.Length:n0})");
return wrap; return wrap;
} }
private void ReleaseUnmanagedResources() => this.device.Reset(); private void ReleaseUnmanagedResources() => this.device.Reset();
/// <summary>Runs the given action in IDXGISwapChain.Present immediately or waiting as needed.</summary>
/// <param name="action">The action to run.</param>
// 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);
}
/// <summary>Runs the given function in IDXGISwapChain.Present immediately or waiting as needed.</summary>
/// <typeparam name="T">The type of the return value.</typeparam>
/// <param name="func">The function to run.</param>
/// <returns>The return value from the function.</returns>
private async Task<T> RunDuringPresent<T>(Func<T> func)
{
if (this.interfaceManager.IsInPresent && ThreadSafety.IsMainThread)
return func();
return await this.interfaceManager.RunBeforeImGuiRender(func);
}
} }

View file

@ -12,7 +12,7 @@ public record struct RawImageSpecification
/// <param name="height">The height of the raw image.</param> /// <param name="height">The height of the raw image.</param>
/// <param name="dxgiFormat">The DXGI format of the raw image.</param> /// <param name="dxgiFormat">The DXGI format of the raw image.</param>
/// <param name="pitch">The pitch of the raw image in bytes. /// <param name="pitch">The pitch of the raw image in bytes.
/// Specify <c>-1</c> to calculate it from other parameters.</param> /// Specify <c>-1</c> to calculate from other parameters.</param>
public RawImageSpecification(int width, int height, int dxgiFormat, int pitch = -1) public RawImageSpecification(int width, int height, int dxgiFormat, int pitch = -1)
{ {
if (pitch < 0) if (pitch < 0)
@ -31,6 +31,14 @@ public record struct RawImageSpecification
this.DxgiFormat = dxgiFormat; this.DxgiFormat = dxgiFormat;
} }
/// <summary>Initializes a new instance of the <see cref="RawImageSpecification"/> class.</summary>
/// <param name="desc">The source texture description.</param>
/// <param name="pitch">The pitch of the raw image in bytes.</param>
internal RawImageSpecification(in D3D11_TEXTURE2D_DESC desc, uint pitch)
: this((int)desc.Width, (int)desc.Height, (int)desc.Format, checked((int)pitch))
{
}
/// <summary>Gets or sets the width of the raw image.</summary> /// <summary>Gets or sets the width of the raw image.</summary>
public int Width { get; set; } public int Width { get; set; }
@ -102,6 +110,10 @@ public record struct RawImageSpecification
public static RawImageSpecification A8(int width, int height) => public static RawImageSpecification A8(int width, int height) =>
new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM, width); new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM, width);
/// <inheritdoc/>
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) private static bool GetFormatInfo(DXGI_FORMAT format, out int bitsPerPixel, out bool isBlockCompression)
{ {
switch (format) switch (format)
@ -246,7 +258,7 @@ public record struct RawImageSpecification
return true; return true;
case DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM: case DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM:
bitsPerPixel = 16; bitsPerPixel = 16;
isBlockCompression = true; isBlockCompression = false;
return true; return true;
default: default:
bitsPerPixel = 0; bitsPerPixel = 0;

View file

@ -1,4 +1,5 @@
using System.Numerics; using System.Numerics;
using System.Text;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@ -52,6 +53,36 @@ public record struct TextureModificationArgs()
/// <summary>Gets the effective value of <see cref="Uv1"/>.</summary> /// <summary>Gets the effective value of <see cref="Uv1"/>.</summary>
internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1; internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
/// <inheritdoc/>
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();
}
/// <summary>Test if this instance of <see cref="TextureModificationArgs"/> does not instruct to change the /// <summary>Test if this instance of <see cref="TextureModificationArgs"/> does not instruct to change the
/// underlying data of a texture.</summary> /// underlying data of a texture.</summary>
/// <param name="desc">The texture description to test against.</param> /// <param name="desc">The texture description to test against.</param>

View file

@ -3,11 +3,11 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.TextureWraps.Internal;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Textures; namespace Dalamud.Interface.Textures.TextureWraps;
/// <summary>Base class for implementations of <see cref="IDalamudTextureWrap"/> that forwards to another.</summary> /// <summary>Base class for implementations of <see cref="IDalamudTextureWrap"/> that forwards to another.</summary>
public abstract class ForwardingTextureWrap : IDalamudTextureWrap public abstract class ForwardingTextureWrap : IDalamudTextureWrap

View file

@ -2,6 +2,8 @@ using System.Numerics;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;

View file

@ -1,6 +1,6 @@
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
namespace Dalamud.Interface.Textures.Internal; namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// <summary>A texture wrap that ignores <see cref="IDisposable.Dispose"/> calls.</summary> /// <summary>A texture wrap that ignores <see cref="IDisposable.Dispose"/> calls.</summary>
internal class DisposeSuppressingTextureWrap : ForwardingTextureWrap internal class DisposeSuppressingTextureWrap : ForwardingTextureWrap

View file

@ -1,11 +1,12 @@
using System.Threading; using System.Threading;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Utility; using Dalamud.Utility;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Textures.Internal; namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// <summary>A texture wrap that is created from an <see cref="IUnknown"/>.</summary> /// <summary>A texture wrap that is created from an <see cref="IUnknown"/>.</summary>
internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferredDisposable internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferredDisposable
@ -50,6 +51,10 @@ internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferred
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <inheritdoc/>
public override string ToString() =>
$"{nameof(UnknownTextureWrap)}({Service<TextureManager>.GetNullable()?.GetBlame(this)?.Name ?? $"{this.imGuiHandle:X}"})";
/// <summary>Actually dispose the wrapped texture.</summary> /// <summary>Actually dispose the wrapped texture.</summary>
void IDeferredDisposable.RealDispose() void IDeferredDisposable.RealDispose()
{ {

View file

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets; using Dalamud.Storage.Assets;
using Dalamud.Utility; using Dalamud.Utility;
@ -15,7 +16,7 @@ using TerraFX.Interop.Windows;
using NotSupportedException = System.NotSupportedException; using NotSupportedException = System.NotSupportedException;
namespace Dalamud.Interface.Textures.Internal; namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// <summary>A texture wrap that takes its buffer from the frame buffer (of swap chain).</summary> /// <summary>A texture wrap that takes its buffer from the frame buffer (of swap chain).</summary>
internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDisposable internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDisposable

View file

@ -32,6 +32,7 @@ namespace Dalamud.Interface;
/// </summary> /// </summary>
public sealed class UiBuilder : IDisposable public sealed class UiBuilder : IDisposable
{ {
private readonly LocalPlugin plugin;
private readonly Stopwatch stopwatch; private readonly Stopwatch stopwatch;
private readonly HitchDetector hitchDetector; private readonly HitchDetector hitchDetector;
private readonly string namespaceName; private readonly string namespaceName;
@ -42,6 +43,8 @@ public sealed class UiBuilder : IDisposable
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get(); private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new(); private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
private readonly TextureManagerPluginScoped scopedTextureProvider; private readonly TextureManagerPluginScoped scopedTextureProvider;
private bool hasErrorWindow = false; private bool hasErrorWindow = false;
@ -64,6 +67,7 @@ public sealed class UiBuilder : IDisposable
this.stopwatch = new Stopwatch(); this.stopwatch = new Stopwatch();
this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch); this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch);
this.namespaceName = namespaceName; this.namespaceName = namespaceName;
this.plugin = plugin;
this.interfaceManager.Draw += this.OnDraw; this.interfaceManager.Draw += this.OnDraw;
this.scopedFinalizer.Add(() => this.interfaceManager.Draw -= this.OnDraw); this.scopedFinalizer.Add(() => this.interfaceManager.Draw -= this.OnDraw);
@ -78,7 +82,10 @@ public sealed class UiBuilder : IDisposable
.Add( .Add(
Service<FontAtlasFactory> Service<FontAtlasFactory>
.Get() .Get()
.CreateFontAtlas(namespaceName, FontAtlasAutoRebuildMode.Disable)); .CreateFontAtlas(
namespaceName,
FontAtlasAutoRebuildMode.Disable,
ownerPlugin: plugin));
this.FontAtlas.BuildStepChange += this.PrivateAtlasOnBuildStepChange; this.FontAtlas.BuildStepChange += this.PrivateAtlasOnBuildStepChange;
this.FontAtlas.RebuildRecommend += this.RebuildFonts; this.FontAtlas.RebuildRecommend += this.RebuildFonts;
} }
@ -565,7 +572,8 @@ public sealed class UiBuilder : IDisposable
.CreateFontAtlas( .CreateFontAtlas(
this.namespaceName + ":" + (debugName ?? "custom"), this.namespaceName + ":" + (debugName ?? "custom"),
autoRebuildMode, autoRebuildMode,
isGlobalScaled)); isGlobalScaled,
this.plugin));
/// <summary> /// <summary>
/// Add a notification to the notification queue. /// Add a notification to the notification queue.

View file

@ -2,6 +2,7 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures;
using Dalamud.Utility; using Dalamud.Utility;
using Lumina.Data.Files; using Lumina.Data.Files;

View file

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Networking.Http; using Dalamud.Networking.Http;
@ -206,7 +207,7 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
this.cancellationTokenSource.Token); this.cancellationTokenSource.Token);
} }
for (var j = RenameAttemptCount;; j--) for (var j = RenameAttemptCount; ; j--)
{ {
try try
{ {
@ -311,17 +312,13 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
var length = checked((int)stream.Length); var length = checked((int)stream.Length);
buf = ArrayPool<byte>.Shared.Rent(length); buf = ArrayPool<byte>.Shared.Rent(length);
stream.ReadExactly(buf, 0, length); stream.ReadExactly(buf, 0, length);
var name = $"{nameof(DalamudAsset)}[{Enum.GetName(asset)}]";
var image = purpose switch var image = purpose switch
{ {
DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync( DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync(buf, name),
buf,
$"{nameof(DalamudAsset)}.{Enum.GetName(asset)}"),
DalamudAssetPurpose.TextureFromRaw => DalamudAssetPurpose.TextureFromRaw =>
asset.GetAttribute<DalamudAssetRawTextureAttribute>() is { } raw asset.GetAttribute<DalamudAssetRawTextureAttribute>() is { } raw
? await tm.CreateFromRawAsync( ? await tm.CreateFromRawAsync(raw.Specification, buf, name)
raw.Specification,
buf,
$"{nameof(DalamudAsset)}.{Enum.GetName(asset)}")
: throw new InvalidOperationException( : throw new InvalidOperationException(
"TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."), "TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."),
_ => null, _ => null,