diff --git a/Dalamud/Interface/Internal/DalamudTextureWrap.cs b/Dalamud/Interface/Internal/DalamudTextureWrap.cs
index 9737d9f7b..b49c6f07b 100644
--- a/Dalamud/Interface/Internal/DalamudTextureWrap.cs
+++ b/Dalamud/Interface/Internal/DalamudTextureWrap.cs
@@ -1,41 +1,14 @@
-using System.Numerics;
+using Dalamud.Utility;
using ImGuiScene;
namespace Dalamud.Interface.Internal;
-///
-/// Base TextureWrap interface for all Dalamud-owned texture wraps.
-/// Used to avoid referencing ImGuiScene.
-///
-public interface IDalamudTextureWrap : IDisposable
-{
- ///
- /// Gets a texture handle suitable for direct use with ImGui functions.
- ///
- IntPtr ImGuiHandle { get; }
-
- ///
- /// Gets the width of the texture.
- ///
- int Width { get; }
-
- ///
- /// Gets the height of the texture.
- ///
- int Height { get; }
-
- ///
- /// Gets the size vector of the texture using Width, Height.
- ///
- Vector2 Size => new(this.Width, this.Height);
-}
-
///
/// Safety harness for ImGuiScene textures that will defer destruction until
/// the end of the frame.
///
-public class DalamudTextureWrap : IDalamudTextureWrap
+public class DalamudTextureWrap : IDalamudTextureWrap, IDeferredDisposable
{
private readonly TextureWrap wrappedWrap;
@@ -83,7 +56,7 @@ public class DalamudTextureWrap : IDalamudTextureWrap
///
/// Actually dispose the wrapped texture.
///
- internal void RealDispose()
+ void IDeferredDisposable.RealDispose()
{
this.wrappedWrap.Dispose();
}
diff --git a/Dalamud/Interface/Internal/IDalamudTextureWrap.cs b/Dalamud/Interface/Internal/IDalamudTextureWrap.cs
new file mode 100644
index 000000000..8e2e56c26
--- /dev/null
+++ b/Dalamud/Interface/Internal/IDalamudTextureWrap.cs
@@ -0,0 +1,55 @@
+using System.Numerics;
+
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.Internal;
+
+///
+/// Base TextureWrap interface for all Dalamud-owned texture wraps.
+/// Used to avoid referencing ImGuiScene.
+///
+public interface IDalamudTextureWrap : IDisposable
+{
+ ///
+ /// Gets a texture handle suitable for direct use with ImGui functions.
+ ///
+ IntPtr ImGuiHandle { get; }
+
+ ///
+ /// Gets the width of the texture.
+ ///
+ int Width { get; }
+
+ ///
+ /// Gets the height of the texture.
+ ///
+ int Height { get; }
+
+ ///
+ /// Gets the size vector of the texture using Width, Height.
+ ///
+ Vector2 Size => new(this.Width, this.Height);
+
+ ///
+ /// Creates a new reference to the resource being pointed by this instance of .
+ ///
+ /// The new reference to this texture wrap.
+ ///
+ /// On calling this function, a new instance of will be returned, but with
+ /// the same . The new instance must be 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.
+ /// 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.
+ /// The default implementation will treat as an .
+ ///
+ 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);
+ }
+}
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 6d93b4bd7..3db799be0 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -62,7 +62,7 @@ internal class InterfaceManager : IDisposable, IServiceType
///
public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f;
- private readonly ConcurrentBag deferredDisposeTextures = new();
+ private readonly ConcurrentBag deferredDisposeTextures = new();
private readonly ConcurrentBag 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.
///
/// The texture.
- public void EnqueueDeferredDispose(DalamudTextureWrap wrap)
+ public void EnqueueDeferredDispose(IDeferredDisposable wrap)
{
this.deferredDisposeTextures.Add(wrap);
}
diff --git a/Dalamud/Interface/Internal/UnknownTextureWrap.cs b/Dalamud/Interface/Internal/UnknownTextureWrap.cs
new file mode 100644
index 000000000..41164f2c3
--- /dev/null
+++ b/Dalamud/Interface/Internal/UnknownTextureWrap.cs
@@ -0,0 +1,77 @@
+using System.Threading;
+
+using Dalamud.Utility;
+
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.Internal;
+
+///
+/// A texture wrap that is created by cloning the underlying .
+///
+internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferredDisposable
+{
+ private IntPtr imGuiHandle;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The pointer to that is suitable for use with
+ /// .
+ /// The width of the texture.
+ /// The height of the texture.
+ /// If true, call .
+ 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();
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~UnknownTextureWrap() => this.Dispose(false);
+
+ ///
+ public nint ImGuiHandle =>
+ this.imGuiHandle == nint.Zero
+ ? throw new ObjectDisposedException(nameof(UnknownTextureWrap))
+ : this.imGuiHandle;
+
+ ///
+ public int Width { get; }
+
+ ///
+ public int Height { get; }
+
+ ///
+ /// Queue the texture to be disposed once the frame ends.
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Actually dispose the wrapped texture.
+ ///
+ 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.GetNullable()?.EnqueueDeferredDispose(this);
+ else
+ ((IDeferredDisposable)this).RealDispose();
+ }
+}
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
index 0cbc401e7..8d6879ac1 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
@@ -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());
}
}
diff --git a/Dalamud/Utility/IDeferredDisposable.cs b/Dalamud/Utility/IDeferredDisposable.cs
new file mode 100644
index 000000000..41a7dd8d3
--- /dev/null
+++ b/Dalamud/Utility/IDeferredDisposable.cs
@@ -0,0 +1,12 @@
+namespace Dalamud.Utility;
+
+///
+/// An extension of which makes queue
+/// to be called at a later time.
+///
+internal interface IDeferredDisposable : IDisposable
+{
+ /// Actually dispose the object.
+ /// Not to be called from the code that uses the end object.
+ void RealDispose();
+}