This commit is contained in:
Soreepeong 2024-02-28 19:16:14 +09:00
parent aa35052a15
commit f8492dc06b
18 changed files with 410 additions and 361 deletions

View file

@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Interface.Internal;
namespace Dalamud.Interface;
/// <summary>
/// Represents a lookup for a game icon.

View file

@ -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;
/// <summary>A texture with a backing instance of <see cref="IDalamudTextureWrap"/> that is shared across multiple
/// requesters.</summary>
/// <remarks>
/// <para>Calling <see cref="IDisposable.Dispose"/> on this interface is a no-op.</para>
/// <para><see cref="GetWrap()"/> and <see cref="TryGetWrap"/> may stop returning the intended texture at any point.
/// Use <see cref="RentAsync"/> to lock the texture for use in any thread for any duration.</para>
/// </remarks>
public interface ISharedImmediateTexture
{
/// <summary>Gets the texture for use with the current frame.</summary>
/// <returns>An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for the current
/// frame being drawn.</returns>
/// <remarks>
/// <para>Calling outside the main thread will fail.</para>
/// <para>This function does not throw.</para>
/// <para><see cref="IDisposable.Dispose"/> will be ignored.</para>
/// <para>If the texture is unavailable for any reason, then the returned instance of
/// <see cref="IDalamudTextureWrap"/> will point to an empty texture instead.</para>
/// </remarks>
IDalamudTextureWrap GetWrap();
/// <summary>Gets the texture for use with the current frame.</summary>
/// <param name="defaultWrap">The default wrap to return if the requested texture was not immediately available.
/// </param>
/// <returns>An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for the current
/// frame being drawn.</returns>
/// <remarks>
/// <para>Calling outside the main thread will fail.</para>
/// <para>This function does not throw.</para>
/// <para><see cref="IDisposable.Dispose"/> will be ignored.</para>
/// <para>If the texture is unavailable for any reason, then <paramref name="defaultWrap"/> will be returned.</para>
/// </remarks>
[return: NotNullIfNotNull(nameof(defaultWrap))]
IDalamudTextureWrap? GetWrap(IDalamudTextureWrap? defaultWrap);
/// <summary>Attempts to get the texture for use with the current frame.</summary>
/// <param name="texture">An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for
/// the current frame being drawn, or <c>null</c> if texture is not loaded (yet).</param>
/// <param name="exception">The load exception, if any.</param>
/// <returns><c>true</c> if <paramref name="texture"/> points to the loaded texture; <c>false</c> if the texture is
/// still being loaded, or the load has failed.</returns>
/// <remarks>
/// <para>Calling outside the main thread will fail.</para>
/// <para>This function does not throw.</para>
/// <para><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored.</para>
/// </remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception);
/// <summary>Creates a new instance of <see cref="IDalamudTextureWrap"/> holding a new reference to this texture.
/// The returned texture is guaranteed to be available until <see cref="IDisposable.Dispose"/> is called.</summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success.</returns>
/// <remarks>
/// <see cref="IDisposable.Dispose"/> must be called on the resulting instance of <see cref="IDalamudTextureWrap"/>
/// from the returned <see cref="Task{TResult}"/> after use. Consider using
/// <see cref="DisposeSafety.ToContentDisposedTask{T}"/> to dispose the result automatically according to the state
/// of the task.</remarks>
Task<IDalamudTextureWrap> RentAsync(CancellationToken cancellationToken = default);
}

View file

@ -4,22 +4,22 @@ using System.Threading.Tasks;
using Dalamud.Utility;
namespace Dalamud.Interface.Internal.SharableTextures;
namespace Dalamud.Interface.Internal.SharedImmediateTextures;
/// <summary>
/// Represents a sharable texture, based on a file on the system filesystem.
/// </summary>
internal sealed class FileSystemSharableTexture : SharableTexture
internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
{
private readonly string path;
/// <summary>
/// Initializes a new instance of the <see cref="FileSystemSharableTexture"/> class.
/// Initializes a new instance of the <see cref="FileSystemSharedImmediateTexture"/> class.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="holdSelfReference">If set to <c>true</c>, this class will hold a reference to self.
/// Otherwise, it is expected that the caller to hold the reference.</param>
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;
/// <summary>
/// Creates a new instance of <see cref="GamePathSharableTexture"/>.
/// Creates a new instance of <see cref="GamePathSharedImmediateTexture"/>.
/// The new instance will hold a reference to itself.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>The new instance.</returns>
public static SharableTexture CreateImmediate(string path) => new FileSystemSharableTexture(path, true);
public static SharedImmediateTexture CreateImmediate(string path) => new FileSystemSharedImmediateTexture(path, true);
/// <summary>
/// Creates a new instance of <see cref="GamePathSharableTexture"/>.
/// Creates a new instance of <see cref="GamePathSharedImmediateTexture"/>.
/// The caller is expected to manage ownership of the new instance.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>The new instance.</returns>
public static SharableTexture CreateAsync(string path) => new FileSystemSharableTexture(path, false);
public static SharedImmediateTexture CreateAsync(string path) => new FileSystemSharedImmediateTexture(path, false);
/// <inheritdoc/>
public override string ToString() =>
$"{nameof(FileSystemSharableTexture)}#{this.InstanceIdForDebug}({this.path})";
$"{nameof(FileSystemSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
/// <inheritdoc/>
protected override void ReleaseResources()

View file

@ -7,22 +7,22 @@ using Dalamud.Utility;
using Lumina.Data.Files;
namespace Dalamud.Interface.Internal.SharableTextures;
namespace Dalamud.Interface.Internal.SharedImmediateTextures;
/// <summary>
/// Represents a sharable texture, based on a file in game resources.
/// </summary>
internal sealed class GamePathSharableTexture : SharableTexture
internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
{
private readonly string path;
/// <summary>
/// Initializes a new instance of the <see cref="GamePathSharableTexture"/> class.
/// Initializes a new instance of the <see cref="GamePathSharedImmediateTexture"/> class.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="holdSelfReference">If set to <c>true</c>, this class will hold a reference to self.
/// Otherwise, it is expected that the caller to hold the reference.</param>
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;
/// <summary>
/// Creates a new instance of <see cref="GamePathSharableTexture"/>.
/// Creates a new instance of <see cref="GamePathSharedImmediateTexture"/>.
/// The new instance will hold a reference to itself.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>The new instance.</returns>
public static SharableTexture CreateImmediate(string path) => new GamePathSharableTexture(path, true);
public static SharedImmediateTexture CreateImmediate(string path) => new GamePathSharedImmediateTexture(path, true);
/// <summary>
/// Creates a new instance of <see cref="GamePathSharableTexture"/>.
/// Creates a new instance of <see cref="GamePathSharedImmediateTexture"/>.
/// The caller is expected to manage ownership of the new instance.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>The new instance.</returns>
public static SharableTexture CreateAsync(string path) => new GamePathSharableTexture(path, false);
public static SharedImmediateTexture CreateAsync(string path) => new GamePathSharedImmediateTexture(path, false);
/// <inheritdoc/>
public override string ToString() => $"{nameof(GamePathSharableTexture)}#{this.InstanceIdForDebug}({this.path})";
public override string ToString() => $"{nameof(GamePathSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
/// <inheritdoc/>
protected override void ReleaseResources()

View file

@ -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;
/// <summary>
/// Represents a texture that may have multiple reference holders (owners).
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="SharableTexture"/> class.
/// Initializes a new instance of the <see cref="SharedImmediateTexture"/> class.
/// </summary>
/// <param name="holdSelfReference">If set to <c>true</c>, this class will hold a reference to self.
/// Otherwise, it is expected that the caller to hold the reference.</param>
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; }
/// <summary>
/// Gets a value indicating whether this instance of <see cref="SharableTexture"/> supports revival.
/// Gets a value indicating whether this instance of <see cref="SharedImmediateTexture"/> supports revival.
/// </summary>
public bool HasRevivalPossibility => this.RevivalPossibility?.TryGetTarget(out _) is true;
@ -99,7 +101,7 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT
/// <summary>
/// Gets a value indicating whether the content has been queried,
/// i.e. <see cref="CreateNewReference"/> or <see cref="GetImmediate"/> is called.
/// i.e. <see cref="TryGetWrap"/> or <see cref="RentAsync"/> is called.
/// </summary>
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
}
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The texture if available; <c>null</c> if not.</returns>
public IDalamudTextureWrap? GetImmediate()
/// <inheritdoc/>
public IDalamudTextureWrap GetWrap() => this.GetWrap(Service<DalamudAssetManager>.Get().Empty4X4);
/// <inheritdoc/>
[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<InterfaceManager>.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;
}
/// <summary>
/// Creates a new reference to this texture. The texture is guaranteed to be available until
/// <see cref="IDisposable.Dispose"/> is called.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The task containing the texture.</returns>
public async Task<IDalamudTextureWrap> CreateNewReference(CancellationToken cancellationToken)
/// <inheritdoc/>
public bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception)
{
ThreadSafety.AssertMainThread();
return this.TryGetWrapCore(out texture, out exception);
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> 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
}
/// <summary>
/// Cleans up this instance of <see cref="SharableTexture"/>.
/// Cleans up this instance of <see cref="SharedImmediateTexture"/>.
/// </summary>
protected abstract void ReleaseResources();
@ -378,14 +358,95 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT
}
}
/// <summary><see cref="ISharedImmediateTexture.TryGetWrap"/>, but without checking for thread.</summary>
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<InterfaceManager>.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;
/// <summary>Initializes a new instance of the <see cref="NotOwnedTextureWrap"/> class.</summary>
/// <param name="wrap">The inner wrap.</param>
/// <param name="owner">The reference counting owner.</param>
public NotOwnedTextureWrap(IDalamudTextureWrap wrap, IRefCountable owner)
{
this.innerWrap = wrap;
this.owner = owner;
}
/// <inheritdoc/>
public IntPtr ImGuiHandle => this.innerWrap.ImGuiHandle;
/// <inheritdoc/>
public int Width => this.innerWrap.Width;
/// <inheritdoc/>
public int Height => this.innerWrap.Height;
/// <inheritdoc/>
public IDalamudTextureWrap CreateWrapSharingLowLevelResource()
{
this.owner.AddRef();
return new RefCountableWrappingTextureWrap(this.innerWrap, this.owner);
}
/// <inheritdoc/>
public void Dispose()
{
}
/// <inheritdoc/>
public override string ToString() => $"{nameof(NotOwnedTextureWrap)}({this.owner})";
}
private sealed class RefCountableWrappingTextureWrap : IDalamudTextureWrap
{
private IDalamudTextureWrap? innerWrap;
private IRefCountable? owner;
/// <summary>
/// Initializes a new instance of the <see cref="RefCountableWrappingTextureWrap"/> class.
/// </summary>
/// <summary>Initializes a new instance of the <see cref="RefCountableWrappingTextureWrap"/> class.</summary>
/// <param name="wrap">The inner wrap.</param>
/// <param name="owner">The reference counting owner.</param>
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));
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
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;
/// <inheritdoc/>
public IntPtr ImGuiHandle => this.GetActualTexture().ImGuiHandle;
public IntPtr ImGuiHandle => this.WaitGet().ImGuiHandle;
/// <inheritdoc/>
public int Width => this.GetActualTexture().Width;
public int Width => this.WaitGet().Width;
/// <inheritdoc/>
public int Height => this.GetActualTexture().Height;
public int Height => this.WaitGet().Height;
/// <inheritdoc/>
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<DalamudAssetManager>.Get().Empty4X4;
}
}
return new RefCountableWrappingTextureWrap(wrap, this.inner);
}
catch
{
this.inner.Release();
throw;
}
}
/// <inheritdoc/>
public void Dispose()
@ -451,13 +554,13 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT
/// <inheritdoc/>
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<DalamudAssetManager>.Get().Empty4X4;
return this.inner.nonOwningWrap ?? Service<DalamudAssetManager>.Get().Empty4X4;
}
}
}

View file

@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
namespace Dalamud.Interface.Internal.SharableTextures;
namespace Dalamud.Interface.Internal;
/// <summary>
/// Service for managing texture loads.

View file

@ -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<TextureLoadThrottler>.Get();
private readonly ConcurrentLru<GameIconLookup, string> lookupToPath = new(PathLookupLruCount);
private readonly ConcurrentDictionary<string, SharableTexture> gamePathTextures = new();
private readonly ConcurrentDictionary<string, SharableTexture> fileSystemTextures = new();
private readonly HashSet<SharableTexture> invalidatedTextures = new();
private readonly ConcurrentDictionary<string, SharedImmediateTexture> gamePathTextures = new();
private readonly ConcurrentDictionary<string, SharedImmediateTexture> fileSystemTextures = new();
private readonly HashSet<SharedImmediateTexture> invalidatedTextures = new();
private bool disposing;
@ -84,12 +84,12 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
/// <summary>
/// Gets all the loaded textures from the game resources. Debug use only.
/// </summary>
public ICollection<SharableTexture> GamePathTextures => this.gamePathTextures.Values;
public ICollection<SharedImmediateTexture> GamePathTextures => this.gamePathTextures.Values;
/// <summary>
/// Gets all the loaded textures from the game resources. Debug use only.
/// </summary>
public ICollection<SharableTexture> FileSystemTextures => this.fileSystemTextures.Values;
public ICollection<SharedImmediateTexture> FileSystemTextures => this.fileSystemTextures.Values;
/// <summary>
/// Gets all the loaded textures that are invalidated from <see cref="InvalidatePaths"/>. 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<SharableTexture> InvalidatedTextures => this.invalidatedTextures;
public ICollection<SharedImmediateTexture> InvalidatedTextures => this.invalidatedTextures;
/// <inheritdoc/>
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
/// <inheritdoc/>
[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
/// <inheritdoc/>
[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));
/// <inheritdoc/>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete("See interface definition.")]
public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false) =>
this.gamePathTextures.GetOrAdd(path, GamePathSharableTexture.CreateImmediate)
language))
.GetAvailableOnAccessWrapForApi9();
/// <inheritdoc/>
[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();
/// <inheritdoc/>
[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
/// <inheritdoc cref="ITextureProvider.GetFromGameIcon"/>
public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) =>
this.GetFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue));
/// <inheritdoc cref="ITextureProvider.GetFromGame"/>
public SharedImmediateTexture GetFromGame(string path) =>
this.gamePathTextures.GetOrAdd(path, GamePathSharedImmediateTexture.CreateImmediate);
/// <inheritdoc cref="ITextureProvider.GetFromFile"/>
public SharedImmediateTexture GetFromFile(string path) =>
this.fileSystemTextures.GetOrAdd(path, FileSystemSharedImmediateTexture.CreateImmediate);
/// <inheritdoc/>
public IDalamudTextureWrap ImmediateGetFromGameIcon(in GameIconLookup lookup) =>
this.ImmediateGetFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue));
ISharedImmediateTexture ITextureProvider.GetFromGameIcon(in GameIconLookup lookup) => this.GetFromGameIcon(lookup);
/// <inheritdoc/>
public IDalamudTextureWrap ImmediateGetFromGame(string path) =>
this.ImmediateTryGetFromGame(path, out var texture, out _)
? texture
: this.dalamudAssetManager.Empty4X4;
ISharedImmediateTexture ITextureProvider.GetFromGame(string path) => this.GetFromGame(path);
/// <inheritdoc/>
public IDalamudTextureWrap ImmediateGetFromFile(string file) =>
this.ImmediateTryGetFromFile(file, out var texture, out _)
? texture
: this.dalamudAssetManager.Empty4X4;
ISharedImmediateTexture ITextureProvider.GetFromFile(string path) => this.GetFromFile(path);
/// <inheritdoc/>
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);
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromGameIconAsync(
in GameIconLookup lookup,
CancellationToken cancellationToken = default) =>
this.GetFromGameAsync(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue), cancellationToken);
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromGameAsync(
string path,
CancellationToken cancellationToken = default) =>
this.gamePathTextures.GetOrAdd(path, GamePathSharableTexture.CreateAsync)
.CreateNewReference(cancellationToken);
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromFileAsync(
string file,
CancellationToken cancellationToken = default) =>
this.fileSystemTextures.GetOrAdd(file, FileSystemSharableTexture.CreateAsync)
.CreateNewReference(cancellationToken);
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromImageAsync(
public Task<IDalamudTextureWrap> CreateFromImageAsync(
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.CreateLoader(
@ -250,7 +195,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
cancellationToken);
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromImageAsync(
public Task<IDalamudTextureWrap> 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();
/// <inheritdoc/>
public IDalamudTextureWrap GetFromRaw(
public IDalamudTextureWrap CreateFromRaw(
RawImageSpecification specs,
ReadOnlySpan<byte> 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));
}
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromRawAsync(
public Task<IDalamudTextureWrap> CreateFromRawAsync(
RawImageSpecification specs,
ReadOnlyMemory<byte> 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);
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromRawAsync(
public Task<IDalamudTextureWrap> 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();
/// <inheritdoc/>
public IDalamudTextureWrap GetTexture(TexFile file) => this.GetFromTexFileAsync(file).Result;
public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromTexFileAsync(
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
TexFile file,
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.CreateLoader(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
ct => Task.Run(() => this.NoThrottleGetFromTexFile(file), ct),
cancellationToken);
/// <inheritdoc/>
public bool SupportsDxgiFormat(int dxgiFormat)
{
@ -489,7 +437,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
/// <summary>
/// Gets a texture from the given image. Skips the load throttler; intended to be used from implementation of
/// <see cref="SharableTexture"/>s.
/// <see cref="SharedImmediateTexture"/>s.
/// </summary>
/// <param name="bytes">The data.</param>
/// <returns>The loaded texture.</returns>
@ -508,7 +456,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
/// <summary>
/// Gets a texture from the given <see cref="TexFile"/>. Skips the load throttler; intended to be used from
/// implementation of <see cref="SharableTexture"/>s.
/// implementation of <see cref="SharedImmediateTexture"/>s.
/// </summary>
/// <param name="file">The data.</param>
/// <returns>The loaded texture.</returns>
@ -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;
}

View file

@ -141,7 +141,7 @@ public class IconBrowserWidget : IDataWindowWidget
var texm = Service<TextureManager>.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);

View file

@ -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
/// <inheritdoc/>
public bool Ready { get; set; }
private ITextureProvider TextureManagerForApi9 => this.textureManager!;
/// <inheritdoc/>
public void Load()
{
@ -145,7 +147,7 @@ internal class TexWidget : IDataWindowWidget
}
}
private unsafe void DrawLoadedTextures(ICollection<SharableTexture> textures)
private unsafe void DrawLoadedTextures(ICollection<SharedImmediateTexture> textures)
{
var im = Service<InterfaceManager>.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;
}

View file

@ -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)
{

View file

@ -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<IDalamudTextureWrap>?[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)

View file

@ -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,

View file

@ -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,

View file

@ -387,8 +387,9 @@ public sealed class UiBuilder : IDisposable
/// <param name="filePath">The full filepath to the image.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[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;
/// <summary>
/// Loads an image from a byte stream, such as a png downloaded into memory.
@ -396,8 +397,9 @@ public sealed class UiBuilder : IDisposable
/// <param name="imageData">A byte array containing the raw image data.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[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;
/// <summary>
/// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use <see cref="LoadImage(byte[])"/>.
@ -408,11 +410,11 @@ public sealed class UiBuilder : IDisposable
/// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[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
/// <param name="filePath">The full filepath to the image.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromFileAsync)}.")]
public Task<IDalamudTextureWrap> LoadImageAsync(string filePath) => this.TextureProvider.GetFromFileAsync(filePath);
[Obsolete($"Use {nameof(ITextureProvider.GetFromFile)}.")]
public Task<IDalamudTextureWrap> LoadImageAsync(string filePath) =>
this.TextureProvider.GetFromFile(filePath).RentAsync();
/// <summary>
/// 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
/// <param name="imageData">A byte array containing the raw image data.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromImageAsync)}.")]
[Obsolete($"Use {nameof(ITextureProvider.CreateFromImageAsync)}.")]
public Task<IDalamudTextureWrap> LoadImageAsync(byte[] imageData) =>
this.TextureProvider.GetFromImageAsync(imageData);
this.TextureProvider.CreateFromImageAsync(imageData);
/// <summary>
/// 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 <see cref="LoadImage(byte[])"/>.
@ -452,11 +455,11 @@ public sealed class UiBuilder : IDisposable
/// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromRawAsync)}.")]
[Obsolete($"Use {nameof(ITextureProvider.CreateFromRawAsync)}.")]
public Task<IDalamudTextureWrap> 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<IDalamudTextureWrap>(new NotSupportedException()),
};

View file

@ -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)

View file

@ -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.
/// </returns>
[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
/// <param name="path">The path to the texture in the game's VFS.</param>
/// <param name="keepAlive">Not used. This parameter is ignored.</param>
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
[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
/// <param name="file">The FileInfo describing the image or texture file.</param>
/// <param name="keepAlive">Not used. This parameter is ignored.</param>
/// <returns>Null, if the file does not exist, or a texture wrap that can be used to render the texture.</returns>
[Obsolete($"Use {nameof(ImmediateGetFromFile)} or {nameof(GetFromFileAsync)}.")]
[Obsolete($"Use {nameof(GetFromFile)}.")]
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false);
}

View file

@ -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;
/// <summary>
/// Service that grants you access to textures you may render via ImGui.
/// </summary>
/// <summary>Service that grants you access to textures you may render via ImGui.</summary>
/// <remarks>
/// <b>Immediate functions</b><br />
/// Immediate functions do not throw, unless they are called outside the UI thread.<br />
/// Instances of <see cref="IDalamudTextureWrap"/> returned from Immediate functions do not have to be disposed.
/// Calling <see cref="IDisposable.Dispose"/> on them is a no-op; it is safe to call <see cref="IDisposable.Dispose"/>
/// on them, as it will do nothing.<br />
/// Use <see cref="ImmediateGetFromGameIcon"/> 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).<br />
/// Use <see cref="ImmediateTryGetFromGameIcon"/> and alike if you do. These functions will return the load state,
/// loaded texture if available, and the load exception on failure.<br />
/// <br />
/// <b>All other functions</b><br />
/// Instances of <see cref="IDalamudTextureWrap"/> or <see cref="Task"/>&lt;<see cref="IDalamudTextureWrap"/>&gt;
/// returned from all other functions must be <see cref="IDisposable.Dispose"/>d after use.
/// <para>
/// <b>Get</b> functions will return a shared texture, and the returnd instance of <see cref="ISharedImmediateTexture"/>
/// do not require calling <see cref="IDisposable.Dispose"/>, unless a new reference has been created by calling
/// <see cref="ISharedImmediateTexture.RentAsync"/>.<br />
/// Use <see cref="ISharedImmediateTexture.TryGetWrap"/> and alike to obtain a reference of
/// <see cref="IDalamudTextureWrap"/> that will stay valid for the rest of the frame.
/// </para>
/// <para>
/// <b>Create</b> functions will return a new texture, and the returned instance of <see cref="IDalamudTextureWrap"/>
/// must be disposed after use.
/// </para>
/// </remarks>
public partial interface ITextureProvider
{
/// <summary>Gets the corresponding game icon for use with the current frame.</summary>
/// <param name="lookup">The icon specifier.</param>
/// <returns>An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for the current
/// frame being drawn.</returns>
/// <remarks><see cref="IDisposable.Dispose"/> will be ignored.<br />
/// If the file is unavailable, then the returned instance of <see cref="IDalamudTextureWrap"/> will point to an
/// empty texture instead.</remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
IDalamudTextureWrap ImmediateGetFromGameIcon(in GameIconLookup lookup);
/// <summary>Gets a shared texture corresponding to the given game resource icon specifier.</summary>
/// <param name="lookup">A game icon specifier.</param>
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup);
/// <summary>Gets a texture from a file shipped as a part of the game resources for use with the current frame.
/// </summary>
/// <param name="path">The game-internal path to a .tex, .atex, or an image file such as .png.</param>
/// <returns>An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for the current
/// frame being drawn.</returns>
/// <remarks><see cref="IDisposable.Dispose"/> will be ignored.<br />
/// If the file is unavailable, then the returned instance of <see cref="IDalamudTextureWrap"/> will point to an
/// empty texture instead.</remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
IDalamudTextureWrap ImmediateGetFromGame(string path);
/// <summary>Gets a shared texture corresponding to the given path to a game resource.</summary>
/// <param name="path">A path to a game resource.</param>
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
ISharedImmediateTexture GetFromGame(string path);
/// <summary>Gets a texture from a file on the filesystem for use with the current frame.</summary>
/// <param name="file">The filesystem path to a .tex, .atex, or an image file such as .png.</param>
/// <returns>An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for the current
/// frame being drawn.</returns>
/// <remarks><see cref="IDisposable.Dispose"/> will be ignored.<br />
/// If the file is unavailable, then the returned instance of <see cref="IDalamudTextureWrap"/> will point to an
/// empty texture instead.</remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
IDalamudTextureWrap ImmediateGetFromFile(string file);
/// <summary>Gets the corresponding game icon for use with the current frame.</summary>
/// <param name="lookup">The icon specifier.</param>
/// <param name="texture">An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for
/// the current frame being drawn, or <c>null</c> if texture is not loaded (yet).</param>
/// <param name="exception">The load exception, if any.</param>
/// <returns><c>true</c> if <paramref name="texture"/> points to the loaded texture; <c>false</c> if the texture is
/// still being loaded, or the load has failed.</returns>
/// <remarks><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored.</remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
bool ImmediateTryGetFromGameIcon(
in GameIconLookup lookup,
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
out Exception? exception);
/// <summary>Gets a texture from a file shipped as a part of the game resources for use with the current frame.
/// </summary>
/// <param name="path">The game-internal path to a .tex, .atex, or an image file such as .png.</param>
/// <param name="texture">An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for
/// the current frame being drawn, or <c>null</c> if texture is not loaded (yet).</param>
/// <param name="exception">The load exception, if any.</param>
/// <returns><c>true</c> if <paramref name="texture"/> points to the loaded texture; <c>false</c> if the texture is
/// still being loaded, or the load has failed.</returns>
/// <remarks><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored.</remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
bool ImmediateTryGetFromGame(
string path,
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
out Exception? exception);
/// <summary>Gets a texture from a file on the filesystem for use with the current frame.</summary>
/// <param name="file">The filesystem path to a .tex, .atex, or an image file such as .png.</param>
/// <param name="texture">An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for
/// the current frame being drawn, or <c>null</c> if texture is not loaded (yet).</param>
/// <param name="exception">The load exception, if any.</param>
/// <returns><c>true</c> if <paramref name="texture"/> points to the loaded texture; <c>false</c> if the texture is
/// still being loaded, or the load has failed.</returns>
/// <remarks><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored.</remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
bool ImmediateTryGetFromFile(
string file,
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
out Exception? exception);
/// <summary>Gets the corresponding game icon for use with the current frame.</summary>
/// <param name="lookup">The icon specifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success. Dispose after use.</returns>
Task<IDalamudTextureWrap> GetFromGameIconAsync(
in GameIconLookup lookup,
CancellationToken cancellationToken = default);
/// <summary>Gets a texture from a file shipped as a part of the game resources.</summary>
/// <param name="path">The game-internal path to a .tex, .atex, or an image file such as .png.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success. Dispose after use.</returns>
Task<IDalamudTextureWrap> GetFromGameAsync(
string path,
CancellationToken cancellationToken = default);
/// <summary>Gets a texture from a file on the filesystem.</summary>
/// <param name="file">The filesystem path to a .tex, .atex, or an image file such as .png.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success. Dispose after use.</returns>
Task<IDalamudTextureWrap> GetFromFileAsync(
string file,
CancellationToken cancellationToken = default);
/// <summary>Gets a shared texture corresponding to the given file on the filesystem.</summary>
/// <param name="path">A path to a file on the filesystem.</param>
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
ISharedImmediateTexture GetFromFile(string path);
/// <summary>Gets a texture from the given bytes, trying to interpret it as a .tex file or other well-known image
/// files, such as .png.</summary>
/// <param name="bytes">The bytes to load.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success. Dispose after use.</returns>
Task<IDalamudTextureWrap> GetFromImageAsync(
Task<IDalamudTextureWrap> CreateFromImageAsync(
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default);
@ -144,7 +58,7 @@ public partial interface ITextureProvider
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success. Dispose after use.</returns>
/// <remarks><paramref name="stream"/> will be closed or not only according to <paramref name="leaveOpen"/>;
/// <paramref name="cancellationToken"/> is irrelevant in closing the stream.</remarks>
Task<IDalamudTextureWrap> GetFromImageAsync(
Task<IDalamudTextureWrap> CreateFromImageAsync(
Stream stream,
bool leaveOpen = false,
CancellationToken cancellationToken = default);
@ -153,7 +67,7 @@ public partial interface ITextureProvider
/// <param name="specs">The specifications for the raw bitmap.</param>
/// <param name="bytes">The bytes to load.</param>
/// <returns>The texture loaded from the supplied raw bitmap. Dispose after use.</returns>
IDalamudTextureWrap GetFromRaw(
IDalamudTextureWrap CreateFromRaw(
RawImageSpecification specs,
ReadOnlySpan<byte> bytes);
@ -162,7 +76,7 @@ public partial interface ITextureProvider
/// <param name="bytes">The bytes to load.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success. Dispose after use.</returns>
Task<IDalamudTextureWrap> GetFromRawAsync(
Task<IDalamudTextureWrap> CreateFromRawAsync(
RawImageSpecification specs,
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default);
@ -175,7 +89,7 @@ public partial interface ITextureProvider
/// <returns>A <see cref="Task{TResult}"/> containing the loaded texture on success. Dispose after use.</returns>
/// <remarks><paramref name="stream"/> will be closed or not only according to <paramref name="leaveOpen"/>;
/// <paramref name="cancellationToken"/> is irrelevant in closing the stream.</remarks>
Task<IDalamudTextureWrap> GetFromRawAsync(
Task<IDalamudTextureWrap> CreateFromRawAsync(
RawImageSpecification specs,
Stream stream,
bool leaveOpen = false,
@ -196,22 +110,31 @@ public partial interface ITextureProvider
/// <param name="path">The resolved path.</param>
/// <returns><c>true</c> if the corresponding file exists and <paramref name="path"/> has been set.</returns>
bool TryGetIconPath(in GameIconLookup lookup, [NotNullWhen(true)] out string? path);
/// <summary>
/// Get a texture handle for the specified Lumina <see cref="TexFile"/>.
/// Alias for fetching <see cref="Task{TResult}.Result"/> from <see cref="GetFromTexFileAsync"/>.
/// Alias for fetching <see cref="Task{TResult}.Result"/> from <see cref="CreateFromTexFileAsync"/>.
/// </summary>
/// <param name="file">The texture to obtain a handle to.</param>
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
IDalamudTextureWrap GetTexture(TexFile file);
/// <remarks>Alias for <see cref="CreateFromTexFile"/>.</remarks>
IDalamudTextureWrap GetTexture(TexFile file) => this.CreateFromTexFile(file);
/// <summary>
/// Get a texture handle for the specified Lumina <see cref="TexFile"/>.
/// Alias for fetching <see cref="Task{TResult}.Result"/> from <see cref="CreateFromTexFileAsync"/>.
/// </summary>
/// <param name="file">The texture to obtain a handle to.</param>
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
IDalamudTextureWrap CreateFromTexFile(TexFile file);
/// <summary>
/// Get a texture handle for the specified Lumina <see cref="TexFile"/>.
/// </summary>
/// <param name="file">The texture to obtain a handle to.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
Task<IDalamudTextureWrap> GetFromTexFileAsync(
Task<IDalamudTextureWrap> CreateFromTexFileAsync(
TexFile file,
CancellationToken cancellationToken = default);

View file

@ -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<DalamudAssetRawTextureAttribute>() is { } raw
? await tm.GetFromRawAsync(raw.Specification, buf)
? await tm.CreateFromRawAsync(raw.Specification, buf)
: throw new InvalidOperationException(
"TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."),
_ => null,