From f8492dc06b5cdbdf841d34df10a023522dc28506 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Wed, 28 Feb 2024 19:16:14 +0900 Subject: [PATCH] changes --- .../{Internal => }/GameIconLookup.cs | 2 +- Dalamud/Interface/ISharedImmediateTexture.cs | 69 ++++++ .../FileSystemSharedImmediateTexture.cs} | 18 +- .../GamePathSharedImmediateTexture.cs} | 18 +- .../SharedImmediateTexture.cs} | 233 +++++++++++++----- .../TextureLoadThrottler.cs | 2 +- Dalamud/Interface/Internal/TextureManager.cs | 180 +++++--------- .../Windows/Data/Widgets/IconBrowserWidget.cs | 2 +- .../Windows/Data/Widgets/TexWidget.cs | 29 ++- .../Internal/Windows/PluginImageCache.cs | 2 +- .../PluginInstaller/PluginInstallerWindow.cs | 4 +- .../FontAtlasFactory.BuildToolkit.cs | 4 +- .../Internals/FontAtlasFactory.cs | 2 +- Dalamud/Interface/UiBuilder.cs | 27 +- Dalamud/Interface/UldWrapper.cs | 2 +- .../Plugin/Services/ITextureProvider.Api9.cs | 6 +- Dalamud/Plugin/Services/ITextureProvider.cs | 167 ++++--------- Dalamud/Storage/Assets/DalamudAssetManager.cs | 4 +- 18 files changed, 410 insertions(+), 361 deletions(-) rename Dalamud/Interface/{Internal => }/GameIconLookup.cs (94%) create mode 100644 Dalamud/Interface/ISharedImmediateTexture.cs rename Dalamud/Interface/Internal/{SharableTextures/FileSystemSharableTexture.cs => SharedImmediateTextures/FileSystemSharedImmediateTexture.cs} (72%) rename Dalamud/Interface/Internal/{SharableTextures/GamePathSharableTexture.cs => SharedImmediateTextures/GamePathSharedImmediateTexture.cs} (73%) rename Dalamud/Interface/Internal/{SharableTextures/SharableTexture.cs => SharedImmediateTextures/SharedImmediateTexture.cs} (71%) rename Dalamud/Interface/Internal/{SharableTextures => }/TextureLoadThrottler.cs (99%) diff --git a/Dalamud/Interface/Internal/GameIconLookup.cs b/Dalamud/Interface/GameIconLookup.cs similarity index 94% rename from Dalamud/Interface/Internal/GameIconLookup.cs rename to Dalamud/Interface/GameIconLookup.cs index b34db9d59..001f519aa 100644 --- a/Dalamud/Interface/Internal/GameIconLookup.cs +++ b/Dalamud/Interface/GameIconLookup.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Dalamud.Interface.Internal; +namespace Dalamud.Interface; /// /// Represents a lookup for a game icon. diff --git a/Dalamud/Interface/ISharedImmediateTexture.cs b/Dalamud/Interface/ISharedImmediateTexture.cs new file mode 100644 index 000000000..d2b22b877 --- /dev/null +++ b/Dalamud/Interface/ISharedImmediateTexture.cs @@ -0,0 +1,69 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +using Dalamud.Interface.Internal; +using Dalamud.Utility; + +namespace Dalamud.Interface; + +/// A texture with a backing instance of that is shared across multiple +/// requesters. +/// +/// Calling on this interface is a no-op. +/// and may stop returning the intended texture at any point. +/// Use to lock the texture for use in any thread for any duration. +/// +public interface ISharedImmediateTexture +{ + /// Gets the texture for use with the current frame. + /// An instance of that is guaranteed to be available for the current + /// frame being drawn. + /// + /// Calling outside the main thread will fail. + /// This function does not throw. + /// will be ignored. + /// If the texture is unavailable for any reason, then the returned instance of + /// will point to an empty texture instead. + /// + IDalamudTextureWrap GetWrap(); + + /// Gets the texture for use with the current frame. + /// The default wrap to return if the requested texture was not immediately available. + /// + /// An instance of that is guaranteed to be available for the current + /// frame being drawn. + /// + /// Calling outside the main thread will fail. + /// This function does not throw. + /// will be ignored. + /// If the texture is unavailable for any reason, then will be returned. + /// + [return: NotNullIfNotNull(nameof(defaultWrap))] + IDalamudTextureWrap? GetWrap(IDalamudTextureWrap? defaultWrap); + + /// Attempts to get the texture for use with the current frame. + /// An instance of that is guaranteed to be available for + /// the current frame being drawn, or null if texture is not loaded (yet). + /// The load exception, if any. + /// true if points to the loaded texture; false if the texture is + /// still being loaded, or the load has failed. + /// + /// Calling outside the main thread will fail. + /// This function does not throw. + /// on the returned will be ignored. + /// + /// Thrown when called outside the UI thread. + bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception); + + /// Creates a new instance of holding a new reference to this texture. + /// The returned texture is guaranteed to be available until is called. + /// The cancellation token. + /// A containing the loaded texture on success. + /// + /// must be called on the resulting instance of + /// from the returned after use. Consider using + /// to dispose the result automatically according to the state + /// of the task. + Task RentAsync(CancellationToken cancellationToken = default); +} diff --git a/Dalamud/Interface/Internal/SharableTextures/FileSystemSharableTexture.cs b/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs similarity index 72% rename from Dalamud/Interface/Internal/SharableTextures/FileSystemSharableTexture.cs rename to Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs index df4d2ca0a..aac2169b9 100644 --- a/Dalamud/Interface/Internal/SharableTextures/FileSystemSharableTexture.cs +++ b/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs @@ -4,22 +4,22 @@ using System.Threading.Tasks; using Dalamud.Utility; -namespace Dalamud.Interface.Internal.SharableTextures; +namespace Dalamud.Interface.Internal.SharedImmediateTextures; /// /// Represents a sharable texture, based on a file on the system filesystem. /// -internal sealed class FileSystemSharableTexture : SharableTexture +internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture { private readonly string path; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The path. /// If set to true, this class will hold a reference to self. /// Otherwise, it is expected that the caller to hold the reference. - private FileSystemSharableTexture(string path, bool holdSelfReference) + private FileSystemSharedImmediateTexture(string path, bool holdSelfReference) : base(holdSelfReference) { this.path = path; @@ -31,24 +31,24 @@ internal sealed class FileSystemSharableTexture : SharableTexture public override string SourcePathForDebug => this.path; /// - /// Creates a new instance of . + /// Creates a new instance of . /// The new instance will hold a reference to itself. /// /// The path. /// The new instance. - public static SharableTexture CreateImmediate(string path) => new FileSystemSharableTexture(path, true); + public static SharedImmediateTexture CreateImmediate(string path) => new FileSystemSharedImmediateTexture(path, true); /// - /// Creates a new instance of . + /// Creates a new instance of . /// The caller is expected to manage ownership of the new instance. /// /// The path. /// The new instance. - public static SharableTexture CreateAsync(string path) => new FileSystemSharableTexture(path, false); + public static SharedImmediateTexture CreateAsync(string path) => new FileSystemSharedImmediateTexture(path, false); /// public override string ToString() => - $"{nameof(FileSystemSharableTexture)}#{this.InstanceIdForDebug}({this.path})"; + $"{nameof(FileSystemSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})"; /// protected override void ReleaseResources() diff --git a/Dalamud/Interface/Internal/SharableTextures/GamePathSharableTexture.cs b/Dalamud/Interface/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs similarity index 73% rename from Dalamud/Interface/Internal/SharableTextures/GamePathSharableTexture.cs rename to Dalamud/Interface/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs index db51159bb..6c709321f 100644 --- a/Dalamud/Interface/Internal/SharableTextures/GamePathSharableTexture.cs +++ b/Dalamud/Interface/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs @@ -7,22 +7,22 @@ using Dalamud.Utility; using Lumina.Data.Files; -namespace Dalamud.Interface.Internal.SharableTextures; +namespace Dalamud.Interface.Internal.SharedImmediateTextures; /// /// Represents a sharable texture, based on a file in game resources. /// -internal sealed class GamePathSharableTexture : SharableTexture +internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture { private readonly string path; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The path. /// If set to true, this class will hold a reference to self. /// Otherwise, it is expected that the caller to hold the reference. - private GamePathSharableTexture(string path, bool holdSelfReference) + private GamePathSharedImmediateTexture(string path, bool holdSelfReference) : base(holdSelfReference) { this.path = path; @@ -34,23 +34,23 @@ internal sealed class GamePathSharableTexture : SharableTexture public override string SourcePathForDebug => this.path; /// - /// Creates a new instance of . + /// Creates a new instance of . /// The new instance will hold a reference to itself. /// /// The path. /// The new instance. - public static SharableTexture CreateImmediate(string path) => new GamePathSharableTexture(path, true); + public static SharedImmediateTexture CreateImmediate(string path) => new GamePathSharedImmediateTexture(path, true); /// - /// Creates a new instance of . + /// Creates a new instance of . /// The caller is expected to manage ownership of the new instance. /// /// The path. /// The new instance. - public static SharableTexture CreateAsync(string path) => new GamePathSharableTexture(path, false); + public static SharedImmediateTexture CreateAsync(string path) => new GamePathSharedImmediateTexture(path, false); /// - public override string ToString() => $"{nameof(GamePathSharableTexture)}#{this.InstanceIdForDebug}({this.path})"; + public override string ToString() => $"{nameof(GamePathSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})"; /// protected override void ReleaseResources() diff --git a/Dalamud/Interface/Internal/SharableTextures/SharableTexture.cs b/Dalamud/Interface/Internal/SharedImmediateTextures/SharedImmediateTexture.cs similarity index 71% rename from Dalamud/Interface/Internal/SharableTextures/SharableTexture.cs rename to Dalamud/Interface/Internal/SharedImmediateTextures/SharedImmediateTexture.cs index cb12c1894..e75f8d038 100644 --- a/Dalamud/Interface/Internal/SharableTextures/SharableTexture.cs +++ b/Dalamud/Interface/Internal/SharedImmediateTextures/SharedImmediateTexture.cs @@ -1,15 +1,17 @@ +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Dalamud.Storage.Assets; using Dalamud.Utility; -namespace Dalamud.Interface.Internal.SharableTextures; +namespace Dalamud.Interface.Internal.SharedImmediateTextures; /// /// Represents a texture that may have multiple reference holders (owners). /// -internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IThrottleBasisProvider +internal abstract class SharedImmediateTexture + : ISharedImmediateTexture, IRefCountable, TextureLoadThrottler.IThrottleBasisProvider { private const int SelfReferenceDurationTicks = 2000; private const long SelfReferenceExpiryExpired = long.MaxValue; @@ -23,14 +25,14 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT private long selfReferenceExpiry; private IDalamudTextureWrap? availableOnAccessWrapForApi9; private CancellationTokenSource? cancellationTokenSource; - private DisposeSuppressingTextureWrap? disposeSuppressingWrap; + private NotOwnedTextureWrap? nonOwningWrap; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// If set to true, this class will hold a reference to self. /// Otherwise, it is expected that the caller to hold the reference. - protected SharableTexture(bool holdSelfReference) + protected SharedImmediateTexture(bool holdSelfReference) { this.InstanceIdForDebug = Interlocked.Increment(ref instanceCounter); @@ -79,7 +81,7 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT public abstract string SourcePathForDebug { get; } /// - /// Gets a value indicating whether this instance of supports revival. + /// Gets a value indicating whether this instance of supports revival. /// public bool HasRevivalPossibility => this.RevivalPossibility?.TryGetTarget(out _) is true; @@ -99,7 +101,7 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT /// /// Gets a value indicating whether the content has been queried, - /// i.e. or is called. + /// i.e. or is called. /// public bool ContentQueried { get; private set; } @@ -125,7 +127,8 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT public int AddRef() => this.TryAddRef(out var newRefCount) switch { IRefCountable.RefCountResult.StillAlive => newRefCount, - IRefCountable.RefCountResult.AlreadyDisposed => throw new ObjectDisposedException(nameof(SharableTexture)), + IRefCountable.RefCountResult.AlreadyDisposed => throw new ObjectDisposedException( + nameof(SharedImmediateTexture)), IRefCountable.RefCountResult.FinalRelease => throw new InvalidOperationException(), _ => throw new InvalidOperationException(), }; @@ -164,7 +167,7 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT this.cancellationTokenSource?.Cancel(); this.cancellationTokenSource = null; - this.disposeSuppressingWrap = null; + this.nonOwningWrap = null; this.ReleaseResources(); this.resourceReleased = true; @@ -173,7 +176,7 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT } case IRefCountable.RefCountResult.AlreadyDisposed: - throw new ObjectDisposedException(nameof(SharableTexture)); + throw new ObjectDisposedException(nameof(SharedImmediateTexture)); default: throw new InvalidOperationException(); @@ -206,48 +209,27 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT } } - /// - /// Gets the texture if immediately available. The texture is guarnateed to be available for the rest of the frame. - /// Invocation from non-main thread will exhibit an undefined behavior. - /// - /// The texture if available; null if not. - public IDalamudTextureWrap? GetImmediate() + /// + public IDalamudTextureWrap GetWrap() => this.GetWrap(Service.Get().Empty4X4); + + /// + [return: NotNullIfNotNull(nameof(defaultWrap))] + public IDalamudTextureWrap? GetWrap(IDalamudTextureWrap? defaultWrap) { - if (this.TryAddRef(out _) != IRefCountable.RefCountResult.StillAlive) - { - this.ContentQueried = true; - return null; - } - - this.ContentQueried = true; - this.LatestRequestedTick = Environment.TickCount64; - var nexp = Environment.TickCount64 + SelfReferenceDurationTicks; - while (true) - { - var exp = this.selfReferenceExpiry; - if (exp != Interlocked.CompareExchange(ref this.selfReferenceExpiry, nexp, exp)) - continue; - - // If below condition is met, the additional reference from above is for the self-reference. - if (exp == SelfReferenceExpiryExpired) - _ = this.AddRef(); - - // Release the reference for rendering, after rendering ImGui. - Service.Get().EnqueueDeferredDispose(this); - - return this.UnderlyingWrap?.IsCompletedSuccessfully is true - ? this.disposeSuppressingWrap ??= new(this.UnderlyingWrap.Result) - : null; - } + if (!this.TryGetWrap(out var texture, out _)) + texture = null; + return texture ?? defaultWrap; } - /// - /// Creates a new reference to this texture. The texture is guaranteed to be available until - /// is called. - /// - /// The cancellation token. - /// The task containing the texture. - public async Task CreateNewReference(CancellationToken cancellationToken) + /// + public bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception) + { + ThreadSafety.AssertMainThread(); + return this.TryGetWrapCore(out texture, out exception); + } + + /// + public async Task RentAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -312,10 +294,8 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT if (this.RevivalPossibility?.TryGetTarget(out this.availableOnAccessWrapForApi9) is true) return this.availableOnAccessWrapForApi9; - var newRefTask = this.CreateNewReference(default); - // Cancellation is not expected for this API - // ReSharper disable once MethodSupportsCancellation - newRefTask.Wait(); + var newRefTask = this.RentAsync(this.LoadCancellationToken); + newRefTask.Wait(this.LoadCancellationToken); if (!newRefTask.IsCompletedSuccessfully) return null; newRefTask.Result.Dispose(); @@ -328,7 +308,7 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT } /// - /// Cleans up this instance of . + /// Cleans up this instance of . /// protected abstract void ReleaseResources(); @@ -378,14 +358,95 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT } } + /// , but without checking for thread. + private bool TryGetWrapCore( + [NotNullWhen(true)] out IDalamudTextureWrap? texture, + out Exception? exception) + { + if (this.TryAddRef(out _) != IRefCountable.RefCountResult.StillAlive) + { + this.ContentQueried = true; + texture = null; + exception = new ObjectDisposedException(this.GetType().Name); + return false; + } + + this.ContentQueried = true; + this.LatestRequestedTick = Environment.TickCount64; + + var nexp = Environment.TickCount64 + SelfReferenceDurationTicks; + while (true) + { + var exp = this.selfReferenceExpiry; + if (exp != Interlocked.CompareExchange(ref this.selfReferenceExpiry, nexp, exp)) + continue; + + // If below condition is met, the additional reference from above is for the self-reference. + if (exp == SelfReferenceExpiryExpired) + _ = this.AddRef(); + + // Release the reference for rendering, after rendering ImGui. + Service.Get().EnqueueDeferredDispose(this); + + var uw = this.UnderlyingWrap; + if (uw?.IsCompletedSuccessfully is true) + { + texture = this.nonOwningWrap ??= new(uw.Result, this); + exception = null; + return true; + } + + texture = null; + exception = uw?.Exception; + return false; + } + } + + private sealed class NotOwnedTextureWrap : IDalamudTextureWrap + { + private readonly IDalamudTextureWrap innerWrap; + private readonly IRefCountable owner; + + /// Initializes a new instance of the class. + /// The inner wrap. + /// The reference counting owner. + public NotOwnedTextureWrap(IDalamudTextureWrap wrap, IRefCountable owner) + { + this.innerWrap = wrap; + this.owner = owner; + } + + /// + public IntPtr ImGuiHandle => this.innerWrap.ImGuiHandle; + + /// + public int Width => this.innerWrap.Width; + + /// + public int Height => this.innerWrap.Height; + + /// + public IDalamudTextureWrap CreateWrapSharingLowLevelResource() + { + this.owner.AddRef(); + return new RefCountableWrappingTextureWrap(this.innerWrap, this.owner); + } + + /// + public void Dispose() + { + } + + /// + public override string ToString() => $"{nameof(NotOwnedTextureWrap)}({this.owner})"; + } + private sealed class RefCountableWrappingTextureWrap : IDalamudTextureWrap { private IDalamudTextureWrap? innerWrap; private IRefCountable? owner; - /// - /// Initializes a new instance of the class. - /// + /// Initializes a new instance of the class. /// The inner wrap. /// The reference counting owner. public RefCountableWrappingTextureWrap(IDalamudTextureWrap wrap, IRefCountable owner) @@ -408,6 +469,18 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT private IDalamudTextureWrap InnerWrapNonDisposed => this.innerWrap ?? throw new ObjectDisposedException(nameof(RefCountableWrappingTextureWrap)); + /// + public IDalamudTextureWrap CreateWrapSharingLowLevelResource() + { + var ownerCopy = this.owner; + var wrapCopy = this.innerWrap; + if (ownerCopy is null || wrapCopy is null) + throw new ObjectDisposedException(nameof(RefCountableWrappingTextureWrap)); + + ownerCopy.AddRef(); + return new RefCountableWrappingTextureWrap(wrapCopy, ownerCopy); + } + /// public void Dispose() { @@ -417,6 +490,8 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT return; if (ownerCopy != Interlocked.CompareExchange(ref this.owner, null, ownerCopy)) continue; + + // Note: do not dispose this; life of the wrap is managed by the owner. this.innerWrap = null; ownerCopy.Release(); GC.SuppressFinalize(this); @@ -427,20 +502,48 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT public override string ToString() => $"{nameof(RefCountableWrappingTextureWrap)}({this.owner})"; } + [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] private sealed class AvailableOnAccessTextureWrap : IDalamudTextureWrap { - private readonly SharableTexture inner; + private readonly SharedImmediateTexture inner; - public AvailableOnAccessTextureWrap(SharableTexture inner) => this.inner = inner; + public AvailableOnAccessTextureWrap(SharedImmediateTexture inner) => this.inner = inner; /// - public IntPtr ImGuiHandle => this.GetActualTexture().ImGuiHandle; + public IntPtr ImGuiHandle => this.WaitGet().ImGuiHandle; /// - public int Width => this.GetActualTexture().Width; + public int Width => this.WaitGet().Width; /// - public int Height => this.GetActualTexture().Height; + public int Height => this.WaitGet().Height; + + /// + public IDalamudTextureWrap CreateWrapSharingLowLevelResource() + { + this.inner.AddRef(); + try + { + if (!this.inner.TryGetWrapCore(out var wrap, out _)) + { + this.inner.UnderlyingWrap?.Wait(); + + if (!this.inner.TryGetWrapCore(out wrap, out _)) + { + // Calling dispose on Empty4x4 is a no-op, so we can just return that. + this.inner.Release(); + return Service.Get().Empty4X4; + } + } + + return new RefCountableWrappingTextureWrap(wrap, this.inner); + } + catch + { + this.inner.Release(); + throw; + } + } /// public void Dispose() @@ -451,13 +554,13 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT /// public override string ToString() => $"{nameof(AvailableOnAccessTextureWrap)}({this.inner})"; - private IDalamudTextureWrap GetActualTexture() + private IDalamudTextureWrap WaitGet() { - if (this.inner.GetImmediate() is { } t) + if (this.inner.TryGetWrapCore(out var t, out _)) return t; this.inner.UnderlyingWrap?.Wait(); - return this.inner.disposeSuppressingWrap ?? Service.Get().Empty4X4; + return this.inner.nonOwningWrap ?? Service.Get().Empty4X4; } } } diff --git a/Dalamud/Interface/Internal/SharableTextures/TextureLoadThrottler.cs b/Dalamud/Interface/Internal/TextureLoadThrottler.cs similarity index 99% rename from Dalamud/Interface/Internal/SharableTextures/TextureLoadThrottler.cs rename to Dalamud/Interface/Internal/TextureLoadThrottler.cs index 47e3ab90f..cb8274f0a 100644 --- a/Dalamud/Interface/Internal/SharableTextures/TextureLoadThrottler.cs +++ b/Dalamud/Interface/Internal/TextureLoadThrottler.cs @@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; -namespace Dalamud.Interface.Internal.SharableTextures; +namespace Dalamud.Interface.Internal; /// /// Service for managing texture loads. diff --git a/Dalamud/Interface/Internal/TextureManager.cs b/Dalamud/Interface/Internal/TextureManager.cs index a4edb4449..a906a214b 100644 --- a/Dalamud/Interface/Internal/TextureManager.cs +++ b/Dalamud/Interface/Internal/TextureManager.cs @@ -9,7 +9,7 @@ using BitFaster.Caching.Lru; using Dalamud.Data; using Dalamud.Game; -using Dalamud.Interface.Internal.SharableTextures; +using Dalamud.Interface.Internal.SharedImmediateTextures; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; @@ -66,9 +66,9 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid private readonly TextureLoadThrottler textureLoadThrottler = Service.Get(); private readonly ConcurrentLru lookupToPath = new(PathLookupLruCount); - private readonly ConcurrentDictionary gamePathTextures = new(); - private readonly ConcurrentDictionary fileSystemTextures = new(); - private readonly HashSet invalidatedTextures = new(); + private readonly ConcurrentDictionary gamePathTextures = new(); + private readonly ConcurrentDictionary fileSystemTextures = new(); + private readonly HashSet invalidatedTextures = new(); private bool disposing; @@ -84,12 +84,12 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid /// /// Gets all the loaded textures from the game resources. Debug use only. /// - public ICollection GamePathTextures => this.gamePathTextures.Values; + public ICollection GamePathTextures => this.gamePathTextures.Values; /// /// Gets all the loaded textures from the game resources. Debug use only. /// - public ICollection FileSystemTextures => this.fileSystemTextures.Values; + public ICollection FileSystemTextures => this.fileSystemTextures.Values; /// /// Gets all the loaded textures that are invalidated from . Debug use only. @@ -99,7 +99,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid "ReSharper", "InconsistentlySynchronizedField", Justification = "Debug use only; users are expected to lock around this")] - public ICollection InvalidatedTextures => this.invalidatedTextures; + public ICollection InvalidatedTextures => this.invalidatedTextures; /// public void Dispose() @@ -118,14 +118,12 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid this.fileSystemTextures.Clear(); } +#region API9 compat #pragma warning disable CS0618 // Type or member is obsolete /// [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete("See interface definition.")] - public string? GetIconPath( - uint iconId, - ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes, - ClientLanguage? language = null) + string? ITextureProvider.GetIconPath(uint iconId, ITextureProvider.IconFlags flags, ClientLanguage? language) => this.TryGetIconPath( new( iconId, @@ -139,109 +137,56 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid /// [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete("See interface definition.")] - public IDalamudTextureWrap? GetIcon( + IDalamudTextureWrap? ITextureProvider.GetIcon( uint iconId, - ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes, - ClientLanguage? language = null, - bool keepAlive = false) => - this.GetTextureFromGame( - this.lookupToPath.GetOrAdd( + ITextureProvider.IconFlags flags, + ClientLanguage? language, + bool keepAlive) => + this.GetFromGameIcon( new( iconId, (flags & ITextureProvider.IconFlags.ItemHighQuality) != 0, (flags & ITextureProvider.IconFlags.HiRes) != 0, - language), - this.GetIconPathByValue)); - - /// - [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] - [Obsolete("See interface definition.")] - public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false) => - this.gamePathTextures.GetOrAdd(path, GamePathSharableTexture.CreateImmediate) + language)) .GetAvailableOnAccessWrapForApi9(); /// [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete("See interface definition.")] - public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false) => - this.fileSystemTextures.GetOrAdd(file.FullName, FileSystemSharableTexture.CreateImmediate) - .GetAvailableOnAccessWrapForApi9(); + IDalamudTextureWrap? ITextureProvider.GetTextureFromGame(string path, bool keepAlive) => + this.GetFromGame(path).GetAvailableOnAccessWrapForApi9(); + + /// + [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] + [Obsolete("See interface definition.")] + IDalamudTextureWrap? ITextureProvider.GetTextureFromFile(FileInfo file, bool keepAlive) => + this.GetFromFile(file.FullName).GetAvailableOnAccessWrapForApi9(); #pragma warning restore CS0618 // Type or member is obsolete +#endregion + + /// + public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) => + this.GetFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue)); + + /// + public SharedImmediateTexture GetFromGame(string path) => + this.gamePathTextures.GetOrAdd(path, GamePathSharedImmediateTexture.CreateImmediate); + + /// + public SharedImmediateTexture GetFromFile(string path) => + this.fileSystemTextures.GetOrAdd(path, FileSystemSharedImmediateTexture.CreateImmediate); /// - public IDalamudTextureWrap ImmediateGetFromGameIcon(in GameIconLookup lookup) => - this.ImmediateGetFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue)); + ISharedImmediateTexture ITextureProvider.GetFromGameIcon(in GameIconLookup lookup) => this.GetFromGameIcon(lookup); /// - public IDalamudTextureWrap ImmediateGetFromGame(string path) => - this.ImmediateTryGetFromGame(path, out var texture, out _) - ? texture - : this.dalamudAssetManager.Empty4X4; + ISharedImmediateTexture ITextureProvider.GetFromGame(string path) => this.GetFromGame(path); /// - public IDalamudTextureWrap ImmediateGetFromFile(string file) => - this.ImmediateTryGetFromFile(file, out var texture, out _) - ? texture - : this.dalamudAssetManager.Empty4X4; + ISharedImmediateTexture ITextureProvider.GetFromFile(string path) => this.GetFromFile(path); /// - public bool ImmediateTryGetFromGameIcon( - in GameIconLookup lookup, - [NotNullWhen(true)] out IDalamudTextureWrap? texture, - out Exception? exception) => - this.ImmediateTryGetFromGame( - this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue), - out texture, - out exception); - - /// - public bool ImmediateTryGetFromGame( - string path, - [NotNullWhen(true)] out IDalamudTextureWrap? texture, - out Exception? exception) - { - ThreadSafety.AssertMainThread(); - var t = this.gamePathTextures.GetOrAdd(path, GamePathSharableTexture.CreateImmediate); - texture = t.GetImmediate(); - exception = t.UnderlyingWrap?.Exception; - return texture is not null; - } - - /// - public bool ImmediateTryGetFromFile( - string file, - [NotNullWhen(true)] out IDalamudTextureWrap? texture, - out Exception? exception) - { - ThreadSafety.AssertMainThread(); - var t = this.fileSystemTextures.GetOrAdd(file, FileSystemSharableTexture.CreateImmediate); - texture = t.GetImmediate(); - exception = t.UnderlyingWrap?.Exception; - return texture is not null; - } - - /// - public Task GetFromGameIconAsync( - in GameIconLookup lookup, - CancellationToken cancellationToken = default) => - this.GetFromGameAsync(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue), cancellationToken); - - /// - public Task GetFromGameAsync( - string path, - CancellationToken cancellationToken = default) => - this.gamePathTextures.GetOrAdd(path, GamePathSharableTexture.CreateAsync) - .CreateNewReference(cancellationToken); - - /// - public Task GetFromFileAsync( - string file, - CancellationToken cancellationToken = default) => - this.fileSystemTextures.GetOrAdd(file, FileSystemSharableTexture.CreateAsync) - .CreateNewReference(cancellationToken); - - /// - public Task GetFromImageAsync( + public Task CreateFromImageAsync( ReadOnlyMemory bytes, CancellationToken cancellationToken = default) => this.textureLoadThrottler.CreateLoader( @@ -250,7 +195,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid cancellationToken); /// - public Task GetFromImageAsync( + public Task CreateFromImageAsync( Stream stream, bool leaveOpen = false, CancellationToken cancellationToken = default) => @@ -260,7 +205,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid { await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new(); await stream.CopyToAsync(ms, ct).ConfigureAwait(false); - return await this.GetFromImageAsync(ms.GetBuffer(), ct); + return await this.CreateFromImageAsync(ms.GetBuffer(), ct); }, cancellationToken) .ContinueWith( @@ -274,7 +219,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid .Unwrap(); /// - public IDalamudTextureWrap GetFromRaw( + public IDalamudTextureWrap CreateFromRaw( RawImageSpecification specs, ReadOnlySpan bytes) { @@ -304,31 +249,34 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid }; using var texture = new Texture2D(scene.Device, texDesc, new DataRectangle(new(pData), specs.Pitch)); - resView = new(scene.Device, texture, new() - { - Format = texDesc.Format, - Dimension = ShaderResourceViewDimension.Texture2D, - Texture2D = { MipLevels = texDesc.MipLevels }, - }); + resView = new( + scene.Device, + texture, + new() + { + Format = texDesc.Format, + Dimension = ShaderResourceViewDimension.Texture2D, + Texture2D = { MipLevels = texDesc.MipLevels }, + }); } } - + // no sampler for now because the ImGui implementation we copied doesn't allow for changing it return new DalamudTextureWrap(new D3DTextureWrap(resView, specs.Width, specs.Height)); } /// - public Task GetFromRawAsync( + public Task CreateFromRawAsync( RawImageSpecification specs, ReadOnlyMemory bytes, CancellationToken cancellationToken = default) => this.textureLoadThrottler.CreateLoader( new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(), - _ => Task.FromResult(this.GetFromRaw(specs, bytes.Span)), + _ => Task.FromResult(this.CreateFromRaw(specs, bytes.Span)), cancellationToken); /// - public Task GetFromRawAsync( + public Task CreateFromRawAsync( RawImageSpecification specs, Stream stream, bool leaveOpen = false, @@ -339,7 +287,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid { await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new(); await stream.CopyToAsync(ms, ct).ConfigureAwait(false); - return await this.GetFromRawAsync(specs, ms.GetBuffer(), ct); + return await this.CreateFromRawAsync(specs, ms.GetBuffer(), ct); }, cancellationToken) .ContinueWith( @@ -353,17 +301,17 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid .Unwrap(); /// - public IDalamudTextureWrap GetTexture(TexFile file) => this.GetFromTexFileAsync(file).Result; + public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result; /// - public Task GetFromTexFileAsync( + public Task CreateFromTexFileAsync( TexFile file, CancellationToken cancellationToken = default) => this.textureLoadThrottler.CreateLoader( new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(), ct => Task.Run(() => this.NoThrottleGetFromTexFile(file), ct), cancellationToken); - + /// public bool SupportsDxgiFormat(int dxgiFormat) { @@ -489,7 +437,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid /// /// Gets a texture from the given image. Skips the load throttler; intended to be used from implementation of - /// s. + /// s. /// /// The data. /// The loaded texture. @@ -508,7 +456,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid /// /// Gets a texture from the given . Skips the load throttler; intended to be used from - /// implementation of s. + /// implementation of s. /// /// The data. /// The loaded texture. @@ -522,7 +470,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8); } - return this.GetFromRaw( + return this.CreateFromRaw( RawImageSpecification.From(buffer.Width, buffer.Height, dxgiFormat), buffer.RawData); } @@ -567,7 +515,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid return; - static bool TextureFinalReleasePredicate(SharableTexture v) => + static bool TextureFinalReleasePredicate(SharedImmediateTexture v) => v.ContentQueried && v.ReleaseSelfReference(false) == 0 && !v.HasRevivalPossibility; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs index 4a5bd89cf..1f4e5f29d 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs @@ -141,7 +141,7 @@ public class IconBrowserWidget : IDataWindowWidget var texm = Service.Get(); var cursor = ImGui.GetCursorScreenPos(); - if (texm.ImmediateTryGetFromGameIcon(new((uint)iconId), out var texture, out var exc)) + if (texm.GetFromGameIcon(new((uint)iconId)).TryGetWrap(out var texture, out var exc)) { ImGui.Image(texture.ImGuiHandle, this.iconSize); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs index f97fd040f..2a4222c5b 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Dalamud.Interface.Components; using Dalamud.Interface.Internal.Notifications; -using Dalamud.Interface.Internal.SharableTextures; +using Dalamud.Interface.Internal.SharedImmediateTextures; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Dalamud.Storage.Assets; @@ -42,6 +42,8 @@ internal class TexWidget : IDataWindowWidget /// public bool Ready { get; set; } + private ITextureProvider TextureManagerForApi9 => this.textureManager!; + /// public void Load() { @@ -145,7 +147,7 @@ internal class TexWidget : IDataWindowWidget } } - private unsafe void DrawLoadedTextures(ICollection textures) + private unsafe void DrawLoadedTextures(ICollection textures) { var im = Service.Get(); if (!ImGui.BeginTable("##table", 6)) @@ -226,7 +228,7 @@ internal class TexWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGuiComponents.IconButton(FontAwesomeIcon.Image); - if (ImGui.IsItemHovered() && texture.GetImmediate() is { } immediate) + if (ImGui.IsItemHovered() && texture.GetWrap(null) is { } immediate) { ImGui.BeginTooltip(); ImGui.Image(immediate.ImGuiHandle, immediate.Size); @@ -274,7 +276,7 @@ internal class TexWidget : IDataWindowWidget flags |= ITextureProvider.IconFlags.ItemHighQuality; if (this.hiRes) flags |= ITextureProvider.IconFlags.HiRes; - this.addedTextures.Add(new(Api9: this.textureManager.GetIcon(uint.Parse(this.iconId), flags))); + this.addedTextures.Add(new(Api9: this.TextureManagerForApi9.GetIcon(uint.Parse(this.iconId), flags))); } #pragma warning restore CS0618 // Type or member is obsolete @@ -283,8 +285,9 @@ internal class TexWidget : IDataWindowWidget { this.addedTextures.Add( new( - Api10: this.textureManager.GetFromGameIconAsync( - new(uint.Parse(this.iconId), this.hq, this.hiRes)))); + Api10: this.textureManager + .GetFromGameIcon(new(uint.Parse(this.iconId), this.hq, this.hiRes)) + .RentAsync())); } ImGui.SameLine(); @@ -300,12 +303,12 @@ internal class TexWidget : IDataWindowWidget #pragma warning disable CS0618 // Type or member is obsolete if (ImGui.Button("Load Tex (API9)")) - this.addedTextures.Add(new(Api9: this.textureManager.GetTextureFromGame(this.inputTexPath))); + this.addedTextures.Add(new(Api9: this.TextureManagerForApi9.GetTextureFromGame(this.inputTexPath))); #pragma warning restore CS0618 // Type or member is obsolete ImGui.SameLine(); if (ImGui.Button("Load Tex (Async)")) - this.addedTextures.Add(new(Api10: this.textureManager.GetFromGameAsync(this.inputTexPath))); + this.addedTextures.Add(new(Api10: this.textureManager.GetFromGame(this.inputTexPath).RentAsync())); ImGui.SameLine(); if (ImGui.Button("Load Tex (Immediate)")) @@ -320,12 +323,12 @@ internal class TexWidget : IDataWindowWidget #pragma warning disable CS0618 // Type or member is obsolete if (ImGui.Button("Load File (API9)")) - this.addedTextures.Add(new(Api9: this.textureManager.GetTextureFromFile(new(this.inputFilePath)))); + this.addedTextures.Add(new(Api9: this.TextureManagerForApi9.GetTextureFromFile(new(this.inputFilePath)))); #pragma warning restore CS0618 // Type or member is obsolete ImGui.SameLine(); if (ImGui.Button("Load File (Async)")) - this.addedTextures.Add(new(Api10: this.textureManager.GetFromFileAsync(this.inputFilePath))); + this.addedTextures.Add(new(Api10: this.textureManager.GetFromFile(this.inputFilePath).RentAsync())); ImGui.SameLine(); if (ImGui.Button("Load File (Immediate)")) @@ -430,11 +433,11 @@ internal class TexWidget : IDataWindowWidget if (this.Api10 is not null) return this.Api10.IsCompletedSuccessfully ? this.Api10.Result : null; if (this.Api10ImmGameIcon is not null) - return tp.ImmediateGetFromGameIcon(this.Api10ImmGameIcon.Value); + return tp.GetFromGameIcon(this.Api10ImmGameIcon.Value).GetWrap(); if (this.Api10ImmGamePath is not null) - return tp.ImmediateGetFromGame(this.Api10ImmGamePath); + return tp.GetFromGame(this.Api10ImmGamePath).GetWrap(); if (this.Api10ImmFile is not null) - return tp.ImmediateGetFromFile(this.Api10ImmFile); + return tp.GetFromFile(this.Api10ImmFile).GetWrap(); return null; } diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 6ae45c962..50450aaae 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -275,7 +275,7 @@ 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.GetFromImageAsync(bytes); + image = await textureManager.CreateFromImageAsync(bytes); } catch (Exception ex) { diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 29e76434b..0e30658ef 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1745,7 +1745,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (!this.testerIconPath.IsNullOrEmpty()) { - this.testerIcon = tm.GetFromFileAsync(this.testerIconPath); + this.testerIcon = tm.GetFromFile(this.testerIconPath).RentAsync(); } this.testerImages = new Task?[this.testerImagePaths.Length]; @@ -1756,7 +1756,7 @@ internal class PluginInstallerWindow : Window, IDisposable continue; _ = this.testerImages[i]?.ToContentDisposedTask(); - this.testerImages[i] = tm.GetFromFileAsync(this.testerImagePaths[i]); + this.testerImages[i] = tm.GetFromFile(this.testerImagePaths[i]).RentAsync(); } } catch (Exception ex) diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs index 0148e80dd..0e344f450 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs @@ -592,7 +592,7 @@ internal sealed partial class FontAtlasFactory } else if (texture.TexPixelsRGBA32 is not null) { - var wrap = this.factory.TextureManager.GetFromRaw( + var wrap = this.factory.TextureManager.CreateFromRaw( RawImageSpecification.Rgba32(width, height), new(texture.TexPixelsRGBA32, width * height * 4)); this.data.AddExistingTexture(wrap); @@ -632,7 +632,7 @@ internal sealed partial class FontAtlasFactory } } - var wrap = this.factory.TextureManager.GetFromRaw( + var wrap = this.factory.TextureManager.CreateFromRaw( new( width, height, diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs index ffddcc272..b3edcc9b2 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs @@ -376,7 +376,7 @@ internal sealed partial class FontAtlasFactory } return this.scopedFinalizer.Add( - this.TextureManager.GetFromRaw( + this.TextureManager.CreateFromRaw( new( texFile.Header.Width, texFile.Header.Height, diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 74a718507..3a718ef4c 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -387,8 +387,9 @@ public sealed class UiBuilder : IDisposable /// The full filepath to the image. /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] - [Obsolete($"Use {nameof(ITextureProvider.GetFromFileAsync)}.")] - public IDalamudTextureWrap LoadImage(string filePath) => this.TextureProvider.GetFromFileAsync(filePath).Result; + [Obsolete($"Use {nameof(ITextureProvider.GetFromFile)}.")] + public IDalamudTextureWrap LoadImage(string filePath) => + this.TextureProvider.GetFromFile(filePath).RentAsync().Result; /// /// Loads an image from a byte stream, such as a png downloaded into memory. @@ -396,8 +397,9 @@ public sealed class UiBuilder : IDisposable /// A byte array containing the raw image data. /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] - [Obsolete($"Use {nameof(ITextureProvider.GetFromImageAsync)}.")] - public IDalamudTextureWrap LoadImage(byte[] imageData) => this.TextureProvider.GetFromImageAsync(imageData).Result; + [Obsolete($"Use {nameof(ITextureProvider.CreateFromImageAsync)}.")] + public IDalamudTextureWrap LoadImage(byte[] imageData) => + this.TextureProvider.CreateFromImageAsync(imageData).Result; /// /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use . @@ -408,11 +410,11 @@ public sealed class UiBuilder : IDisposable /// The number of channels (bytes per pixel) of the image contained in . This should usually be 4. /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] - [Obsolete($"Use {nameof(ITextureProvider.GetFromRaw)} or {nameof(ITextureProvider.GetFromRawAsync)}.")] + [Obsolete($"Use {nameof(ITextureProvider.CreateFromRaw)} or {nameof(ITextureProvider.CreateFromRawAsync)}.")] public IDalamudTextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) => numChannels switch { - 4 => this.TextureProvider.GetFromRaw(RawImageSpecification.Rgba32(width, height), imageData), + 4 => this.TextureProvider.CreateFromRaw(RawImageSpecification.Rgba32(width, height), imageData), _ => throw new NotSupportedException(), }; @@ -430,8 +432,9 @@ public sealed class UiBuilder : IDisposable /// The full filepath to the image. /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] - [Obsolete($"Use {nameof(ITextureProvider.GetFromFileAsync)}.")] - public Task LoadImageAsync(string filePath) => this.TextureProvider.GetFromFileAsync(filePath); + [Obsolete($"Use {nameof(ITextureProvider.GetFromFile)}.")] + public Task LoadImageAsync(string filePath) => + this.TextureProvider.GetFromFile(filePath).RentAsync(); /// /// Asynchronously loads an image from a byte stream, such as a png downloaded into memory, when it's possible to do so. @@ -439,9 +442,9 @@ public sealed class UiBuilder : IDisposable /// A byte array containing the raw image data. /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] - [Obsolete($"Use {nameof(ITextureProvider.GetFromImageAsync)}.")] + [Obsolete($"Use {nameof(ITextureProvider.CreateFromImageAsync)}.")] public Task LoadImageAsync(byte[] imageData) => - this.TextureProvider.GetFromImageAsync(imageData); + this.TextureProvider.CreateFromImageAsync(imageData); /// /// Asynchronously loads an image from raw unformatted pixel data, with no type or header information, when it's possible to do so. To load formatted data, use . @@ -452,11 +455,11 @@ public sealed class UiBuilder : IDisposable /// The number of channels (bytes per pixel) of the image contained in . This should usually be 4. /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] - [Obsolete($"Use {nameof(ITextureProvider.GetFromRawAsync)}.")] + [Obsolete($"Use {nameof(ITextureProvider.CreateFromRawAsync)}.")] public Task LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) => numChannels switch { - 4 => this.TextureProvider.GetFromRawAsync(RawImageSpecification.Rgba32(width, height), imageData), + 4 => this.TextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(width, height), imageData), _ => Task.FromException(new NotSupportedException()), }; diff --git a/Dalamud/Interface/UldWrapper.cs b/Dalamud/Interface/UldWrapper.cs index 4b0c9cf4f..289db6faf 100644 --- a/Dalamud/Interface/UldWrapper.cs +++ b/Dalamud/Interface/UldWrapper.cs @@ -125,7 +125,7 @@ public class UldWrapper : IDisposable inputSlice.CopyTo(outputSlice); } - return this.textureManager.GetFromRaw(RawImageSpecification.Rgba32(part.W, part.H), imageData); + return this.textureManager.CreateFromRaw(RawImageSpecification.Rgba32(part.W, part.H), imageData); } private (uint Id, int Width, int Height, bool HD, byte[] RgbaData)? GetTexture(string texturePath) diff --git a/Dalamud/Plugin/Services/ITextureProvider.Api9.cs b/Dalamud/Plugin/Services/ITextureProvider.Api9.cs index db033778e..2a1a3a9a5 100644 --- a/Dalamud/Plugin/Services/ITextureProvider.Api9.cs +++ b/Dalamud/Plugin/Services/ITextureProvider.Api9.cs @@ -52,7 +52,7 @@ public partial interface ITextureProvider /// Null, if the icon does not exist in the specified configuration, or a texture wrap that can be used /// to render the icon. /// - [Obsolete($"Use {nameof(ImmediateGetFromGameIcon)} or {nameof(GetFromGameIconAsync)}.")] + [Obsolete($"Use {nameof(GetFromGameIcon)}.")] [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] public IDalamudTextureWrap? GetIcon(uint iconId, IconFlags flags = IconFlags.HiRes, ClientLanguage? language = null, bool keepAlive = false); @@ -80,7 +80,7 @@ public partial interface ITextureProvider /// The path to the texture in the game's VFS. /// Not used. This parameter is ignored. /// Null, if the icon does not exist, or a texture wrap that can be used to render the texture. - [Obsolete($"Use {nameof(ImmediateGetFromGame)} or {nameof(GetFromGameAsync)}.")] + [Obsolete($"Use {nameof(GetFromGame)}.")] [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false); @@ -93,7 +93,7 @@ public partial interface ITextureProvider /// The FileInfo describing the image or texture file. /// Not used. This parameter is ignored. /// Null, if the file does not exist, or a texture wrap that can be used to render the texture. - [Obsolete($"Use {nameof(ImmediateGetFromFile)} or {nameof(GetFromFileAsync)}.")] + [Obsolete($"Use {nameof(GetFromFile)}.")] [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false); } diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs index b6c7e2cbd..c63e7ae4f 100644 --- a/Dalamud/Plugin/Services/ITextureProvider.cs +++ b/Dalamud/Plugin/Services/ITextureProvider.cs @@ -1,138 +1,52 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using System.Threading.Tasks; +using Dalamud.Interface; using Dalamud.Interface.Internal; using Lumina.Data.Files; namespace Dalamud.Plugin.Services; -/// -/// Service that grants you access to textures you may render via ImGui. -/// +/// Service that grants you access to textures you may render via ImGui. /// -/// Immediate functions
-/// Immediate functions do not throw, unless they are called outside the UI thread.
-/// Instances of returned from Immediate functions do not have to be disposed. -/// Calling on them is a no-op; it is safe to call -/// on them, as it will do nothing.
-/// Use and alike if you don't care about the load state and dimensions of the -/// texture to be loaded. These functions will return a valid texture that is empty (fully transparent).
-/// Use and alike if you do. These functions will return the load state, -/// loaded texture if available, and the load exception on failure.
-///
-/// All other functions
-/// Instances of or <> -/// returned from all other functions must be d 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 +/// .
+/// Use and alike to obtain a reference of +/// 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. +/// ///
public partial interface ITextureProvider { - /// Gets the corresponding game icon for use with the current frame. - /// The icon specifier. - /// An instance of that is guaranteed to be available for the current - /// frame being drawn. - /// will be ignored.
- /// If the file is unavailable, then the returned instance of will point to an - /// empty texture instead.
- /// Thrown when called outside the UI thread. - IDalamudTextureWrap ImmediateGetFromGameIcon(in GameIconLookup lookup); + /// Gets a shared texture corresponding to the given game resource icon specifier. + /// A game icon specifier. + /// The shared texture that you may use to obtain the loaded texture wrap and load states. + ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup); - /// Gets a texture from a file shipped as a part of the game resources for use with the current frame. - /// - /// The game-internal path to a .tex, .atex, or an image file such as .png. - /// An instance of that is guaranteed to be available for the current - /// frame being drawn. - /// will be ignored.
- /// If the file is unavailable, then the returned instance of will point to an - /// empty texture instead.
- /// Thrown when called outside the UI thread. - IDalamudTextureWrap ImmediateGetFromGame(string path); + /// Gets a shared texture corresponding to the given path to a game resource. + /// A path to a game resource. + /// The shared texture that you may use to obtain the loaded texture wrap and load states. + ISharedImmediateTexture GetFromGame(string path); - /// Gets a texture from a file on the filesystem for use with the current frame. - /// The filesystem path to a .tex, .atex, or an image file such as .png. - /// An instance of that is guaranteed to be available for the current - /// frame being drawn. - /// will be ignored.
- /// If the file is unavailable, then the returned instance of will point to an - /// empty texture instead.
- /// Thrown when called outside the UI thread. - IDalamudTextureWrap ImmediateGetFromFile(string file); - - /// Gets the corresponding game icon for use with the current frame. - /// The icon specifier. - /// An instance of that is guaranteed to be available for - /// the current frame being drawn, or null if texture is not loaded (yet). - /// The load exception, if any. - /// true if points to the loaded texture; false if the texture is - /// still being loaded, or the load has failed. - /// on the returned will be ignored. - /// Thrown when called outside the UI thread. - bool ImmediateTryGetFromGameIcon( - in GameIconLookup lookup, - [NotNullWhen(true)] out IDalamudTextureWrap? texture, - out Exception? exception); - - /// Gets a texture from a file shipped as a part of the game resources for use with the current frame. - /// - /// The game-internal path to a .tex, .atex, or an image file such as .png. - /// An instance of that is guaranteed to be available for - /// the current frame being drawn, or null if texture is not loaded (yet). - /// The load exception, if any. - /// true if points to the loaded texture; false if the texture is - /// still being loaded, or the load has failed. - /// on the returned will be ignored. - /// Thrown when called outside the UI thread. - bool ImmediateTryGetFromGame( - string path, - [NotNullWhen(true)] out IDalamudTextureWrap? texture, - out Exception? exception); - - /// Gets a texture from a file on the filesystem for use with the current frame. - /// The filesystem path to a .tex, .atex, or an image file such as .png. - /// An instance of that is guaranteed to be available for - /// the current frame being drawn, or null if texture is not loaded (yet). - /// The load exception, if any. - /// true if points to the loaded texture; false if the texture is - /// still being loaded, or the load has failed. - /// on the returned will be ignored. - /// Thrown when called outside the UI thread. - bool ImmediateTryGetFromFile( - string file, - [NotNullWhen(true)] out IDalamudTextureWrap? texture, - out Exception? exception); - - /// Gets the corresponding game icon for use with the current frame. - /// The icon specifier. - /// The cancellation token. - /// A containing the loaded texture on success. Dispose after use. - Task GetFromGameIconAsync( - in GameIconLookup lookup, - CancellationToken cancellationToken = default); - - /// Gets a texture from a file shipped as a part of the game resources. - /// The game-internal path to a .tex, .atex, or an image file such as .png. - /// The cancellation token. - /// A containing the loaded texture on success. Dispose after use. - Task GetFromGameAsync( - string path, - CancellationToken cancellationToken = default); - - /// Gets a texture from a file on the filesystem. - /// The filesystem path to a .tex, .atex, or an image file such as .png. - /// The cancellation token. - /// A containing the loaded texture on success. Dispose after use. - Task GetFromFileAsync( - string file, - CancellationToken cancellationToken = default); + /// Gets a shared texture corresponding to the given file on the filesystem. + /// A path to a file on the filesystem. + /// The shared texture that you may use to obtain the loaded texture wrap and load states. + ISharedImmediateTexture GetFromFile(string path); /// 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. /// The cancellation token. /// A containing the loaded texture on success. Dispose after use. - Task GetFromImageAsync( + Task CreateFromImageAsync( ReadOnlyMemory bytes, CancellationToken cancellationToken = default); @@ -144,7 +58,7 @@ public partial interface ITextureProvider /// A containing the loaded texture on success. Dispose after use. /// will be closed or not only according to ; /// is irrelevant in closing the stream. - Task GetFromImageAsync( + Task CreateFromImageAsync( Stream stream, bool leaveOpen = false, CancellationToken cancellationToken = default); @@ -153,7 +67,7 @@ public partial interface ITextureProvider /// The specifications for the raw bitmap. /// The bytes to load. /// The texture loaded from the supplied raw bitmap. Dispose after use. - IDalamudTextureWrap GetFromRaw( + IDalamudTextureWrap CreateFromRaw( RawImageSpecification specs, ReadOnlySpan bytes); @@ -162,7 +76,7 @@ public partial interface ITextureProvider /// The bytes to load. /// The cancellation token. /// A containing the loaded texture on success. Dispose after use. - Task GetFromRawAsync( + Task CreateFromRawAsync( RawImageSpecification specs, ReadOnlyMemory bytes, CancellationToken cancellationToken = default); @@ -175,7 +89,7 @@ public partial interface ITextureProvider /// A containing the loaded texture on success. Dispose after use. /// will be closed or not only according to ; /// is irrelevant in closing the stream. - Task GetFromRawAsync( + Task CreateFromRawAsync( RawImageSpecification specs, Stream stream, bool leaveOpen = false, @@ -196,22 +110,31 @@ public partial interface ITextureProvider /// The resolved path. /// true if the corresponding file exists and has been set. bool TryGetIconPath(in GameIconLookup lookup, [NotNullWhen(true)] out string? path); - + /// /// Get a texture handle for the specified Lumina . - /// Alias for fetching from . + /// Alias for fetching from . /// /// The texture to obtain a handle to. /// A texture wrap that can be used to render the texture. Dispose after use. - IDalamudTextureWrap GetTexture(TexFile file); - + /// Alias for . + IDalamudTextureWrap GetTexture(TexFile file) => this.CreateFromTexFile(file); + + /// + /// Get a texture handle for the specified Lumina . + /// Alias for fetching from . + /// + /// The texture to obtain a handle to. + /// A texture wrap that can be used to render the texture. Dispose after use. + IDalamudTextureWrap CreateFromTexFile(TexFile file); + /// /// Get a texture handle for the specified Lumina . /// /// The texture to obtain a handle to. /// The cancellation token. /// A texture wrap that can be used to render the texture. Dispose after use. - Task GetFromTexFileAsync( + Task CreateFromTexFileAsync( TexFile file, CancellationToken cancellationToken = default); diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs index 83f03e274..9db6d55a4 100644 --- a/Dalamud/Storage/Assets/DalamudAssetManager.cs +++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs @@ -309,10 +309,10 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA stream.ReadExactly(buf, 0, length); var image = purpose switch { - DalamudAssetPurpose.TextureFromPng => await tm.GetFromImageAsync(buf), + DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync(buf), DalamudAssetPurpose.TextureFromRaw => asset.GetAttribute() is { } raw - ? await tm.GetFromRawAsync(raw.Specification, buf) + ? await tm.CreateFromRawAsync(raw.Specification, buf) : throw new InvalidOperationException( "TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."), _ => null,