mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-15 21:24:16 +01:00
more
This commit is contained in:
parent
9ba0a297c9
commit
70eecdaaef
30 changed files with 767 additions and 345 deletions
|
|
@ -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);
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
55
Dalamud/Interface/Textures/GameIconLookup.cs
Normal file
55
Dalamud/Interface/Textures/GameIconLookup.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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/>
|
||||||
|
|
|
||||||
|
|
@ -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/>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue