mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-15 13:14:17 +01:00
More cleanup
This commit is contained in:
parent
0a658477c6
commit
2572f24e08
11 changed files with 635 additions and 253 deletions
|
|
@ -2,7 +2,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||
|
||||
|
|
@ -13,10 +12,8 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
|
|||
|
||||
/// <summary>Initializes a new instance of the <see cref="FileSystemSharedImmediateTexture"/> class.</summary>
|
||||
/// <param name="path">The path.</param>
|
||||
private FileSystemSharedImmediateTexture(string path) => this.path = path;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string SourcePathForDebug => this.path;
|
||||
private FileSystemSharedImmediateTexture(string path)
|
||||
: base(path) => this.path = path;
|
||||
|
||||
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
|
||||
/// <param name="path">The path.</param>
|
||||
|
|
@ -28,20 +25,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
|
|||
$"{nameof(FileSystemSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ReleaseResources()
|
||||
{
|
||||
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
|
||||
this.UnderlyingWrap = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ReviveResources() =>
|
||||
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
|
||||
this,
|
||||
this.CreateTextureAsync,
|
||||
this.LoadCancellationToken);
|
||||
|
||||
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
protected override async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var tm = await Service<TextureManager>.GetAsync();
|
||||
return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.Threading.Tasks;
|
|||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Data.Files;
|
||||
|
||||
|
|
@ -17,10 +16,8 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
|
|||
|
||||
/// <summary>Initializes a new instance of the <see cref="GamePathSharedImmediateTexture"/> class.</summary>
|
||||
/// <param name="path">The path.</param>
|
||||
private GamePathSharedImmediateTexture(string path) => this.path = path;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string SourcePathForDebug => this.path;
|
||||
private GamePathSharedImmediateTexture(string path)
|
||||
: base(path) => this.path = path;
|
||||
|
||||
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
|
||||
/// <param name="path">The path.</param>
|
||||
|
|
@ -32,20 +29,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
|
|||
$"{nameof(GamePathSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ReleaseResources()
|
||||
{
|
||||
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
|
||||
this.UnderlyingWrap = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ReviveResources() =>
|
||||
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
|
||||
this,
|
||||
this.CreateTextureAsync,
|
||||
this.LoadCancellationToken);
|
||||
|
||||
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
protected override async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var dm = await Service<DataManager>.GetAsync();
|
||||
var tm = await Service<TextureManager>.GetAsync();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||
|
||||
|
|
@ -19,14 +18,12 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
|
|||
/// <param name="assembly">The assembly containing manifest resources.</param>
|
||||
/// <param name="name">The case-sensitive name of the manifest resource being requested.</param>
|
||||
private ManifestResourceSharedImmediateTexture(Assembly assembly, string name)
|
||||
: base($"{assembly.GetName().FullName}:{name}")
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string SourcePathForDebug => $"{this.assembly.GetName().FullName}:{this.name}";
|
||||
|
||||
/// <summary>Creates a new placeholder instance of <see cref="ManifestResourceSharedImmediateTexture"/>.</summary>
|
||||
/// <param name="args">The arguments to pass to the constructor.</param>
|
||||
/// <returns>The new instance.</returns>
|
||||
|
|
@ -34,32 +31,15 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
|
|||
new ManifestResourceSharedImmediateTexture(args.Assembly, args.Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() =>
|
||||
$"{nameof(ManifestResourceSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.SourcePathForDebug})";
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ReleaseResources()
|
||||
{
|
||||
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
|
||||
this.UnderlyingWrap = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ReviveResources() =>
|
||||
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
|
||||
this,
|
||||
this.CreateTextureAsync,
|
||||
this.LoadCancellationToken);
|
||||
|
||||
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
protected override async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await using var stream = this.assembly.GetManifestResourceStream(this.name);
|
||||
if (stream is null)
|
||||
throw new FileNotFoundException("The resource file could not be found.");
|
||||
|
||||
var tm = await Service<TextureManager>.GetAsync();
|
||||
var ms = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0);
|
||||
var ms = new MemoryStream(stream.CanSeek ? checked((int)stream.Length) : 0);
|
||||
await stream.CopyToAsync(ms, cancellationToken);
|
||||
return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, (int)ms.Length), cancellationToken);
|
||||
return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, checked((int)ms.Length)), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
|||
|
||||
/// <summary>Represents a texture that may have multiple reference holders (owners).</summary>
|
||||
internal abstract class SharedImmediateTexture
|
||||
: ISharedImmediateTexture, IRefCountable, TextureLoadThrottler.IThrottleBasisProvider
|
||||
: ISharedImmediateTexture, IRefCountable, DynamicPriorityQueueLoader.IThrottleBasisProvider
|
||||
{
|
||||
private const int SelfReferenceDurationTicks = 2000;
|
||||
private const long SelfReferenceExpiryExpired = long.MaxValue;
|
||||
|
|
@ -28,9 +28,11 @@ internal abstract class SharedImmediateTexture
|
|||
private NotOwnedTextureWrap? nonOwningWrap;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="SharedImmediateTexture"/> class.</summary>
|
||||
/// <param name="sourcePathForDebug">Name of the underlying resource.</param>
|
||||
/// <remarks>The new instance is a placeholder instance.</remarks>
|
||||
protected SharedImmediateTexture()
|
||||
protected SharedImmediateTexture(string sourcePathForDebug)
|
||||
{
|
||||
this.SourcePathForDebug = sourcePathForDebug;
|
||||
this.InstanceIdForDebug = Interlocked.Increment(ref instanceCounter);
|
||||
this.refCount = 0;
|
||||
this.selfReferenceExpiry = SelfReferenceExpiryExpired;
|
||||
|
|
@ -53,7 +55,7 @@ internal abstract class SharedImmediateTexture
|
|||
public int RefCountForDebug => this.refCount;
|
||||
|
||||
/// <summary>Gets the source path. Debug use only.</summary>
|
||||
public abstract string SourcePathForDebug { get; }
|
||||
public string SourcePathForDebug { get; }
|
||||
|
||||
/// <summary>Gets a value indicating whether this instance of <see cref="SharedImmediateTexture"/> supports revival.
|
||||
/// </summary>
|
||||
|
|
@ -76,7 +78,7 @@ internal abstract class SharedImmediateTexture
|
|||
public bool ContentQueried { get; private set; }
|
||||
|
||||
/// <summary>Gets a cancellation token for cancelling load.
|
||||
/// Intended to be called from implementors' constructors and <see cref="ReviveResources"/>.</summary>
|
||||
/// Intended to be called from implementors' constructors and <see cref="LoadUnderlyingWrap"/>.</summary>
|
||||
protected CancellationToken LoadCancellationToken => this.cancellationTokenSource?.Token ?? default;
|
||||
|
||||
/// <summary>Gets or sets a weak reference to an object that demands this objects to be alive.</summary>
|
||||
|
|
@ -134,7 +136,7 @@ internal abstract class SharedImmediateTexture
|
|||
this.cancellationTokenSource?.Cancel();
|
||||
this.cancellationTokenSource = null;
|
||||
this.nonOwningWrap = null;
|
||||
this.ReleaseResources();
|
||||
this.ClearUnderlyingWrap();
|
||||
this.resourceReleased = true;
|
||||
|
||||
return newRefCount;
|
||||
|
|
@ -272,11 +274,27 @@ internal abstract class SharedImmediateTexture
|
|||
return this.availableOnAccessWrapForApi9;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{this.GetType().Name}#{this.InstanceIdForDebug}({this.SourcePathForDebug})";
|
||||
|
||||
/// <summary>Cleans up this instance of <see cref="SharedImmediateTexture"/>.</summary>
|
||||
protected abstract void ReleaseResources();
|
||||
protected void ClearUnderlyingWrap()
|
||||
{
|
||||
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
|
||||
this.UnderlyingWrap = null;
|
||||
}
|
||||
|
||||
/// <summary>Attempts to restore the reference to this texture.</summary>
|
||||
protected abstract void ReviveResources();
|
||||
protected void LoadUnderlyingWrap() =>
|
||||
this.UnderlyingWrap = Service<TextureManager>.Get().DynamicPriorityTextureLoader.LoadAsync(
|
||||
this,
|
||||
this.CreateTextureAsync,
|
||||
this.LoadCancellationToken);
|
||||
|
||||
/// <summary>Creates the texture.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The task resulting in a loaded texture.</returns>
|
||||
protected abstract Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken);
|
||||
|
||||
private IRefCountable.RefCountResult TryAddRef(out int newRefCount)
|
||||
{
|
||||
|
|
@ -301,7 +319,7 @@ internal abstract class SharedImmediateTexture
|
|||
this.cancellationTokenSource = new();
|
||||
try
|
||||
{
|
||||
this.ReviveResources();
|
||||
this.LoadUnderlyingWrap();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Buffers;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
|
||||
|
|
@ -18,13 +17,6 @@ internal sealed partial class TextureManager
|
|||
{
|
||||
private SimpleDrawerImpl? simpleDrawer;
|
||||
|
||||
[ServiceManager.CallWhenServicesReady("Need device")]
|
||||
private unsafe void ContinueConstructionFromExistingTextures(InterfaceManager.InterfaceManagerWithScene withScene)
|
||||
{
|
||||
this.simpleDrawer = new();
|
||||
this.simpleDrawer.Setup(this.Device.Get());
|
||||
}
|
||||
|
||||
/// <summary>A class for drawing simple stuff.</summary>
|
||||
[SuppressMessage(
|
||||
"StyleCop.CSharp.LayoutRules",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ internal sealed partial class TextureManager
|
|||
}
|
||||
|
||||
D3D11_FORMAT_SUPPORT supported;
|
||||
if (this.Device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
|
||||
if (this.device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
|
||||
return false;
|
||||
|
||||
const D3D11_FORMAT_SUPPORT required =
|
||||
|
|
@ -48,52 +48,48 @@ internal sealed partial class TextureManager
|
|||
IDalamudTextureWrap wrap,
|
||||
TextureModificationArgs args = default,
|
||||
bool leaveWrapOpen = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
ImmediateLoadFunction,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.DynamicPriorityTextureLoader.LoadAsync<IDalamudTextureWrap>(
|
||||
null,
|
||||
async _ =>
|
||||
{
|
||||
// leaveWrapOpen is taken care from calling LoadTextureAsync
|
||||
using var wrapAux = new WrapAux(wrap, true);
|
||||
using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrapAux, args);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
|
||||
tex,
|
||||
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
|
||||
using var srv = default(ComPtr<ID3D11ShaderResourceView>);
|
||||
this.device.Get()->CreateShaderResourceView(
|
||||
(ID3D11Resource*)tex.Get(),
|
||||
&srvDesc,
|
||||
srv.GetAddressOf())
|
||||
.ThrowOnError();
|
||||
|
||||
var desc = default(D3D11_TEXTURE2D_DESC);
|
||||
tex.Get()->GetDesc(&desc);
|
||||
return new UnknownTextureWrap(
|
||||
(IUnknown*)srv.Get(),
|
||||
(int)desc.Width,
|
||||
(int)desc.Height,
|
||||
true);
|
||||
}
|
||||
},
|
||||
cancellationToken,
|
||||
leaveWrapOpen ? null : wrap);
|
||||
|
||||
async Task<IDalamudTextureWrap> ImmediateLoadFunction(CancellationToken ct)
|
||||
{
|
||||
// leaveWrapOpen is taken care from calling LoadTextureAsync
|
||||
using var wrapAux = new WrapAux(wrap, true);
|
||||
using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrapAux, args);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
|
||||
tex,
|
||||
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
|
||||
using var srv = default(ComPtr<ID3D11ShaderResourceView>);
|
||||
this.Device.Get()->CreateShaderResourceView(
|
||||
(ID3D11Resource*)tex.Get(),
|
||||
&srvDesc,
|
||||
srv.GetAddressOf())
|
||||
.ThrowOnError();
|
||||
|
||||
var desc = default(D3D11_TEXTURE2D_DESC);
|
||||
tex.Get()->GetDesc(&desc);
|
||||
return new UnknownTextureWrap(
|
||||
(IUnknown*)srv.Get(),
|
||||
(int)desc.Width,
|
||||
(int)desc.Height,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
|
||||
public Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
|
||||
ImGuiViewportTextureArgs args,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// This constructor may throw; keep the function "async", to wrap the exception as a Task.
|
||||
args.ThrowOnInvalidValues();
|
||||
var t = new ViewportTextureWrap(args, cancellationToken);
|
||||
t.QueueUpdate();
|
||||
return await t.FirstUpdateTask;
|
||||
return t.FirstUpdateTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -210,7 +206,7 @@ internal sealed partial class TextureManager
|
|||
CPUAccessFlags = 0u,
|
||||
MiscFlags = 0u,
|
||||
};
|
||||
this.Device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
|
||||
this.device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
|
||||
}
|
||||
|
||||
await this.interfaceManager.RunBeforeImGuiRender(
|
||||
|
|
@ -222,7 +218,7 @@ internal sealed partial class TextureManager
|
|||
var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
|
||||
tex2DCopyTemp,
|
||||
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
|
||||
this.Device.Get()->CreateRenderTargetView(
|
||||
this.device.Get()->CreateRenderTargetView(
|
||||
(ID3D11Resource*)tex2DCopyTemp.Get(),
|
||||
&rtvCopyTempDesc,
|
||||
rtvCopyTemp.GetAddressOf()).ThrowOnError();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
|
|
@ -22,22 +24,22 @@ using TerraFX.Interop.Windows;
|
|||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITextureProvider>]
|
||||
[ResolveVia<ITextureSubstitutionProvider>]
|
||||
[ResolveVia<ITextureReadbackProvider>]
|
||||
#pragma warning restore SA1015
|
||||
internal sealed partial class TextureManager
|
||||
: IServiceType, IDisposable, ITextureProvider, ITextureSubstitutionProvider, ITextureReadbackProvider
|
||||
: IServiceType,
|
||||
IDisposable,
|
||||
ITextureProvider,
|
||||
ITextureSubstitutionProvider,
|
||||
ITextureReadbackProvider
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(TextureManager));
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Dalamud dalamud = Service<Dalamud>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration dalamudConfiguration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
|
|
@ -47,54 +49,56 @@ internal sealed partial class TextureManager
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly TextureLoadThrottler textureLoadThrottler = Service<TextureLoadThrottler>.Get();
|
||||
|
||||
private DynamicPriorityQueueLoader? dynamicPriorityTextureLoader;
|
||||
private SharedTextureManager? sharedTextureManager;
|
||||
private WicManager? wicManager;
|
||||
private bool disposing;
|
||||
private ComPtr<ID3D11Device> device;
|
||||
|
||||
[SuppressMessage(
|
||||
"StyleCop.CSharp.LayoutRules",
|
||||
"SA1519:Braces should not be omitted from multi-line child statement",
|
||||
Justification = "Multiple fixed blocks")]
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private TextureManager()
|
||||
private unsafe TextureManager(InterfaceManager.InterfaceManagerWithScene withScene)
|
||||
{
|
||||
this.sharedTextureManager = new(this);
|
||||
this.wicManager = new(this);
|
||||
using var failsafe = new DisposeSafety.ScopedFinalizer();
|
||||
failsafe.Add(this.device = new((ID3D11Device*)withScene.Manager.Device!.NativePointer));
|
||||
failsafe.Add(this.dynamicPriorityTextureLoader = new(Math.Max(1, Environment.ProcessorCount - 1)));
|
||||
failsafe.Add(this.sharedTextureManager = new(this));
|
||||
failsafe.Add(this.wicManager = new(this));
|
||||
failsafe.Add(this.simpleDrawer = new());
|
||||
this.simpleDrawer.Setup(this.device.Get());
|
||||
|
||||
failsafe.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>Gets the D3D11 Device used to create textures. Ownership is not transferred.</summary>
|
||||
public unsafe ComPtr<ID3D11Device> Device
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.interfaceManager.Scene is not { } scene)
|
||||
{
|
||||
_ = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
|
||||
scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
|
||||
}
|
||||
/// <summary>Finalizes an instance of the <see cref="TextureManager"/> class.</summary>
|
||||
~TextureManager() => this.ReleaseUnmanagedResources();
|
||||
|
||||
var device = default(ComPtr<ID3D11Device>);
|
||||
device.Attach((ID3D11Device*)scene.Device.NativePointer);
|
||||
return device;
|
||||
}
|
||||
/// <summary>Gets the dynamic-priority queue texture loader.</summary>
|
||||
public DynamicPriorityQueueLoader DynamicPriorityTextureLoader
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.dynamicPriorityTextureLoader ?? throw new ObjectDisposedException(nameof(TextureManager));
|
||||
}
|
||||
|
||||
/// <summary>Gets a simpler drawer.</summary>
|
||||
public SimpleDrawerImpl SimpleDrawer =>
|
||||
this.simpleDrawer ?? throw new ObjectDisposedException(nameof(TextureManager));
|
||||
public SimpleDrawerImpl SimpleDrawer
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.simpleDrawer ?? throw new ObjectDisposedException(nameof(TextureManager));
|
||||
}
|
||||
|
||||
/// <summary>Gets the shared texture manager.</summary>
|
||||
public SharedTextureManager Shared =>
|
||||
this.sharedTextureManager ??
|
||||
throw new ObjectDisposedException(nameof(TextureManager));
|
||||
public SharedTextureManager Shared
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.sharedTextureManager ?? throw new ObjectDisposedException(nameof(TextureManager));
|
||||
}
|
||||
|
||||
/// <summary>Gets the WIC manager.</summary>
|
||||
public WicManager Wic =>
|
||||
this.wicManager ??
|
||||
throw new ObjectDisposedException(nameof(TextureManager));
|
||||
public WicManager Wic
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.wicManager ?? throw new ObjectDisposedException(nameof(TextureManager));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
|
@ -104,17 +108,28 @@ internal sealed partial class TextureManager
|
|||
|
||||
this.disposing = true;
|
||||
|
||||
Interlocked.Exchange(ref this.dynamicPriorityTextureLoader, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.simpleDrawer, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.sharedTextureManager, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.wicManager, null)?.Dispose();
|
||||
this.ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Puts a plugin on blame for a texture.</summary>
|
||||
/// <param name="textureWrap">The texture.</param>
|
||||
/// <param name="ownerPlugin">The plugin.</param>
|
||||
public void Blame(IDalamudTextureWrap textureWrap, LocalPlugin ownerPlugin)
|
||||
{
|
||||
// nop for now
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> CreateFromImageAsync(
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
this.DynamicPriorityTextureLoader.LoadAsync(
|
||||
null,
|
||||
ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
|
||||
cancellationToken);
|
||||
|
||||
|
|
@ -123,24 +138,16 @@ internal sealed partial class TextureManager
|
|||
Stream stream,
|
||||
bool leaveOpen = false,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
async ct =>
|
||||
{
|
||||
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
|
||||
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
|
||||
return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
|
||||
},
|
||||
cancellationToken)
|
||||
.ContinueWith(
|
||||
r =>
|
||||
{
|
||||
if (!leaveOpen)
|
||||
stream.Dispose();
|
||||
return r;
|
||||
},
|
||||
default(CancellationToken))
|
||||
.Unwrap();
|
||||
this.DynamicPriorityTextureLoader.LoadAsync(
|
||||
null,
|
||||
async ct =>
|
||||
{
|
||||
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
|
||||
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
|
||||
return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
|
||||
},
|
||||
cancellationToken,
|
||||
leaveOpen ? null : stream);
|
||||
|
||||
/// <inheritdoc/>
|
||||
// It probably doesn't make sense to throttle this, as it copies the passed bytes to GPU without any transformation.
|
||||
|
|
@ -153,8 +160,8 @@ internal sealed partial class TextureManager
|
|||
RawImageSpecification specs,
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
this.DynamicPriorityTextureLoader.LoadAsync(
|
||||
null,
|
||||
_ => Task.FromResult(this.NoThrottleCreateFromRaw(specs, bytes.Span)),
|
||||
cancellationToken);
|
||||
|
||||
|
|
@ -164,24 +171,16 @@ internal sealed partial class TextureManager
|
|||
Stream stream,
|
||||
bool leaveOpen = false,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
async ct =>
|
||||
{
|
||||
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
|
||||
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
|
||||
return this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length));
|
||||
},
|
||||
cancellationToken)
|
||||
.ContinueWith(
|
||||
r =>
|
||||
{
|
||||
if (!leaveOpen)
|
||||
stream.Dispose();
|
||||
return r;
|
||||
},
|
||||
default(CancellationToken))
|
||||
.Unwrap();
|
||||
this.DynamicPriorityTextureLoader.LoadAsync(
|
||||
null,
|
||||
async ct =>
|
||||
{
|
||||
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
|
||||
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
|
||||
return this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length));
|
||||
},
|
||||
cancellationToken,
|
||||
leaveOpen ? null : stream);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
|
||||
|
|
@ -190,9 +189,9 @@ internal sealed partial class TextureManager
|
|||
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
|
||||
TexFile file,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
|
||||
this.DynamicPriorityTextureLoader.LoadAsync(
|
||||
null,
|
||||
_ => Task.FromResult(this.NoThrottleCreateFromTexFile(file)),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -203,7 +202,7 @@ internal sealed partial class TextureManager
|
|||
public unsafe bool IsDxgiFormatSupported(DXGI_FORMAT dxgiFormat)
|
||||
{
|
||||
D3D11_FORMAT_SUPPORT supported;
|
||||
if (this.Device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
|
||||
if (this.device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
|
||||
return false;
|
||||
|
||||
const D3D11_FORMAT_SUPPORT required = D3D11_FORMAT_SUPPORT.D3D11_FORMAT_SUPPORT_TEXTURE2D;
|
||||
|
|
@ -215,8 +214,6 @@ internal sealed partial class TextureManager
|
|||
RawImageSpecification specs,
|
||||
ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var device = this.Device;
|
||||
|
||||
var texd = new D3D11_TEXTURE2D_DESC
|
||||
{
|
||||
Width = (uint)specs.Width,
|
||||
|
|
@ -234,7 +231,7 @@ internal sealed partial class TextureManager
|
|||
fixed (void* dataPtr = bytes)
|
||||
{
|
||||
var subrdata = new D3D11_SUBRESOURCE_DATA { pSysMem = dataPtr, SysMemPitch = (uint)specs.Pitch };
|
||||
device.Get()->CreateTexture2D(&texd, &subrdata, texture.GetAddressOf()).ThrowOnError();
|
||||
this.device.Get()->CreateTexture2D(&texd, &subrdata, texture.GetAddressOf()).ThrowOnError();
|
||||
}
|
||||
|
||||
var viewDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC
|
||||
|
|
@ -244,7 +241,7 @@ internal sealed partial class TextureManager
|
|||
Texture2D = new() { MipLevels = texd.MipLevels },
|
||||
};
|
||||
using var view = default(ComPtr<ID3D11ShaderResourceView>);
|
||||
device.Get()->CreateShaderResourceView((ID3D11Resource*)texture.Get(), &viewDesc, view.GetAddressOf())
|
||||
this.device.Get()->CreateShaderResourceView((ID3D11Resource*)texture.Get(), &viewDesc, view.GetAddressOf())
|
||||
.ThrowOnError();
|
||||
|
||||
return new UnknownTextureWrap((IUnknown*)view.Get(), specs.Width, specs.Height, true);
|
||||
|
|
@ -294,4 +291,6 @@ internal sealed partial class TextureManager
|
|||
// Note: FileInfo and FilePath are not used from TexFile; skip it.
|
||||
return this.NoThrottleCreateFromTexFile(tf);
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources() => this.device.Reset();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
using System.IO;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
/// <summary>Plugin-scoped version of <see cref="TextureManager"/>.</summary>
|
||||
internal sealed partial class TextureManagerPluginScoped
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
public IDalamudTextureWrap? GetIcon(
|
||||
uint iconId,
|
||||
ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes,
|
||||
ClientLanguage? language = null,
|
||||
bool keepAlive = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
public string? GetIconPath(
|
||||
uint iconId,
|
||||
ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes,
|
||||
ClientLanguage? language = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
public IDalamudTextureWrap? GetTextureFromGame(
|
||||
string path,
|
||||
bool keepAlive = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
public IDalamudTextureWrap? GetTextureFromFile(
|
||||
FileInfo file,
|
||||
bool keepAlive = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Internal.Types.Manifest;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Data.Files;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
/// <summary>Plugin-scoped version of <see cref="TextureManager"/>.</summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITextureProvider>]
|
||||
[ResolveVia<ITextureSubstitutionProvider>]
|
||||
[ResolveVia<ITextureReadbackProvider>]
|
||||
#pragma warning restore SA1015
|
||||
internal sealed partial class TextureManagerPluginScoped
|
||||
: IServiceType,
|
||||
IDisposable,
|
||||
ITextureProvider,
|
||||
ITextureSubstitutionProvider,
|
||||
ITextureReadbackProvider
|
||||
{
|
||||
private readonly LocalPlugin plugin;
|
||||
private readonly bool nonAsyncFunctionAccessDuringLoadIsError;
|
||||
|
||||
private Task<TextureManager>? managerTaskNullable;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private TextureManagerPluginScoped(LocalPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
if (plugin.Manifest is LocalPluginManifest lpm)
|
||||
this.nonAsyncFunctionAccessDuringLoadIsError = lpm.LoadSync && lpm.LoadRequiredState != 0;
|
||||
|
||||
this.managerTaskNullable =
|
||||
Service<TextureManager>
|
||||
.GetAsync()
|
||||
.ContinueWith(
|
||||
r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
r.Result.InterceptTexDataLoad += this.ResultOnInterceptTexDataLoad;
|
||||
return r;
|
||||
})
|
||||
.Unwrap();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
||||
|
||||
/// <summary>Gets the task resulting in an instance of <see cref="TextureManager"/>.</summary>
|
||||
/// <exception cref="ObjectDisposedException">Thrown if disposed.</exception>
|
||||
private Task<TextureManager> ManagerTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.managerTaskNullable ?? throw new ObjectDisposedException(this.ToString());
|
||||
}
|
||||
|
||||
/// <summary>Gets an instance of <see cref="TextureManager"/>.</summary>
|
||||
/// <exception cref="ObjectDisposedException">Thrown if disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if called at an unfortune time.</exception>
|
||||
private TextureManager ManagerOrThrow
|
||||
{
|
||||
get
|
||||
{
|
||||
var task = this.ManagerTask;
|
||||
|
||||
// Check for IMWS too, as TextureManager is constructed after IMWS, and UiBuilder.RunWhenUiPrepared gets
|
||||
// resolved when IMWS is constructed.
|
||||
if (!task.IsCompleted && Service<InterfaceManager.InterfaceManagerWithScene>.GetNullable() is null)
|
||||
{
|
||||
if (this.nonAsyncFunctionAccessDuringLoadIsError && this.plugin.State != PluginState.Loaded)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The function you've called will wait for the drawing facilities to be available, and as " +
|
||||
"Dalamud is already waiting for your plugin to be fully constructed before even attempting " +
|
||||
"to initialize the drawing facilities, calling this function will stall the game until and " +
|
||||
"is forbidden until your plugin has been fully loaded.\n" +
|
||||
$"Consider using {nameof(UiBuilder.RunWhenUiPrepared)} to wait for the right moment.\n" +
|
||||
"\n" +
|
||||
$"Note that your plugin has {nameof(LocalPluginManifest.LoadSync)} set and " +
|
||||
$"{nameof(LocalPluginManifest.LoadRequiredState)} that is nonzero.");
|
||||
}
|
||||
|
||||
if (ThreadSafety.IsMainThread)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The function you've called will wait for the drawing facilities to be available, and as " +
|
||||
"the drawing facilities are initialized from the main thread, calling this function will " +
|
||||
"stall the game until and is forbidden until your plugin has been fully loaded.\n" +
|
||||
$"Consider using {nameof(UiBuilder.RunWhenUiPrepared)} to wait for the right moment.");
|
||||
}
|
||||
}
|
||||
|
||||
return task.Result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref this.managerTaskNullable, null) is not { } task)
|
||||
return;
|
||||
task.ContinueWith(
|
||||
r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
r.Result.InterceptTexDataLoad -= this.ResultOnInterceptTexDataLoad;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.managerTaskNullable is null
|
||||
? $"{nameof(TextureManagerPluginScoped)}({this.plugin.Name}, disposed)"
|
||||
: $"{nameof(TextureManagerPluginScoped)}({this.plugin.Name})";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromExistingTextureAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
TextureModificationArgs args = default,
|
||||
bool leaveWrapOpen = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
var textureWrap = await manager.CreateFromExistingTextureAsync(
|
||||
wrap,
|
||||
args,
|
||||
leaveWrapOpen,
|
||||
cancellationToken);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
|
||||
ImGuiViewportTextureArgs args,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
var textureWrap = await manager.CreateFromImGuiViewportAsync(args, cancellationToken);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromImageAsync(
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
var textureWrap = await manager.CreateFromImageAsync(bytes, cancellationToken);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromImageAsync(
|
||||
Stream stream,
|
||||
bool leaveOpen = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
var textureWrap = await manager.CreateFromImageAsync(stream, leaveOpen, cancellationToken);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap CreateFromRaw(
|
||||
RawImageSpecification specs,
|
||||
ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var manager = this.ManagerOrThrow;
|
||||
var textureWrap = manager.CreateFromRaw(specs, bytes);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromRawAsync(
|
||||
RawImageSpecification specs,
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
var textureWrap = await manager.CreateFromRawAsync(specs, bytes, cancellationToken);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromRawAsync(
|
||||
RawImageSpecification specs,
|
||||
Stream stream,
|
||||
bool leaveOpen = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
var textureWrap = await manager.CreateFromRawAsync(specs, stream, leaveOpen, cancellationToken);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap CreateFromTexFile(TexFile file)
|
||||
{
|
||||
var manager = this.ManagerOrThrow;
|
||||
var textureWrap = manager.CreateFromTexFile(file);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IDalamudTextureWrap> CreateFromTexFileAsync(
|
||||
TexFile file,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
var textureWrap = await manager.CreateFromTexFileAsync(file, cancellationToken);
|
||||
manager.Blame(textureWrap, this.plugin);
|
||||
return textureWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
|
||||
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup)
|
||||
{
|
||||
return this.ManagerOrThrow.Shared.GetFromGameIcon(lookup);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISharedImmediateTexture GetFromGame(string path)
|
||||
{
|
||||
return this.ManagerOrThrow.Shared.GetFromGame(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISharedImmediateTexture GetFromFile(string path)
|
||||
{
|
||||
return this.ManagerOrThrow.Shared.GetFromFile(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISharedImmediateTexture GetFromManifestResource(Assembly assembly, string name)
|
||||
{
|
||||
return this.ManagerOrThrow.Shared.GetFromManifestResource(assembly, name);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetIconPath(in GameIconLookup lookup) => this.ManagerOrThrow.GetIconPath(lookup);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetIconPath(in GameIconLookup lookup, out string? path) =>
|
||||
this.ManagerOrThrow.TryGetIconPath(lookup, out path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDxgiFormatSupported(int dxgiFormat) =>
|
||||
this.ManagerOrThrow.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat) =>
|
||||
this.ManagerOrThrow.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)dxgiFormat);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetSubstitutedPath(string originalPath) =>
|
||||
this.ManagerOrThrow.GetSubstitutedPath(originalPath);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InvalidatePaths(IEnumerable<string> paths) =>
|
||||
this.ManagerOrThrow.InvalidatePaths(paths);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawImageAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
TextureModificationArgs args = default,
|
||||
bool leaveWrapOpen = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
return await manager.GetRawImageAsync(wrap, args, leaveWrapOpen, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IBitmapCodecInfo> GetSupportedImageEncoderInfos() =>
|
||||
this.ManagerOrThrow.Wic.GetSupportedEncoderInfos();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SaveToStreamAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
Guid containerGuid,
|
||||
Stream stream,
|
||||
IReadOnlyDictionary<string, object>? props = null,
|
||||
bool leaveWrapOpen = false,
|
||||
bool leaveStreamOpen = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
await manager.SaveToStreamAsync(
|
||||
wrap,
|
||||
containerGuid,
|
||||
stream,
|
||||
props,
|
||||
leaveWrapOpen,
|
||||
leaveStreamOpen,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SaveToFileAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
Guid containerGuid,
|
||||
string path,
|
||||
IReadOnlyDictionary<string, object>? props = null,
|
||||
bool leaveWrapOpen = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var manager = await this.ManagerTask;
|
||||
await manager.SaveToFileAsync(
|
||||
wrap,
|
||||
containerGuid,
|
||||
path,
|
||||
props,
|
||||
leaveWrapOpen,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private void ResultOnInterceptTexDataLoad(string path, ref string? replacementPath) =>
|
||||
this.InterceptTexDataLoad?.Invoke(path, ref replacementPath);
|
||||
}
|
||||
|
|
@ -34,8 +34,6 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public ViewportTextureWrap(ImGuiViewportTextureArgs args, CancellationToken cancellationToken)
|
||||
{
|
||||
args.ThrowOnInvalidValues();
|
||||
|
||||
this.args = args;
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,10 @@ using System.Threading;
|
|||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing texture loads.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class TextureLoadThrottler : IServiceType, IDisposable
|
||||
/// <summary>Base class for loading resources in dynamic order.</summary>
|
||||
internal class DynamicPriorityQueueLoader : IDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource disposeCancellationTokenSource = new();
|
||||
private readonly Task adderTask;
|
||||
|
|
@ -24,19 +19,20 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
|
||||
private bool disposing;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private TextureLoadThrottler()
|
||||
/// <summary>Initializes a new instance of the <see cref="DynamicPriorityQueueLoader"/> class.</summary>
|
||||
/// <param name="concurrency">Maximum number of concurrent load tasks.</param>
|
||||
public DynamicPriorityQueueLoader(int concurrency)
|
||||
{
|
||||
this.newItemChannel = Channel.CreateUnbounded<WorkItem>(new() { SingleReader = true });
|
||||
this.workTokenChannel = Channel.CreateUnbounded<object?>(new() { SingleWriter = true });
|
||||
|
||||
this.adderTask = Task.Run(this.LoopAddWorkItemAsync);
|
||||
this.workerTasks = new Task[Math.Max(1, Environment.ProcessorCount - 1)];
|
||||
this.workerTasks = new Task[concurrency];
|
||||
foreach (ref var task in this.workerTasks.AsSpan())
|
||||
task = Task.Run(this.LoopProcessWorkItemAsync);
|
||||
}
|
||||
|
||||
/// <summary>Basis for throttling. Values may be changed anytime.</summary>
|
||||
/// <summary>Provider for priority metrics.</summary>
|
||||
internal interface IThrottleBasisProvider
|
||||
{
|
||||
/// <summary>Gets a value indicating whether the resource is requested in an opportunistic way.</summary>
|
||||
|
|
@ -68,27 +64,36 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
_ = t.Exception;
|
||||
}
|
||||
|
||||
/// <summary>Loads a texture according to some order.</summary>
|
||||
/// <param name="basis">The throttle basis.</param>
|
||||
/// <summary>Loads a resource according to some order.</summary>
|
||||
/// <typeparam name="T">The type of resource.</typeparam>
|
||||
/// <param name="basis">The throttle basis. <c>null</c> may be used to create a new instance of
|
||||
/// <see cref="IThrottleBasisProvider"/> that is not opportunistic with time values of now.</param>
|
||||
/// <param name="immediateLoadFunction">The immediate load function.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="disposables">Disposables to dispose when the task completes.</param>
|
||||
/// <returns>The task.</returns>
|
||||
public Task<IDalamudTextureWrap> LoadTextureAsync(
|
||||
IThrottleBasisProvider basis,
|
||||
Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction,
|
||||
/// <remarks>
|
||||
/// <paramref name="immediateLoadFunction"/> may throw immediately without returning anything, or the returned
|
||||
/// <see cref="Task{TResult}"/> may complete in failure.
|
||||
/// </remarks>
|
||||
public Task<T> LoadAsync<T>(
|
||||
IThrottleBasisProvider? basis,
|
||||
Func<CancellationToken, Task<T>> immediateLoadFunction,
|
||||
CancellationToken cancellationToken,
|
||||
params IDisposable?[] disposables)
|
||||
{
|
||||
var work = new WorkItem(basis, immediateLoadFunction, cancellationToken, disposables);
|
||||
basis ??= new ReadOnlyThrottleBasisProvider();
|
||||
var work = new WorkItem<T>(basis, immediateLoadFunction, cancellationToken, disposables);
|
||||
|
||||
if (this.newItemChannel.Writer.TryWrite(work))
|
||||
return work.Task;
|
||||
|
||||
work.Dispose();
|
||||
return Task.FromException<IDalamudTextureWrap>(new ObjectDisposedException(nameof(TextureLoadThrottler)));
|
||||
return Task.FromException<T>(new ObjectDisposedException(this.GetType().Name));
|
||||
}
|
||||
|
||||
/// <summary>Continuously transfers work items added from <see cref="LoadAsync{T}"/> to
|
||||
/// <see cref="workItemPending"/>, until all items are transferred and <see cref="Dispose"/> is called.</summary>
|
||||
private async Task LoopAddWorkItemAsync()
|
||||
{
|
||||
const int batchAddSize = 64;
|
||||
|
|
@ -109,6 +114,8 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>Continuously processes work items in <see cref="workItemPending"/>, until all items are processed and
|
||||
/// <see cref="Dispose"/> is called.</summary>
|
||||
private async Task LoopProcessWorkItemAsync()
|
||||
{
|
||||
var reader = this.workTokenChannel.Reader;
|
||||
|
|
@ -167,10 +174,8 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A read-only implementation of <see cref="IThrottleBasisProvider"/>.
|
||||
/// </summary>
|
||||
public class ReadOnlyThrottleBasisProvider : IThrottleBasisProvider
|
||||
/// <summary>A read-only implementation of <see cref="IThrottleBasisProvider"/>.</summary>
|
||||
private class ReadOnlyThrottleBasisProvider : IThrottleBasisProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public bool IsOpportunistic { get; init; } = false;
|
||||
|
|
@ -182,27 +187,23 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
public long LatestRequestedTick { get; init; } = Environment.TickCount64;
|
||||
}
|
||||
|
||||
private sealed class WorkItem : IComparable<WorkItem>, IDisposable
|
||||
/// <summary>Represents a work item added from <see cref="LoadAsync{T}"/>.</summary>
|
||||
private abstract class WorkItem : IComparable<WorkItem>, IDisposable
|
||||
{
|
||||
private readonly TaskCompletionSource<IDalamudTextureWrap> taskCompletionSource;
|
||||
private readonly IThrottleBasisProvider basis;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction;
|
||||
private readonly IDisposable?[] disposables;
|
||||
|
||||
public WorkItem(
|
||||
protected WorkItem(
|
||||
IThrottleBasisProvider basis,
|
||||
Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction,
|
||||
CancellationToken cancellationToken, IDisposable?[] disposables)
|
||||
CancellationToken cancellationToken,
|
||||
params IDisposable?[] disposables)
|
||||
{
|
||||
this.taskCompletionSource = new();
|
||||
this.basis = basis;
|
||||
this.cancellationToken = cancellationToken;
|
||||
this.CancellationToken = cancellationToken;
|
||||
this.disposables = disposables;
|
||||
this.immediateLoadFunction = immediateLoadFunction;
|
||||
}
|
||||
|
||||
public Task<IDalamudTextureWrap> Task => this.taskCompletionSource.Task;
|
||||
protected CancellationToken CancellationToken { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -219,13 +220,37 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
return this.basis.FirstRequestedTick.CompareTo(other.basis.FirstRequestedTick);
|
||||
}
|
||||
|
||||
public bool CancelAsRequested()
|
||||
public abstract bool CancelAsRequested();
|
||||
|
||||
public abstract ValueTask Process(CancellationToken serviceDisposeToken);
|
||||
}
|
||||
|
||||
/// <summary>Typed version of <see cref="WorkItem"/>.</summary>
|
||||
private sealed class WorkItem<T> : WorkItem
|
||||
{
|
||||
private readonly TaskCompletionSource<T> taskCompletionSource;
|
||||
private readonly Func<CancellationToken, Task<T>> immediateLoadFunction;
|
||||
|
||||
public WorkItem(
|
||||
IThrottleBasisProvider basis,
|
||||
Func<CancellationToken, Task<T>> immediateLoadFunction,
|
||||
CancellationToken cancellationToken,
|
||||
params IDisposable?[] disposables)
|
||||
: base(basis, cancellationToken, disposables)
|
||||
{
|
||||
if (!this.cancellationToken.IsCancellationRequested)
|
||||
this.taskCompletionSource = new();
|
||||
this.immediateLoadFunction = immediateLoadFunction;
|
||||
}
|
||||
|
||||
public Task<T> Task => this.taskCompletionSource.Task;
|
||||
|
||||
public override bool CancelAsRequested()
|
||||
{
|
||||
if (!this.CancellationToken.IsCancellationRequested)
|
||||
return false;
|
||||
|
||||
// Cancel the load task and move on.
|
||||
this.taskCompletionSource.TrySetCanceled(this.cancellationToken);
|
||||
this.taskCompletionSource.TrySetCanceled(this.CancellationToken);
|
||||
|
||||
// Suppress the OperationCanceledException caused from the above.
|
||||
_ = this.taskCompletionSource.Task.Exception;
|
||||
|
|
@ -233,16 +258,16 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask Process(CancellationToken serviceDisposeToken)
|
||||
public override async ValueTask Process(CancellationToken serviceDisposeToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
IDalamudTextureWrap wrap;
|
||||
if (this.cancellationToken.CanBeCanceled)
|
||||
T wrap;
|
||||
if (this.CancellationToken.CanBeCanceled)
|
||||
{
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
serviceDisposeToken,
|
||||
this.cancellationToken);
|
||||
this.CancellationToken);
|
||||
wrap = await this.immediateLoadFunction(cts.Token);
|
||||
}
|
||||
else
|
||||
|
|
@ -251,7 +276,7 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
|||
}
|
||||
|
||||
if (!this.taskCompletionSource.TrySetResult(wrap))
|
||||
wrap.Dispose();
|
||||
(wrap as IDisposable)?.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Loading…
Add table
Add a link
Reference in a new issue