Merge pull request #1684 from Soreepeong/feature/idtw-cloneable

Add IDalamudTextureWrap.CreateWrapSharingLowLevelResource
This commit is contained in:
goat 2024-02-27 19:43:21 +01:00 committed by GitHub
commit 8e5a84792e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 153 additions and 32 deletions

View file

@ -1,41 +1,14 @@
using System.Numerics;
using Dalamud.Utility;
using ImGuiScene;
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>
/// Safety harness for ImGuiScene textures that will defer destruction until
/// the end of the frame.
/// </summary>
public class DalamudTextureWrap : IDalamudTextureWrap
public class DalamudTextureWrap : IDalamudTextureWrap, IDeferredDisposable
{
private readonly TextureWrap wrappedWrap;
@ -83,7 +56,7 @@ public class DalamudTextureWrap : IDalamudTextureWrap
/// <summary>
/// Actually dispose the wrapped texture.
/// </summary>
internal void RealDispose()
void IDeferredDisposable.RealDispose()
{
this.wrappedWrap.Dispose();
}

View file

@ -0,0 +1,55 @@
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
{
/// <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 the resource being pointed by this instance of <see cref="IDalamudTextureWrap"/>.
/// </summary>
/// <returns>The new reference to this texture wrap.</returns>
/// <remarks>
/// On calling this function, a new instance of <see cref="IDalamudTextureWrap"/> will be returned, but with
/// the same <see cref="ImGuiHandle"/>. The new instance must be <see cref="IDisposable.Dispose"/>d, as the backing
/// resource will stay alive until all the references are released. The old instance may be disposed as needed,
/// once this function returns; the new instance will stay alive regardless of whether the old instance has been
/// disposed.<br />
/// Primary purpose of this function is to share textures across plugin boundaries. When texture wraps get passed
/// across plugin boundaries for use for an indeterminate duration, the receiver should call this function to
/// obtain a new reference to the texture received, so that it gets its own "copy" of the texture and the caller
/// may dispose the texture anytime without any care for the receiver.<br />
/// The default implementation will treat <see cref="ImGuiHandle"/> as an <see cref="IUnknown"/>.
/// </remarks>
unsafe IDalamudTextureWrap CreateWrapSharingLowLevelResource()
{
// Dalamud specific: IDalamudTextureWrap always points to an ID3D11ShaderResourceView.
var handle = (IUnknown*)this.ImGuiHandle;
return new UnknownTextureWrap(handle, this.Width, this.Height, true);
}
}

View file

@ -62,7 +62,7 @@ internal class InterfaceManager : IDisposable, IServiceType
/// </summary>
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();
[ServiceManager.ServiceDependency]
@ -402,7 +402,7 @@ internal class InterfaceManager : IDisposable, IServiceType
/// Enqueue a texture to be disposed at the end of the frame.
/// </summary>
/// <param name="wrap">The texture.</param>
public void EnqueueDeferredDispose(DalamudTextureWrap wrap)
public void EnqueueDeferredDispose(IDeferredDisposable 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}"))
toRemove = tex;
ImGui.SameLine();
if (ImGui.Button($"Clone##{i}"))
this.addedTextures.Add(tex.CreateWrapSharingLowLevelResource());
}
}

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();
}