mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Implement DrawListTextureWrap (#2036)
* Implement DrawListTextureWrap * Fix unloading * minor fixes * Add CreateFromClipboardAsync --------- Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
This commit is contained in:
parent
a12c63d6a2
commit
4dce0c00e8
25 changed files with 2821 additions and 35 deletions
|
|
@ -114,6 +114,13 @@
|
||||||
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\LineBreak.txt" LogicalName="LineBreak.txt" />
|
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\LineBreak.txt" LogicalName="LineBreak.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.ps.bin" />
|
||||||
|
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.vs.bin" />
|
||||||
|
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.ps.bin" />
|
||||||
|
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.vs.bin" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
|
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
|
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
|
||||||
|
|
|
||||||
282
Dalamud/Interface/Internal/StaThreadService.cs
Normal file
282
Dalamud/Interface/Internal/StaThreadService.cs
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Internal;
|
||||||
|
|
||||||
|
/// <summary>Dedicated thread for OLE operations, and possibly more native thread-serialized operations.</summary>
|
||||||
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
internal partial class StaThreadService : IInternalDisposableService
|
||||||
|
{
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
private readonly Thread thread;
|
||||||
|
private readonly ThreadBoundTaskScheduler taskScheduler;
|
||||||
|
private readonly TaskFactory taskFactory;
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource<HWND> messageReceiverHwndTask =
|
||||||
|
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private StaThreadService()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.thread = new(this.OleThreadBody);
|
||||||
|
this.thread.SetApartmentState(ApartmentState.STA);
|
||||||
|
|
||||||
|
this.taskScheduler = new(this.thread);
|
||||||
|
this.taskScheduler.TaskQueued += this.TaskSchedulerOnTaskQueued;
|
||||||
|
this.taskFactory = new(
|
||||||
|
this.cancellationTokenSource.Token,
|
||||||
|
TaskCreationOptions.None,
|
||||||
|
TaskContinuationOptions.None,
|
||||||
|
this.taskScheduler);
|
||||||
|
|
||||||
|
this.thread.Start();
|
||||||
|
this.messageReceiverHwndTask.Task.Wait();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.cancellationTokenSource.Cancel();
|
||||||
|
this.messageReceiverHwndTask.SetException(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets all the available clipboard formats.</summary>
|
||||||
|
public IReadOnlySet<uint> AvailableClipboardFormats { get; private set; } = ImmutableSortedSet<uint>.Empty;
|
||||||
|
|
||||||
|
/// <summary>Places a pointer to a specific data object onto the clipboard. This makes the data object accessible
|
||||||
|
/// to the <see cref="OleGetClipboard(IDataObject**)"/> function.</summary>
|
||||||
|
/// <param name="pdo">Pointer to the <see cref="IDataObject"/> interface on the data object from which the data to
|
||||||
|
/// be placed on the clipboard can be obtained. This parameter can be NULL; in which case the clipboard is emptied.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>This function returns <see cref="S.S_OK"/> on success.</returns>
|
||||||
|
[LibraryImport("ole32.dll")]
|
||||||
|
public static unsafe partial int OleSetClipboard(IDataObject* pdo);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="OleSetClipboard(IDataObject*)"/>
|
||||||
|
public static unsafe void OleSetClipboard(ComPtr<IDataObject> pdo) =>
|
||||||
|
Marshal.ThrowExceptionForHR(OleSetClipboard(pdo.Get()));
|
||||||
|
|
||||||
|
/// <summary>Retrieves a data object that you can use to access the contents of the clipboard.</summary>
|
||||||
|
/// <param name="pdo">Address of <see cref="IDataObject"/> pointer variable that receives the interface pointer to
|
||||||
|
/// the clipboard data object.</param>
|
||||||
|
/// <returns>This function returns <see cref="S.S_OK"/> on success.</returns>
|
||||||
|
[LibraryImport("ole32.dll")]
|
||||||
|
public static unsafe partial int OleGetClipboard(IDataObject** pdo);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="OleGetClipboard(IDataObject**)"/>
|
||||||
|
public static unsafe ComPtr<IDataObject> OleGetClipboard()
|
||||||
|
{
|
||||||
|
var pdo = default(ComPtr<IDataObject>);
|
||||||
|
Marshal.ThrowExceptionForHR(OleGetClipboard(pdo.GetAddressOf()));
|
||||||
|
return pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Calls the appropriate method or function to release the specified storage medium.</summary>
|
||||||
|
/// <param name="stgm">Address of <see cref="STGMEDIUM"/> to release.</param>
|
||||||
|
[LibraryImport("ole32.dll")]
|
||||||
|
public static unsafe partial void ReleaseStgMedium(STGMEDIUM* stgm);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ReleaseStgMedium(STGMEDIUM*)"/>
|
||||||
|
public static unsafe void ReleaseStgMedium(ref STGMEDIUM stgm)
|
||||||
|
{
|
||||||
|
fixed (STGMEDIUM* pstgm = &stgm)
|
||||||
|
ReleaseStgMedium(pstgm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.cancellationTokenSource.Cancel();
|
||||||
|
if (this.messageReceiverHwndTask.Task.IsCompletedSuccessfully)
|
||||||
|
SendMessageW(this.messageReceiverHwndTask.Task.Result, WM.WM_CLOSE, 0, 0);
|
||||||
|
|
||||||
|
this.thread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Runs a given delegate in the messaging thread.</summary>
|
||||||
|
/// <param name="action">Delegate to run.</param>
|
||||||
|
/// <param name="cancellationToken">Optional cancellation token.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> representating the state of the operation.</returns>
|
||||||
|
public async Task Run(Action action, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
|
this.cancellationTokenSource.Token,
|
||||||
|
cancellationToken);
|
||||||
|
await this.taskFactory.StartNew(action, cancellationToken).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Runs a given delegate in the messaging thread.</summary>
|
||||||
|
/// <typeparam name="T">Type of the return value.</typeparam>
|
||||||
|
/// <param name="func">Delegate to run.</param>
|
||||||
|
/// <param name="cancellationToken">Optional cancellation token.</param>
|
||||||
|
/// <returns>A <see cref="Task{T}"/> representating the state of the operation.</returns>
|
||||||
|
public async Task<T> Run<T>(Func<T> func, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
|
this.cancellationTokenSource.Token,
|
||||||
|
cancellationToken);
|
||||||
|
return await this.taskFactory.StartNew(func, cancellationToken).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Runs a given delegate in the messaging thread.</summary>
|
||||||
|
/// <param name="func">Delegate to run.</param>
|
||||||
|
/// <param name="cancellationToken">Optional cancellation token.</param>
|
||||||
|
/// <returns>A <see cref="Task{T}"/> representating the state of the operation.</returns>
|
||||||
|
public async Task Run(Func<Task> func, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
|
this.cancellationTokenSource.Token,
|
||||||
|
cancellationToken);
|
||||||
|
await await this.taskFactory.StartNew(func, cancellationToken).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Runs a given delegate in the messaging thread.</summary>
|
||||||
|
/// <typeparam name="T">Type of the return value.</typeparam>
|
||||||
|
/// <param name="func">Delegate to run.</param>
|
||||||
|
/// <param name="cancellationToken">Optional cancellation token.</param>
|
||||||
|
/// <returns>A <see cref="Task{T}"/> representating the state of the operation.</returns>
|
||||||
|
public async Task<T> Run<T>(Func<Task<T>> func, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
|
this.cancellationTokenSource.Token,
|
||||||
|
cancellationToken);
|
||||||
|
return await await this.taskFactory.StartNew(func, cancellationToken).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[LibraryImport("ole32.dll")]
|
||||||
|
private static partial int OleInitialize(nint reserved);
|
||||||
|
|
||||||
|
[LibraryImport("ole32.dll")]
|
||||||
|
private static partial void OleUninitialize();
|
||||||
|
|
||||||
|
[LibraryImport("ole32.dll")]
|
||||||
|
private static partial int OleFlushClipboard();
|
||||||
|
|
||||||
|
private void TaskSchedulerOnTaskQueued() =>
|
||||||
|
PostMessageW(this.messageReceiverHwndTask.Task.Result, WM.WM_NULL, 0, 0);
|
||||||
|
|
||||||
|
private void UpdateAvailableClipboardFormats(HWND hWnd)
|
||||||
|
{
|
||||||
|
if (!OpenClipboard(hWnd))
|
||||||
|
{
|
||||||
|
this.AvailableClipboardFormats = ImmutableSortedSet<uint>.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formats = new SortedSet<uint>();
|
||||||
|
for (var cf = EnumClipboardFormats(0); cf != 0; cf = EnumClipboardFormats(cf))
|
||||||
|
formats.Add(cf);
|
||||||
|
this.AvailableClipboardFormats = formats;
|
||||||
|
CloseClipboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LRESULT MessageReceiverWndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
this.taskScheduler.Run();
|
||||||
|
|
||||||
|
switch (uMsg)
|
||||||
|
{
|
||||||
|
case WM.WM_CLIPBOARDUPDATE:
|
||||||
|
this.UpdateAvailableClipboardFormats(hWnd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_DESTROY:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void OleThreadBody()
|
||||||
|
{
|
||||||
|
var hInstance = (HINSTANCE)Marshal.GetHINSTANCE(typeof(StaThreadService).Module);
|
||||||
|
ushort wndClassAtom = 0;
|
||||||
|
var gch = GCHandle.Alloc(this);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
((HRESULT)OleInitialize(0)).ThrowOnError();
|
||||||
|
|
||||||
|
fixed (char* name = typeof(StaThreadService).FullName!)
|
||||||
|
{
|
||||||
|
var wndClass = new WNDCLASSEXW
|
||||||
|
{
|
||||||
|
cbSize = (uint)sizeof(WNDCLASSEXW),
|
||||||
|
lpfnWndProc = &MessageReceiverWndProcStatic,
|
||||||
|
hInstance = hInstance,
|
||||||
|
hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1),
|
||||||
|
lpszClassName = (ushort*)name,
|
||||||
|
};
|
||||||
|
|
||||||
|
wndClassAtom = RegisterClassExW(&wndClass);
|
||||||
|
if (wndClassAtom == 0)
|
||||||
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||||
|
|
||||||
|
this.messageReceiverHwndTask.SetResult(
|
||||||
|
CreateWindowExW(
|
||||||
|
0,
|
||||||
|
(ushort*)wndClassAtom,
|
||||||
|
(ushort*)name,
|
||||||
|
0,
|
||||||
|
CW_USEDEFAULT,
|
||||||
|
CW_USEDEFAULT,
|
||||||
|
CW_USEDEFAULT,
|
||||||
|
CW_USEDEFAULT,
|
||||||
|
default,
|
||||||
|
default,
|
||||||
|
hInstance,
|
||||||
|
(void*)GCHandle.ToIntPtr(gch)));
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static LRESULT MessageReceiverWndProcStatic(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
nint gchn;
|
||||||
|
if (uMsg == WM.WM_NCCREATE)
|
||||||
|
{
|
||||||
|
gchn = (nint)((CREATESTRUCTW*)lParam)->lpCreateParams;
|
||||||
|
SetWindowLongPtrW(hWnd, GWLP.GWLP_USERDATA, gchn);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gchn = GetWindowLongPtrW(hWnd, GWLP.GWLP_USERDATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gchn == 0)
|
||||||
|
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
|
||||||
|
|
||||||
|
return ((StaThreadService)GCHandle.FromIntPtr(gchn).Target!)
|
||||||
|
.MessageReceiverWndProc(hWnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddClipboardFormatListener(this.messageReceiverHwndTask.Task.Result);
|
||||||
|
this.UpdateAvailableClipboardFormats(this.messageReceiverHwndTask.Task.Result);
|
||||||
|
|
||||||
|
for (MSG msg; GetMessageW(&msg, default, 0, 0);)
|
||||||
|
{
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageW(&msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
gch.Free();
|
||||||
|
_ = OleFlushClipboard();
|
||||||
|
OleUninitialize();
|
||||||
|
if (wndClassAtom != 0)
|
||||||
|
UnregisterClassW((ushort*)wndClassAtom, hInstance);
|
||||||
|
this.messageReceiverHwndTask.TrySetException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -182,6 +182,15 @@ internal class TexWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
|
ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
|
||||||
|
|
||||||
|
if (!this.textureManager.HasClipboardImage())
|
||||||
|
{
|
||||||
|
ImGuiComponents.DisabledButton("Paste from Clipboard");
|
||||||
|
}
|
||||||
|
else if (ImGui.Button("Paste from Clipboard"))
|
||||||
|
{
|
||||||
|
this.addedTextures.Add(new(Api10: this.textureManager.CreateFromClipboardAsync()));
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon)))
|
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon)))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawGetFromGameIcon));
|
ImGui.PushID(nameof(this.DrawGetFromGameIcon));
|
||||||
|
|
|
||||||
498
Dalamud/Interface/Textures/Internal/TextureManager.Clipboard.cs
Normal file
498
Dalamud/Interface/Textures/Internal/TextureManager.Clipboard.cs
Normal file
|
|
@ -0,0 +1,498 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
using TerraFX.Interop.DirectX;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Textures.Internal;
|
||||||
|
|
||||||
|
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||||
|
internal sealed partial class TextureManager
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task CopyToClipboardAsync(
|
||||||
|
IDalamudTextureWrap wrap,
|
||||||
|
string? preferredFileNameWithoutExtension = null,
|
||||||
|
bool leaveWrapOpen = false,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this);
|
||||||
|
|
||||||
|
using var wrapAux = new WrapAux(wrap, leaveWrapOpen);
|
||||||
|
bool hasAlphaChannel;
|
||||||
|
switch (wrapAux.Desc.Format)
|
||||||
|
{
|
||||||
|
case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM:
|
||||||
|
case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
|
||||||
|
hasAlphaChannel = false;
|
||||||
|
break;
|
||||||
|
case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM:
|
||||||
|
case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
|
||||||
|
case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM:
|
||||||
|
case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
||||||
|
hasAlphaChannel = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await this.CopyToClipboardAsync(
|
||||||
|
await this.CreateFromExistingTextureAsync(
|
||||||
|
wrap,
|
||||||
|
new() { Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM },
|
||||||
|
cancellationToken: cancellationToken),
|
||||||
|
preferredFileNameWithoutExtension,
|
||||||
|
false,
|
||||||
|
cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/15689541/win32-clipboard-and-alpha-channel-images
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/shell/clipboard
|
||||||
|
using var pdo = default(ComPtr<IDataObject>);
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (Guid* piid = &IID.IID_IDataObject)
|
||||||
|
SHCreateDataObject(null, 1, null, null, piid, (void**)pdo.GetAddressOf()).ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
{
|
||||||
|
ms.SetLength(ms.Position = 0);
|
||||||
|
await this.SaveToStreamAsync(
|
||||||
|
wrap,
|
||||||
|
GUID.GUID_ContainerFormatPng,
|
||||||
|
ms,
|
||||||
|
new Dictionary<string, object> { ["InterlaceOption"] = true },
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
using var ims = default(ComPtr<IStream>);
|
||||||
|
fixed (byte* p = ms.GetBuffer())
|
||||||
|
ims.Attach(SHCreateMemStream(p, (uint)ms.Length));
|
||||||
|
if (ims.IsEmpty())
|
||||||
|
throw new OutOfMemoryException();
|
||||||
|
|
||||||
|
AddToDataObject(
|
||||||
|
pdo,
|
||||||
|
ClipboardFormats.Png,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_ISTREAM,
|
||||||
|
pstm = ims.Get(),
|
||||||
|
});
|
||||||
|
AddToDataObject(
|
||||||
|
pdo,
|
||||||
|
ClipboardFormats.FileContents,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_ISTREAM,
|
||||||
|
pstm = ims.Get(),
|
||||||
|
});
|
||||||
|
ims.Get()->AddRef();
|
||||||
|
ims.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredFileNameWithoutExtension is not null)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
preferredFileNameWithoutExtension += ".png";
|
||||||
|
if (preferredFileNameWithoutExtension.Length >= 260)
|
||||||
|
preferredFileNameWithoutExtension = preferredFileNameWithoutExtension[..^4] + ".png";
|
||||||
|
var namea = (CodePagesEncodingProvider.Instance.GetEncoding(0) ?? Encoding.UTF8)
|
||||||
|
.GetBytes(preferredFileNameWithoutExtension);
|
||||||
|
if (namea.Length > 260)
|
||||||
|
{
|
||||||
|
namea.AsSpan()[^4..].CopyTo(namea.AsSpan(256, 4));
|
||||||
|
Array.Resize(ref namea, 260);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fgda = new FILEGROUPDESCRIPTORA
|
||||||
|
{
|
||||||
|
cItems = 1,
|
||||||
|
fgd = new()
|
||||||
|
{
|
||||||
|
e0 = new()
|
||||||
|
{
|
||||||
|
dwFlags = unchecked((uint)FD_FLAGS.FD_FILESIZE | (uint)FD_FLAGS.FD_UNICODE),
|
||||||
|
nFileSizeHigh = (uint)(ms.Length >> 32),
|
||||||
|
nFileSizeLow = (uint)ms.Length,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
namea.AsSpan().CopyTo(new(fgda.fgd.e0.cFileName, 260));
|
||||||
|
|
||||||
|
AddToDataObject(
|
||||||
|
pdo,
|
||||||
|
ClipboardFormats.FileDescriptorA,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_HGLOBAL,
|
||||||
|
hGlobal = CreateHGlobalFromMemory<FILEGROUPDESCRIPTORA>(new(ref fgda)),
|
||||||
|
});
|
||||||
|
|
||||||
|
var fgdw = new FILEGROUPDESCRIPTORW
|
||||||
|
{
|
||||||
|
cItems = 1,
|
||||||
|
fgd = new()
|
||||||
|
{
|
||||||
|
e0 = new()
|
||||||
|
{
|
||||||
|
dwFlags = unchecked((uint)FD_FLAGS.FD_FILESIZE | (uint)FD_FLAGS.FD_UNICODE),
|
||||||
|
nFileSizeHigh = (uint)(ms.Length >> 32),
|
||||||
|
nFileSizeLow = (uint)ms.Length,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
preferredFileNameWithoutExtension.AsSpan().CopyTo(new(fgdw.fgd.e0.cFileName, 260));
|
||||||
|
|
||||||
|
AddToDataObject(
|
||||||
|
pdo,
|
||||||
|
ClipboardFormats.FileDescriptorW,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_HGLOBAL,
|
||||||
|
hGlobal = CreateHGlobalFromMemory<FILEGROUPDESCRIPTORW>(new(ref fgdw)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ms.SetLength(ms.Position = 0);
|
||||||
|
await this.SaveToStreamAsync(
|
||||||
|
wrap,
|
||||||
|
GUID.GUID_ContainerFormatBmp,
|
||||||
|
ms,
|
||||||
|
new Dictionary<string, object> { ["EnableV5Header32bppBGRA"] = false },
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
cancellationToken);
|
||||||
|
AddToDataObject(
|
||||||
|
pdo,
|
||||||
|
CF.CF_DIB,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_HGLOBAL,
|
||||||
|
hGlobal = CreateHGlobalFromMemory<byte>(
|
||||||
|
ms.GetBuffer().AsSpan(0, (int)ms.Length)[Unsafe.SizeOf<BITMAPFILEHEADER>()..]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAlphaChannel)
|
||||||
|
{
|
||||||
|
ms.SetLength(ms.Position = 0);
|
||||||
|
await this.SaveToStreamAsync(
|
||||||
|
wrap,
|
||||||
|
GUID.GUID_ContainerFormatBmp,
|
||||||
|
ms,
|
||||||
|
new Dictionary<string, object> { ["EnableV5Header32bppBGRA"] = true },
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
cancellationToken);
|
||||||
|
AddToDataObject(
|
||||||
|
pdo,
|
||||||
|
CF.CF_DIBV5,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_HGLOBAL,
|
||||||
|
hGlobal = CreateHGlobalFromMemory<byte>(
|
||||||
|
ms.GetBuffer().AsSpan(0, (int)ms.Length)[Unsafe.SizeOf<BITMAPFILEHEADER>()..]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var omts = await Service<StaThreadService>.GetAsync();
|
||||||
|
await omts.Run(() => StaThreadService.OleSetClipboard(pdo), cancellationToken);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
static unsafe void AddToDataObject(ComPtr<IDataObject> pdo, uint clipboardFormat, STGMEDIUM stg)
|
||||||
|
{
|
||||||
|
var fec = new FORMATETC
|
||||||
|
{
|
||||||
|
cfFormat = (ushort)clipboardFormat,
|
||||||
|
ptd = null,
|
||||||
|
dwAspect = (uint)DVASPECT.DVASPECT_CONTENT,
|
||||||
|
lindex = 0,
|
||||||
|
tymed = stg.tymed,
|
||||||
|
};
|
||||||
|
pdo.Get()->SetData(&fec, &stg, true).ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsafe HGLOBAL CreateHGlobalFromMemory<T>(ReadOnlySpan<T> data) where T : unmanaged
|
||||||
|
{
|
||||||
|
var h = GlobalAlloc(GMEM.GMEM_MOVEABLE, (nuint)(data.Length * sizeof(T)));
|
||||||
|
if (h == 0)
|
||||||
|
throw new OutOfMemoryException("Failed to allocate.");
|
||||||
|
|
||||||
|
var p = GlobalLock(h);
|
||||||
|
data.CopyTo(new(p, data.Length));
|
||||||
|
GlobalUnlock(h);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool HasClipboardImage()
|
||||||
|
{
|
||||||
|
var acf = Service<StaThreadService>.Get().AvailableClipboardFormats;
|
||||||
|
return acf.Contains(CF.CF_DIBV5)
|
||||||
|
|| acf.Contains(CF.CF_DIB)
|
||||||
|
|| acf.Contains(ClipboardFormats.Png)
|
||||||
|
|| acf.Contains(ClipboardFormats.FileContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<IDalamudTextureWrap> CreateFromClipboardAsync(
|
||||||
|
string? debugName = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var omts = await Service<StaThreadService>.GetAsync();
|
||||||
|
var (stgm, clipboardFormat) = await omts.Run(GetSupportedClipboardData, cancellationToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.BlameSetName(
|
||||||
|
await this.DynamicPriorityTextureLoader.LoadAsync(
|
||||||
|
null,
|
||||||
|
ct =>
|
||||||
|
clipboardFormat is CF.CF_DIB or CF.CF_DIBV5
|
||||||
|
? CreateTextureFromStorageMediumDib(this, stgm, ct)
|
||||||
|
: CreateTextureFromStorageMedium(this, stgm, ct),
|
||||||
|
cancellationToken),
|
||||||
|
debugName ?? $"{nameof(this.CreateFromClipboardAsync)}({(TYMED)stgm.tymed})");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
StaThreadService.ReleaseStgMedium(ref stgm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a CF_DIB/V5 format to a full BMP format, for WIC consumption.
|
||||||
|
static unsafe Task<IDalamudTextureWrap> CreateTextureFromStorageMediumDib(
|
||||||
|
TextureManager textureManager,
|
||||||
|
scoped in STGMEDIUM stgm,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
switch ((TYMED)stgm.tymed)
|
||||||
|
{
|
||||||
|
case TYMED.TYMED_HGLOBAL when stgm.hGlobal != default:
|
||||||
|
{
|
||||||
|
var pMem = GlobalLock(stgm.hGlobal);
|
||||||
|
if (pMem is null)
|
||||||
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var size = (int)GlobalSize(stgm.hGlobal);
|
||||||
|
ms.SetLength(sizeof(BITMAPFILEHEADER) + size);
|
||||||
|
new ReadOnlySpan<byte>(pMem, size).CopyTo(ms.GetBuffer().AsSpan(sizeof(BITMAPFILEHEADER)));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GlobalUnlock(stgm.hGlobal);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TYMED.TYMED_ISTREAM when stgm.pstm is not null:
|
||||||
|
{
|
||||||
|
STATSTG stat;
|
||||||
|
if (stgm.pstm->Stat(&stat, (uint)STATFLAG.STATFLAG_NONAME).SUCCEEDED && stat.cbSize.QuadPart > 0)
|
||||||
|
ms.SetLength(sizeof(BITMAPFILEHEADER) + (int)stat.cbSize.QuadPart);
|
||||||
|
else
|
||||||
|
ms.SetLength(8192);
|
||||||
|
|
||||||
|
var offset = (uint)sizeof(BITMAPFILEHEADER);
|
||||||
|
for (var read = 1u; read != 0;)
|
||||||
|
{
|
||||||
|
if (offset == ms.Length)
|
||||||
|
ms.SetLength(ms.Length * 2);
|
||||||
|
fixed (byte* pMem = ms.GetBuffer().AsSpan((int)offset))
|
||||||
|
{
|
||||||
|
stgm.pstm->Read(pMem, (uint)(ms.Length - offset), &read).ThrowOnError();
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.SetLength(offset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Task.FromException<IDalamudTextureWrap>(new NotSupportedException());
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var bfh = ref Unsafe.As<byte, BITMAPFILEHEADER>(ref ms.GetBuffer()[0]);
|
||||||
|
bfh.bfType = 0x4D42;
|
||||||
|
bfh.bfSize = (uint)ms.Length;
|
||||||
|
|
||||||
|
ref var bih = ref Unsafe.As<byte, BITMAPINFOHEADER>(ref ms.GetBuffer()[sizeof(BITMAPFILEHEADER)]);
|
||||||
|
bfh.bfOffBits = (uint)(sizeof(BITMAPFILEHEADER) + bih.biSize);
|
||||||
|
|
||||||
|
if (bih.biSize >= sizeof(BITMAPINFOHEADER))
|
||||||
|
{
|
||||||
|
if (bih.biBitCount > 8)
|
||||||
|
{
|
||||||
|
if (bih.biCompression == BI.BI_BITFIELDS)
|
||||||
|
bfh.bfOffBits += (uint)(3 * sizeof(RGBQUAD));
|
||||||
|
else if (bih.biCompression == 6 /* BI_ALPHABITFIELDS */)
|
||||||
|
bfh.bfOffBits += (uint)(4 * sizeof(RGBQUAD));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bih.biClrUsed > 0)
|
||||||
|
bfh.bfOffBits += (uint)(bih.biClrUsed * sizeof(RGBQUAD));
|
||||||
|
else if (bih.biBitCount <= 8)
|
||||||
|
bfh.bfOffBits += (uint)(sizeof(RGBQUAD) << bih.biBitCount);
|
||||||
|
|
||||||
|
using var pinned = ms.GetBuffer().AsMemory().Pin();
|
||||||
|
using var strm = textureManager.Wic.CreateIStreamViewOfMemory(pinned, (int)ms.Length);
|
||||||
|
return Task.FromResult(textureManager.Wic.NoThrottleCreateFromWicStream(strm, ct));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interprets a data as an image file using WIC.
|
||||||
|
static unsafe Task<IDalamudTextureWrap> CreateTextureFromStorageMedium(
|
||||||
|
TextureManager textureManager,
|
||||||
|
scoped in STGMEDIUM stgm,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
switch ((TYMED)stgm.tymed)
|
||||||
|
{
|
||||||
|
case TYMED.TYMED_HGLOBAL when stgm.hGlobal != default:
|
||||||
|
{
|
||||||
|
var pMem = GlobalLock(stgm.hGlobal);
|
||||||
|
if (pMem is null)
|
||||||
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var size = (int)GlobalSize(stgm.hGlobal);
|
||||||
|
using var strm = textureManager.Wic.CreateIStreamViewOfMemory(pMem, size);
|
||||||
|
return Task.FromResult(textureManager.Wic.NoThrottleCreateFromWicStream(strm, ct));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GlobalUnlock(stgm.hGlobal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case TYMED.TYMED_FILE when stgm.lpszFileName is not null:
|
||||||
|
{
|
||||||
|
var fileName = MemoryHelper.ReadString((nint)stgm.lpszFileName, Encoding.Unicode, short.MaxValue);
|
||||||
|
return textureManager.NoThrottleCreateFromFileAsync(fileName, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
case TYMED.TYMED_ISTREAM when stgm.pstm is not null:
|
||||||
|
{
|
||||||
|
using var strm = new ComPtr<IStream>(stgm.pstm);
|
||||||
|
return Task.FromResult(textureManager.Wic.NoThrottleCreateFromWicStream(strm, ct));
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Task.FromException<IDalamudTextureWrap>(new NotSupportedException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsafe bool TryGetClipboardDataAs(
|
||||||
|
ComPtr<IDataObject> pdo,
|
||||||
|
uint clipboardFormat,
|
||||||
|
uint tymed,
|
||||||
|
out STGMEDIUM stgm)
|
||||||
|
{
|
||||||
|
var fec = new FORMATETC
|
||||||
|
{
|
||||||
|
cfFormat = (ushort)clipboardFormat,
|
||||||
|
ptd = null,
|
||||||
|
dwAspect = (uint)DVASPECT.DVASPECT_CONTENT,
|
||||||
|
lindex = -1,
|
||||||
|
tymed = tymed,
|
||||||
|
};
|
||||||
|
fixed (STGMEDIUM* pstgm = &stgm)
|
||||||
|
return pdo.Get()->GetData(&fec, pstgm).SUCCEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a data from clipboard for use with WIC.
|
||||||
|
static unsafe (STGMEDIUM Stgm, uint ClipboardFormat) GetSupportedClipboardData()
|
||||||
|
{
|
||||||
|
using var pdo = StaThreadService.OleGetClipboard();
|
||||||
|
const uint tymeds = (uint)TYMED.TYMED_HGLOBAL |
|
||||||
|
(uint)TYMED.TYMED_FILE |
|
||||||
|
(uint)TYMED.TYMED_ISTREAM;
|
||||||
|
const uint sharedRead = STGM.STGM_READ | STGM.STGM_SHARE_DENY_WRITE;
|
||||||
|
|
||||||
|
// Try taking data from clipboard as-is.
|
||||||
|
if (TryGetClipboardDataAs(pdo, CF.CF_DIBV5, tymeds, out var stgm))
|
||||||
|
return (stgm, CF.CF_DIBV5);
|
||||||
|
if (TryGetClipboardDataAs(pdo, ClipboardFormats.FileContents, tymeds, out stgm))
|
||||||
|
return (stgm, ClipboardFormats.FileContents);
|
||||||
|
if (TryGetClipboardDataAs(pdo, ClipboardFormats.Png, tymeds, out stgm))
|
||||||
|
return (stgm, ClipboardFormats.Png);
|
||||||
|
if (TryGetClipboardDataAs(pdo, CF.CF_DIB, tymeds, out stgm))
|
||||||
|
return (stgm, CF.CF_DIB);
|
||||||
|
|
||||||
|
// Try reading file from the path stored in clipboard.
|
||||||
|
if (TryGetClipboardDataAs(pdo, ClipboardFormats.FileNameW, (uint)TYMED.TYMED_HGLOBAL, out stgm))
|
||||||
|
{
|
||||||
|
var pPath = GlobalLock(stgm.hGlobal);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IStream* pfs;
|
||||||
|
SHCreateStreamOnFileW((ushort*)pPath, sharedRead, &pfs).ThrowOnError();
|
||||||
|
|
||||||
|
var stgm2 = new STGMEDIUM
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_ISTREAM,
|
||||||
|
pstm = pfs,
|
||||||
|
pUnkForRelease = (IUnknown*)pfs,
|
||||||
|
};
|
||||||
|
return (stgm2, ClipboardFormats.FileContents);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (pPath is not null)
|
||||||
|
GlobalUnlock(stgm.hGlobal);
|
||||||
|
StaThreadService.ReleaseStgMedium(ref stgm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetClipboardDataAs(pdo, ClipboardFormats.FileNameA, (uint)TYMED.TYMED_HGLOBAL, out stgm))
|
||||||
|
{
|
||||||
|
var pPath = GlobalLock(stgm.hGlobal);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IStream* pfs;
|
||||||
|
SHCreateStreamOnFileA((sbyte*)pPath, sharedRead, &pfs).ThrowOnError();
|
||||||
|
|
||||||
|
var stgm2 = new STGMEDIUM
|
||||||
|
{
|
||||||
|
tymed = (uint)TYMED.TYMED_ISTREAM,
|
||||||
|
pstm = pfs,
|
||||||
|
pUnkForRelease = (IUnknown*)pfs,
|
||||||
|
};
|
||||||
|
return (stgm2, ClipboardFormats.FileContents);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (pPath is not null)
|
||||||
|
GlobalUnlock(stgm.hGlobal);
|
||||||
|
StaThreadService.ReleaseStgMedium(ref stgm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("No compatible clipboard format found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Interface.Internal;
|
|
||||||
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
@ -173,7 +172,7 @@ internal sealed partial class TextureManager
|
||||||
ReadOnlyMemory<byte> bytes,
|
ReadOnlyMemory<byte> bytes,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this);
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -204,7 +203,7 @@ internal sealed partial class TextureManager
|
||||||
string path,
|
string path,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this);
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -359,11 +358,18 @@ internal sealed partial class TextureManager
|
||||||
/// <param name="handle">An instance of <see cref="MemoryHandle"/>.</param>
|
/// <param name="handle">An instance of <see cref="MemoryHandle"/>.</param>
|
||||||
/// <param name="length">The number of bytes in the memory.</param>
|
/// <param name="length">The number of bytes in the memory.</param>
|
||||||
/// <returns>The new instance of <see cref="IStream"/>.</returns>
|
/// <returns>The new instance of <see cref="IStream"/>.</returns>
|
||||||
public unsafe ComPtr<IStream> CreateIStreamViewOfMemory(MemoryHandle handle, int length)
|
public unsafe ComPtr<IStream> CreateIStreamViewOfMemory(MemoryHandle handle, int length) =>
|
||||||
|
this.CreateIStreamViewOfMemory((byte*)handle.Pointer, length);
|
||||||
|
|
||||||
|
/// <summary>Creates a new instance of <see cref="IStream"/> from a fixed memory allocation.</summary>
|
||||||
|
/// <param name="address">Address of the data.</param>
|
||||||
|
/// <param name="length">The number of bytes in the memory.</param>
|
||||||
|
/// <returns>The new instance of <see cref="IStream"/>.</returns>
|
||||||
|
public unsafe ComPtr<IStream> CreateIStreamViewOfMemory(void* address, int length)
|
||||||
{
|
{
|
||||||
using var wicStream = default(ComPtr<IWICStream>);
|
using var wicStream = default(ComPtr<IWICStream>);
|
||||||
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
|
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
|
||||||
wicStream.Get()->InitializeFromMemory((byte*)handle.Pointer, checked((uint)length)).ThrowOnError();
|
wicStream.Get()->InitializeFromMemory((byte*)address, checked((uint)length)).ThrowOnError();
|
||||||
|
|
||||||
var res = default(ComPtr<IStream>);
|
var res = default(ComPtr<IStream>);
|
||||||
wicStream.As(ref res).ThrowOnError();
|
wicStream.As(ref res).ThrowOnError();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.Interface.Textures.TextureWraps.Internal;
|
using Dalamud.Interface.Textures.TextureWraps.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.TerraFxCom;
|
using Dalamud.Utility.TerraFxCom;
|
||||||
|
|
||||||
|
|
@ -48,10 +50,11 @@ internal sealed partial class TextureManager
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource disposeCts = new();
|
||||||
|
|
||||||
private DynamicPriorityQueueLoader? dynamicPriorityTextureLoader;
|
private DynamicPriorityQueueLoader? dynamicPriorityTextureLoader;
|
||||||
private SharedTextureManager? sharedTextureManager;
|
private SharedTextureManager? sharedTextureManager;
|
||||||
private WicManager? wicManager;
|
private WicManager? wicManager;
|
||||||
private bool disposing;
|
|
||||||
private ComPtr<ID3D11Device> device;
|
private ComPtr<ID3D11Device> device;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
|
|
@ -104,10 +107,10 @@ internal sealed partial class TextureManager
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
if (this.disposing)
|
if (this.disposeCts.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.disposing = true;
|
this.disposeCts.Cancel();
|
||||||
|
|
||||||
Interlocked.Exchange(ref this.dynamicPriorityTextureLoader, null)?.Dispose();
|
Interlocked.Exchange(ref this.dynamicPriorityTextureLoader, null)?.Dispose();
|
||||||
Interlocked.Exchange(ref this.simpleDrawer, null)?.Dispose();
|
Interlocked.Exchange(ref this.simpleDrawer, null)?.Dispose();
|
||||||
|
|
@ -269,6 +272,21 @@ internal sealed partial class TextureManager
|
||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IDrawListTextureWrap CreateDrawListTexture(string? debugName = null) => this.CreateDrawListTexture(null, debugName);
|
||||||
|
|
||||||
|
/// <summary><inheritdoc cref="CreateDrawListTexture(string?)" path="/summary"/></summary>
|
||||||
|
/// <param name="plugin">Plugin that created the draw list.</param>
|
||||||
|
/// <param name="debugName"><inheritdoc cref="CreateDrawListTexture(string?)" path="/param[name=debugName]"/></param>
|
||||||
|
/// <returns><inheritdoc cref="CreateDrawListTexture(string?)" path="/returns"/></returns>
|
||||||
|
public IDrawListTextureWrap CreateDrawListTexture(LocalPlugin? plugin, string? debugName = null) =>
|
||||||
|
new DrawListTextureWrap(
|
||||||
|
new(this.device),
|
||||||
|
this,
|
||||||
|
Service<DalamudAssetManager>.Get().Empty4X4,
|
||||||
|
plugin,
|
||||||
|
debugName ?? $"{nameof(this.CreateDrawListTexture)}");
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
|
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
|
||||||
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
|
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
|
||||||
|
|
@ -330,7 +348,7 @@ internal sealed partial class TextureManager
|
||||||
/// <returns>The loaded texture.</returns>
|
/// <returns>The loaded texture.</returns>
|
||||||
internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile file)
|
internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile file)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this);
|
||||||
|
|
||||||
var buffer = file.TextureBuffer;
|
var buffer = file.TextureBuffer;
|
||||||
var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false);
|
var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false);
|
||||||
|
|
@ -354,7 +372,7 @@ internal sealed partial class TextureManager
|
||||||
/// <returns>The loaded texture.</returns>
|
/// <returns>The loaded texture.</returns>
|
||||||
internal IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan<byte> fileBytes)
|
internal IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan<byte> fileBytes)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this);
|
||||||
|
|
||||||
if (!TexFileExtensions.IsPossiblyTexFile2D(fileBytes))
|
if (!TexFileExtensions.IsPossiblyTexFile2D(fileBytes))
|
||||||
throw new InvalidDataException("The file is not a TexFile.");
|
throw new InvalidDataException("The file is not a TexFile.");
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,10 @@ internal sealed class TextureManagerPluginScoped
|
||||||
return textureWrap;
|
return textureWrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IDrawListTextureWrap CreateDrawListTexture(string? debugName = null) =>
|
||||||
|
this.ManagerOrThrow.CreateDrawListTexture(this.plugin, debugName);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<IDalamudTextureWrap> CreateFromExistingTextureAsync(
|
public async Task<IDalamudTextureWrap> CreateFromExistingTextureAsync(
|
||||||
IDalamudTextureWrap wrap,
|
IDalamudTextureWrap wrap,
|
||||||
|
|
@ -267,6 +271,17 @@ internal sealed class TextureManagerPluginScoped
|
||||||
return textureWrap;
|
return textureWrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<IDalamudTextureWrap> CreateFromClipboardAsync(
|
||||||
|
string? debugName = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var manager = await this.ManagerTask;
|
||||||
|
var textureWrap = await manager.CreateFromClipboardAsync(debugName, cancellationToken);
|
||||||
|
manager.Blame(textureWrap, this.plugin);
|
||||||
|
return textureWrap;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
|
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
|
||||||
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
|
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
|
||||||
|
|
@ -279,6 +294,9 @@ internal sealed class TextureManagerPluginScoped
|
||||||
return shared;
|
return shared;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool HasClipboardImage() => this.ManagerOrThrow.HasClipboardImage();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool TryGetFromGameIcon(in GameIconLookup lookup, [NotNullWhen(true)] out ISharedImmediateTexture? texture)
|
public bool TryGetFromGameIcon(in GameIconLookup lookup, [NotNullWhen(true)] out ISharedImmediateTexture? texture)
|
||||||
{
|
{
|
||||||
|
|
@ -411,6 +429,17 @@ internal sealed class TextureManagerPluginScoped
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task CopyToClipboardAsync(
|
||||||
|
IDalamudTextureWrap wrap,
|
||||||
|
string? preferredFileNameWithoutExtension = null,
|
||||||
|
bool leaveWrapOpen = false,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var manager = await this.ManagerTask;
|
||||||
|
await manager.CopyToClipboardAsync(wrap, preferredFileNameWithoutExtension, leaveWrapOpen, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
private void ResultOnInterceptTexDataLoad(string path, ref string? replacementPath) =>
|
private void ResultOnInterceptTexDataLoad(string path, ref string? replacementPath) =>
|
||||||
this.InterceptTexDataLoad?.Invoke(path, ref replacementPath);
|
this.InterceptTexDataLoad?.Invoke(path, ref replacementPath);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps.Internal;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
|
||||||
|
/// <summary>A texture wrap that can be drawn using ImGui draw data.</summary>
|
||||||
|
public interface IDrawListTextureWrap : IDalamudTextureWrap
|
||||||
|
{
|
||||||
|
/// <summary>Gets or sets the width of the texture.</summary>
|
||||||
|
/// <remarks>If <see cref="Height"/> is to be set together, set use <see cref="Size"/> instead.</remarks>
|
||||||
|
new int Width { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the width of the texture.</summary>
|
||||||
|
/// <remarks>If <see cref="Width"/> is to be set together, set use <see cref="Size"/> instead.</remarks>
|
||||||
|
new int Height { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the size of the texture.</summary>
|
||||||
|
/// <remarks>Components will be rounded up.</remarks>
|
||||||
|
new Vector2 Size { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IDalamudTextureWrap.Width => this.Width;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IDalamudTextureWrap.Height => this.Height;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
Vector2 IDalamudTextureWrap.Size => this.Size;
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the color to use when clearing this texture.</summary>
|
||||||
|
/// <value>Color in RGBA. Defaults to <see cref="Vector4.Zero"/>, which is full transparency.</value>
|
||||||
|
Vector4 ClearColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Draws a draw list to this texture.</summary>
|
||||||
|
/// <param name="drawListPtr">Draw list to draw from.</param>
|
||||||
|
/// <param name="displayPos">Left-top coordinates of the draw commands in the draw list.</param>
|
||||||
|
/// <param name="scale">Scale to apply to all draw commands in the draw list.</param>
|
||||||
|
/// <remarks>This function can be called only from the main thread.</remarks>
|
||||||
|
void Draw(ImDrawListPtr drawListPtr, Vector2 displayPos, Vector2 scale);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="DrawListTextureWrap.Draw(ImGuiNET.ImDrawDataPtr)"/>
|
||||||
|
void Draw(scoped in ImDrawData drawData);
|
||||||
|
|
||||||
|
/// <summary>Draws from a draw data to this texture.</summary>
|
||||||
|
/// <param name="drawData">Draw data to draw.</param>
|
||||||
|
/// <remarks><ul>
|
||||||
|
/// <li>Texture size will be kept as specified in <see cref="Size"/>. <see cref="ImDrawData.DisplaySize"/> will be
|
||||||
|
/// used only as shader parameters.</li>
|
||||||
|
/// <li>This function can be called only from the main thread.</li>
|
||||||
|
/// </ul></remarks>
|
||||||
|
void Draw(ImDrawDataPtr drawData);
|
||||||
|
|
||||||
|
/// <summary>Resizes this texture and draws an ImGui window.</summary>
|
||||||
|
/// <param name="windowName">Name and ID of the window to draw. Use the value that goes into
|
||||||
|
/// <see cref="ImGui.Begin(string)"/>.</param>
|
||||||
|
/// <param name="scale">Scale to apply to all draw commands in the draw list.</param>
|
||||||
|
void ResizeAndDrawWindow(ReadOnlySpan<char> windowName, Vector2 scale);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Textures.Internal;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using TerraFX.Interop.DirectX;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Textures.TextureWraps.Internal;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDrawListTextureWrap"/>
|
||||||
|
internal sealed unsafe partial class DrawListTextureWrap : IDrawListTextureWrap, IDeferredDisposable
|
||||||
|
{
|
||||||
|
private readonly TextureManager textureManager;
|
||||||
|
private readonly IDalamudTextureWrap emptyTexture;
|
||||||
|
private readonly LocalPlugin? plugin;
|
||||||
|
private readonly string debugName;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11Device> device;
|
||||||
|
private ComPtr<ID3D11DeviceContext> deviceContext;
|
||||||
|
private ComPtr<ID3D11Texture2D> tex;
|
||||||
|
private ComPtr<ID3D11ShaderResourceView> srv;
|
||||||
|
private ComPtr<ID3D11RenderTargetView> rtv;
|
||||||
|
private ComPtr<ID3D11UnorderedAccessView> uav;
|
||||||
|
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
private DXGI_FORMAT format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="DrawListTextureWrap"/> class.</summary>
|
||||||
|
/// <param name="device">Pointer to a D3D11 device. Ownership is taken.</param>
|
||||||
|
/// <param name="textureManager">Instance of the <see cref="ITextureProvider"/> class.</param>
|
||||||
|
/// <param name="emptyTexture">Texture to use, if <see cref="Width"/> or <see cref="Height"/> is <c>0</c>.</param>
|
||||||
|
/// <param name="plugin">Plugin that holds responsible for this texture.</param>
|
||||||
|
/// <param name="debugName">Name for debug display purposes.</param>
|
||||||
|
public DrawListTextureWrap(
|
||||||
|
ComPtr<ID3D11Device> device,
|
||||||
|
TextureManager textureManager,
|
||||||
|
IDalamudTextureWrap emptyTexture,
|
||||||
|
LocalPlugin? plugin,
|
||||||
|
string debugName)
|
||||||
|
{
|
||||||
|
this.textureManager = textureManager;
|
||||||
|
this.emptyTexture = emptyTexture;
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.debugName = debugName;
|
||||||
|
|
||||||
|
if (device.IsEmpty())
|
||||||
|
throw new ArgumentNullException(nameof(device));
|
||||||
|
|
||||||
|
this.device.Swap(ref device);
|
||||||
|
fixed (ID3D11DeviceContext** pdc = &this.deviceContext.GetPinnableReference())
|
||||||
|
this.device.Get()->GetImmediateContext(pdc);
|
||||||
|
|
||||||
|
this.emptyTexture = emptyTexture;
|
||||||
|
this.srv = new((ID3D11ShaderResourceView*)emptyTexture.ImGuiHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Finalizes an instance of the <see cref="DrawListTextureWrap"/> class.</summary>
|
||||||
|
~DrawListTextureWrap() => this.RealDispose();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public nint ImGuiHandle => (nint)this.srv.Get();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDrawListTextureWrap.Width"/>
|
||||||
|
public int Width
|
||||||
|
{
|
||||||
|
get => this.width;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(value, nameof(value));
|
||||||
|
this.Resize(value, this.height, this.format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDrawListTextureWrap.Height"/>
|
||||||
|
public int Height
|
||||||
|
{
|
||||||
|
get => this.height;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(value, nameof(value));
|
||||||
|
this.Resize(this.width, value, this.format).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDrawListTextureWrap.Size"/>
|
||||||
|
public Vector2 Size
|
||||||
|
{
|
||||||
|
get => new(this.width, this.height);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.X is <= 0 or float.NaN)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), value, "X component is invalid.");
|
||||||
|
if (value.Y is <= 0 or float.NaN)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), value, "Y component is invalid.");
|
||||||
|
this.Resize((int)MathF.Ceiling(value.X), (int)MathF.Ceiling(value.Y), this.format).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Vector4 ClearColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the <see cref="DXGI_FORMAT"/>.</summary>
|
||||||
|
public int DxgiFormat
|
||||||
|
{
|
||||||
|
get => (int)this.format;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!this.textureManager.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)value))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Specified format is not a supported rendering target format.",
|
||||||
|
nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Resize(this.width, this.Height, (DXGI_FORMAT)value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Service<InterfaceManager>.GetNullable() is { } im)
|
||||||
|
im.EnqueueDeferredDispose(this);
|
||||||
|
else
|
||||||
|
this.RealDispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RealDispose()
|
||||||
|
{
|
||||||
|
this.srv.Reset();
|
||||||
|
this.tex.Reset();
|
||||||
|
this.rtv.Reset();
|
||||||
|
this.uav.Reset();
|
||||||
|
this.device.Reset();
|
||||||
|
this.deviceContext.Reset();
|
||||||
|
|
||||||
|
#pragma warning disable CA1816
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
#pragma warning restore CA1816
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Draw(ImDrawListPtr drawListPtr, Vector2 displayPos, Vector2 scale) =>
|
||||||
|
this.Draw(
|
||||||
|
new ImDrawData
|
||||||
|
{
|
||||||
|
Valid = 1,
|
||||||
|
CmdListsCount = 1,
|
||||||
|
TotalIdxCount = drawListPtr.IdxBuffer.Size,
|
||||||
|
TotalVtxCount = drawListPtr.VtxBuffer.Size,
|
||||||
|
CmdLists = (ImDrawList**)(&drawListPtr),
|
||||||
|
DisplayPos = displayPos,
|
||||||
|
DisplaySize = this.Size,
|
||||||
|
FramebufferScale = scale,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Draw(scoped in ImDrawData drawData)
|
||||||
|
{
|
||||||
|
fixed (ImDrawData* pDrawData = &drawData)
|
||||||
|
this.Draw(new(pDrawData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Draw(ImDrawDataPtr drawData)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
|
||||||
|
// Do nothing if the render target is empty.
|
||||||
|
if (this.rtv.IsEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Clear the texture first, as the texture exists.
|
||||||
|
var clearColor = this.ClearColor;
|
||||||
|
this.deviceContext.Get()->ClearRenderTargetView(this.rtv.Get(), (float*)&clearColor);
|
||||||
|
|
||||||
|
// If there is nothing to draw, then stop.
|
||||||
|
if (!drawData.Valid
|
||||||
|
|| drawData.CmdListsCount < 1
|
||||||
|
|| drawData.TotalIdxCount < 1
|
||||||
|
|| drawData.TotalVtxCount < 1
|
||||||
|
|| drawData.CmdLists == 0
|
||||||
|
|| drawData.DisplaySize.X <= 0
|
||||||
|
|| drawData.DisplaySize.Y <= 0
|
||||||
|
|| drawData.FramebufferScale.X == 0
|
||||||
|
|| drawData.FramebufferScale.Y == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (new DeviceContextStateBackup(this.device.Get()->GetFeatureLevel(), this.deviceContext))
|
||||||
|
{
|
||||||
|
Service<Renderer>.Get().RenderDrawData(this.rtv.Get(), drawData);
|
||||||
|
Service<Renderer>.Get().MakeStraight(this.uav.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Resizes the texture.</summary>
|
||||||
|
/// <param name="newWidth">New texture width.</param>
|
||||||
|
/// <param name="newHeight">New texture height.</param>
|
||||||
|
/// <param name="newFormat">New format.</param>
|
||||||
|
/// <returns><see cref="S.S_OK"/> if the texture has been resized, <see cref="S.S_FALSE"/> if the texture has not
|
||||||
|
/// been resized, or a value with <see cref="HRESULT.FAILED"/> that evaluates to <see langword="true"/>.</returns>
|
||||||
|
private HRESULT Resize(int newWidth, int newHeight, DXGI_FORMAT newFormat)
|
||||||
|
{
|
||||||
|
if (newWidth < 0 || newHeight < 0)
|
||||||
|
return E.E_INVALIDARG;
|
||||||
|
|
||||||
|
if (newWidth == 0 || newHeight == 0)
|
||||||
|
{
|
||||||
|
this.tex.Reset();
|
||||||
|
this.srv.Reset();
|
||||||
|
this.rtv.Reset();
|
||||||
|
this.uav.Reset();
|
||||||
|
this.width = newWidth;
|
||||||
|
this.Height = newHeight;
|
||||||
|
this.srv = new((ID3D11ShaderResourceView*)this.emptyTexture.ImGuiHandle);
|
||||||
|
return S.S_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.width == newWidth && this.height == newHeight)
|
||||||
|
return S.S_FALSE;
|
||||||
|
|
||||||
|
// These new resources will take replace the existing resources, only once all allocations are completed.
|
||||||
|
using var tmptex = default(ComPtr<ID3D11Texture2D>);
|
||||||
|
using var tmpsrv = default(ComPtr<ID3D11ShaderResourceView>);
|
||||||
|
using var tmprtv = default(ComPtr<ID3D11RenderTargetView>);
|
||||||
|
using var tmpuav = default(ComPtr<ID3D11UnorderedAccessView>);
|
||||||
|
|
||||||
|
var tmpTexDesc = new D3D11_TEXTURE2D_DESC
|
||||||
|
{
|
||||||
|
Width = (uint)newWidth,
|
||||||
|
Height = (uint)newHeight,
|
||||||
|
MipLevels = 1,
|
||||||
|
ArraySize = 1,
|
||||||
|
Format = newFormat,
|
||||||
|
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 |
|
||||||
|
D3D11_BIND_FLAG.D3D11_BIND_UNORDERED_ACCESS),
|
||||||
|
CPUAccessFlags = 0u,
|
||||||
|
MiscFlags = 0u,
|
||||||
|
};
|
||||||
|
var hr = this.device.Get()->CreateTexture2D(&tmpTexDesc, null, tmptex.GetAddressOf());
|
||||||
|
if (hr.FAILED)
|
||||||
|
return hr;
|
||||||
|
|
||||||
|
var tmpres = (ID3D11Resource*)tmptex.Get();
|
||||||
|
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(tmptex, D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
|
||||||
|
hr = this.device.Get()->CreateShaderResourceView(tmpres, &srvDesc, tmpsrv.GetAddressOf());
|
||||||
|
if (hr.FAILED)
|
||||||
|
return hr;
|
||||||
|
|
||||||
|
var rtvDesc = new D3D11_RENDER_TARGET_VIEW_DESC(tmptex, D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
|
||||||
|
hr = this.device.Get()->CreateRenderTargetView(tmpres, &rtvDesc, tmprtv.GetAddressOf());
|
||||||
|
if (hr.FAILED)
|
||||||
|
return hr;
|
||||||
|
|
||||||
|
var uavDesc = new D3D11_UNORDERED_ACCESS_VIEW_DESC(tmptex, D3D11_UAV_DIMENSION.D3D11_UAV_DIMENSION_TEXTURE2D);
|
||||||
|
hr = this.device.Get()->CreateUnorderedAccessView(tmpres, &uavDesc, tmpuav.GetAddressOf());
|
||||||
|
if (hr.FAILED)
|
||||||
|
return hr;
|
||||||
|
|
||||||
|
tmptex.Swap(ref this.tex);
|
||||||
|
tmpsrv.Swap(ref this.srv);
|
||||||
|
tmprtv.Swap(ref this.rtv);
|
||||||
|
tmpuav.Swap(ref this.uav);
|
||||||
|
this.width = newWidth;
|
||||||
|
this.height = newHeight;
|
||||||
|
this.format = newFormat;
|
||||||
|
|
||||||
|
this.textureManager.BlameSetName(this, this.debugName);
|
||||||
|
this.textureManager.Blame(this, this.plugin);
|
||||||
|
return S.S_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,669 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using TerraFX.Interop.DirectX;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Textures.TextureWraps.Internal;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDrawListTextureWrap"/>
|
||||||
|
internal sealed unsafe partial class DrawListTextureWrap
|
||||||
|
{
|
||||||
|
/// <summary>Captures states of a <see cref="ID3D11DeviceContext"/>.</summary>
|
||||||
|
// TODO: Use the one in https://github.com/goatcorp/Dalamud/pull/1923 once the PR goes in
|
||||||
|
internal struct DeviceContextStateBackup : IDisposable
|
||||||
|
{
|
||||||
|
private InputAssemblerState inputAssemblerState;
|
||||||
|
private RasterizerState rasterizerState;
|
||||||
|
private OutputMergerState outputMergerState;
|
||||||
|
private VertexShaderState vertexShaderState;
|
||||||
|
private HullShaderState hullShaderState;
|
||||||
|
private DomainShaderState domainShaderState;
|
||||||
|
private GeometryShaderState geometryShaderState;
|
||||||
|
private PixelShaderState pixelShaderState;
|
||||||
|
private ComputeShaderState computeShaderState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DeviceContextStateBackup"/> struct,
|
||||||
|
/// by capturing all states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="featureLevel">The feature level.</param>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
public DeviceContextStateBackup(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
this.inputAssemblerState = InputAssemblerState.From(ctx);
|
||||||
|
this.rasterizerState = RasterizerState.From(ctx);
|
||||||
|
this.outputMergerState = OutputMergerState.From(featureLevel, ctx);
|
||||||
|
this.vertexShaderState = VertexShaderState.From(ctx);
|
||||||
|
this.hullShaderState = HullShaderState.From(ctx);
|
||||||
|
this.domainShaderState = DomainShaderState.From(ctx);
|
||||||
|
this.geometryShaderState = GeometryShaderState.From(ctx);
|
||||||
|
this.pixelShaderState = PixelShaderState.From(ctx);
|
||||||
|
this.computeShaderState = ComputeShaderState.From(featureLevel, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.inputAssemblerState.Dispose();
|
||||||
|
this.rasterizerState.Dispose();
|
||||||
|
this.outputMergerState.Dispose();
|
||||||
|
this.vertexShaderState.Dispose();
|
||||||
|
this.hullShaderState.Dispose();
|
||||||
|
this.domainShaderState.Dispose();
|
||||||
|
this.geometryShaderState.Dispose();
|
||||||
|
this.pixelShaderState.Dispose();
|
||||||
|
this.computeShaderState.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Input Assembler states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct InputAssemblerState : IDisposable
|
||||||
|
{
|
||||||
|
private const int BufferCount = D3D11.D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11InputLayout> layout;
|
||||||
|
private ComPtr<ID3D11Buffer> indexBuffer;
|
||||||
|
private DXGI_FORMAT indexFormat;
|
||||||
|
private uint indexOffset;
|
||||||
|
private D3D_PRIMITIVE_TOPOLOGY topology;
|
||||||
|
private fixed ulong buffers[BufferCount];
|
||||||
|
private fixed uint strides[BufferCount];
|
||||||
|
private fixed uint offsets[BufferCount];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="InputAssemblerState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static InputAssemblerState From(ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(InputAssemblerState);
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
ctx->IAGetInputLayout(state.layout.GetAddressOf());
|
||||||
|
ctx->IAGetPrimitiveTopology(&state.topology);
|
||||||
|
ctx->IAGetIndexBuffer(state.indexBuffer.GetAddressOf(), &state.indexFormat, &state.indexOffset);
|
||||||
|
ctx->IAGetVertexBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers, state.strides, state.offsets);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (InputAssemblerState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->IASetInputLayout(pThis->layout);
|
||||||
|
ctx->IASetPrimitiveTopology(pThis->topology);
|
||||||
|
ctx->IASetIndexBuffer(pThis->indexBuffer, pThis->indexFormat, pThis->indexOffset);
|
||||||
|
ctx->IASetVertexBuffers(
|
||||||
|
0,
|
||||||
|
BufferCount,
|
||||||
|
(ID3D11Buffer**)pThis->buffers,
|
||||||
|
pThis->strides,
|
||||||
|
pThis->offsets);
|
||||||
|
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->layout.Dispose();
|
||||||
|
pThis->indexBuffer.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
|
||||||
|
b.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Rasterizer states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct RasterizerState : IDisposable
|
||||||
|
{
|
||||||
|
private const int Count = D3D11.D3D11_VIEWPORT_AND_SCISSORRECT_MAX_INDEX;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11RasterizerState> state;
|
||||||
|
private fixed byte viewports[24 * Count];
|
||||||
|
private fixed ulong scissorRects[16 * Count];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="RasterizerState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static RasterizerState From(ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(RasterizerState);
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
ctx->RSGetState(state.state.GetAddressOf());
|
||||||
|
uint n = Count;
|
||||||
|
ctx->RSGetViewports(&n, (D3D11_VIEWPORT*)state.viewports);
|
||||||
|
n = Count;
|
||||||
|
ctx->RSGetScissorRects(&n, (RECT*)state.scissorRects);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (RasterizerState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->RSSetState(pThis->state);
|
||||||
|
ctx->RSSetViewports(Count, (D3D11_VIEWPORT*)pThis->viewports);
|
||||||
|
ctx->RSSetScissorRects(Count, (RECT*)pThis->scissorRects);
|
||||||
|
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->state.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Output Merger states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct OutputMergerState : IDisposable
|
||||||
|
{
|
||||||
|
private const int RtvCount = D3D11.D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT;
|
||||||
|
private const int UavCountMax = D3D11.D3D11_1_UAV_SLOT_COUNT;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11BlendState> blendState;
|
||||||
|
private fixed float blendFactor[4];
|
||||||
|
private uint sampleMask;
|
||||||
|
private uint stencilRef;
|
||||||
|
private ComPtr<ID3D11DepthStencilState> depthStencilState;
|
||||||
|
private fixed ulong rtvs[RtvCount]; // ID3D11RenderTargetView*[RtvCount]
|
||||||
|
private ComPtr<ID3D11DepthStencilView> dsv;
|
||||||
|
private fixed ulong uavs[UavCountMax]; // ID3D11UnorderedAccessView*[UavCount]
|
||||||
|
private int uavCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="OutputMergerState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="featureLevel">The feature level.</param>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static OutputMergerState From(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(OutputMergerState);
|
||||||
|
state.uavCount = featureLevel >= D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1
|
||||||
|
? D3D11.D3D11_1_UAV_SLOT_COUNT
|
||||||
|
: D3D11.D3D11_PS_CS_UAV_REGISTER_COUNT;
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
ctx->OMGetBlendState(state.blendState.GetAddressOf(), state.blendFactor, &state.sampleMask);
|
||||||
|
ctx->OMGetDepthStencilState(state.depthStencilState.GetAddressOf(), &state.stencilRef);
|
||||||
|
ctx->OMGetRenderTargetsAndUnorderedAccessViews(
|
||||||
|
RtvCount,
|
||||||
|
(ID3D11RenderTargetView**)state.rtvs,
|
||||||
|
state.dsv.GetAddressOf(),
|
||||||
|
0,
|
||||||
|
(uint)state.uavCount,
|
||||||
|
(ID3D11UnorderedAccessView**)state.uavs);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (OutputMergerState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->OMSetBlendState(pThis->blendState, pThis->blendFactor, pThis->sampleMask);
|
||||||
|
ctx->OMSetDepthStencilState(pThis->depthStencilState, pThis->stencilRef);
|
||||||
|
var rtvc = (uint)RtvCount;
|
||||||
|
while (rtvc > 0 && pThis->rtvs[rtvc - 1] == 0)
|
||||||
|
rtvc--;
|
||||||
|
|
||||||
|
var uavlb = rtvc;
|
||||||
|
while (uavlb < this.uavCount && pThis->uavs[uavlb] == 0)
|
||||||
|
uavlb++;
|
||||||
|
|
||||||
|
var uavc = (uint)this.uavCount;
|
||||||
|
while (uavc > uavlb && pThis->uavs[uavc - 1] == 0)
|
||||||
|
uavlb--;
|
||||||
|
uavc -= uavlb;
|
||||||
|
|
||||||
|
ctx->OMSetRenderTargetsAndUnorderedAccessViews(
|
||||||
|
rtvc,
|
||||||
|
(ID3D11RenderTargetView**)pThis->rtvs,
|
||||||
|
pThis->dsv,
|
||||||
|
uavc == 0 ? 0 : uavlb,
|
||||||
|
uavc,
|
||||||
|
uavc == 0 ? null : (ID3D11UnorderedAccessView**)pThis->uavs,
|
||||||
|
null);
|
||||||
|
|
||||||
|
this.context.Reset();
|
||||||
|
this.blendState.Reset();
|
||||||
|
this.depthStencilState.Reset();
|
||||||
|
this.dsv.Reset();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11RenderTargetView>>(pThis->rtvs, RtvCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11UnorderedAccessView>>(pThis->uavs, this.uavCount))
|
||||||
|
b.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Vertex Shader states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct VertexShaderState : IDisposable
|
||||||
|
{
|
||||||
|
private const int BufferCount = D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
|
||||||
|
private const int SamplerCount = D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
|
||||||
|
private const int ResourceCount = D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
|
||||||
|
private const int ClassInstanceCount = 256; // According to msdn
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11VertexShader> shader;
|
||||||
|
private fixed ulong insts[ClassInstanceCount];
|
||||||
|
private fixed ulong buffers[BufferCount];
|
||||||
|
private fixed ulong samplers[SamplerCount];
|
||||||
|
private fixed ulong resources[ResourceCount];
|
||||||
|
private uint instCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="VertexShaderState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static VertexShaderState From(ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(VertexShaderState);
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
state.instCount = ClassInstanceCount;
|
||||||
|
ctx->VSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
|
||||||
|
ctx->VSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
|
||||||
|
ctx->VSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
|
||||||
|
ctx->VSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (VertexShaderState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->VSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
|
||||||
|
ctx->VSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
|
||||||
|
ctx->VSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
|
||||||
|
ctx->VSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
|
||||||
|
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
|
||||||
|
b.Dispose();
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->shader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Hull Shader states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct HullShaderState : IDisposable
|
||||||
|
{
|
||||||
|
private const int BufferCount = D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
|
||||||
|
private const int SamplerCount = D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
|
||||||
|
private const int ResourceCount = D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
|
||||||
|
private const int ClassInstanceCount = 256; // According to msdn
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11HullShader> shader;
|
||||||
|
private fixed ulong insts[ClassInstanceCount];
|
||||||
|
private fixed ulong buffers[BufferCount];
|
||||||
|
private fixed ulong samplers[SamplerCount];
|
||||||
|
private fixed ulong resources[ResourceCount];
|
||||||
|
private uint instCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="HullShaderState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static HullShaderState From(ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(HullShaderState);
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
state.instCount = ClassInstanceCount;
|
||||||
|
ctx->HSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
|
||||||
|
ctx->HSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
|
||||||
|
ctx->HSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
|
||||||
|
ctx->HSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (HullShaderState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->HSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
|
||||||
|
ctx->HSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
|
||||||
|
ctx->HSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
|
||||||
|
ctx->HSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
|
||||||
|
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
|
||||||
|
b.Dispose();
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->shader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Domain Shader states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct DomainShaderState : IDisposable
|
||||||
|
{
|
||||||
|
private const int BufferCount = D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
|
||||||
|
private const int SamplerCount = D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
|
||||||
|
private const int ResourceCount = D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
|
||||||
|
private const int ClassInstanceCount = 256; // According to msdn
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11DomainShader> shader;
|
||||||
|
private fixed ulong insts[ClassInstanceCount];
|
||||||
|
private fixed ulong buffers[BufferCount];
|
||||||
|
private fixed ulong samplers[SamplerCount];
|
||||||
|
private fixed ulong resources[ResourceCount];
|
||||||
|
private uint instCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="DomainShaderState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static DomainShaderState From(ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(DomainShaderState);
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
state.instCount = ClassInstanceCount;
|
||||||
|
ctx->DSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
|
||||||
|
ctx->DSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
|
||||||
|
ctx->DSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
|
||||||
|
ctx->DSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (DomainShaderState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->DSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
|
||||||
|
ctx->DSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
|
||||||
|
ctx->DSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
|
||||||
|
ctx->DSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
|
||||||
|
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
|
||||||
|
b.Dispose();
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->shader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Geometry Shader states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct GeometryShaderState : IDisposable
|
||||||
|
{
|
||||||
|
private const int BufferCount = D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
|
||||||
|
private const int SamplerCount = D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
|
||||||
|
private const int ResourceCount = D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
|
||||||
|
private const int ClassInstanceCount = 256; // According to msdn
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11GeometryShader> shader;
|
||||||
|
private fixed ulong insts[ClassInstanceCount];
|
||||||
|
private fixed ulong buffers[BufferCount];
|
||||||
|
private fixed ulong samplers[SamplerCount];
|
||||||
|
private fixed ulong resources[ResourceCount];
|
||||||
|
private uint instCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="GeometryShaderState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static GeometryShaderState From(ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(GeometryShaderState);
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
state.instCount = ClassInstanceCount;
|
||||||
|
ctx->GSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
|
||||||
|
ctx->GSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
|
||||||
|
ctx->GSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
|
||||||
|
ctx->GSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (GeometryShaderState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->GSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
|
||||||
|
ctx->GSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
|
||||||
|
ctx->GSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
|
||||||
|
ctx->GSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
|
||||||
|
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
|
||||||
|
b.Dispose();
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->shader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Pixel Shader states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct PixelShaderState : IDisposable
|
||||||
|
{
|
||||||
|
private const int BufferCount = D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
|
||||||
|
private const int SamplerCount = D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
|
||||||
|
private const int ResourceCount = D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
|
||||||
|
private const int ClassInstanceCount = 256; // According to msdn
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11PixelShader> shader;
|
||||||
|
private fixed ulong insts[ClassInstanceCount];
|
||||||
|
private fixed ulong buffers[BufferCount];
|
||||||
|
private fixed ulong samplers[SamplerCount];
|
||||||
|
private fixed ulong resources[ResourceCount];
|
||||||
|
private uint instCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="PixelShaderState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static PixelShaderState From(ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(PixelShaderState);
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
state.instCount = ClassInstanceCount;
|
||||||
|
ctx->PSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
|
||||||
|
ctx->PSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
|
||||||
|
ctx->PSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
|
||||||
|
ctx->PSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (PixelShaderState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->PSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
|
||||||
|
ctx->PSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
|
||||||
|
ctx->PSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
|
||||||
|
ctx->PSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
|
||||||
|
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
|
||||||
|
b.Dispose();
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->shader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures Compute Shader states of a <see cref="ID3D11DeviceContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct ComputeShaderState : IDisposable
|
||||||
|
{
|
||||||
|
private const int BufferCount = D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
|
||||||
|
private const int SamplerCount = D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
|
||||||
|
private const int ResourceCount = D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
|
||||||
|
private const int InstanceCount = 256; // According to msdn
|
||||||
|
private const int UavCountMax = D3D11.D3D11_1_UAV_SLOT_COUNT;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11DeviceContext> context;
|
||||||
|
private ComPtr<ID3D11ComputeShader> shader;
|
||||||
|
private fixed ulong insts[InstanceCount]; // ID3D11ClassInstance*[BufferCount]
|
||||||
|
private fixed ulong buffers[BufferCount]; // ID3D11Buffer*[BufferCount]
|
||||||
|
private fixed ulong samplers[SamplerCount]; // ID3D11SamplerState*[SamplerCount]
|
||||||
|
private fixed ulong resources[ResourceCount]; // ID3D11ShaderResourceView*[ResourceCount]
|
||||||
|
private fixed ulong uavs[UavCountMax]; // ID3D11UnorderedAccessView*[UavCountMax]
|
||||||
|
private uint instCount;
|
||||||
|
private int uavCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ComputeShaderState"/> from <paramref name="ctx"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="featureLevel">The feature level.</param>
|
||||||
|
/// <param name="ctx">The device context.</param>
|
||||||
|
/// <returns>The captured state.</returns>
|
||||||
|
public static ComputeShaderState From(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
|
||||||
|
{
|
||||||
|
var state = default(ComputeShaderState);
|
||||||
|
state.uavCount = featureLevel >= D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1
|
||||||
|
? D3D11.D3D11_1_UAV_SLOT_COUNT
|
||||||
|
: D3D11.D3D11_PS_CS_UAV_REGISTER_COUNT;
|
||||||
|
state.context.Attach(ctx);
|
||||||
|
ctx->AddRef();
|
||||||
|
state.instCount = InstanceCount;
|
||||||
|
ctx->CSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
|
||||||
|
ctx->CSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
|
||||||
|
ctx->CSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
|
||||||
|
ctx->CSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
|
||||||
|
ctx->CSGetUnorderedAccessViews(0, (uint)state.uavCount, (ID3D11UnorderedAccessView**)state.uavs);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var ctx = this.context.Get();
|
||||||
|
if (ctx is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixed (ComputeShaderState* pThis = &this)
|
||||||
|
{
|
||||||
|
ctx->CSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
|
||||||
|
ctx->CSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
|
||||||
|
ctx->CSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
|
||||||
|
ctx->CSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
|
||||||
|
ctx->CSSetUnorderedAccessViews(
|
||||||
|
0,
|
||||||
|
(uint)this.uavCount,
|
||||||
|
(ID3D11UnorderedAccessView**)pThis->uavs,
|
||||||
|
null);
|
||||||
|
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
|
||||||
|
b.Dispose();
|
||||||
|
foreach (ref var b in new Span<ComPtr<ID3D11UnorderedAccessView>>(pThis->uavs, this.uavCount))
|
||||||
|
b.Dispose();
|
||||||
|
pThis->context.Dispose();
|
||||||
|
pThis->shader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
cbuffer TransformationBuffer : register(b0) {
|
||||||
|
float4x4 g_view;
|
||||||
|
float4 g_colorMultiplier;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#include "DrawListTexture.Renderer.Common.hlsl"
|
||||||
|
|
||||||
|
struct ImDrawVert {
|
||||||
|
float2 position : POSITION;
|
||||||
|
float2 uv : TEXCOORD0;
|
||||||
|
float4 color : COLOR0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VsData {
|
||||||
|
float4 position : SV_POSITION;
|
||||||
|
float2 uv : TEXCOORD0;
|
||||||
|
float4 color : COLOR0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PsData {
|
||||||
|
float4 color : COLOR0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Texture2D s_texture : register(t0);
|
||||||
|
SamplerState s_sampler : register(s0);
|
||||||
|
RWTexture2D<float4> s_output : register(u1);
|
||||||
|
|
||||||
|
VsData vs_main(const ImDrawVert idv) {
|
||||||
|
VsData result;
|
||||||
|
result.position = mul(g_view, float4(idv.position, 0, 1));
|
||||||
|
result.uv = idv.uv;
|
||||||
|
result.color = idv.color;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 ps_main(const VsData vd) : SV_TARGET {
|
||||||
|
return s_texture.Sample(s_sampler, vd.uv) * vd.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
fxc /Zi /T vs_5_0 /E vs_main /Fo DrawListTexture.Renderer.DrawToPremul.vs.bin DrawListTexture.Renderer.DrawToPremul.hlsl
|
||||||
|
fxc /Zi /T ps_5_0 /E ps_main /Fo DrawListTexture.Renderer.DrawToPremul.ps.bin DrawListTexture.Renderer.DrawToPremul.hlsl
|
||||||
|
|
||||||
|
*/
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,22 @@
|
||||||
|
RWTexture2D<unorm float4> s_output : register(u1);
|
||||||
|
|
||||||
|
float4 vs_main(const float2 position : POSITION) : SV_POSITION {
|
||||||
|
return float4(position, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 ps_main(const float4 position : SV_POSITION) : SV_TARGET {
|
||||||
|
const float4 src = s_output[position.xy];
|
||||||
|
s_output[position.xy] =
|
||||||
|
src.a > 0
|
||||||
|
? float4(src.rgb / src.a, src.a)
|
||||||
|
: float4(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return float4(0, 0, 0, 0); // unused
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
fxc /Zi /T vs_5_0 /E vs_main /Fo DrawListTexture.Renderer.MakeStraight.vs.bin DrawListTexture.Renderer.MakeStraight.hlsl
|
||||||
|
fxc /Zi /T ps_5_0 /E ps_main /Fo DrawListTexture.Renderer.MakeStraight.ps.bin DrawListTexture.Renderer.MakeStraight.hlsl
|
||||||
|
|
||||||
|
*/
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,595 @@
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using TerraFX.Interop.DirectX;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Textures.TextureWraps.Internal;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDrawListTextureWrap"/>
|
||||||
|
internal sealed unsafe partial class DrawListTextureWrap
|
||||||
|
{
|
||||||
|
/// <summary>The renderer.</summary>
|
||||||
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
internal sealed class Renderer : IInternalDisposableService
|
||||||
|
{
|
||||||
|
private ComPtr<ID3D11Device> device;
|
||||||
|
private ComPtr<ID3D11DeviceContext> deviceContext;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11VertexShader> drawToPremulVertexShader;
|
||||||
|
private ComPtr<ID3D11PixelShader> drawToPremulPixelShader;
|
||||||
|
private ComPtr<ID3D11InputLayout> drawToPremulInputLayout;
|
||||||
|
private ComPtr<ID3D11Buffer> drawToPremulVertexBuffer;
|
||||||
|
private ComPtr<ID3D11Buffer> drawToPremulVertexConstantBuffer;
|
||||||
|
private ComPtr<ID3D11Buffer> drawToPremulIndexBuffer;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11VertexShader> makeStraightVertexShader;
|
||||||
|
private ComPtr<ID3D11PixelShader> makeStraightPixelShader;
|
||||||
|
private ComPtr<ID3D11InputLayout> makeStraightInputLayout;
|
||||||
|
private ComPtr<ID3D11Buffer> makeStraightVertexBuffer;
|
||||||
|
private ComPtr<ID3D11Buffer> makeStraightIndexBuffer;
|
||||||
|
|
||||||
|
private ComPtr<ID3D11SamplerState> samplerState;
|
||||||
|
private ComPtr<ID3D11BlendState> blendState;
|
||||||
|
private ComPtr<ID3D11RasterizerState> rasterizerState;
|
||||||
|
private ComPtr<ID3D11DepthStencilState> depthStencilState;
|
||||||
|
private int vertexBufferSize;
|
||||||
|
private int indexBufferSize;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private Renderer(InterfaceManager.InterfaceManagerWithScene iwms)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.device = new((ID3D11Device*)iwms.Manager.Device!.NativePointer);
|
||||||
|
fixed (ID3D11DeviceContext** p = &this.deviceContext.GetPinnableReference())
|
||||||
|
this.device.Get()->GetImmediateContext(p);
|
||||||
|
this.deviceContext.Get()->AddRef();
|
||||||
|
|
||||||
|
this.Setup();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
this.ReleaseUnmanagedResources();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Finalizes an instance of the <see cref="Renderer"/> class.</summary>
|
||||||
|
~Renderer() => this.ReleaseUnmanagedResources();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void DisposeService() => this.ReleaseUnmanagedResources();
|
||||||
|
|
||||||
|
/// <summary>Renders draw data.</summary>
|
||||||
|
/// <param name="prtv">The render target.</param>
|
||||||
|
/// <param name="drawData">Pointer to the draw data.</param>
|
||||||
|
public void RenderDrawData(ID3D11RenderTargetView* prtv, ImDrawDataPtr drawData)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
|
||||||
|
if (drawData.DisplaySize.X <= 0 || drawData.DisplaySize.Y <= 0
|
||||||
|
|| !drawData.Valid || drawData.CmdListsCount < 1)
|
||||||
|
return;
|
||||||
|
var cmdLists = new Span<ImDrawListPtr>(drawData.NativePtr->CmdLists, drawData.NativePtr->CmdListsCount);
|
||||||
|
|
||||||
|
// Create and grow vertex/index buffers if needed
|
||||||
|
if (this.vertexBufferSize < drawData.TotalVtxCount)
|
||||||
|
this.drawToPremulVertexBuffer.Dispose();
|
||||||
|
if (this.drawToPremulVertexBuffer.Get() is null)
|
||||||
|
{
|
||||||
|
this.vertexBufferSize = drawData.TotalVtxCount + 5000;
|
||||||
|
var desc = new D3D11_BUFFER_DESC(
|
||||||
|
(uint)(sizeof(ImDrawVert) * this.vertexBufferSize),
|
||||||
|
(uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER,
|
||||||
|
D3D11_USAGE.D3D11_USAGE_DYNAMIC,
|
||||||
|
(uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE);
|
||||||
|
var buffer = default(ID3D11Buffer*);
|
||||||
|
this.device.Get()->CreateBuffer(&desc, null, &buffer).ThrowOnError();
|
||||||
|
this.drawToPremulVertexBuffer.Attach(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.indexBufferSize < drawData.TotalIdxCount)
|
||||||
|
this.drawToPremulIndexBuffer.Dispose();
|
||||||
|
if (this.drawToPremulIndexBuffer.Get() is null)
|
||||||
|
{
|
||||||
|
this.indexBufferSize = drawData.TotalIdxCount + 5000;
|
||||||
|
var desc = new D3D11_BUFFER_DESC(
|
||||||
|
(uint)(sizeof(ushort) * this.indexBufferSize),
|
||||||
|
(uint)D3D11_BIND_FLAG.D3D11_BIND_INDEX_BUFFER,
|
||||||
|
D3D11_USAGE.D3D11_USAGE_DYNAMIC,
|
||||||
|
(uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE);
|
||||||
|
var buffer = default(ID3D11Buffer*);
|
||||||
|
this.device.Get()->CreateBuffer(&desc, null, &buffer).ThrowOnError();
|
||||||
|
this.drawToPremulIndexBuffer.Attach(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload vertex/index data into a single contiguous GPU buffer
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var vertexData = default(D3D11_MAPPED_SUBRESOURCE);
|
||||||
|
var indexData = default(D3D11_MAPPED_SUBRESOURCE);
|
||||||
|
this.deviceContext.Get()->Map(
|
||||||
|
(ID3D11Resource*)this.drawToPremulVertexBuffer.Get(),
|
||||||
|
0,
|
||||||
|
D3D11_MAP.D3D11_MAP_WRITE_DISCARD,
|
||||||
|
0,
|
||||||
|
&vertexData).ThrowOnError();
|
||||||
|
this.deviceContext.Get()->Map(
|
||||||
|
(ID3D11Resource*)this.drawToPremulIndexBuffer.Get(),
|
||||||
|
0,
|
||||||
|
D3D11_MAP.D3D11_MAP_WRITE_DISCARD,
|
||||||
|
0,
|
||||||
|
&indexData).ThrowOnError();
|
||||||
|
|
||||||
|
var targetVertices = new Span<ImDrawVert>(vertexData.pData, this.vertexBufferSize);
|
||||||
|
var targetIndices = new Span<ushort>(indexData.pData, this.indexBufferSize);
|
||||||
|
foreach (ref var cmdList in cmdLists)
|
||||||
|
{
|
||||||
|
var vertices = new ImVectorWrapper<ImDrawVert>(&cmdList.NativePtr->VtxBuffer);
|
||||||
|
var indices = new ImVectorWrapper<ushort>(&cmdList.NativePtr->IdxBuffer);
|
||||||
|
|
||||||
|
vertices.DataSpan.CopyTo(targetVertices);
|
||||||
|
indices.DataSpan.CopyTo(targetIndices);
|
||||||
|
|
||||||
|
targetVertices = targetVertices[vertices.Length..];
|
||||||
|
targetIndices = targetIndices[indices.Length..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.deviceContext.Get()->Unmap((ID3D11Resource*)this.drawToPremulVertexBuffer.Get(), 0);
|
||||||
|
this.deviceContext.Get()->Unmap((ID3D11Resource*)this.drawToPremulIndexBuffer.Get(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup orthographic projection matrix into our constant buffer.
|
||||||
|
// Our visible imgui space lies from DisplayPos (LT) to DisplayPos+DisplaySize (RB).
|
||||||
|
// DisplayPos is (0,0) for single viewport apps.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = default(D3D11_MAPPED_SUBRESOURCE);
|
||||||
|
this.deviceContext.Get()->Map(
|
||||||
|
(ID3D11Resource*)this.drawToPremulVertexConstantBuffer.Get(),
|
||||||
|
0,
|
||||||
|
D3D11_MAP.D3D11_MAP_WRITE_DISCARD,
|
||||||
|
0,
|
||||||
|
&data).ThrowOnError();
|
||||||
|
ref var xform = ref *(TransformationBuffer*)data.pData;
|
||||||
|
xform.View =
|
||||||
|
Matrix4x4.CreateOrthographicOffCenter(
|
||||||
|
drawData.DisplayPos.X,
|
||||||
|
drawData.DisplayPos.X + drawData.DisplaySize.X,
|
||||||
|
drawData.DisplayPos.Y + drawData.DisplaySize.Y,
|
||||||
|
drawData.DisplayPos.Y,
|
||||||
|
1f,
|
||||||
|
0f);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.deviceContext.Get()->Unmap((ID3D11Resource*)this.drawToPremulVertexConstantBuffer.Get(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up render state
|
||||||
|
{
|
||||||
|
this.deviceContext.Get()->IASetInputLayout(this.drawToPremulInputLayout);
|
||||||
|
var buffer = this.drawToPremulVertexBuffer.Get();
|
||||||
|
var stride = (uint)sizeof(ImDrawVert);
|
||||||
|
var offset = 0u;
|
||||||
|
this.deviceContext.Get()->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
|
||||||
|
this.deviceContext.Get()->IASetIndexBuffer(
|
||||||
|
this.drawToPremulIndexBuffer,
|
||||||
|
DXGI_FORMAT.DXGI_FORMAT_R16_UINT,
|
||||||
|
0);
|
||||||
|
this.deviceContext.Get()->IASetPrimitiveTopology(
|
||||||
|
D3D_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||||
|
|
||||||
|
var viewport = new D3D11_VIEWPORT(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
drawData.DisplaySize.X * drawData.FramebufferScale.X,
|
||||||
|
drawData.DisplaySize.Y * drawData.FramebufferScale.Y);
|
||||||
|
this.deviceContext.Get()->RSSetState(this.rasterizerState);
|
||||||
|
this.deviceContext.Get()->RSSetViewports(1, &viewport);
|
||||||
|
|
||||||
|
var blendColor = default(Vector4);
|
||||||
|
this.deviceContext.Get()->OMSetBlendState(this.blendState, (float*)&blendColor, 0xffffffff);
|
||||||
|
this.deviceContext.Get()->OMSetDepthStencilState(this.depthStencilState, 0);
|
||||||
|
this.deviceContext.Get()->OMSetRenderTargets(1, &prtv, null);
|
||||||
|
|
||||||
|
this.deviceContext.Get()->VSSetShader(this.drawToPremulVertexShader.Get(), null, 0);
|
||||||
|
buffer = this.drawToPremulVertexConstantBuffer.Get();
|
||||||
|
this.deviceContext.Get()->VSSetConstantBuffers(0, 1, &buffer);
|
||||||
|
|
||||||
|
// PS handled later
|
||||||
|
|
||||||
|
this.deviceContext.Get()->GSSetShader(null, null, 0);
|
||||||
|
this.deviceContext.Get()->HSSetShader(null, null, 0);
|
||||||
|
this.deviceContext.Get()->DSSetShader(null, null, 0);
|
||||||
|
this.deviceContext.Get()->CSSetShader(null, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render command lists
|
||||||
|
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||||
|
var vertexOffset = 0;
|
||||||
|
var indexOffset = 0;
|
||||||
|
var clipOff = new Vector4(drawData.DisplayPos, drawData.DisplayPos.X, drawData.DisplayPos.Y);
|
||||||
|
var frameBufferScaleV4 =
|
||||||
|
new Vector4(drawData.FramebufferScale, drawData.FramebufferScale.X, drawData.FramebufferScale.Y);
|
||||||
|
foreach (ref var cmdList in cmdLists)
|
||||||
|
{
|
||||||
|
var cmds = new ImVectorWrapper<ImDrawCmd>(&cmdList.NativePtr->CmdBuffer);
|
||||||
|
foreach (ref var cmd in cmds.DataSpan)
|
||||||
|
{
|
||||||
|
var clipV4 = (cmd.ClipRect - clipOff) * frameBufferScaleV4;
|
||||||
|
var clipRect = new RECT((int)clipV4.X, (int)clipV4.Y, (int)clipV4.Z, (int)clipV4.W);
|
||||||
|
|
||||||
|
// Skip the draw if nothing would be visible
|
||||||
|
if (clipRect.left >= clipRect.right || clipRect.top >= clipRect.bottom || cmd.ElemCount == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.deviceContext.Get()->RSSetScissorRects(1, &clipRect);
|
||||||
|
|
||||||
|
if (cmd.UserCallback == nint.Zero)
|
||||||
|
{
|
||||||
|
// Bind texture and draw
|
||||||
|
var samplerp = this.samplerState.Get();
|
||||||
|
var srvp = (ID3D11ShaderResourceView*)cmd.TextureId;
|
||||||
|
this.deviceContext.Get()->PSSetShader(this.drawToPremulPixelShader, null, 0);
|
||||||
|
this.deviceContext.Get()->PSSetSamplers(0, 1, &samplerp);
|
||||||
|
this.deviceContext.Get()->PSSetShaderResources(0, 1, &srvp);
|
||||||
|
this.deviceContext.Get()->DrawIndexed(
|
||||||
|
cmd.ElemCount,
|
||||||
|
(uint)(cmd.IdxOffset + indexOffset),
|
||||||
|
(int)(cmd.VtxOffset + vertexOffset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOffset += cmdList.IdxBuffer.Size;
|
||||||
|
vertexOffset += cmdList.VtxBuffer.Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Renders draw data.</summary>
|
||||||
|
/// <param name="puav">The pointer to a Texture2D UAV to make straight.</param>
|
||||||
|
public void MakeStraight(ID3D11UnorderedAccessView* puav)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC texDesc;
|
||||||
|
using (var texRes = default(ComPtr<ID3D11Resource>))
|
||||||
|
{
|
||||||
|
puav->GetResource(texRes.GetAddressOf());
|
||||||
|
|
||||||
|
using var tex = default(ComPtr<ID3D11Texture2D>);
|
||||||
|
texRes.As(&tex).ThrowOnError();
|
||||||
|
tex.Get()->GetDesc(&texDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deviceContext.Get()->IASetInputLayout(this.makeStraightInputLayout);
|
||||||
|
var buffer = this.makeStraightVertexBuffer.Get();
|
||||||
|
var stride = (uint)sizeof(Vector2);
|
||||||
|
var offset = 0u;
|
||||||
|
this.deviceContext.Get()->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
|
||||||
|
this.deviceContext.Get()->IASetIndexBuffer(
|
||||||
|
this.makeStraightIndexBuffer,
|
||||||
|
DXGI_FORMAT.DXGI_FORMAT_R16_UINT,
|
||||||
|
0);
|
||||||
|
this.deviceContext.Get()->IASetPrimitiveTopology(
|
||||||
|
D3D_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||||
|
|
||||||
|
var scissorRect = new RECT(0, 0, (int)texDesc.Width, (int)texDesc.Height);
|
||||||
|
this.deviceContext.Get()->RSSetScissorRects(1, &scissorRect);
|
||||||
|
this.deviceContext.Get()->RSSetState(this.rasterizerState);
|
||||||
|
var viewport = new D3D11_VIEWPORT(0, 0, texDesc.Width, texDesc.Height);
|
||||||
|
this.deviceContext.Get()->RSSetViewports(1, &viewport);
|
||||||
|
|
||||||
|
this.deviceContext.Get()->OMSetBlendState(null, null, 0xFFFFFFFF);
|
||||||
|
this.deviceContext.Get()->OMSetDepthStencilState(this.depthStencilState, 0);
|
||||||
|
var nullrtv = default(ID3D11RenderTargetView*);
|
||||||
|
this.deviceContext.Get()->OMSetRenderTargetsAndUnorderedAccessViews(1, &nullrtv, null, 1, 1, &puav, null);
|
||||||
|
|
||||||
|
this.deviceContext.Get()->VSSetShader(this.makeStraightVertexShader.Get(), null, 0);
|
||||||
|
this.deviceContext.Get()->PSSetShader(this.makeStraightPixelShader.Get(), null, 0);
|
||||||
|
this.deviceContext.Get()->GSSetShader(null, null, 0);
|
||||||
|
this.deviceContext.Get()->HSSetShader(null, null, 0);
|
||||||
|
this.deviceContext.Get()->DSSetShader(null, null, 0);
|
||||||
|
this.deviceContext.Get()->CSSetShader(null, null, 0);
|
||||||
|
|
||||||
|
this.deviceContext.Get()->DrawIndexed(6, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage(
|
||||||
|
"StyleCop.CSharp.LayoutRules",
|
||||||
|
"SA1519:Braces should not be omitted from multi-line child statement",
|
||||||
|
Justification = "Multiple fixed")]
|
||||||
|
private void Setup()
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
var rendererName = typeof(Renderer).FullName!.Replace('+', '.');
|
||||||
|
|
||||||
|
if (this.drawToPremulVertexShader.IsEmpty() || this.drawToPremulInputLayout.IsEmpty())
|
||||||
|
{
|
||||||
|
using var stream = assembly.GetManifestResourceStream($"{rendererName}.DrawToPremul.vs.bin")!;
|
||||||
|
var array = ArrayPool<byte>.Shared.Rent((int)stream.Length);
|
||||||
|
stream.ReadExactly(array, 0, (int)stream.Length);
|
||||||
|
|
||||||
|
using var tempShader = default(ComPtr<ID3D11VertexShader>);
|
||||||
|
using var tempInputLayout = default(ComPtr<ID3D11InputLayout>);
|
||||||
|
|
||||||
|
fixed (byte* pArray = array)
|
||||||
|
fixed (void* pszPosition = "POSITION"u8)
|
||||||
|
fixed (void* pszTexCoord = "TEXCOORD"u8)
|
||||||
|
fixed (void* pszColor = "COLOR"u8)
|
||||||
|
{
|
||||||
|
this.device.Get()->CreateVertexShader(
|
||||||
|
pArray,
|
||||||
|
(nuint)stream.Length,
|
||||||
|
null,
|
||||||
|
tempShader.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
|
var ied = stackalloc D3D11_INPUT_ELEMENT_DESC[]
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
SemanticName = (sbyte*)pszPosition,
|
||||||
|
Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT,
|
||||||
|
AlignedByteOffset = uint.MaxValue,
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
SemanticName = (sbyte*)pszTexCoord,
|
||||||
|
Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT,
|
||||||
|
AlignedByteOffset = uint.MaxValue,
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
SemanticName = (sbyte*)pszColor,
|
||||||
|
Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||||
|
AlignedByteOffset = uint.MaxValue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.device.Get()->CreateInputLayout(
|
||||||
|
ied,
|
||||||
|
3,
|
||||||
|
pArray,
|
||||||
|
(nuint)stream.Length,
|
||||||
|
tempInputLayout.GetAddressOf())
|
||||||
|
.ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayPool<byte>.Shared.Return(array);
|
||||||
|
|
||||||
|
tempShader.Swap(ref this.drawToPremulVertexShader);
|
||||||
|
tempInputLayout.Swap(ref this.drawToPremulInputLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.drawToPremulPixelShader.IsEmpty())
|
||||||
|
{
|
||||||
|
using var stream = assembly.GetManifestResourceStream($"{rendererName}.DrawToPremul.ps.bin")!;
|
||||||
|
var array = ArrayPool<byte>.Shared.Rent((int)stream.Length);
|
||||||
|
stream.ReadExactly(array, 0, (int)stream.Length);
|
||||||
|
|
||||||
|
using var tmp = default(ComPtr<ID3D11PixelShader>);
|
||||||
|
fixed (byte* pArray = array)
|
||||||
|
{
|
||||||
|
this.device.Get()->CreatePixelShader(pArray, (nuint)stream.Length, null, tmp.GetAddressOf())
|
||||||
|
.ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayPool<byte>.Shared.Return(array);
|
||||||
|
|
||||||
|
tmp.Swap(ref this.drawToPremulPixelShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.makeStraightVertexShader.IsEmpty() || this.makeStraightInputLayout.IsEmpty())
|
||||||
|
{
|
||||||
|
using var stream = assembly.GetManifestResourceStream($"{rendererName}.MakeStraight.vs.bin")!;
|
||||||
|
var array = ArrayPool<byte>.Shared.Rent((int)stream.Length);
|
||||||
|
stream.ReadExactly(array, 0, (int)stream.Length);
|
||||||
|
|
||||||
|
using var tempShader = default(ComPtr<ID3D11VertexShader>);
|
||||||
|
using var tempInputLayout = default(ComPtr<ID3D11InputLayout>);
|
||||||
|
|
||||||
|
fixed (byte* pArray = array)
|
||||||
|
fixed (void* pszPosition = "POSITION"u8)
|
||||||
|
{
|
||||||
|
this.device.Get()->CreateVertexShader(
|
||||||
|
pArray,
|
||||||
|
(nuint)stream.Length,
|
||||||
|
null,
|
||||||
|
tempShader.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
|
var ied = stackalloc D3D11_INPUT_ELEMENT_DESC[]
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
SemanticName = (sbyte*)pszPosition,
|
||||||
|
Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT,
|
||||||
|
AlignedByteOffset = uint.MaxValue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.device.Get()->CreateInputLayout(
|
||||||
|
ied,
|
||||||
|
1,
|
||||||
|
pArray,
|
||||||
|
(nuint)stream.Length,
|
||||||
|
tempInputLayout.GetAddressOf())
|
||||||
|
.ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayPool<byte>.Shared.Return(array);
|
||||||
|
|
||||||
|
tempShader.Swap(ref this.makeStraightVertexShader);
|
||||||
|
tempInputLayout.Swap(ref this.makeStraightInputLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.makeStraightPixelShader.IsEmpty())
|
||||||
|
{
|
||||||
|
using var stream = assembly.GetManifestResourceStream($"{rendererName}.MakeStraight.ps.bin")!;
|
||||||
|
var array = ArrayPool<byte>.Shared.Rent((int)stream.Length);
|
||||||
|
stream.ReadExactly(array, 0, (int)stream.Length);
|
||||||
|
|
||||||
|
using var tmp = default(ComPtr<ID3D11PixelShader>);
|
||||||
|
fixed (byte* pArray = array)
|
||||||
|
{
|
||||||
|
this.device.Get()->CreatePixelShader(pArray, (nuint)stream.Length, null, tmp.GetAddressOf())
|
||||||
|
.ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayPool<byte>.Shared.Return(array);
|
||||||
|
|
||||||
|
tmp.Swap(ref this.makeStraightPixelShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.makeStraightVertexBuffer.IsEmpty())
|
||||||
|
{
|
||||||
|
using var tmp = default(ComPtr<ID3D11Buffer>);
|
||||||
|
var desc = new D3D11_BUFFER_DESC(
|
||||||
|
(uint)(sizeof(Vector2) * 4),
|
||||||
|
(uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER,
|
||||||
|
D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
|
||||||
|
var data = stackalloc Vector2[] { new(-1, 1), new(-1, -1), new(1, 1), new(1, -1) };
|
||||||
|
var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = data };
|
||||||
|
this.device.Get()->CreateBuffer(&desc, &subr, tmp.GetAddressOf()).ThrowOnError();
|
||||||
|
tmp.Swap(ref this.makeStraightVertexBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.makeStraightIndexBuffer.IsEmpty())
|
||||||
|
{
|
||||||
|
using var tmp = default(ComPtr<ID3D11Buffer>);
|
||||||
|
var desc = new D3D11_BUFFER_DESC(
|
||||||
|
sizeof(ushort) * 6,
|
||||||
|
(uint)D3D11_BIND_FLAG.D3D11_BIND_INDEX_BUFFER,
|
||||||
|
D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
|
||||||
|
var data = stackalloc ushort[] { 0, 1, 2, 1, 2, 3 };
|
||||||
|
var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = data };
|
||||||
|
this.device.Get()->CreateBuffer(&desc, &subr, tmp.GetAddressOf()).ThrowOnError();
|
||||||
|
tmp.Swap(ref this.makeStraightIndexBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.drawToPremulVertexConstantBuffer.IsEmpty())
|
||||||
|
{
|
||||||
|
using var tmp = default(ComPtr<ID3D11Buffer>);
|
||||||
|
var bufferDesc = new D3D11_BUFFER_DESC(
|
||||||
|
(uint)sizeof(TransformationBuffer),
|
||||||
|
(uint)D3D11_BIND_FLAG.D3D11_BIND_CONSTANT_BUFFER,
|
||||||
|
D3D11_USAGE.D3D11_USAGE_DYNAMIC,
|
||||||
|
(uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE);
|
||||||
|
this.device.Get()->CreateBuffer(&bufferDesc, null, tmp.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
|
tmp.Swap(ref this.drawToPremulVertexConstantBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.samplerState.IsEmpty())
|
||||||
|
{
|
||||||
|
using var tmp = default(ComPtr<ID3D11SamplerState>);
|
||||||
|
var samplerDesc = new D3D11_SAMPLER_DESC
|
||||||
|
{
|
||||||
|
Filter = D3D11_FILTER.D3D11_FILTER_MIN_MAG_MIP_LINEAR,
|
||||||
|
AddressU = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
|
||||||
|
AddressV = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
|
||||||
|
AddressW = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
|
||||||
|
MipLODBias = 0,
|
||||||
|
MaxAnisotropy = 0,
|
||||||
|
ComparisonFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS,
|
||||||
|
MinLOD = 0,
|
||||||
|
MaxLOD = 0,
|
||||||
|
};
|
||||||
|
this.device.Get()->CreateSamplerState(&samplerDesc, tmp.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
|
tmp.Swap(ref this.samplerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the blending setup
|
||||||
|
if (this.blendState.IsEmpty())
|
||||||
|
{
|
||||||
|
using var tmp = default(ComPtr<ID3D11BlendState>);
|
||||||
|
var blendStateDesc = new D3D11_BLEND_DESC
|
||||||
|
{
|
||||||
|
RenderTarget =
|
||||||
|
{
|
||||||
|
e0 =
|
||||||
|
{
|
||||||
|
BlendEnable = true,
|
||||||
|
SrcBlend = D3D11_BLEND.D3D11_BLEND_SRC_ALPHA,
|
||||||
|
DestBlend = D3D11_BLEND.D3D11_BLEND_INV_SRC_ALPHA,
|
||||||
|
BlendOp = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
|
||||||
|
SrcBlendAlpha = D3D11_BLEND.D3D11_BLEND_INV_DEST_ALPHA,
|
||||||
|
DestBlendAlpha = D3D11_BLEND.D3D11_BLEND_ONE,
|
||||||
|
BlendOpAlpha = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
|
||||||
|
RenderTargetWriteMask = (byte)D3D11_COLOR_WRITE_ENABLE.D3D11_COLOR_WRITE_ENABLE_ALL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.device.Get()->CreateBlendState(&blendStateDesc, tmp.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
|
tmp.Swap(ref this.blendState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the rasterizer state
|
||||||
|
if (this.rasterizerState.IsEmpty())
|
||||||
|
{
|
||||||
|
using var tmp = default(ComPtr<ID3D11RasterizerState>);
|
||||||
|
var rasterizerDesc = new D3D11_RASTERIZER_DESC
|
||||||
|
{
|
||||||
|
FillMode = D3D11_FILL_MODE.D3D11_FILL_SOLID,
|
||||||
|
CullMode = D3D11_CULL_MODE.D3D11_CULL_NONE,
|
||||||
|
ScissorEnable = true,
|
||||||
|
DepthClipEnable = true,
|
||||||
|
};
|
||||||
|
this.device.Get()->CreateRasterizerState(&rasterizerDesc, tmp.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
|
tmp.Swap(ref this.rasterizerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the depth-stencil State
|
||||||
|
if (this.depthStencilState.IsEmpty())
|
||||||
|
{
|
||||||
|
using var tmp = default(ComPtr<ID3D11DepthStencilState>);
|
||||||
|
var dsDesc = new D3D11_DEPTH_STENCIL_DESC
|
||||||
|
{
|
||||||
|
DepthEnable = false,
|
||||||
|
StencilEnable = false,
|
||||||
|
};
|
||||||
|
this.device.Get()->CreateDepthStencilState(&dsDesc, tmp.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
|
tmp.Swap(ref this.depthStencilState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseUnmanagedResources()
|
||||||
|
{
|
||||||
|
this.device.Reset();
|
||||||
|
this.deviceContext.Reset();
|
||||||
|
this.drawToPremulVertexShader.Reset();
|
||||||
|
this.drawToPremulPixelShader.Reset();
|
||||||
|
this.drawToPremulInputLayout.Reset();
|
||||||
|
this.makeStraightVertexShader.Reset();
|
||||||
|
this.makeStraightPixelShader.Reset();
|
||||||
|
this.makeStraightInputLayout.Reset();
|
||||||
|
this.samplerState.Reset();
|
||||||
|
this.drawToPremulVertexConstantBuffer.Reset();
|
||||||
|
this.blendState.Reset();
|
||||||
|
this.rasterizerState.Reset();
|
||||||
|
this.depthStencilState.Reset();
|
||||||
|
this.drawToPremulVertexBuffer.Reset();
|
||||||
|
this.drawToPremulIndexBuffer.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct TransformationBuffer
|
||||||
|
{
|
||||||
|
public Matrix4x4 View;
|
||||||
|
public Vector4 ColorMultiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Textures.TextureWraps.Internal;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDrawListTextureWrap"/>
|
||||||
|
internal sealed unsafe partial class DrawListTextureWrap
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ResizeAndDrawWindow(ReadOnlySpan<char> windowName, Vector2 scale)
|
||||||
|
{
|
||||||
|
ref var window = ref ImGuiWindow.FindWindowByName(windowName);
|
||||||
|
if (Unsafe.IsNullRef(ref window))
|
||||||
|
throw new ArgumentException("Window not found", nameof(windowName));
|
||||||
|
|
||||||
|
this.Size = window.Size;
|
||||||
|
|
||||||
|
var numDrawList = CountDrawList(ref window);
|
||||||
|
var drawLists = stackalloc ImDrawList*[numDrawList];
|
||||||
|
var drawData = new ImDrawData
|
||||||
|
{
|
||||||
|
Valid = 1,
|
||||||
|
CmdListsCount = numDrawList,
|
||||||
|
TotalIdxCount = 0,
|
||||||
|
TotalVtxCount = 0,
|
||||||
|
CmdLists = drawLists,
|
||||||
|
DisplayPos = window.Pos,
|
||||||
|
DisplaySize = window.Size,
|
||||||
|
FramebufferScale = scale,
|
||||||
|
};
|
||||||
|
AddWindowToDrawData(ref window, ref drawLists);
|
||||||
|
for (var i = 0; i < numDrawList; i++)
|
||||||
|
{
|
||||||
|
drawData.TotalVtxCount += drawData.CmdLists[i]->VtxBuffer.Size;
|
||||||
|
drawData.TotalIdxCount += drawData.CmdLists[i]->IdxBuffer.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Draw(drawData);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
static bool IsWindowActiveAndVisible(scoped in ImGuiWindow window) =>
|
||||||
|
window.Active != 0 && window.Hidden == 0;
|
||||||
|
|
||||||
|
static void AddWindowToDrawData(scoped ref ImGuiWindow window, ref ImDrawList** wptr)
|
||||||
|
{
|
||||||
|
switch (window.DrawList.CmdBuffer.Size)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case 1 when window.DrawList.CmdBuffer[0].ElemCount == 0 &&
|
||||||
|
window.DrawList.CmdBuffer[0].UserCallback == 0:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*wptr++ = window.DrawList;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < window.DC.ChildWindows.Size; i++)
|
||||||
|
{
|
||||||
|
ref var child = ref *(ImGuiWindow*)window.DC.ChildWindows[i];
|
||||||
|
if (IsWindowActiveAndVisible(in child)) // Clipped children may have been marked not active
|
||||||
|
AddWindowToDrawData(ref child, ref wptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int CountDrawList(scoped ref ImGuiWindow window)
|
||||||
|
{
|
||||||
|
var res = window.DrawList.CmdBuffer.Size switch
|
||||||
|
{
|
||||||
|
0 => 0,
|
||||||
|
1 when window.DrawList.CmdBuffer[0].ElemCount == 0 &&
|
||||||
|
window.DrawList.CmdBuffer[0].UserCallback == 0 => 0,
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
for (var i = 0; i < window.DC.ChildWindows.Size; i++)
|
||||||
|
res += CountDrawList(ref *(ImGuiWindow*)window.DC.ChildWindows[i]);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0x448)]
|
||||||
|
private struct ImGuiWindow
|
||||||
|
{
|
||||||
|
[FieldOffset(0x048)]
|
||||||
|
public Vector2 Pos;
|
||||||
|
|
||||||
|
[FieldOffset(0x050)]
|
||||||
|
public Vector2 Size;
|
||||||
|
|
||||||
|
[FieldOffset(0x0CB)]
|
||||||
|
public byte Active;
|
||||||
|
|
||||||
|
[FieldOffset(0x0D2)]
|
||||||
|
public byte Hidden;
|
||||||
|
|
||||||
|
[FieldOffset(0x118)]
|
||||||
|
public ImGuiWindowTempData DC;
|
||||||
|
|
||||||
|
[FieldOffset(0x2C0)]
|
||||||
|
public ImDrawListPtr DrawList;
|
||||||
|
|
||||||
|
private static nint pfnImGuiFindWindowByName;
|
||||||
|
|
||||||
|
public static ref ImGuiWindow FindWindowByName(ReadOnlySpan<char> name)
|
||||||
|
{
|
||||||
|
var nb = Encoding.UTF8.GetByteCount(name);
|
||||||
|
var buf = stackalloc byte[nb + 1];
|
||||||
|
buf[Encoding.UTF8.GetBytes(name, new(buf, nb))] = 0;
|
||||||
|
if (pfnImGuiFindWindowByName == 0)
|
||||||
|
{
|
||||||
|
pfnImGuiFindWindowByName =
|
||||||
|
Process
|
||||||
|
.GetCurrentProcess()
|
||||||
|
.Modules
|
||||||
|
.Cast<ProcessModule>()
|
||||||
|
.First(x => x.ModuleName == "cimgui.dll")
|
||||||
|
.BaseAddress + 0x357F0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref *((delegate* unmanaged<byte*, ImGuiWindow*>)pfnImGuiFindWindowByName)(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0xF0)]
|
||||||
|
public struct ImGuiWindowTempData
|
||||||
|
{
|
||||||
|
[FieldOffset(0x98)]
|
||||||
|
public ImVector<nint> ChildWindows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Interface.ImGuiFileDialog;
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
|
|
@ -48,6 +49,19 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
string name,
|
string name,
|
||||||
Task<IDalamudTextureWrap> texture)
|
Task<IDalamudTextureWrap> texture)
|
||||||
{
|
{
|
||||||
|
name = new StringBuilder(name)
|
||||||
|
.Replace('<', '_')
|
||||||
|
.Replace('>', '_')
|
||||||
|
.Replace(':', '_')
|
||||||
|
.Replace('"', '_')
|
||||||
|
.Replace('/', '_')
|
||||||
|
.Replace('\\', '_')
|
||||||
|
.Replace('|', '_')
|
||||||
|
.Replace('?', '_')
|
||||||
|
.Replace('*', '_')
|
||||||
|
.ToString();
|
||||||
|
|
||||||
|
var isCopy = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var initiatorScreenOffset = ImGui.GetMousePos();
|
var initiatorScreenOffset = ImGui.GetMousePos();
|
||||||
|
|
@ -55,11 +69,12 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
var textureManager = await Service<TextureManager>.GetAsync();
|
var textureManager = await Service<TextureManager>.GetAsync();
|
||||||
var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.ImGuiHandle:X}";
|
var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.ImGuiHandle:X}";
|
||||||
|
|
||||||
BitmapCodecInfo encoder;
|
BitmapCodecInfo? encoder;
|
||||||
{
|
{
|
||||||
var first = true;
|
var first = true;
|
||||||
var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList();
|
var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList();
|
||||||
var tcs = new TaskCompletionSource<BitmapCodecInfo>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var tcs = new TaskCompletionSource<BitmapCodecInfo?>(
|
||||||
|
TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
Service<InterfaceManager>.Get().Draw += DrawChoices;
|
Service<InterfaceManager>.Get().Draw += DrawChoices;
|
||||||
|
|
||||||
encoder = await tcs.Task;
|
encoder = await tcs.Task;
|
||||||
|
|
@ -85,6 +100,8 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.Selectable("Copy"))
|
||||||
|
tcs.TrySetResult(null);
|
||||||
foreach (var encoder2 in encoders)
|
foreach (var encoder2 in encoders)
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(encoder2.Name))
|
if (ImGui.Selectable(encoder2.Name))
|
||||||
|
|
@ -106,8 +123,21 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string path;
|
if (encoder is null)
|
||||||
{
|
{
|
||||||
|
isCopy = true;
|
||||||
|
await textureManager.CopyToClipboardAsync(textureWrap, name, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var props = new Dictionary<string, object>();
|
||||||
|
if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
|
||||||
|
props["CompressionQuality"] = 1.0f;
|
||||||
|
else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
|
||||||
|
encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
|
||||||
|
encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
|
||||||
|
props["ImageQuality"] = 1.0f;
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
this.fileDialogManager.SaveFileDialog(
|
this.fileDialogManager.SaveFileDialog(
|
||||||
"Save texture...",
|
"Save texture...",
|
||||||
|
|
@ -121,30 +151,23 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
else
|
else
|
||||||
tcs.SetResult(path2);
|
tcs.SetResult(path2);
|
||||||
});
|
});
|
||||||
path = await tcs.Task.ConfigureAwait(false);
|
var path = await tcs.Task.ConfigureAwait(false);
|
||||||
}
|
|
||||||
|
|
||||||
var props = new Dictionary<string, object>();
|
await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props);
|
||||||
if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
|
|
||||||
props["CompressionQuality"] = 1.0f;
|
|
||||||
else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
|
|
||||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
|
|
||||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
|
|
||||||
props["ImageQuality"] = 1.0f;
|
|
||||||
await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props);
|
|
||||||
|
|
||||||
var notif = Service<NotificationManager>.Get().AddNotification(
|
var notif = Service<NotificationManager>.Get().AddNotification(
|
||||||
new()
|
new()
|
||||||
|
{
|
||||||
|
Content = $"File saved to: {path}",
|
||||||
|
Title = initiatorName,
|
||||||
|
Type = NotificationType.Success,
|
||||||
|
});
|
||||||
|
notif.Click += n =>
|
||||||
{
|
{
|
||||||
Content = $"File saved to: {path}",
|
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||||
Title = initiatorName,
|
n.Notification.DismissNow();
|
||||||
Type = NotificationType.Success,
|
};
|
||||||
});
|
}
|
||||||
notif.Click += n =>
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
|
||||||
n.Notification.DismissNow();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -155,7 +178,9 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
e,
|
e,
|
||||||
$"{nameof(DalamudInterface)}.{nameof(this.ShowTextureSaveMenuAsync)}({initiatorName}, {name})");
|
$"{nameof(DalamudInterface)}.{nameof(this.ShowTextureSaveMenuAsync)}({initiatorName}, {name})");
|
||||||
Service<NotificationManager>.Get().AddNotification(
|
Service<NotificationManager>.Get().AddNotification(
|
||||||
$"Failed to save file: {e}",
|
isCopy
|
||||||
|
? $"Failed to copy file: {e}"
|
||||||
|
: $"Failed to save file: {e}",
|
||||||
initiatorName,
|
initiatorName,
|
||||||
NotificationType.Error);
|
NotificationType.Error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
||||||
|
|
@ -11,7 +12,10 @@ using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Textures.Internal;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Internal;
|
||||||
using Dalamud.Interface.Windowing.Persistence;
|
using Dalamud.Interface.Windowing.Persistence;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
|
@ -408,6 +412,7 @@ public abstract class Window
|
||||||
var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
|
var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
|
||||||
internalDrawFlags.HasFlag(WindowDrawFlags.UseAdditionalOptions) &&
|
internalDrawFlags.HasFlag(WindowDrawFlags.UseAdditionalOptions) &&
|
||||||
flagsApplicableForTitleBarIcons;
|
flagsApplicableForTitleBarIcons;
|
||||||
|
var printWindow = false;
|
||||||
if (showAdditions)
|
if (showAdditions)
|
||||||
{
|
{
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
|
||||||
|
|
@ -482,6 +487,9 @@ public abstract class Window
|
||||||
if (!isAvailable)
|
if (!isAvailable)
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
|
||||||
|
printWindow = true;
|
||||||
|
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -540,6 +548,17 @@ public abstract class Window
|
||||||
|
|
||||||
ImGui.End();
|
ImGui.End();
|
||||||
|
|
||||||
|
if (printWindow)
|
||||||
|
{
|
||||||
|
var tex = Service<TextureManager>.Get().CreateDrawListTexture(
|
||||||
|
Loc.Localize("WindowSystemContextActionPrintWindow", "Print window"));
|
||||||
|
tex.ResizeAndDrawWindow(this.WindowName, Vector2.One);
|
||||||
|
_ = Service<DevTextureSaveMenu>.Get().ShowTextureSaveMenuAsync(
|
||||||
|
this.WindowName,
|
||||||
|
this.WindowName,
|
||||||
|
Task.FromResult<IDalamudTextureWrap>(tex));
|
||||||
|
}
|
||||||
|
|
||||||
this.PostDraw();
|
this.PostDraw();
|
||||||
|
|
||||||
this.PostHandlePreset(persistence);
|
this.PostHandlePreset(persistence);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
using Dalamud.Interface.Textures;
|
using Dalamud.Interface.Textures;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Services;
|
namespace Dalamud.Plugin.Services;
|
||||||
|
|
@ -45,6 +47,14 @@ public interface ITextureProvider
|
||||||
bool cpuWrite,
|
bool cpuWrite,
|
||||||
string? debugName = null);
|
string? debugName = null);
|
||||||
|
|
||||||
|
/// <summary>Creates a texture that can be drawn from an <see cref="ImDrawList"/> or an <see cref="ImDrawData"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="debugName">Name for debug display purposes.</param>
|
||||||
|
/// <returns>A new draw list texture.</returns>
|
||||||
|
/// <remarks>No new resource is allocated upfront; it will be done when <see cref="IDrawListTextureWrap.Size"/> is
|
||||||
|
/// set with positive values for both components.</remarks>
|
||||||
|
IDrawListTextureWrap CreateDrawListTexture(string? debugName = null);
|
||||||
|
|
||||||
/// <summary>Creates a texture from the given existing texture, cropping and converting pixel format as needed.
|
/// <summary>Creates a texture from the given existing texture, cropping and converting pixel format as needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="wrap">The source texture wrap. The passed value may be disposed once this function returns,
|
/// <param name="wrap">The source texture wrap. The passed value may be disposed once this function returns,
|
||||||
|
|
@ -169,6 +179,14 @@ public interface ITextureProvider
|
||||||
string? debugName = null,
|
string? debugName = null,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>Creates a texture from clipboard.</summary>
|
||||||
|
/// <param name="debugName">Name for debug display purposes.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> representing the status of the operation.</returns>
|
||||||
|
Task<IDalamudTextureWrap> CreateFromClipboardAsync(
|
||||||
|
string? debugName = null,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>Gets the supported bitmap decoders.</summary>
|
/// <summary>Gets the supported bitmap decoders.</summary>
|
||||||
/// <returns>The supported bitmap decoders.</returns>
|
/// <returns>The supported bitmap decoders.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|
@ -192,6 +210,11 @@ public interface ITextureProvider
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup);
|
ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup);
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether the current desktop clipboard contains an image that can be attempted
|
||||||
|
/// to read using <see cref="CreateFromClipboardAsync"/>.</summary>
|
||||||
|
/// <returns><c>true</c> if it is the case.</returns>
|
||||||
|
bool HasClipboardImage();
|
||||||
|
|
||||||
/// <summary>Gets a shared texture corresponding to the given game resource icon specifier.</summary>
|
/// <summary>Gets a shared texture corresponding to the given game resource icon specifier.</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>This function does not throw exceptions.</para>
|
/// <para>This function does not throw exceptions.</para>
|
||||||
|
|
|
||||||
|
|
@ -106,4 +106,17 @@ public interface ITextureReadbackProvider
|
||||||
IReadOnlyDictionary<string, object>? props = null,
|
IReadOnlyDictionary<string, object>? props = null,
|
||||||
bool leaveWrapOpen = false,
|
bool leaveWrapOpen = false,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>Copies the texture to clipboard.</summary>
|
||||||
|
/// <param name="wrap">Texture wrap to copy.</param>
|
||||||
|
/// <param name="preferredFileNameWithoutExtension">Preferred file name.</param>
|
||||||
|
/// <param name="leaveWrapOpen">Whether to leave <paramref name="wrap"/> non-disposed when the returned
|
||||||
|
/// <see cref="Task{TResult}"/> completes.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> representing the status of the operation.</returns>
|
||||||
|
Task CopyToClipboardAsync(
|
||||||
|
IDalamudTextureWrap wrap,
|
||||||
|
string? preferredFileNameWithoutExtension = null,
|
||||||
|
bool leaveWrapOpen = false,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
Dalamud/Utility/ClipboardFormats.cs
Normal file
40
Dalamud/Utility/ClipboardFormats.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Utility;
|
||||||
|
|
||||||
|
/// <summary>Clipboard formats, looked up by their names.</summary>
|
||||||
|
internal static class ClipboardFormats
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="CFSTR.CFSTR_FILECONTENTS"/>
|
||||||
|
public static uint FileContents { get; } = ClipboardFormatFromName(CFSTR.CFSTR_FILECONTENTS);
|
||||||
|
|
||||||
|
/// <summary>Gets the clipboard format corresponding to the PNG file format.</summary>
|
||||||
|
public static uint Png { get; } = ClipboardFormatFromName("PNG");
|
||||||
|
|
||||||
|
/// <inheritdoc cref="CFSTR.CFSTR_FILEDESCRIPTORW"/>
|
||||||
|
public static uint FileDescriptorW { get; } = ClipboardFormatFromName(CFSTR.CFSTR_FILEDESCRIPTORW);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="CFSTR.CFSTR_FILEDESCRIPTORA"/>
|
||||||
|
public static uint FileDescriptorA { get; } = ClipboardFormatFromName(CFSTR.CFSTR_FILEDESCRIPTORA);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="CFSTR.CFSTR_FILENAMEW"/>
|
||||||
|
public static uint FileNameW { get; } = ClipboardFormatFromName(CFSTR.CFSTR_FILENAMEW);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="CFSTR.CFSTR_FILENAMEA"/>
|
||||||
|
public static uint FileNameA { get; } = ClipboardFormatFromName(CFSTR.CFSTR_FILENAMEA);
|
||||||
|
|
||||||
|
private static unsafe uint ClipboardFormatFromName(ReadOnlySpan<char> name)
|
||||||
|
{
|
||||||
|
uint cf;
|
||||||
|
fixed (void* p = name)
|
||||||
|
cf = RegisterClipboardFormatW((ushort*)p);
|
||||||
|
if (cf != 0)
|
||||||
|
return cf;
|
||||||
|
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ??
|
||||||
|
new InvalidOperationException($"RegisterClipboardFormatW({name}) failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,8 +22,14 @@ internal class ThreadBoundTaskScheduler : TaskScheduler
|
||||||
public ThreadBoundTaskScheduler(Thread? boundThread = null)
|
public ThreadBoundTaskScheduler(Thread? boundThread = null)
|
||||||
{
|
{
|
||||||
this.BoundThread = boundThread;
|
this.BoundThread = boundThread;
|
||||||
|
this.TaskQueued += static () => { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when a task has been posted.
|
||||||
|
/// </summary>
|
||||||
|
public event Action TaskQueued;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the thread this task scheduler is bound to.
|
/// Gets or sets the thread this task scheduler is bound to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -57,6 +63,7 @@ internal class ThreadBoundTaskScheduler : TaskScheduler
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void QueueTask(Task task)
|
protected override void QueueTask(Task task)
|
||||||
{
|
{
|
||||||
|
this.TaskQueued.Invoke();
|
||||||
this.scheduledTasks[task] = Scheduled;
|
this.scheduledTasks[task] = Scheduled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue