This commit is contained in:
Soreepeong 2024-03-03 20:41:41 +09:00
parent c04ce36b9c
commit e1bdba06de
6 changed files with 245 additions and 164 deletions

View file

@ -53,6 +53,8 @@ internal class TexWidget : IDataWindowWidget
private FileDialogManager fileDialogManager = null!;
private ExistingTextureModificationArgs existingTextureModificationArgs;
private ImGuiViewportTextureArgs viewportTextureArgs;
private int viewportIndexInt;
private string[]? supportedRenderTargetFormatNames;
private DXGI_FORMAT[]? supportedRenderTargetFormats;
private int renderTargetChoiceInt;
@ -84,6 +86,7 @@ internal class TexWidget : IDataWindowWidget
this.inputManifestResourceNameIndex = 0;
this.supportedRenderTargetFormats = null;
this.supportedRenderTargetFormatNames = null;
this.renderTargetChoiceInt = 0;
this.fileDialogManager = new();
this.existingTextureModificationArgs = new()
{
@ -92,6 +95,8 @@ internal class TexWidget : IDataWindowWidget
NewWidth = 320,
NewHeight = 240,
};
this.viewportTextureArgs = default;
this.viewportIndexInt = 0;
this.Ready = true;
}
@ -135,57 +140,41 @@ internal class TexWidget : IDataWindowWidget
ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Capture: ");
if (ImGui.Button("Game"))
this.addedTextures.Add(new() { Api10 = this.textureManager.CreateFromGameScreen() });
ImGui.SameLine();
if (ImGui.Button("Game (Auto)"))
this.addedTextures.Add(new() { Api10 = this.textureManager.CreateFromGameScreen(true) });
ImGui.SameLine();
if (ImGui.Button("Main Viewport"))
{
this.addedTextures.Add(
new() { Api10 = this.textureManager.CreateFromImGuiViewport(ImGui.GetMainViewport().ID) });
}
ImGui.SameLine();
if (ImGui.Button("Main Viewport (Auto)"))
{
this.addedTextures.Add(
new() { Api10 = this.textureManager.CreateFromImGuiViewport(ImGui.GetMainViewport().ID, true) });
}
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon), ImGuiTreeNodeFlags.DefaultOpen))
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon)))
{
ImGui.PushID(nameof(this.DrawGetFromGameIcon));
this.DrawGetFromGameIcon();
ImGui.PopID();
}
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGame), ImGuiTreeNodeFlags.DefaultOpen))
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGame)))
{
ImGui.PushID(nameof(this.DrawGetFromGame));
this.DrawGetFromGame();
ImGui.PopID();
}
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromFile), ImGuiTreeNodeFlags.DefaultOpen))
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromFile)))
{
ImGui.PushID(nameof(this.DrawGetFromFile));
this.DrawGetFromFile();
ImGui.PopID();
}
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromManifestResource), ImGuiTreeNodeFlags.DefaultOpen))
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromManifestResource)))
{
ImGui.PushID(nameof(this.DrawGetFromManifestResource));
this.DrawGetFromManifestResource();
ImGui.PopID();
}
if (ImGui.CollapsingHeader(nameof(ITextureProvider.CreateFromImGuiViewportAsync)))
{
ImGui.PushID(nameof(this.DrawCreateFromImGuiViewportAsync));
this.DrawCreateFromImGuiViewportAsync();
ImGui.PopID();
}
if (ImGui.CollapsingHeader("UV"))
{
ImGui.PushID(nameof(this.DrawUvInput));
@ -574,6 +563,57 @@ internal class TexWidget : IDataWindowWidget
ImGuiHelpers.ScaledDummy(10);
}
private void DrawCreateFromImGuiViewportAsync()
{
var viewports = ImGui.GetPlatformIO().Viewports;
if (ImGui.BeginCombo(
nameof(this.viewportTextureArgs.ViewportId),
$"{this.viewportIndexInt}. {viewports[this.viewportIndexInt].ID:X08}"))
{
for (var i = 0; i < viewports.Size; i++)
{
var sel = this.viewportIndexInt == i;
if (ImGui.Selectable($"#{i}: {viewports[i].ID:X08}", ref sel))
{
this.viewportIndexInt = i;
ImGui.SetItemDefaultFocus();
}
}
ImGui.EndCombo();
}
var b = this.viewportTextureArgs.KeepTransparency;
if (ImGui.Checkbox(nameof(this.viewportTextureArgs.KeepTransparency), ref b))
this.viewportTextureArgs.KeepTransparency = b;
b = this.viewportTextureArgs.AutoUpdate;
if (ImGui.Checkbox(nameof(this.viewportTextureArgs.AutoUpdate), ref b))
this.viewportTextureArgs.AutoUpdate = b;
b = this.viewportTextureArgs.TakeBeforeImGuiRender;
if (ImGui.Checkbox(nameof(this.viewportTextureArgs.TakeBeforeImGuiRender), ref b))
this.viewportTextureArgs.TakeBeforeImGuiRender = b;
var vec2 = this.viewportTextureArgs.Uv0;
if (ImGui.InputFloat2(nameof(this.viewportTextureArgs.Uv0), ref vec2))
this.viewportTextureArgs.Uv0 = vec2;
vec2 = this.viewportTextureArgs.Uv1;
if (ImGui.InputFloat2(nameof(this.viewportTextureArgs.Uv1), ref vec2))
this.viewportTextureArgs.Uv1 = vec2;
if (ImGui.Button("Create") && this.viewportIndexInt >= 0 && this.viewportIndexInt < viewports.Size)
{
this.addedTextures.Add(
new()
{
Api10 = this.textureManager.CreateFromImGuiViewportAsync(
this.viewportTextureArgs with { ViewportId = viewports[this.viewportIndexInt].ID }),
});
}
}
private void DrawUvInput()
{
ImGui.InputFloat2("UV0", ref this.inputTexUv0);
@ -586,25 +626,8 @@ internal class TexWidget : IDataWindowWidget
private void DrawExistingTextureModificationArgs()
{
var vec2 = this.existingTextureModificationArgs.Uv0;
if (ImGui.InputFloat2("UV0", ref vec2))
this.existingTextureModificationArgs.Uv0 = vec2;
vec2 = this.existingTextureModificationArgs.Uv1;
if (ImGui.InputFloat2("UV1", ref vec2))
this.existingTextureModificationArgs.Uv1 = vec2;
Span<int> wh = stackalloc int[2];
wh[0] = this.existingTextureModificationArgs.NewWidth;
wh[1] = this.existingTextureModificationArgs.NewHeight;
if (ImGui.InputInt2("New Size", ref wh[0]))
{
this.existingTextureModificationArgs.NewWidth = wh[0];
this.existingTextureModificationArgs.NewHeight = wh[1];
}
var b = this.existingTextureModificationArgs.MakeOpaque;
if (ImGui.Checkbox("Make Opaque", ref b))
if (ImGui.Checkbox(nameof(this.existingTextureModificationArgs.MakeOpaque), ref b))
this.existingTextureModificationArgs.MakeOpaque = b;
if (this.supportedRenderTargetFormats is null)
@ -619,11 +642,30 @@ internal class TexWidget : IDataWindowWidget
this.supportedRenderTargetFormatNames ??= this.supportedRenderTargetFormats.Select(Enum.GetName).ToArray();
ImGui.Combo(
"Format",
nameof(this.existingTextureModificationArgs.DxgiFormat),
ref this.renderTargetChoiceInt,
this.supportedRenderTargetFormatNames,
this.supportedRenderTargetFormatNames.Length);
Span<int> wh = stackalloc int[2];
wh[0] = this.existingTextureModificationArgs.NewWidth;
wh[1] = this.existingTextureModificationArgs.NewHeight;
if (ImGui.InputInt2(
$"{nameof(this.existingTextureModificationArgs.NewWidth)}/{nameof(this.existingTextureModificationArgs.NewHeight)}",
ref wh[0]))
{
this.existingTextureModificationArgs.NewWidth = wh[0];
this.existingTextureModificationArgs.NewHeight = wh[1];
}
var vec2 = this.existingTextureModificationArgs.Uv0;
if (ImGui.InputFloat2(nameof(this.existingTextureModificationArgs.Uv0), ref vec2))
this.existingTextureModificationArgs.Uv0 = vec2;
vec2 = this.existingTextureModificationArgs.Uv1;
if (ImGui.InputFloat2(nameof(this.existingTextureModificationArgs.Uv1), ref vec2))
this.existingTextureModificationArgs.Uv1 = vec2;
ImGuiHelpers.ScaledDummy(10);
}

View file

@ -9,15 +9,18 @@ namespace Dalamud.Interface.Textures;
/// <summary>Describes how to modify an existing texture.</summary>
public record struct ExistingTextureModificationArgs()
{
/// <summary>Gets or sets the left top coordinates relative to the size of the source texture.</summary>
/// <para>Coordinates should be in range between 0 and 1.</para>
public Vector2 Uv0 { get; set; } = Vector2.Zero;
/// <summary>Gets or sets a value indicating whether to make the texture opaque.</summary>
/// <remarks>If <c>true</c>, then the alpha channel values will be filled with 1.0.</remarks>
public bool MakeOpaque { get; set; } = false;
/// <summary>Gets or sets the right bottom coordinates relative to the size of the source texture.</summary>
/// <para>Coordinates should be in range between 0 and 1.</para>
/// <remarks>If set to <see cref="Vector2.Zero"/>, then it will be interpreted as <see cref="Vector2.One"/>,
/// to accommodate the use of default value of this record struct.</remarks>
public Vector2 Uv1 { get; set; } = Vector2.One;
/// <summary>Gets or sets the new DXGI format.</summary>
/// <remarks>
/// <para>Set to 0 (<see cref="DXGI_FORMAT.DXGI_FORMAT_UNKNOWN"/>) to use the source pixel format.</para>
/// <para>Supported values can be queried with
/// <see cref="ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync"/>. This may not necessarily
/// match <see cref="ITextureProvider.IsDxgiFormatSupported"/>.
/// </para></remarks>
public int DxgiFormat { get; set; } = (int)DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
/// <summary>Gets or sets the new width.</summary>
/// <remarks>Set to 0 to automatically calculate according to the original texture size, <see cref="Uv0"/>, and
@ -29,18 +32,15 @@ public record struct ExistingTextureModificationArgs()
/// <see cref="Uv1"/>.</remarks>
public int NewHeight { get; set; }
/// <summary>Gets or sets a value indicating whether to make the texture opaque.</summary>
/// <remarks>Alpha channel values will be filled with 1.0.</remarks>
public bool MakeOpaque { get; set; } = false;
/// <summary>Gets or sets the left top coordinates relative to the size of the source texture.</summary>
/// <para>Coordinates should be in range between 0 and 1.</para>
public Vector2 Uv0 { get; set; } = Vector2.Zero;
/// <summary>Gets or sets the new DXGI format.</summary>
/// <remarks>
/// <para>Set to 0 (<see cref="DXGI_FORMAT.DXGI_FORMAT_UNKNOWN"/>) to use the source pixel format.</para>
/// <para>Supported values can be queried with
/// <see cref="ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync"/>. This may not necessarily
/// match <see cref="ITextureProvider.IsDxgiFormatSupported"/>.
/// </para></remarks>
public int DxgiFormat { get; set; } = (int)DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
/// <summary>Gets or sets the right bottom coordinates relative to the size of the source texture.</summary>
/// <para>Coordinates should be in range between 0 and 1.</para>
/// <remarks>If set to <see cref="Vector2.Zero"/>, then it will be interpreted as <see cref="Vector2.One"/>,
/// to accommodate the use of default value of this record struct.</remarks>
public Vector2 Uv1 { get; set; } = Vector2.One;
/// <summary>Gets or sets the format (typed).</summary>
internal DXGI_FORMAT Format

View file

@ -0,0 +1,68 @@
using System.Numerics;
using Dalamud.Interface.Internal;
using ImGuiNET;
namespace Dalamud.Interface.Textures;
/// <summary>Describes how to take a texture of an existing ImGui viewport.</summary>
public record struct ImGuiViewportTextureArgs()
{
/// <summary>Gets or sets the ImGui Viewport ID to capture.</summary>
/// <remarks>Use <see cref="ImGuiViewport.ID"/> from <see cref="ImGui.GetMainViewport"/> to take the main viewport,
/// where the game renders to.</remarks>
public uint ViewportId { get; set; }
/// <summary>Gets or sets a value indicating whether to automatically update the texture.</summary>
/// <remarks>Enabling this will also update <see cref="IDalamudTextureWrap.Size"/> as needed.</remarks>
public bool AutoUpdate { get; set; }
/// <summary>Gets or sets a value indicating whether to get the texture before rendering ImGui.</summary>
/// <remarks>It probably makes no sense to enable this unless <see cref="ViewportId"/> points to the main viewport.
/// </remarks>
public bool TakeBeforeImGuiRender { get; set; }
/// <summary>Gets or sets a value indicating whether to keep the transparency.</summary>
/// <remarks>
/// <para>If <c>true</c>, then the alpha channel values will be filled with 1.0.</para>
/// <para>Keep in mind that screen captures generally do not need alpha values.</para>
/// </remarks>
// Intentionally not "MakeOpaque", to accommodate the use of default value of this record struct.
public bool KeepTransparency { get; set; } = false;
/// <summary>Gets or sets the left top coordinates relative to the size of the source texture.</summary>
/// <para>Coordinates should be in range between 0 and 1.</para>
public Vector2 Uv0 { get; set; } = Vector2.Zero;
/// <summary>Gets or sets the right bottom coordinates relative to the size of the source texture.</summary>
/// <para>Coordinates should be in range between 0 and 1.</para>
/// <remarks>If set to <see cref="Vector2.Zero"/>, then it will be interpreted as <see cref="Vector2.One"/>,
/// to accommodate the use of default value of this record struct.</remarks>
public Vector2 Uv1 { get; set; } = Vector2.One;
/// <summary>Gets the effective value of <see cref="Uv1"/>.</summary>
internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
/// <summary>Checks the properties and throws an exception if values are invalid.</summary>
internal void ThrowOnInvalidValues()
{
if (this.Uv0.X is < 0 or > 1 or float.NaN)
throw new ArgumentException($"{nameof(this.Uv0)}.X is out of range.");
if (this.Uv0.Y is < 0 or > 1 or float.NaN)
throw new ArgumentException($"{nameof(this.Uv0)}.Y is out of range.");
if (this.Uv1Effective.X is < 0 or > 1 or float.NaN)
throw new ArgumentException($"{nameof(this.Uv1)}.X is out of range.");
if (this.Uv1Effective.Y is < 0 or > 1 or float.NaN)
throw new ArgumentException($"{nameof(this.Uv1)}.Y is out of range.");
if (this.Uv0.X >= this.Uv1Effective.X || this.Uv0.Y >= this.Uv1Effective.Y)
{
throw new ArgumentException(
$"{nameof(this.Uv0)} must be strictly less than {nameof(this.Uv1)} in a componentwise way.");
}
}
}

View file

@ -5,8 +5,6 @@ using Dalamud.Interface.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using ImGuiNET;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
@ -85,47 +83,15 @@ internal sealed partial class TextureManager
}
/// <inheritdoc/>
public Task<IDalamudTextureWrap> CreateFromGameScreen(
bool autoUpdate = false,
CancellationToken cancellationToken = default) =>
this.interfaceManager.RunBeforeImGuiRender(
() =>
{
cancellationToken.ThrowIfCancellationRequested();
var t = new ViewportTextureWrap(ImGui.GetMainViewport().ID, true, autoUpdate, cancellationToken);
t.Update();
try
{
return t.FirstUpdateTask.Result;
}
catch
{
t.Dispose();
throw;
}
});
/// <inheritdoc/>
public Task<IDalamudTextureWrap> CreateFromImGuiViewport(
uint viewportId,
bool autoUpdate = false,
CancellationToken cancellationToken = default) =>
this.interfaceManager.RunBeforeImGuiRender(
() =>
{
cancellationToken.ThrowIfCancellationRequested();
var t = new ViewportTextureWrap(viewportId, false, autoUpdate, cancellationToken);
t.Update();
try
{
return t.FirstUpdateTask.Result;
}
catch
{
t.Dispose();
throw;
}
});
public async Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
ImGuiViewportTextureArgs args,
CancellationToken cancellationToken = default)
{
// This constructor may throw; keep the function "async", to wrap the exception as a Task.
var t = new ViewportTextureWrap(args, cancellationToken);
t.QueueUpdate();
return await t.FirstUpdateTask;
}
/// <inheritdoc/>
public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataFromExistingTextureAsync(

View file

@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
@ -19,33 +18,25 @@ namespace Dalamud.Interface.Textures.Internal;
/// <summary>A texture wrap that takes its buffer from the frame buffer (of swap chain).</summary>
internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDisposable
{
private readonly uint viewportId;
private readonly bool beforeImGuiRender;
private readonly CancellationToken cancellationToken;
private readonly TaskCompletionSource<IDalamudTextureWrap> firstUpdateTaskCompletionSource = new();
private ImGuiViewportTextureArgs args;
private D3D11_TEXTURE2D_DESC desc;
private ComPtr<ID3D11Texture2D> tex;
private ComPtr<ID3D11ShaderResourceView> srv;
private ComPtr<ID3D11RenderTargetView> rtv;
private bool autoUpdate;
private bool disposed;
/// <summary>Initializes a new instance of the <see cref="ViewportTextureWrap"/> class.</summary>
/// <param name="viewportId">The source viewport ID.</param>
/// <param name="beforeImGuiRender">Capture before calling <see cref="ImGui.Render"/>.</param>
/// <param name="autoUpdate">If <c>true</c>, automatically update the underlying texture.</param>
/// <param name="args">The arguments for creating a texture.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public ViewportTextureWrap(
uint viewportId,
bool beforeImGuiRender,
bool autoUpdate,
CancellationToken cancellationToken)
public ViewportTextureWrap(ImGuiViewportTextureArgs args, CancellationToken cancellationToken)
{
this.viewportId = viewportId;
this.beforeImGuiRender = beforeImGuiRender;
this.autoUpdate = autoUpdate;
args.ThrowOnInvalidValues();
this.args = args;
this.cancellationToken = cancellationToken;
}
@ -77,7 +68,7 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
{
ThreadSafety.AssertMainThread();
using var backBuffer = GetImGuiViewportBackBuffer(this.viewportId);
using var backBuffer = GetImGuiViewportBackBuffer(this.args.ViewportId);
D3D11_TEXTURE2D_DESC newDesc;
backBuffer.Get()->GetDesc(&newDesc);
@ -90,14 +81,24 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
using var context = default(ComPtr<ID3D11DeviceContext>);
device.Get()->GetImmediateContext(context.GetAddressOf());
if (this.desc.Width != newDesc.Width
|| this.desc.Height != newDesc.Height
var copyBox = new D3D11_BOX
{
left = (uint)MathF.Round(newDesc.Width * this.args.Uv0.X),
top = (uint)MathF.Round(newDesc.Height * this.args.Uv0.Y),
right = (uint)MathF.Round(newDesc.Width * this.args.Uv1Effective.X),
bottom = (uint)MathF.Round(newDesc.Height * this.args.Uv1Effective.Y),
front = 0,
back = 1,
};
if (this.desc.Width != copyBox.right - copyBox.left
|| this.desc.Height != copyBox.bottom - copyBox.top
|| this.desc.Format != newDesc.Format)
{
var texDesc = new D3D11_TEXTURE2D_DESC
{
Width = newDesc.Width,
Height = newDesc.Height,
Width = copyBox.right - copyBox.left,
Height = copyBox.bottom - copyBox.top,
MipLevels = 1,
ArraySize = 1,
Format = newDesc.Format,
@ -131,19 +132,32 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
srvTemp.GetAddressOf())
.ThrowOnError();
this.desc = newDesc;
this.desc = texDesc;
srvTemp.Swap(ref this.srv);
rtvTemp.Swap(ref this.rtv);
texTemp.Swap(ref this.tex);
}
context.Get()->CopyResource((ID3D11Resource*)this.tex.Get(), (ID3D11Resource*)backBuffer.Get());
var rtvLocal = this.rtv.Get();
context.Get()->OMSetRenderTargets(1u, &rtvLocal, null);
Service<TextureManager>.Get().SimpleDrawer.StripAlpha(context.Get());
// context.Get()->CopyResource((ID3D11Resource*)this.tex.Get(), (ID3D11Resource*)backBuffer.Get());
context.Get()->CopySubresourceRegion(
(ID3D11Resource*)this.tex.Get(),
0,
0,
0,
0,
(ID3D11Resource*)backBuffer.Get(),
0,
&copyBox);
var dummy = default(ID3D11RenderTargetView*);
context.Get()->OMSetRenderTargets(1u, &dummy, null);
if (!this.args.KeepTransparency)
{
var rtvLocal = this.rtv.Get();
context.Get()->OMSetRenderTargets(1u, &rtvLocal, null);
Service<TextureManager>.Get().SimpleDrawer.StripAlpha(context.Get());
var dummy = default(ID3D11RenderTargetView*);
context.Get()->OMSetRenderTargets(1u, &dummy, null);
}
this.firstUpdateTaskCompletionSource.TrySetResult(this);
}
@ -152,20 +166,22 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
this.firstUpdateTaskCompletionSource.TrySetException(e);
}
if (this.autoUpdate)
{
Service<Framework>.Get().RunOnTick(
() =>
{
if (this.beforeImGuiRender)
Service<InterfaceManager>.Get().RunBeforeImGuiRender(this.Update);
else
Service<InterfaceManager>.Get().RunAfterImGuiRender(this.Update);
},
cancellationToken: this.cancellationToken);
}
if (this.args.AutoUpdate)
this.QueueUpdate();
}
/// <summary>Queues a call to <see cref="Update"/>.</summary>
public void QueueUpdate() =>
Service<Framework>.Get().RunOnTick(
() =>
{
if (this.args.TakeBeforeImGuiRender)
Service<InterfaceManager>.Get().RunBeforeImGuiRender(this.Update);
else
Service<InterfaceManager>.Get().RunAfterImGuiRender(this.Update);
},
cancellationToken: this.cancellationToken);
/// <summary>Queue the texture to be disposed once the frame ends. </summary>
public void Dispose()
{
@ -230,7 +246,7 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
private void Dispose(bool disposing)
{
this.disposed = true;
this.autoUpdate = false;
this.args.AutoUpdate = false;
if (disposing)
Service<InterfaceManager>.GetNullable()?.EnqueueDeferredDispose(this);
else

View file

@ -45,27 +45,16 @@ public partial interface ITextureProvider
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default);
/// <summary>Creates a texture from the game screen, before rendering Dalamud.</summary>
/// <param name="autoUpdate">If <c>true</c>, automatically update the underlying texture.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the copied texture on success. Dispose after use.</returns>
/// <remarks><para>This function may throw an exception.</para></remarks>
Task<IDalamudTextureWrap> CreateFromGameScreen(
bool autoUpdate = false,
CancellationToken cancellationToken = default);
/// <summary>Creates a texture from the game screen, before rendering Dalamud.</summary>
/// <param name="viewportId">The viewport ID.</param>
/// <param name="autoUpdate">If <c>true</c>, automatically update the underlying texture.</param>
/// <summary>Creates a texture from an ImGui viewport.</summary>
/// <param name="args">The arguments for creating a texture.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the copied texture on success. Dispose after use.</returns>
/// <remarks>
/// <para>Use <c>ImGui.GetMainViewport().ID</c> to capture the game screen with Dalamud rendered.</para>
/// <para>This function may throw an exception.</para>
/// </remarks>
Task<IDalamudTextureWrap> CreateFromImGuiViewport(
uint viewportId,
bool autoUpdate = false,
Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
ImGuiViewportTextureArgs args,
CancellationToken cancellationToken = default);
/// <summary>Gets a texture from the given bytes, trying to interpret it as a .tex file or other well-known image