mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
277 lines
10 KiB
C#
277 lines
10 KiB
C#
using System.Diagnostics;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
using Dalamud.Game;
|
|
using Dalamud.Interface.Internal;
|
|
using Dalamud.Interface.Textures.Internal;
|
|
using Dalamud.Plugin.Internal.Types;
|
|
using Dalamud.Storage.Assets;
|
|
using Dalamud.Utility;
|
|
|
|
using ImGuiNET;
|
|
|
|
using TerraFX.Interop.DirectX;
|
|
using TerraFX.Interop.Windows;
|
|
|
|
using NotSupportedException = System.NotSupportedException;
|
|
|
|
namespace Dalamud.Interface.Textures.TextureWraps.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 string? debugName;
|
|
private readonly LocalPlugin? ownerPlugin;
|
|
private readonly CancellationToken cancellationToken;
|
|
private readonly TaskCompletionSource<IDalamudTextureWrap> firstUpdateTaskCompletionSource =
|
|
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
private ImGuiViewportTextureArgs args;
|
|
private D3D11_TEXTURE2D_DESC desc;
|
|
private ComPtr<ID3D11Texture2D> tex;
|
|
private ComPtr<ID3D11ShaderResourceView> srv;
|
|
private ComPtr<ID3D11RenderTargetView> rtv;
|
|
|
|
private bool disposed;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ViewportTextureWrap"/> class.</summary>
|
|
/// <param name="args">The arguments for creating a texture.</param>
|
|
/// <param name="debugName">Name for debug display purposes.</param>
|
|
/// <param name="ownerPlugin">The owner plugin.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
public ViewportTextureWrap(
|
|
ImGuiViewportTextureArgs args, string? debugName, LocalPlugin? ownerPlugin, CancellationToken cancellationToken)
|
|
{
|
|
this.args = args;
|
|
this.debugName = debugName;
|
|
this.ownerPlugin = ownerPlugin;
|
|
this.cancellationToken = cancellationToken;
|
|
}
|
|
|
|
/// <summary>Finalizes an instance of the <see cref="ViewportTextureWrap"/> class.</summary>
|
|
~ViewportTextureWrap() => this.Dispose(false);
|
|
|
|
/// <inheritdoc/>
|
|
public unsafe nint ImGuiHandle
|
|
{
|
|
get
|
|
{
|
|
var t = (nint)this.srv.Get();
|
|
return t == nint.Zero ? Service<DalamudAssetManager>.Get().Empty4X4.ImGuiHandle : t;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public int Width => (int)this.desc.Width;
|
|
|
|
/// <inheritdoc/>
|
|
public int Height => (int)this.desc.Height;
|
|
|
|
/// <summary>Gets the task representing the first <see cref="Update"/> call.</summary>
|
|
public Task<IDalamudTextureWrap> FirstUpdateTask => this.firstUpdateTaskCompletionSource.Task;
|
|
|
|
/// <summary>Updates the texture from the source viewport.</summary>
|
|
public unsafe void Update()
|
|
{
|
|
if (this.cancellationToken.IsCancellationRequested || this.disposed)
|
|
{
|
|
this.firstUpdateTaskCompletionSource.TrySetCanceled();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
ThreadSafety.AssertMainThread();
|
|
|
|
using var backBuffer = GetImGuiViewportBackBuffer(this.args.ViewportId);
|
|
D3D11_TEXTURE2D_DESC newDesc;
|
|
backBuffer.Get()->GetDesc(&newDesc);
|
|
|
|
if (newDesc.SampleDesc.Count > 1)
|
|
throw new NotSupportedException("Multisampling is not expected");
|
|
|
|
using var device = default(ComPtr<ID3D11Device>);
|
|
backBuffer.Get()->GetDevice(device.GetAddressOf());
|
|
|
|
using var context = default(ComPtr<ID3D11DeviceContext>);
|
|
device.Get()->GetImmediateContext(context.GetAddressOf());
|
|
|
|
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 = copyBox.right - copyBox.left,
|
|
Height = copyBox.bottom - copyBox.top,
|
|
MipLevels = 1,
|
|
ArraySize = 1,
|
|
Format = newDesc.Format,
|
|
SampleDesc = new(1, 0),
|
|
Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
|
|
BindFlags = (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE |
|
|
D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET),
|
|
CPUAccessFlags = 0u,
|
|
MiscFlags = 0u,
|
|
};
|
|
|
|
using var texTemp = default(ComPtr<ID3D11Texture2D>);
|
|
device.Get()->CreateTexture2D(&texDesc, null, texTemp.GetAddressOf()).ThrowOnError();
|
|
|
|
var rtvDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
|
|
texTemp,
|
|
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
|
|
using var rtvTemp = default(ComPtr<ID3D11RenderTargetView>);
|
|
device.Get()->CreateRenderTargetView(
|
|
(ID3D11Resource*)texTemp.Get(),
|
|
&rtvDesc,
|
|
rtvTemp.GetAddressOf()).ThrowOnError();
|
|
|
|
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
|
|
texTemp,
|
|
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
|
|
using var srvTemp = default(ComPtr<ID3D11ShaderResourceView>);
|
|
device.Get()->CreateShaderResourceView(
|
|
(ID3D11Resource*)texTemp.Get(),
|
|
&srvDesc,
|
|
srvTemp.GetAddressOf())
|
|
.ThrowOnError();
|
|
|
|
this.desc = texDesc;
|
|
srvTemp.Swap(ref this.srv);
|
|
rtvTemp.Swap(ref this.rtv);
|
|
texTemp.Swap(ref this.tex);
|
|
|
|
Service<TextureManager>.Get().Blame(this, this.ownerPlugin);
|
|
Service<TextureManager>.Get().BlameSetName(
|
|
this,
|
|
this.debugName ?? $"{nameof(ViewportTextureWrap)}({this.args})");
|
|
}
|
|
|
|
// 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,
|
|
©Box);
|
|
|
|
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);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
this.firstUpdateTaskCompletionSource.TrySetException(e);
|
|
}
|
|
|
|
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()
|
|
{
|
|
this.Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>Actually dispose the wrapped texture.</summary>
|
|
void IDeferredDisposable.RealDispose()
|
|
{
|
|
_ = this.FirstUpdateTask.Exception;
|
|
this.tex.Reset();
|
|
this.srv.Reset();
|
|
this.rtv.Reset();
|
|
}
|
|
|
|
private static unsafe ComPtr<ID3D11Texture2D> GetImGuiViewportBackBuffer(uint viewportId)
|
|
{
|
|
ThreadSafety.AssertMainThread();
|
|
var viewports = ImGui.GetPlatformIO().Viewports;
|
|
var viewportIndex = 0;
|
|
for (; viewportIndex < viewports.Size; viewportIndex++)
|
|
{
|
|
if (viewports[viewportIndex].ID == viewportId)
|
|
break;
|
|
}
|
|
|
|
if (viewportIndex >= viewports.Size)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(viewportId),
|
|
viewportId,
|
|
"Could not find a viewport with the given ID.");
|
|
}
|
|
|
|
var texture = default(ComPtr<ID3D11Texture2D>);
|
|
|
|
Debug.Assert(viewports[0].ID == ImGui.GetMainViewport().ID, "ImGui has changed");
|
|
if (viewportId == viewports[0].ID)
|
|
{
|
|
var device = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device.Instance();
|
|
fixed (Guid* piid = &IID.IID_ID3D11Texture2D)
|
|
{
|
|
((IDXGISwapChain*)device->SwapChain->DXGISwapChain)->GetBuffer(0, piid, (void**)texture.GetAddressOf())
|
|
.ThrowOnError();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// See: ImGui_Impl_DX11.ImGuiViewportDataDx11
|
|
var rud = (nint*)viewports[viewportIndex].RendererUserData;
|
|
if (rud == null || rud[0] == nint.Zero || rud[1] == nint.Zero)
|
|
throw new InvalidOperationException();
|
|
|
|
using var resource = default(ComPtr<ID3D11Resource>);
|
|
((ID3D11RenderTargetView*)rud[1])->GetResource(resource.GetAddressOf());
|
|
resource.As(&texture).ThrowOnError();
|
|
}
|
|
|
|
return texture;
|
|
}
|
|
|
|
private void Dispose(bool disposing)
|
|
{
|
|
this.disposed = true;
|
|
this.args.AutoUpdate = false;
|
|
if (disposing)
|
|
Service<InterfaceManager>.GetNullable()?.EnqueueDeferredDispose(this);
|
|
else
|
|
((IDeferredDisposable)this).RealDispose();
|
|
}
|
|
}
|