Make IDalamudTextureWrap ICloneable

This commit is contained in:
Soreepeong 2024-02-25 21:21:50 +09:00
parent c1c85e5236
commit f6be80a5fb
6 changed files with 145 additions and 32 deletions

View file

@ -1,41 +1,14 @@
using System.Numerics; using Dalamud.Utility;
using ImGuiScene; using ImGuiScene;
namespace Dalamud.Interface.Internal; namespace Dalamud.Interface.Internal;
/// <summary>
/// Base TextureWrap interface for all Dalamud-owned texture wraps.
/// Used to avoid referencing ImGuiScene.
/// </summary>
public interface IDalamudTextureWrap : IDisposable
{
/// <summary>
/// Gets a texture handle suitable for direct use with ImGui functions.
/// </summary>
IntPtr ImGuiHandle { get; }
/// <summary>
/// Gets the width of the texture.
/// </summary>
int Width { get; }
/// <summary>
/// Gets the height of the texture.
/// </summary>
int Height { get; }
/// <summary>
/// Gets the size vector of the texture using Width, Height.
/// </summary>
Vector2 Size => new(this.Width, this.Height);
}
/// <summary> /// <summary>
/// Safety harness for ImGuiScene textures that will defer destruction until /// Safety harness for ImGuiScene textures that will defer destruction until
/// the end of the frame. /// the end of the frame.
/// </summary> /// </summary>
public class DalamudTextureWrap : IDalamudTextureWrap public class DalamudTextureWrap : IDalamudTextureWrap, IDeferredDisposable
{ {
private readonly TextureWrap wrappedWrap; private readonly TextureWrap wrappedWrap;
@ -83,7 +56,7 @@ public class DalamudTextureWrap : IDalamudTextureWrap
/// <summary> /// <summary>
/// Actually dispose the wrapped texture. /// Actually dispose the wrapped texture.
/// </summary> /// </summary>
internal void RealDispose() void IDeferredDisposable.RealDispose()
{ {
this.wrappedWrap.Dispose(); this.wrappedWrap.Dispose();
} }

View file

@ -0,0 +1,47 @@
using System.Numerics;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Internal;
/// <summary>
/// Base TextureWrap interface for all Dalamud-owned texture wraps.
/// Used to avoid referencing ImGuiScene.
/// </summary>
public interface IDalamudTextureWrap : IDisposable, ICloneable
{
/// <summary>
/// Gets a texture handle suitable for direct use with ImGui functions.
/// </summary>
IntPtr ImGuiHandle { get; }
/// <summary>
/// Gets the width of the texture.
/// </summary>
int Width { get; }
/// <summary>
/// Gets the height of the texture.
/// </summary>
int Height { get; }
/// <summary>
/// Gets the size vector of the texture using Width, Height.
/// </summary>
Vector2 Size => new(this.Width, this.Height);
/// <summary>
/// Creates a new reference to this texture wrap.
/// </summary>
/// <returns>The new reference to this texture wrap.</returns>
/// <remarks>The default implementation will treat <see cref="ImGuiHandle"/> as an <see cref="IUnknown"/>.</remarks>
new unsafe IDalamudTextureWrap Clone()
{
// Dalamud specific: IDalamudTextureWrap always points to an ID3D11ShaderResourceView.
var handle = (IUnknown*)this.ImGuiHandle;
return new UnknownTextureWrap(handle, this.Width, this.Height, true);
}
/// <inheritdoc />
object ICloneable.Clone() => this.Clone();
}

View file

@ -62,7 +62,7 @@ internal class InterfaceManager : IDisposable, IServiceType
/// </summary> /// </summary>
public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f; public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f;
private readonly ConcurrentBag<DalamudTextureWrap> deferredDisposeTextures = new(); private readonly ConcurrentBag<IDeferredDisposable> deferredDisposeTextures = new();
private readonly ConcurrentBag<ILockedImFont> deferredDisposeImFontLockeds = new(); private readonly ConcurrentBag<ILockedImFont> deferredDisposeImFontLockeds = new();
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
@ -402,7 +402,7 @@ internal class InterfaceManager : IDisposable, IServiceType
/// Enqueue a texture to be disposed at the end of the frame. /// Enqueue a texture to be disposed at the end of the frame.
/// </summary> /// </summary>
/// <param name="wrap">The texture.</param> /// <param name="wrap">The texture.</param>
public void EnqueueDeferredDispose(DalamudTextureWrap wrap) public void EnqueueDeferredDispose(IDeferredDisposable wrap)
{ {
this.deferredDisposeTextures.Add(wrap); this.deferredDisposeTextures.Add(wrap);
} }

View file

@ -0,0 +1,77 @@
using System.Threading;
using Dalamud.Utility;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Internal;
/// <summary>
/// A texture wrap that is created by cloning the underlying <see cref="IDalamudTextureWrap.ImGuiHandle"/>.
/// </summary>
internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferredDisposable
{
private IntPtr imGuiHandle;
/// <summary>
/// Initializes a new instance of the <see cref="UnknownTextureWrap"/> class.
/// </summary>
/// <param name="unknown">The pointer to <see cref="IUnknown"/> that is suitable for use with
/// <see cref="IDalamudTextureWrap.ImGuiHandle"/>.</param>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="callAddRef">If <c>true</c>, call <see cref="IUnknown.AddRef"/>.</param>
public UnknownTextureWrap(IUnknown* unknown, int width, int height, bool callAddRef)
{
ObjectDisposedException.ThrowIf(unknown is null, typeof(IUnknown));
this.imGuiHandle = (nint)unknown;
this.Width = width;
this.Height = height;
if (callAddRef)
unknown->AddRef();
}
/// <summary>
/// Finalizes an instance of the <see cref="UnknownTextureWrap"/> class.
/// </summary>
~UnknownTextureWrap() => this.Dispose(false);
/// <inheritdoc/>
public nint ImGuiHandle =>
this.imGuiHandle == nint.Zero
? throw new ObjectDisposedException(nameof(UnknownTextureWrap))
: this.imGuiHandle;
/// <inheritdoc/>
public int Width { get; }
/// <inheritdoc/>
public int Height { get; }
/// <summary>
/// Queue the texture to be disposed once the frame ends.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Actually dispose the wrapped texture.
/// </summary>
void IDeferredDisposable.RealDispose()
{
var handle = Interlocked.Exchange(ref this.imGuiHandle, nint.Zero);
if (handle != nint.Zero)
((IUnknown*)handle)->Release();
}
private void Dispose(bool disposing)
{
if (disposing)
Service<InterfaceManager>.GetNullable()?.EnqueueDeferredDispose(this);
else
((IDeferredDisposable)this).RealDispose();
}
}

View file

@ -119,6 +119,10 @@ internal class TexWidget : IDataWindowWidget
if (ImGui.Button($"X##{i}")) if (ImGui.Button($"X##{i}"))
toRemove = tex; toRemove = tex;
ImGui.SameLine();
if (ImGui.Button($"Clone##{i}"))
this.addedTextures.Add(tex.Clone());
} }
} }

View file

@ -0,0 +1,12 @@
namespace Dalamud.Utility;
/// <summary>
/// An extension of <see cref="IDisposable"/> which makes <see cref="IDisposable.Dispose"/> queue
/// <see cref="RealDispose"/> to be called at a later time.
/// </summary>
internal interface IDeferredDisposable : IDisposable
{
/// <summary>Actually dispose the object.</summary>
/// <remarks>Not to be called from the code that uses the end object.</remarks>
void RealDispose();
}