Add ITextureProvider.ConvertToKernelTexture (#2003)

* Add ITextureProvider.ConvertToKernelTexture

Lets you obtain an instance of Kernel::Texture from IDalamudTextureWrap.

* Docs wip
This commit is contained in:
srkizer 2024-11-05 00:06:42 +09:00 committed by GitHub
parent 9a0bc50e23
commit 74ab9191d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 125 additions and 5 deletions

View file

@ -1,7 +1,9 @@
using System.Numerics;
using Dalamud.Game;
using Dalamud.Game.Gui;
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Utility;
@ -317,6 +319,32 @@ internal unsafe class UiDebug
ImGui.TreePop();
}
}
if (ImGui.Button($"Replace with a random image##{(ulong)textureInfo:X}"))
{
var texm = Service<TextureManager>.Get();
texm.Shared
.GetFromGame(
Random.Shared.Next(0, 1) == 0
? $"ui/loadingimage/-nowloading_base{Random.Shared.Next(1, 33)}.tex"
: $"ui/loadingimage/-nowloading_base{Random.Shared.Next(1, 33)}_hr1.tex")
.RentAsync()
.ContinueWith(
r => Service<Framework>.Get().RunOnFrameworkThread(
() =>
{
if (!r.IsCompletedSuccessfully)
return;
using (r.Result)
{
textureInfo->AtkTexture.ReleaseTexture();
textureInfo->AtkTexture.KernelTexture =
texm.ConvertToKernelTexture(r.Result);
textureInfo->AtkTexture.TextureType = TextureType.KernelTexture;
}
}));
}
}
}
else

View file

@ -25,7 +25,8 @@ public interface ISharedImmediateTexture
/// <see cref="ISharedImmediateTexture"/>s may be cached, but the performance benefit will be minimal.</para>
/// <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><see cref="IDisposable.Dispose"/> will be ignored, including the cases when the returned texture wrap
/// is passed to a function with <c>leaveWrapOpen</c> parameter.</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>
@ -42,7 +43,8 @@ public interface ISharedImmediateTexture
/// <see cref="ISharedImmediateTexture"/>s may be cached, but the performance benefit will be minimal.</para>
/// <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><see cref="IDisposable.Dispose"/> will be ignored, including the cases when the returned texture wrap
/// is passed to a function with <c>leaveWrapOpen</c> parameter.</para>
/// <para>If the texture is unavailable for any reason, then <paramref name="defaultWrap"/> will be returned.</para>
/// </remarks>
[return: NotNullIfNotNull(nameof(defaultWrap))]
@ -59,7 +61,8 @@ public interface ISharedImmediateTexture
/// <see cref="ISharedImmediateTexture"/>s may be cached, but the performance benefit will be minimal.</para>
/// <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>
/// <para><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored, including
/// the cases when the returned texture wrap is passed to a function with <c>leaveWrapOpen</c> parameter.</para>
/// </remarks>
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception);

View file

@ -2,7 +2,6 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Plugin.Internal.Types;
@ -10,6 +9,8 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Dalamud.Utility.TerraFxCom;
using Lumina.Data.Files;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
@ -18,6 +19,72 @@ namespace Dalamud.Interface.Textures.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
internal sealed partial class TextureManager
{
/// <inheritdoc/>
unsafe nint ITextureProvider.ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen) =>
(nint)this.ConvertToKernelTexture(wrap, leaveWrapOpen);
/// <inheritdoc cref="ITextureProvider.ConvertToKernelTexture"/>
public unsafe FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture* ConvertToKernelTexture(
IDalamudTextureWrap wrap,
bool leaveWrapOpen = false)
{
using var wrapAux = new WrapAux(wrap, leaveWrapOpen);
var flags = TexFile.Attribute.TextureType2D;
if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_IMMUTABLE)
flags |= TexFile.Attribute.Immutable;
if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_DYNAMIC)
flags |= TexFile.Attribute.ReadWrite;
if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) != 0)
flags |= TexFile.Attribute.CpuRead;
if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET) != 0)
flags |= TexFile.Attribute.TextureRenderTarget;
if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_DEPTH_STENCIL) != 0)
flags |= TexFile.Attribute.TextureDepthStencil;
if (wrapAux.Desc.ArraySize != 1)
throw new NotSupportedException("TextureArray2D is currently not supported.");
var gtex = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture.CreateTexture2D(
(int)wrapAux.Desc.Width,
(int)wrapAux.Desc.Height,
(byte)wrapAux.Desc.MipLevels,
(uint)TexFile.TextureFormat.Null, // instructs the game to skip preprocessing it seems
(uint)flags,
0);
// Kernel::Texture owns these resources. We're passing the ownership to them.
wrapAux.TexPtr->AddRef();
wrapAux.SrvPtr->AddRef();
// Not sure this is needed
var ltf = wrapAux.Desc.Format switch
{
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TexFile.TextureFormat.R32G32B32A32F,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TexFile.TextureFormat.R16G16B16A16F,
DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT => TexFile.TextureFormat.R32G32F,
DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT => TexFile.TextureFormat.R16G16F,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => TexFile.TextureFormat.R32F,
DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS => TexFile.TextureFormat.D24S8,
DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS => TexFile.TextureFormat.D16,
DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => TexFile.TextureFormat.A8,
DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM => TexFile.TextureFormat.BC1,
DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM => TexFile.TextureFormat.BC2,
DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM => TexFile.TextureFormat.BC3,
DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM => TexFile.TextureFormat.BC5,
DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM => TexFile.TextureFormat.B4G4R4A4,
DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => TexFile.TextureFormat.B5G5R5A1,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TexFile.TextureFormat.B8G8R8A8,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => TexFile.TextureFormat.B8G8R8X8,
DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM => TexFile.TextureFormat.BC7,
_ => TexFile.TextureFormat.Null,
};
gtex->TextureFormat = (FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.TextureFormat)ltf;
gtex->D3D11Texture2D = wrapAux.TexPtr;
gtex->D3D11ShaderResourceView = wrapAux.SrvPtr;
return gtex;
}
/// <inheritdoc/>
bool ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat) =>
this.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)dxgiFormat);

View file

@ -134,6 +134,10 @@ internal sealed class TextureManagerPluginScoped
: $"{nameof(TextureManagerPluginScoped)}({this.plugin.Name})";
}
/// <inheritdoc/>
public unsafe nint ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false) =>
(nint)this.ManagerOrThrow.ConvertToKernelTexture(wrap, leaveWrapOpen);
/// <inheritdoc/>
public IDalamudTextureWrap CreateEmpty(
RawImageSpecification specs,

View file

@ -5,7 +5,6 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.Windows.Data.Widgets;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
@ -281,4 +280,20 @@ public interface ITextureProvider
/// <returns><c>true</c> if supported.</returns>
/// <remarks><para>This function does not throw exceptions.</para></remarks>
bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat);
/// <summary>Converts an existing <see cref="IDalamudTextureWrap"/> instance to a new instance of
/// <see cref="FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture"/> which can be used to supply a custom
/// texture onto an in-game addon (UI element.)</summary>
/// <param name="wrap">Instance of <see cref="IDalamudTextureWrap"/> to convert.</param>
/// <param name="leaveWrapOpen">Whether to leave <paramref name="wrap"/> non-disposed when the returned
/// <see cref="Task{TResult}"/> completes.</param>
/// <returns>Address of the new <see cref="FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture"/>.</returns>
/// <example>See <c>PrintTextureInfo</c> in <see cref="Interface.Internal.UiDebug.PrintSimpleNode"/> for an example
/// of replacing the texture of an image node.</example>
/// <remarks>
/// <para>If the returned kernel texture is to be destroyed, call the fourth function in its vtable, by calling
/// <see cref="FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture.DecRef"/> or
/// <c>((delegate* unmanaged&lt;nint, void&gt;)(*(nint**)ptr)[3](ptr)</c>.</para>
/// </remarks>
nint ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false);
}

View file

@ -22,6 +22,9 @@ public interface ITextureReadbackProvider
/// <remarks>
/// <para>The length of the returned <c>RawData</c> may not match
/// <see cref="RawImageSpecification.Height"/> * <see cref="RawImageSpecification.Pitch"/>.</para>
/// <para><see cref="RawImageSpecification.Pitch"/> may not be the minimal value required to represent the texture
/// bitmap data. For example, if a texture is 4x4 B8G8R8A8, the minimal pitch would be 32, but the function may
/// return 64 instead.</para>
/// <para>This function may throw an exception.</para>
/// </remarks>
Task<(RawImageSpecification Specification, byte[] RawData)> GetRawImageAsync(