diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 1042b0741..078b4cc8d 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -276,7 +276,9 @@ internal class PluginImageCache : IDisposable, IServiceType // FIXME(goat): This is a hack around this call failing randomly in certain situations. Might be related to not being called on the main thread. try { - image = await textureManager.CreateFromImageAsync(bytes); + image = await textureManager.CreateFromImageAsync( + bytes, + $"{nameof(PluginImageCache)}({name} for {manifest.InternalName} at {loc})"); } catch (Exception ex) { diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs index 811937949..f091e3164 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs @@ -586,8 +586,12 @@ internal sealed partial class FontAtlasFactory var bpp = use4 ? 2 : 4; var width = this.NewImAtlas.TexWidth; var height = this.NewImAtlas.TexHeight; - foreach (ref var texture in this.data.ImTextures.DataSpan) + var textureSpan = this.data.ImTextures.DataSpan; + for (var i = 0; i < textureSpan.Length; i++) { + ref var texture = ref textureSpan[i]; + var name = + $"FontAtlas.{ this.data.Owner?.Name ?? "(no owner or name)"}[0x{(long)this.data.Atlas.NativePtr:X}][{i}]"; if (texture.TexID != 0) { // Nothing to do @@ -596,7 +600,8 @@ internal sealed partial class FontAtlasFactory { var wrap = this.factory.TextureManager.CreateFromRaw( RawImageSpecification.Rgba32(width, height), - new(texture.TexPixelsRGBA32, width * height * 4)); + new(texture.TexPixelsRGBA32, width * height * 4), + name); this.data.AddExistingTexture(wrap); texture.TexID = wrap.ImGuiHandle; } @@ -640,7 +645,8 @@ internal sealed partial class FontAtlasFactory height, (int)(use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm), width * bpp), - buf); + buf, + name); this.data.AddExistingTexture(wrap); texture.TexID = wrap.ImGuiHandle; continue; diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs index a9b393d3a..59df710d6 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs @@ -362,7 +362,8 @@ internal sealed partial class FontAtlasFactory ? DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM : DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM), texFile.Header.Width * bpp), - buffer)); + buffer, + $"{nameof(FontAtlasFactory)}:{texPathFormat.Format(fileIndex)}:{channelIndex}")); } finally { diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs index 41829f88c..9d10457dc 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs @@ -49,6 +49,7 @@ internal sealed partial class TextureManager IDalamudTextureWrap wrap, TextureModificationArgs args = default, bool leaveWrapOpen = false, + string? debugName = null, CancellationToken cancellationToken = default) => this.DynamicPriorityTextureLoader.LoadAsync( null, @@ -80,6 +81,7 @@ internal sealed partial class TextureManager true); this.BlameSetName( outWrap, + debugName ?? $"{nameof(this.CreateFromExistingTextureAsync)}({nameof(wrap)}, {nameof(args)}, {nameof(leaveWrapOpen)}, {nameof(cancellationToken)})"); return outWrap; } @@ -90,16 +92,19 @@ internal sealed partial class TextureManager /// Task ITextureProvider.CreateFromImGuiViewportAsync( ImGuiViewportTextureArgs args, - CancellationToken cancellationToken) => this.CreateFromImGuiViewportAsync(args, null, cancellationToken); + string? debugName, + CancellationToken cancellationToken) => + this.CreateFromImGuiViewportAsync(args, null, debugName, cancellationToken); /// public Task CreateFromImGuiViewportAsync( ImGuiViewportTextureArgs args, LocalPlugin? ownerPlugin, + string? debugName = null, CancellationToken cancellationToken = default) { args.ThrowOnInvalidValues(); - var t = new ViewportTextureWrap(args, ownerPlugin, cancellationToken); + var t = new ViewportTextureWrap(args, debugName, ownerPlugin, cancellationToken); t.QueueUpdate(); return t.FirstUpdateTask; } diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs index 6d631a8ec..8fa9efa25 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs @@ -118,6 +118,7 @@ internal sealed partial class TextureManager /// public Task CreateFromImageAsync( ReadOnlyMemory bytes, + string? debugName = null, CancellationToken cancellationToken = default) => this.DynamicPriorityTextureLoader.LoadAsync( null, @@ -125,6 +126,7 @@ internal sealed partial class TextureManager () => this.BlameSetName( this.NoThrottleCreateFromImage(bytes.ToArray(), ct), + debugName ?? $"{nameof(this.CreateFromImageAsync)}({nameof(bytes)}, {nameof(cancellationToken)})"), ct), cancellationToken); @@ -133,6 +135,7 @@ internal sealed partial class TextureManager public Task CreateFromImageAsync( Stream stream, bool leaveOpen = false, + string? debugName = null, CancellationToken cancellationToken = default) => this.DynamicPriorityTextureLoader.LoadAsync( null, @@ -142,6 +145,7 @@ internal sealed partial class TextureManager await stream.CopyToAsync(ms, ct).ConfigureAwait(false); return this.BlameSetName( this.NoThrottleCreateFromImage(ms.GetBuffer(), ct), + debugName ?? $"{nameof(this.CreateFromImageAsync)}({nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})"); }, cancellationToken, @@ -151,21 +155,24 @@ internal sealed partial class TextureManager // It probably doesn't make sense to throttle this, as it copies the passed bytes to GPU without any transformation. public IDalamudTextureWrap CreateFromRaw( RawImageSpecification specs, - ReadOnlySpan bytes) => + ReadOnlySpan bytes, + string? debugName = null) => this.BlameSetName( this.NoThrottleCreateFromRaw(specs, bytes), - $"{nameof(this.CreateFromRaw)}({nameof(specs)}, {nameof(bytes)})"); + debugName ?? $"{nameof(this.CreateFromRaw)}({nameof(specs)}, {nameof(bytes)})"); /// public Task CreateFromRawAsync( RawImageSpecification specs, ReadOnlyMemory bytes, + string? debugName = null, CancellationToken cancellationToken = default) => this.DynamicPriorityTextureLoader.LoadAsync( null, _ => Task.FromResult( this.BlameSetName( this.NoThrottleCreateFromRaw(specs, bytes.Span), + debugName ?? $"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(bytes)}, {nameof(cancellationToken)})")), cancellationToken); @@ -174,6 +181,7 @@ internal sealed partial class TextureManager RawImageSpecification specs, Stream stream, bool leaveOpen = false, + string? debugName = null, CancellationToken cancellationToken = default) => this.DynamicPriorityTextureLoader.LoadAsync( null, @@ -183,6 +191,7 @@ internal sealed partial class TextureManager await stream.CopyToAsync(ms, ct).ConfigureAwait(false); return this.BlameSetName( this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length)), + debugName ?? $"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})"); }, cancellationToken, @@ -197,13 +206,14 @@ internal sealed partial class TextureManager /// public Task CreateFromTexFileAsync( TexFile file, + string? debugName = null, CancellationToken cancellationToken = default) => this.DynamicPriorityTextureLoader.LoadAsync( null, _ => Task.FromResult( this.BlameSetName( this.NoThrottleCreateFromTexFile(file), - $"{nameof(this.CreateFromTexFile)}({nameof(file)})")), + debugName ?? $"{nameof(this.CreateFromTexFile)}({nameof(file)})")), cancellationToken); /// diff --git a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs index 8971409a7..39b91edda 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs @@ -139,6 +139,7 @@ internal sealed partial class TextureManagerPluginScoped IDalamudTextureWrap wrap, TextureModificationArgs args = default, bool leaveWrapOpen = false, + string? debugName = null, CancellationToken cancellationToken = default) { var manager = await this.ManagerTask; @@ -146,6 +147,7 @@ internal sealed partial class TextureManagerPluginScoped wrap, args, leaveWrapOpen, + debugName, cancellationToken); manager.Blame(textureWrap, this.plugin); return textureWrap; @@ -154,10 +156,11 @@ internal sealed partial class TextureManagerPluginScoped /// public async Task CreateFromImGuiViewportAsync( ImGuiViewportTextureArgs args, + string? debugName = null, CancellationToken cancellationToken = default) { var manager = await this.ManagerTask; - var textureWrap = await manager.CreateFromImGuiViewportAsync(args, this.plugin, cancellationToken); + var textureWrap = await manager.CreateFromImGuiViewportAsync(args, this.plugin, debugName, cancellationToken); manager.Blame(textureWrap, this.plugin); return textureWrap; } @@ -165,10 +168,11 @@ internal sealed partial class TextureManagerPluginScoped /// public async Task CreateFromImageAsync( ReadOnlyMemory bytes, + string? debugName = null, CancellationToken cancellationToken = default) { var manager = await this.ManagerTask; - var textureWrap = await manager.CreateFromImageAsync(bytes, cancellationToken); + var textureWrap = await manager.CreateFromImageAsync(bytes, debugName, cancellationToken); manager.Blame(textureWrap, this.plugin); return textureWrap; } @@ -177,10 +181,11 @@ internal sealed partial class TextureManagerPluginScoped public async Task CreateFromImageAsync( Stream stream, bool leaveOpen = false, + string? debugName = null, CancellationToken cancellationToken = default) { var manager = await this.ManagerTask; - var textureWrap = await manager.CreateFromImageAsync(stream, leaveOpen, cancellationToken); + var textureWrap = await manager.CreateFromImageAsync(stream, leaveOpen, debugName, cancellationToken); manager.Blame(textureWrap, this.plugin); return textureWrap; } @@ -188,10 +193,11 @@ internal sealed partial class TextureManagerPluginScoped /// public IDalamudTextureWrap CreateFromRaw( RawImageSpecification specs, - ReadOnlySpan bytes) + ReadOnlySpan bytes, + string? debugName = null) { var manager = this.ManagerOrThrow; - var textureWrap = manager.CreateFromRaw(specs, bytes); + var textureWrap = manager.CreateFromRaw(specs, bytes, debugName); manager.Blame(textureWrap, this.plugin); return textureWrap; } @@ -200,10 +206,11 @@ internal sealed partial class TextureManagerPluginScoped public async Task CreateFromRawAsync( RawImageSpecification specs, ReadOnlyMemory bytes, + string? debugName = null, CancellationToken cancellationToken = default) { var manager = await this.ManagerTask; - var textureWrap = await manager.CreateFromRawAsync(specs, bytes, cancellationToken); + var textureWrap = await manager.CreateFromRawAsync(specs, bytes, debugName, cancellationToken); manager.Blame(textureWrap, this.plugin); return textureWrap; } @@ -213,10 +220,11 @@ internal sealed partial class TextureManagerPluginScoped RawImageSpecification specs, Stream stream, bool leaveOpen = false, + string? debugName = null, CancellationToken cancellationToken = default) { var manager = await this.ManagerTask; - var textureWrap = await manager.CreateFromRawAsync(specs, stream, leaveOpen, cancellationToken); + var textureWrap = await manager.CreateFromRawAsync(specs, stream, leaveOpen, debugName, cancellationToken); manager.Blame(textureWrap, this.plugin); return textureWrap; } @@ -233,10 +241,11 @@ internal sealed partial class TextureManagerPluginScoped /// public async Task CreateFromTexFileAsync( TexFile file, + string? debugName = null, CancellationToken cancellationToken = default) { var manager = await this.ManagerTask; - var textureWrap = await manager.CreateFromTexFileAsync(file, cancellationToken); + var textureWrap = await manager.CreateFromTexFileAsync(file, debugName, cancellationToken); manager.Blame(textureWrap, this.plugin); return textureWrap; } diff --git a/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs b/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs index daa247170..77ddc2e34 100644 --- a/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs +++ b/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs @@ -20,6 +20,7 @@ namespace Dalamud.Interface.Textures.Internal; /// A texture wrap that takes its buffer from the frame buffer (of swap chain). internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDisposable { + private readonly string? debugName; private readonly LocalPlugin? ownerPlugin; private readonly CancellationToken cancellationToken; private readonly TaskCompletionSource firstUpdateTaskCompletionSource = new(); @@ -34,12 +35,14 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos /// Initializes a new instance of the class. /// The arguments for creating a texture. + /// Name for debug display purposes. /// The owner plugin. /// The cancellation token. public ViewportTextureWrap( - ImGuiViewportTextureArgs args, LocalPlugin? ownerPlugin, CancellationToken cancellationToken) + ImGuiViewportTextureArgs args, string? debugName, LocalPlugin? ownerPlugin, CancellationToken cancellationToken) { this.args = args; + this.debugName = debugName; this.ownerPlugin = ownerPlugin; this.cancellationToken = cancellationToken; } @@ -151,7 +154,7 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos Service.Get().Blame(this, this.ownerPlugin); Service.Get().BlameSetName( this, - $"{nameof(ViewportTextureWrap)}({this.args})"); + this.debugName ?? $"{nameof(ViewportTextureWrap)}({this.args})"); } // context.Get()->CopyResource((ID3D11Resource*)this.tex.Get(), (ID3D11Resource*)backBuffer.Get()); diff --git a/Dalamud/Interface/UldWrapper.cs b/Dalamud/Interface/UldWrapper.cs index f70dd88e4..35330c5d0 100644 --- a/Dalamud/Interface/UldWrapper.cs +++ b/Dalamud/Interface/UldWrapper.cs @@ -126,7 +126,10 @@ public class UldWrapper : IDisposable inputSlice.CopyTo(outputSlice); } - return this.textureManager.CreateFromRaw(RawImageSpecification.Rgba32(part.W, part.H), imageData); + return this.textureManager.CreateFromRaw( + RawImageSpecification.Rgba32(part.W, part.H), + imageData, + $"{nameof(UldWrapper)}({this.Uld?.FilePath.Path}: {part.TextureId})"); } private (uint Id, int Width, int Height, bool HD, byte[] RgbaData)? GetTexture(string texturePath) diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs index 59c9af2fc..61f400a0e 100644 --- a/Dalamud/Plugin/Services/ITextureProvider.cs +++ b/Dalamud/Plugin/Services/ITextureProvider.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Dalamud.Interface; using Dalamud.Interface.Internal; +using Dalamud.Interface.Internal.Windows.Data.Widgets; using Dalamud.Interface.Textures; using Lumina.Data.Files; @@ -16,6 +17,10 @@ namespace Dalamud.Plugin.Services; /// Service that grants you access to textures you may render via ImGui. /// /// +/// Create functions will return a new texture, and the returned instance of +/// must be disposed after use. +/// +/// /// Get functions will return a shared texture, and the returnd instance of /// do not require calling , unless a new reference has been created by calling /// .
@@ -23,8 +28,8 @@ namespace Dalamud.Plugin.Services; /// that will stay valid for the rest of the frame. ///
/// -/// Create functions will return a new texture, and the returned instance of -/// must be disposed after use. +/// debugName parameter can be used to name your textures, to aid debugging resource leaks using +/// . /// ///
public partial interface ITextureProvider @@ -36,6 +41,7 @@ public partial interface ITextureProvider /// The texture modification arguments. /// Whether to leave non-disposed when the returned /// completes. + /// Name for debug display purposes. /// The cancellation token. /// A containing the copied texture on success. Dispose after use. /// This function may throw an exception. @@ -43,10 +49,12 @@ public partial interface ITextureProvider IDalamudTextureWrap wrap, TextureModificationArgs args = default, bool leaveWrapOpen = false, + string? debugName = null, CancellationToken cancellationToken = default); /// Creates a texture from an ImGui viewport. /// The arguments for creating a texture. + /// Name for debug display purposes. /// The cancellation token. /// A containing the copied texture on success. Dispose after use. /// @@ -55,22 +63,26 @@ public partial interface ITextureProvider /// Task CreateFromImGuiViewportAsync( ImGuiViewportTextureArgs args, + string? debugName = null, CancellationToken cancellationToken = default); /// Gets a texture from the given bytes, trying to interpret it as a .tex file or other well-known image /// files, such as .png. /// The bytes to load. + /// Name for debug display purposes. /// The cancellation token. /// A containing the loaded texture on success. Dispose after use. /// This function may throw an exception. Task CreateFromImageAsync( ReadOnlyMemory bytes, + string? debugName = null, CancellationToken cancellationToken = default); /// Gets a texture from the given stream, trying to interpret it as a .tex file or other well-known image /// files, such as .png. /// The stream to load data from. /// Whether to leave the stream open once the task completes, sucessfully or not. + /// Name for debug display purposes. /// The cancellation token. /// A containing the loaded texture on success. Dispose after use. /// @@ -80,32 +92,38 @@ public partial interface ITextureProvider Task CreateFromImageAsync( Stream stream, bool leaveOpen = false, + string? debugName = null, CancellationToken cancellationToken = default); /// Gets a texture from the given bytes, interpreting it as a raw bitmap. /// The specifications for the raw bitmap. /// The bytes to load. + /// Name for debug display purposes. /// The texture loaded from the supplied raw bitmap. Dispose after use. /// This function may throw an exception. IDalamudTextureWrap CreateFromRaw( RawImageSpecification specs, - ReadOnlySpan bytes); + ReadOnlySpan bytes, + string? debugName = null); /// Gets a texture from the given bytes, interpreting it as a raw bitmap. /// The specifications for the raw bitmap. /// The bytes to load. + /// Name for debug display purposes. /// The cancellation token. /// A containing the loaded texture on success. Dispose after use. /// This function may throw an exception. Task CreateFromRawAsync( RawImageSpecification specs, ReadOnlyMemory bytes, + string? debugName = null, CancellationToken cancellationToken = default); /// Gets a texture from the given stream, interpreting the read data as a raw bitmap. /// The specifications for the raw bitmap. /// The stream to load data from. /// Whether to leave the stream open once the task completes, sucessfully or not. + /// Name for debug display purposes. /// The cancellation token. /// A containing the loaded texture on success. Dispose after use. /// @@ -117,6 +135,7 @@ public partial interface ITextureProvider RawImageSpecification specs, Stream stream, bool leaveOpen = false, + string? debugName = null, CancellationToken cancellationToken = default); /// @@ -130,11 +149,13 @@ public partial interface ITextureProvider /// Get a texture handle for the specified Lumina . /// The texture to obtain a handle to. + /// Name for debug display purposes. /// The cancellation token. /// A texture wrap that can be used to render the texture. Dispose after use. /// This function may throw an exception. Task CreateFromTexFileAsync( TexFile file, + string? debugName = null, CancellationToken cancellationToken = default); /// Gets the supported bitmap decoders. @@ -144,8 +165,8 @@ public partial interface ITextureProvider ///
    ///
  • ///
  • - ///
  • - ///
  • + ///
  • + ///
  • ///
/// This function may throw an exception. /// diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs index e51788c0b..a8566d5eb 100644 --- a/Dalamud/Storage/Assets/DalamudAssetManager.cs +++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs @@ -58,7 +58,7 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA this.fileStreams = Enum.GetValues().ToDictionary(x => x, _ => (Task?)null); this.textureWraps = Enum.GetValues().ToDictionary(x => x, _ => (Task?)null); - + // Block until all the required assets to be ready. var loadTimings = Timings.Start("DAM LoadAll"); registerStartupBlocker( @@ -72,11 +72,11 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA "Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available."); Task.WhenAll( - Enum.GetValues() - .Where(x => x is not DalamudAsset.Empty4X4) - .Where(x => x.GetAttribute()?.Required is false) - .Select(this.CreateStreamAsync) - .Select(x => x.ToContentDisposedTask())) + Enum.GetValues() + .Where(x => x is not DalamudAsset.Empty4X4) + .Where(x => x.GetAttribute()?.Required is false) + .Select(this.CreateStreamAsync) + .Select(x => x.ToContentDisposedTask())) .ContinueWith(r => Log.Verbose($"Optional assets load state: {r}")); } @@ -206,7 +206,7 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA this.cancellationTokenSource.Token); } - for (var j = RenameAttemptCount; ; j--) + for (var j = RenameAttemptCount;; j--) { try { @@ -313,10 +313,15 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA stream.ReadExactly(buf, 0, length); var image = purpose switch { - DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync(buf), + DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync( + buf, + $"{nameof(DalamudAsset)}.{Enum.GetName(asset)}"), DalamudAssetPurpose.TextureFromRaw => asset.GetAttribute() is { } raw - ? await tm.CreateFromRawAsync(raw.Specification, buf) + ? await tm.CreateFromRawAsync( + raw.Specification, + buf, + $"{nameof(DalamudAsset)}.{Enum.GetName(asset)}") : throw new InvalidOperationException( "TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."), _ => null,