diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 16266beeba..1389ba68ee 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -90,7 +90,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -103,11 +102,12 @@ - - - - - + + imgui-frag.hlsl.bytes + + + imgui-vertex.hlsl.bytes + diff --git a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs index ba880d0e54..c9ccb876e9 100644 --- a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs +++ b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; - using Dalamud.Game.Internal.DXGI.Definitions; +using Dalamud.Interface.ImGuiBackend.Helpers; + using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Serilog; + +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; namespace Dalamud.Game.Internal.DXGI; @@ -17,30 +17,23 @@ namespace Dalamud.Game.Internal.DXGI; internal class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver { /// - public IntPtr Present { get; set; } + public nint Present { get; set; } /// - public IntPtr ResizeBuffers { get; set; } - - /// - /// Gets a value indicating whether or not ReShade is loaded/used. - /// - public bool IsReshade { get; private set; } + public nint ResizeBuffers { get; set; } /// protected override unsafe void Setup64Bit(ISigScanner sig) { - Device* kernelDev; - SwapChain* swapChain; void* dxgiSwapChain; while (true) { - kernelDev = Device.Instance(); + var kernelDev = Device.Instance(); if (kernelDev == null) continue; - swapChain = kernelDev->SwapChain; + var swapChain = kernelDev->SwapChain; if (swapChain == null) continue; @@ -51,119 +44,10 @@ protected override unsafe void Setup64Bit(ISigScanner sig) break; } - var scVtbl = GetVTblAddresses(new IntPtr(dxgiSwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); - - this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present]; - - var modules = Process.GetCurrentProcess().Modules; - foreach (ProcessModule processModule in modules) - { - if (processModule.FileName == null || !processModule.FileName.EndsWith("game\\dxgi.dll")) - continue; - - try - { - var fileInfo = FileVersionInfo.GetVersionInfo(processModule.FileName); - - if (fileInfo.FileDescription == null) - break; - - if (!fileInfo.FileDescription.Contains("GShade") && !fileInfo.FileDescription.Contains("ReShade")) - break; - - // warning: these comments may no longer be accurate. - // reshade master@4232872 RVA - // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present - // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present - // DXGISwapChain::handle_device_loss =>df DXGISwapChain::Present => DXGISwapChain::runtime_present - // 5.2+ - F6 C2 01 0F 85 - // 6.0+ - F6 C2 01 0F 85 88 - - var scanner = new SigScanner(processModule); - var reShadeDxgiPresent = IntPtr.Zero; - - if (fileInfo.FileVersion?.StartsWith("6.") == true) - { - // No Addon - if (scanner.TryScanText("F6 C2 01 0F 85 A8", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for ReShade 6 No-Addon"); - } - - // Addon - else if (scanner.TryScanText("F6 C2 01 0F 85 88", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for ReShade 6 Addon"); - } - - // Fallback - else - { - Log.Error("Failed to get ReShade 6 DXGISwapChain::on_present offset!"); - } - } - - // Looks like this sig only works for GShade 4 - if (reShadeDxgiPresent == IntPtr.Zero && fileInfo.FileDescription?.Contains("GShade 4.") == true) - { - if (scanner.TryScanText("E8 ?? ?? ?? ?? 45 0F B6 5E ??", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for GShade 4"); - } - else - { - Log.Error("Failed to find GShade 4 DXGISwapChain::on_present offset!"); - } - } - - if (reShadeDxgiPresent == IntPtr.Zero) - { - if (scanner.TryScanText("F6 C2 01 0F 85", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for ReShade with fallback 5.X sig"); - } - else - { - Log.Error("Failed to find ReShade DXGISwapChain::on_present offset with fallback sig!"); - } - } - - Log.Information("ReShade DLL: {FileName} ({Info} - {Version}) with DXGISwapChain::on_present at {Address}", - processModule.FileName, - fileInfo.FileDescription ?? "Unknown", - fileInfo.FileVersion ?? "Unknown", - reShadeDxgiPresent.ToString("X")); - - if (reShadeDxgiPresent != IntPtr.Zero) - { - this.Present = reShadeDxgiPresent; - this.IsReshade = true; - } - - break; - } - catch (Exception e) - { - Log.Error(e, "Failed to get ReShade version info"); - break; - } - } - - this.ResizeBuffers = scVtbl[(int)IDXGISwapChainVtbl.ResizeBuffers]; - } - - private static List GetVTblAddresses(IntPtr pointer, int numberOfMethods) - { - return GetVTblAddresses(pointer, 0, numberOfMethods); - } - - private static List GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods) - { - var vtblAddresses = new List(); - var vTable = Marshal.ReadIntPtr(pointer); - for (var i = startIndex; i < startIndex + numberOfMethods; i++) - vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes + using var sc = new ComPtr((IDXGISwapChain*)dxgiSwapChain); + ReShadePeeler.PeelSwapChain(&sc); - return vtblAddresses; + this.Present = (nint)sc.Get()->lpVtbl[(int)IDXGISwapChainVtbl.Present]; + this.ResizeBuffers = (nint)sc.Get()->lpVtbl[(int)IDXGISwapChainVtbl.ResizeBuffers]; } } diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index c9f0f9b80c..f3acd704d9 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -32,7 +32,7 @@ private DragDropManager() Service.GetAsync() .ContinueWith(t => { - this.windowHandlePtr = t.Result.Manager.WindowHandlePtr; + this.windowHandlePtr = t.Result.Manager.GameWindowHandle; this.Enable(); }); } diff --git a/Dalamud/Interface/ImGuiBackend/Dx11Win32Backend.cs b/Dalamud/Interface/ImGuiBackend/Dx11Win32Backend.cs new file mode 100644 index 0000000000..a8a84b20d2 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Dx11Win32Backend.cs @@ -0,0 +1,222 @@ +using System.Diagnostics.CodeAnalysis; + +using Dalamud.Interface.ImGuiBackend.Helpers; +using Dalamud.Interface.ImGuiBackend.InputHandler; +using Dalamud.Interface.ImGuiBackend.Renderers; +using Dalamud.Utility; + +using ImGuiNET; + +using ImGuizmoNET; + +using ImPlotNET; + +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +namespace Dalamud.Interface.ImGuiBackend; + +/// +/// Backend for ImGui, using and . +/// +[SuppressMessage( + "StyleCop.CSharp.LayoutRules", + "SA1519:Braces should not be omitted from multi-line child statement", + Justification = "Multiple fixed/using scopes")] +internal sealed unsafe class Dx11Win32Backend : IWin32Backend +{ + private readonly Dx11Renderer imguiRenderer; + private readonly Win32InputHandler imguiInput; + + private ComPtr swapChainPossiblyWrapped; + private ComPtr swapChain; + private ComPtr device; + private ComPtr deviceContext; + + private int targetWidth; + private int targetHeight; + + /// + /// Initializes a new instance of the class. + /// + /// The pointer to an instance of . The reference is copied. + public Dx11Win32Backend(IDXGISwapChain* swapChain) + { + try + { + this.swapChainPossiblyWrapped = new(swapChain); + this.swapChain = new(swapChain); + fixed (ComPtr* ppSwapChain = &this.swapChain) + ReShadePeeler.PeelSwapChain(ppSwapChain); + + fixed (Guid* guid = &IID.IID_ID3D11Device) + fixed (ID3D11Device** pp = &this.device.GetPinnableReference()) + this.swapChain.Get()->GetDevice(guid, (void**)pp).ThrowOnError(); + + fixed (ID3D11DeviceContext** pp = &this.deviceContext.GetPinnableReference()) + this.device.Get()->GetImmediateContext(pp); + + using var buffer = default(ComPtr); + fixed (Guid* guid = &IID.IID_ID3D11Resource) + this.swapChain.Get()->GetBuffer(0, guid, (void**)buffer.GetAddressOf()).ThrowOnError(); + + var desc = default(DXGI_SWAP_CHAIN_DESC); + this.swapChain.Get()->GetDesc(&desc).ThrowOnError(); + this.targetWidth = (int)desc.BufferDesc.Width; + this.targetHeight = (int)desc.BufferDesc.Height; + this.WindowHandle = desc.OutputWindow; + + var ctx = ImGui.CreateContext(); + ImGuizmo.SetImGuiContext(ctx); + ImPlot.SetImGuiContext(ctx); + ImPlot.CreateContext(); + + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable; + + this.imguiRenderer = new(this.SwapChain, this.Device, this.DeviceContext); + this.imguiInput = new(this.WindowHandle); + } + catch + { + this.Dispose(); + throw; + } + } + + /// + /// Finalizes an instance of the class. + /// + ~Dx11Win32Backend() => this.ReleaseUnmanagedResources(); + + /// + public event IImGuiBackend.BuildUiDelegate? BuildUi; + + /// + public event IImGuiBackend.NewInputFrameDelegate? NewInputFrame; + + /// + public event IImGuiBackend.NewRenderFrameDelegate? NewRenderFrame; + + /// + public bool UpdateCursor + { + get => this.imguiInput.UpdateCursor; + set => this.imguiInput.UpdateCursor = value; + } + + /// + public string? IniPath + { + get => this.imguiInput.IniPath; + set => this.imguiInput.IniPath = value; + } + + /// + public IImGuiInputHandler InputHandler => this.imguiInput; + + /// + public IImGuiRenderer Renderer => this.imguiRenderer; + + /// + /// Gets the pointer to an instance of . + /// + public IDXGISwapChain* SwapChain => this.swapChain; + + /// + /// Gets the pointer to an instance of . + /// + public ID3D11Device* Device => this.device; + + /// + /// Gets the pointer to an instance of , in . + /// + public nint DeviceHandle => (nint)this.device.Get(); + + /// + /// Gets the pointer to an instance of . + /// + public ID3D11DeviceContext* DeviceContext => this.deviceContext; + + /// + /// Gets the window handle. + /// + public HWND WindowHandle { get; } + + /// + public void Dispose() + { + this.ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + /// + public nint? ProcessWndProcW(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam) => + this.imguiInput.ProcessWndProcW(hWnd, msg, wParam, lParam); + + /// + public void Render() + { + this.imguiRenderer.OnNewFrame(); + this.NewRenderFrame?.Invoke(); + this.imguiInput.NewFrame(this.targetWidth, this.targetHeight); + this.NewInputFrame?.Invoke(); + + ImGui.NewFrame(); + ImGuizmo.BeginFrame(); + + this.BuildUi?.Invoke(); + + ImGui.Render(); + + this.imguiRenderer.RenderDrawData(ImGui.GetDrawData()); + + ImGui.UpdatePlatformWindows(); + ImGui.RenderPlatformWindowsDefault(); + } + + /// + public void OnPreResize() => this.imguiRenderer.OnPreResize(); + + /// + public void OnPostResize(int newWidth, int newHeight) + { + this.imguiRenderer.OnPostResize(newWidth, newHeight); + this.targetWidth = newWidth; + this.targetHeight = newHeight; + } + + /// + public void InvalidateFonts() => this.imguiRenderer.RebuildFontTexture(); + + /// + public bool IsImGuiCursor(nint cursorHandle) => this.imguiInput.IsImGuiCursor(cursorHandle); + + /// + public bool IsAttachedToPresentationTarget(nint targetHandle) => + this.swapChain.Get() == (void*)targetHandle + || this.swapChainPossiblyWrapped.Get() == (void*)targetHandle; + + /// + public bool IsMainViewportFullScreen() + { + BOOL fullscreen; + this.swapChain.Get()->GetFullscreenState(&fullscreen, null); + return fullscreen; + } + + private void ReleaseUnmanagedResources() + { + if (this.device.IsEmpty()) + return; + + this.imguiRenderer.Dispose(); + this.imguiInput.Dispose(); + + ImGui.DestroyContext(); + + this.swapChain.Dispose(); + this.deviceContext.Dispose(); + this.device.Dispose(); + this.swapChainPossiblyWrapped.Dispose(); + } +} diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/D3D11DeviceContextStateBackup.cs b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/D3D11DeviceContextStateBackup.cs new file mode 100644 index 0000000000..df1087ce3c --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/D3D11DeviceContextStateBackup.cs @@ -0,0 +1,655 @@ +using System.Runtime.InteropServices; + +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +namespace Dalamud.Interface.ImGuiBackend.Helpers.D3D11; + +/// +/// Captures states of a . +/// +internal unsafe struct D3D11DeviceContextStateBackup : 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; + + /// + /// Initializes a new instance of the struct, + /// by capturing all states of a . + /// + /// The feature level. + /// The device context. + public D3D11DeviceContextStateBackup(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); + } + + /// + 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(); + } + + /// + /// Captures Input Assembler states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct InputAssemblerState : IDisposable + { + private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT; + + private ComPtr context; + private ComPtr layout; + private ComPtr 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]; + + /// + /// Creates a new instance of from . + /// + /// The device context. + /// The captured state. + 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; + } + + /// + 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>(pThis->buffers, BufferCount)) + b.Dispose(); + } + } + } + + /// + /// Captures Rasterizer states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct RasterizerState : IDisposable + { + private const int Count = TerraFX.Interop.DirectX.D3D11.D3D11_VIEWPORT_AND_SCISSORRECT_MAX_INDEX; + + private ComPtr context; + private ComPtr state; + private fixed byte viewports[24 * Count]; + private fixed ulong scissorRects[16 * Count]; + + /// + /// Creates a new instance of from . + /// + /// The device context. + /// The captured state. + 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; + } + + /// + 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(); + } + } + } + + /// + /// Captures Output Merger states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct OutputMergerState : IDisposable + { + private const int RtvCount = TerraFX.Interop.DirectX.D3D11.D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; + private const int UavCountMax = TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT; + + private ComPtr context; + private ComPtr blendState; + private fixed float blendFactor[4]; + private uint sampleMask; + private uint stencilRef; + private ComPtr depthStencilState; + private fixed ulong rtvs[RtvCount]; // ID3D11RenderTargetView*[RtvCount] + private ComPtr dsv; + private fixed ulong uavs[UavCountMax]; // ID3D11UnorderedAccessView*[UavCount] + private int uavCount; + + /// + /// Creates a new instance of from . + /// + /// The feature level. + /// The device context. + /// The captured state. + 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 + ? TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT + : TerraFX.Interop.DirectX.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; + } + + /// + 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>(pThis->rtvs, RtvCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->uavs, this.uavCount)) + b.Dispose(); + } + } + } + + /// + /// Captures Vertex Shader states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct VertexShaderState : IDisposable + { + private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; + private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; + private const int ClassInstanceCount = 256; // According to msdn + + private ComPtr context; + private ComPtr shader; + private fixed ulong insts[ClassInstanceCount]; + private fixed ulong buffers[BufferCount]; + private fixed ulong samplers[SamplerCount]; + private fixed ulong resources[ResourceCount]; + private uint instCount; + + /// + /// Creates a new instance of from . + /// + /// The device context. + /// The captured state. + 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; + } + + /// + 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>(pThis->buffers, BufferCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->samplers, SamplerCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->resources, ResourceCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount)) + b.Dispose(); + pThis->context.Dispose(); + pThis->shader.Dispose(); + } + } + } + + /// + /// Captures Hull Shader states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct HullShaderState : IDisposable + { + private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; + private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; + private const int ClassInstanceCount = 256; // According to msdn + + private ComPtr context; + private ComPtr shader; + private fixed ulong insts[ClassInstanceCount]; + private fixed ulong buffers[BufferCount]; + private fixed ulong samplers[SamplerCount]; + private fixed ulong resources[ResourceCount]; + private uint instCount; + + /// + /// Creates a new instance of from . + /// + /// The device context. + /// The captured state. + 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; + } + + /// + 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>(pThis->buffers, BufferCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->samplers, SamplerCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->resources, ResourceCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount)) + b.Dispose(); + pThis->context.Dispose(); + pThis->shader.Dispose(); + } + } + } + + /// + /// Captures Domain Shader states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct DomainShaderState : IDisposable + { + private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; + private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; + private const int ClassInstanceCount = 256; // According to msdn + + private ComPtr context; + private ComPtr shader; + private fixed ulong insts[ClassInstanceCount]; + private fixed ulong buffers[BufferCount]; + private fixed ulong samplers[SamplerCount]; + private fixed ulong resources[ResourceCount]; + private uint instCount; + + /// + /// Creates a new instance of from . + /// + /// The device context. + /// The captured state. + 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; + } + + /// + 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>(pThis->buffers, BufferCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->samplers, SamplerCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->resources, ResourceCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount)) + b.Dispose(); + pThis->context.Dispose(); + pThis->shader.Dispose(); + } + } + } + + /// + /// Captures Geometry Shader states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct GeometryShaderState : IDisposable + { + private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; + private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; + private const int ClassInstanceCount = 256; // According to msdn + + private ComPtr context; + private ComPtr shader; + private fixed ulong insts[ClassInstanceCount]; + private fixed ulong buffers[BufferCount]; + private fixed ulong samplers[SamplerCount]; + private fixed ulong resources[ResourceCount]; + private uint instCount; + + /// + /// Creates a new instance of from . + /// + /// The device context. + /// The captured state. + 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; + } + + /// + 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>(pThis->buffers, BufferCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->samplers, SamplerCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->resources, ResourceCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount)) + b.Dispose(); + pThis->context.Dispose(); + pThis->shader.Dispose(); + } + } + } + + /// + /// Captures Pixel Shader states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct PixelShaderState : IDisposable + { + private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; + private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; + private const int ClassInstanceCount = 256; // According to msdn + + private ComPtr context; + private ComPtr shader; + private fixed ulong insts[ClassInstanceCount]; + private fixed ulong buffers[BufferCount]; + private fixed ulong samplers[SamplerCount]; + private fixed ulong resources[ResourceCount]; + private uint instCount; + + /// + /// Creates a new instance of from . + /// + /// The device context. + /// The captured state. + 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; + } + + /// + 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>(pThis->buffers, BufferCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->samplers, SamplerCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->resources, ResourceCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount)) + b.Dispose(); + pThis->context.Dispose(); + pThis->shader.Dispose(); + } + } + } + + /// + /// Captures Compute Shader states of a . + /// + [StructLayout(LayoutKind.Sequential)] + public struct ComputeShaderState : IDisposable + { + private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; + private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; + private const int InstanceCount = 256; // According to msdn + private const int UavCountMax = TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT; + + private ComPtr context; + private ComPtr 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; + + /// + /// Creates a new instance of from . + /// + /// The feature level. + /// The device context. + /// The captured state. + 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 + ? TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT + : TerraFX.Interop.DirectX.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; + } + + /// + 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>(pThis->buffers, BufferCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->samplers, SamplerCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->resources, ResourceCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount)) + b.Dispose(); + foreach (ref var b in new Span>(pThis->uavs, this.uavCount)) + b.Dispose(); + pThis->context.Dispose(); + pThis->shader.Dispose(); + } + } + } +} diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/Extensions.cs b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/Extensions.cs new file mode 100644 index 0000000000..4d81cb5fc4 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/Extensions.cs @@ -0,0 +1,26 @@ +using System.Text; + +using Dalamud.Utility; + +using TerraFX.Interop.DirectX; + +namespace Dalamud.Interface.ImGuiBackend.Helpers.D3D11; + +/// Utility extension methods for D3D11 objects. +internal static class Extensions +{ + /// Sets the name for debugging. + /// D3D11 object. + /// Debug name. + /// Object type. + public static unsafe void SetDebugName(ref this T child, string name) + where T : unmanaged, ID3D11DeviceChild.Interface + { + var len = Encoding.UTF8.GetByteCount(name); + var buf = stackalloc byte[len + 1]; + Encoding.UTF8.GetBytes(name, new(buf, len + 1)); + buf[len] = 0; + fixed (Guid* pId = &DirectX.WKPDID_D3DDebugObjectName) + child.SetPrivateData(pId, (uint)(len + 1), buf).ThrowOnError(); + } +} diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/ImGuiViewportHelpers.cs b/Dalamud/Interface/ImGuiBackend/Helpers/ImGuiViewportHelpers.cs new file mode 100644 index 0000000000..c951eab1be --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Helpers/ImGuiViewportHelpers.cs @@ -0,0 +1,165 @@ +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; + +using ImGuiNET; + +using TerraFX.Interop.Windows; + +using static TerraFX.Interop.Windows.Windows; + +namespace Dalamud.Interface.ImGuiBackend.Helpers; + +/// +/// Helpers for using ImGui Viewports. +/// +internal static class ImGuiViewportHelpers +{ + /// + /// Delegate to be called when a window should be created. + /// + /// An instance of . + public delegate void CreateWindowDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when a window should be destroyed. + /// + /// An instance of . + public delegate void DestroyWindowDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when a window should be resized. + /// + /// An instance of . + /// Size of the new window. + public delegate void SetWindowSizeDelegate(ImGuiViewportPtr viewport, Vector2 size); + + /// + /// Delegate to be called when a window should be rendered. + /// + /// An instance of . + /// Custom user-provided argument from . + public delegate void RenderWindowDelegate(ImGuiViewportPtr viewport, nint v); + + /// + /// Delegate to be called when buffers for the window should be swapped. + /// + /// An instance of . + /// Custom user-provided argument from . + public delegate void SwapBuffersDelegate(ImGuiViewportPtr viewport, nint v); + + /// + /// Delegate to be called when the window should be showed. + /// + /// An instance of . + public delegate void ShowWindowDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when the window should be updated. + /// + /// An instance of . + public delegate void UpdateWindowDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when the window position is queried. + /// + /// The return value storage. + /// An instance of . + /// Same value with . + public unsafe delegate Vector2* GetWindowPosDelegate(Vector2* returnStorage, ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when the window should be moved. + /// + /// An instance of . + /// The new position. + public delegate void SetWindowPosDelegate(ImGuiViewportPtr viewport, Vector2 pos); + + /// + /// Delegate to be called when the window size is queried. + /// + /// The return value storage. + /// An instance of . + /// Same value with . + public unsafe delegate Vector2* GetWindowSizeDelegate(Vector2* returnStorage, ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when the window should be given focus. + /// + /// An instance of . + public delegate void SetWindowFocusDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when the window is focused. + /// + /// An instance of . + /// Whether the window is focused. + public delegate bool GetWindowFocusDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when whether the window is minimized is queried. + /// + /// An instance of . + /// Whether the window is minimized. + public delegate bool GetWindowMinimizedDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when the window title should be changed. + /// + /// An instance of . + /// The new title. + public delegate void SetWindowTitleDelegate(ImGuiViewportPtr viewport, string title); + + /// + /// Delegate to be called when the window alpha should be changed. + /// + /// An instance of . + /// The new alpha. + public delegate void SetWindowAlphaDelegate(ImGuiViewportPtr viewport, float alpha); + + /// + /// Delegate to be called when the IME input position should be changed. + /// + /// An instance of . + /// The new position. + public delegate void SetImeInputPosDelegate(ImGuiViewportPtr viewport, Vector2 pos); + + /// + /// Delegate to be called when the window's DPI scale value is queried. + /// + /// An instance of . + /// The DPI scale. + public delegate float GetWindowDpiScaleDelegate(ImGuiViewportPtr viewport); + + /// + /// Delegate to be called when viewport is changed. + /// + /// An instance of . + public delegate void ChangedViewportDelegate(ImGuiViewportPtr viewport); + + /// + /// Disables ImGui from disabling alpha for Viewport window backgrounds. + /// + public static unsafe void EnableViewportWindowBackgroundAlpha() + { + // TODO: patch imgui.cpp:6126, which disables background transparency for extra viewport windows + var offset = 0x00007FFB6ADA632C - 0x00007FFB6AD60000; + offset += Process.GetCurrentProcess().Modules.Cast().First(x => x.ModuleName == "cimgui.dll") + .BaseAddress; + var b = (byte*)offset; + uint old; + if (!VirtualProtect(b, 1, PAGE.PAGE_EXECUTE_READWRITE, &old)) + { + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) + ?? throw new InvalidOperationException($"{nameof(VirtualProtect)} failed."); + } + + *b = 0xEB; + if (!VirtualProtect(b, 1, old, &old)) + { + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) + ?? throw new InvalidOperationException($"{nameof(VirtualProtect)} failed."); + } + } +} diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs new file mode 100644 index 0000000000..c165840249 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs @@ -0,0 +1,134 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +using static TerraFX.Interop.Windows.Windows; + +namespace Dalamud.Interface.ImGuiBackend.Helpers; + +/// +/// Peels ReShade off stuff. +/// +[SuppressMessage( + "StyleCop.CSharp.LayoutRules", + "SA1519:Braces should not be omitted from multi-line child statement", + Justification = "Multiple fixed blocks")] +internal static unsafe class ReShadePeeler +{ + /// + /// Peels if it is wrapped by ReShade. + /// + /// [inout] The COM pointer to an instance of . + /// A COM type that is or extends . + /// true if peeled. + public static bool PeelSwapChain(ComPtr* comptr) + where T : unmanaged, IDXGISwapChain.Interface => + PeelIUnknown(comptr, 0x10); + + /// + /// Peels if it is wrapped by ReShade. + /// + /// [inout] The COM pointer to an instance of . + /// A COM type that is or extends . + /// true if peeled. + public static bool PeelD3D12Device(ComPtr* comptr) + where T : unmanaged, ID3D12Device.Interface => + PeelIUnknown(comptr, 0x10); + + /// + /// Peels if it is wrapped by ReShade. + /// + /// [inout] The COM pointer to an instance of . + /// A COM type that is or extends . + /// true if peeled. + public static bool PeelD3D12CommandQueue(ComPtr* comptr) + where T : unmanaged, ID3D12CommandQueue.Interface => + PeelIUnknown(comptr, 0x10); + + private static bool PeelIUnknown(ComPtr* comptr, nint offset) + where T : unmanaged, IUnknown.Interface + { + if (comptr->Get() == null || !IsReShadedComObject(comptr->Get())) + return false; + + var punk = new ComPtr(*(IUnknown**)((nint)comptr->Get() + offset)); + using var comptr2 = default(ComPtr); + if (punk.As(&comptr2).FAILED) + return false; + comptr2.Swap(comptr); + return true; + } + + private static bool BelongsInReShadeDll(nint ptr) + { + foreach (ProcessModule processModule in Process.GetCurrentProcess().Modules) + { + if (ptr < processModule.BaseAddress) + continue; + + var dosh = (IMAGE_DOS_HEADER*)processModule.BaseAddress; + var nth = (IMAGE_NT_HEADERS64*)(processModule.BaseAddress + dosh->e_lfanew); + if (ptr >= processModule.BaseAddress + nth->OptionalHeader.SizeOfImage) + continue; + + fixed (byte* pfn0 = "CreateDXGIFactory"u8) + fixed (byte* pfn1 = "D2D1CreateDevice"u8) + fixed (byte* pfn2 = "D3D10CreateDevice"u8) + fixed (byte* pfn3 = "D3D11CreateDevice"u8) + fixed (byte* pfn4 = "D3D12CreateDevice"u8) + fixed (byte* pfn5 = "glBegin"u8) + fixed (byte* pfn6 = "vkCreateDevice"u8) + { + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0) + continue; + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0) + continue; + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0) + continue; + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0) + continue; + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0) + continue; + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0) + continue; + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0) + continue; + } + + var fileInfo = FileVersionInfo.GetVersionInfo(processModule.FileName); + + if (fileInfo.FileDescription == null) + continue; + + if (!fileInfo.FileDescription.Contains("GShade") && !fileInfo.FileDescription.Contains("ReShade")) + continue; + + return true; + } + + return false; + } + + private static bool IsReShadedComObject(T* obj) + where T : unmanaged, IUnknown.Interface + { + try + { + var vtbl = (nint**)Marshal.ReadIntPtr((nint)obj); + for (var i = 0; i < 3; i++) + { + if (!BelongsInReShadeDll(Marshal.ReadIntPtr((nint)(vtbl + i)))) + return false; + } + + return true; + } + catch + { + return false; + } + } +} diff --git a/Dalamud/Interface/ImGuiBackend/IImGuiBackend.cs b/Dalamud/Interface/ImGuiBackend/IImGuiBackend.cs new file mode 100644 index 0000000000..9248b4b5a4 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/IImGuiBackend.cs @@ -0,0 +1,72 @@ +using Dalamud.Interface.ImGuiBackend.InputHandler; +using Dalamud.Interface.ImGuiBackend.Renderers; + +namespace Dalamud.Interface.ImGuiBackend; + +/// Backend for ImGui. +internal interface IImGuiBackend : IDisposable +{ + /// Delegate to be called when ImGui should be used to layout now. + public delegate void BuildUiDelegate(); + + /// Delegate to be called on new input frame. + public delegate void NewInputFrameDelegate(); + + /// Delegaet to be called on new render frame. + public delegate void NewRenderFrameDelegate(); + + /// User methods invoked every ImGui frame to construct custom UIs. + event BuildUiDelegate? BuildUi; + + /// User methods invoked every ImGui frame on handling inputs. + event NewInputFrameDelegate? NewInputFrame; + + /// User methods invoked every ImGui frame on handling renders. + event NewRenderFrameDelegate? NewRenderFrame; + + /// Gets or sets a value indicating whether the cursor should be overridden with the ImGui cursor. + /// + bool UpdateCursor { get; set; } + + /// Gets or sets the path of ImGui configuration .ini file. + string? IniPath { get; set; } + + /// Gets the device handle. + nint DeviceHandle { get; } + + /// Gets the input handler. + IImGuiInputHandler InputHandler { get; } + + /// Gets the renderer. + IImGuiRenderer Renderer { get; } + + /// Performs a render cycle. + void Render(); + + /// Handles stuff before resizing happens. + void OnPreResize(); + + /// Handles stuff after resizing happens. + /// The new width. + /// The new height. + void OnPostResize(int newWidth, int newHeight); + + /// Invalidates fonts immediately. + /// Call this while handling . + void InvalidateFonts(); + + /// Determines if is owned by this. + /// The cursor. + /// Whether it is the case. + bool IsImGuiCursor(nint cursorHandle); + + /// Determines if this instance of is rendering to + /// . + /// The present target handle. + /// Whether it is the case. + bool IsAttachedToPresentationTarget(nint targetHandle); + + /// Determines if the main viewport is full screen. + /// Whether it is the case. + bool IsMainViewportFullScreen(); +} diff --git a/Dalamud/Interface/ImGuiBackend/IWin32Backend.cs b/Dalamud/Interface/ImGuiBackend/IWin32Backend.cs new file mode 100644 index 0000000000..0b158dfbc2 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/IWin32Backend.cs @@ -0,0 +1,15 @@ +using TerraFX.Interop.Windows; + +namespace Dalamud.Interface.ImGuiBackend; + +/// with Win32 support. +internal interface IWin32Backend : IImGuiBackend +{ + /// Processes window messages. + /// Handle of the window. + /// Type of window message. + /// wParam. + /// lParam. + /// Return value. + public nint? ProcessWndProcW(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam); +} diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/IImGuiInputHandler.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/IImGuiInputHandler.cs new file mode 100644 index 0000000000..e6da2281e0 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/InputHandler/IImGuiInputHandler.cs @@ -0,0 +1,22 @@ +namespace Dalamud.Interface.ImGuiBackend.InputHandler; + +/// A simple shared public interface that all ImGui input implementations follows. +internal interface IImGuiInputHandler : IDisposable +{ + /// Gets or sets a value indicating whether the cursor should be overridden with the ImGui cursor. + /// + public bool UpdateCursor { get; set; } + + /// Gets or sets the path of ImGui configuration .ini file. + string? IniPath { get; set; } + + /// Determines if is owned by this. + /// The cursor. + /// Whether it is the case. + public bool IsImGuiCursor(nint cursorHandle); + + /// Marks the beginning of a new frame. + /// The width of the new frame. + /// The height of the new frame. + void NewFrame(int width, int height); +} diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs new file mode 100644 index 0000000000..7732f0867d --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs @@ -0,0 +1,309 @@ +using System.Runtime.CompilerServices; + +using ImGuiNET; + +using TerraFX.Interop.Windows; + +namespace Dalamud.Interface.ImGuiBackend.InputHandler; + +/// +/// An implementation of , using Win32 APIs. +/// +internal sealed partial class Win32InputHandler +{ + /// + /// Maps a to . + /// + /// The virtual key. + /// The corresponding . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImGuiKey VirtualKeyToImGuiKey(int key) => key switch + { + VK.VK_TAB => ImGuiKey.Tab, + VK.VK_LEFT => ImGuiKey.LeftArrow, + VK.VK_RIGHT => ImGuiKey.RightArrow, + VK.VK_UP => ImGuiKey.UpArrow, + VK.VK_DOWN => ImGuiKey.DownArrow, + VK.VK_PRIOR => ImGuiKey.PageUp, + VK.VK_NEXT => ImGuiKey.PageDown, + VK.VK_HOME => ImGuiKey.Home, + VK.VK_END => ImGuiKey.End, + VK.VK_INSERT => ImGuiKey.Insert, + VK.VK_DELETE => ImGuiKey.Delete, + VK.VK_BACK => ImGuiKey.Backspace, + VK.VK_SPACE => ImGuiKey.Space, + VK.VK_RETURN => ImGuiKey.Enter, + VK.VK_ESCAPE => ImGuiKey.Escape, + VK.VK_OEM_7 => ImGuiKey.Apostrophe, + VK.VK_OEM_COMMA => ImGuiKey.Comma, + VK.VK_OEM_MINUS => ImGuiKey.Minus, + VK.VK_OEM_PERIOD => ImGuiKey.Period, + VK.VK_OEM_2 => ImGuiKey.Slash, + VK.VK_OEM_1 => ImGuiKey.Semicolon, + VK.VK_OEM_PLUS => ImGuiKey.Equal, + VK.VK_OEM_4 => ImGuiKey.LeftBracket, + VK.VK_OEM_5 => ImGuiKey.Backslash, + VK.VK_OEM_6 => ImGuiKey.RightBracket, + VK.VK_OEM_3 => ImGuiKey.GraveAccent, + VK.VK_CAPITAL => ImGuiKey.CapsLock, + VK.VK_SCROLL => ImGuiKey.ScrollLock, + VK.VK_NUMLOCK => ImGuiKey.NumLock, + VK.VK_SNAPSHOT => ImGuiKey.PrintScreen, + VK.VK_PAUSE => ImGuiKey.Pause, + VK.VK_NUMPAD0 => ImGuiKey.Keypad0, + VK.VK_NUMPAD1 => ImGuiKey.Keypad1, + VK.VK_NUMPAD2 => ImGuiKey.Keypad2, + VK.VK_NUMPAD3 => ImGuiKey.Keypad3, + VK.VK_NUMPAD4 => ImGuiKey.Keypad4, + VK.VK_NUMPAD5 => ImGuiKey.Keypad5, + VK.VK_NUMPAD6 => ImGuiKey.Keypad6, + VK.VK_NUMPAD7 => ImGuiKey.Keypad7, + VK.VK_NUMPAD8 => ImGuiKey.Keypad8, + VK.VK_NUMPAD9 => ImGuiKey.Keypad9, + VK.VK_DECIMAL => ImGuiKey.KeypadDecimal, + VK.VK_DIVIDE => ImGuiKey.KeypadDivide, + VK.VK_MULTIPLY => ImGuiKey.KeypadMultiply, + VK.VK_SUBTRACT => ImGuiKey.KeypadSubtract, + VK.VK_ADD => ImGuiKey.KeypadAdd, + VK.VK_RETURN + 256 => ImGuiKey.KeypadEnter, + VK.VK_LSHIFT => ImGuiKey.LeftShift, + VK.VK_LCONTROL => ImGuiKey.LeftCtrl, + VK.VK_LMENU => ImGuiKey.LeftAlt, + VK.VK_LWIN => ImGuiKey.LeftSuper, + VK.VK_RSHIFT => ImGuiKey.RightShift, + VK.VK_RCONTROL => ImGuiKey.RightCtrl, + VK.VK_RMENU => ImGuiKey.RightAlt, + VK.VK_RWIN => ImGuiKey.RightSuper, + VK.VK_APPS => ImGuiKey.Menu, + '0' => ImGuiKey._0, + '1' => ImGuiKey._1, + '2' => ImGuiKey._2, + '3' => ImGuiKey._3, + '4' => ImGuiKey._4, + '5' => ImGuiKey._5, + '6' => ImGuiKey._6, + '7' => ImGuiKey._7, + '8' => ImGuiKey._8, + '9' => ImGuiKey._9, + 'A' => ImGuiKey.A, + 'B' => ImGuiKey.B, + 'C' => ImGuiKey.C, + 'D' => ImGuiKey.D, + 'E' => ImGuiKey.E, + 'F' => ImGuiKey.F, + 'G' => ImGuiKey.G, + 'H' => ImGuiKey.H, + 'I' => ImGuiKey.I, + 'J' => ImGuiKey.J, + 'K' => ImGuiKey.K, + 'L' => ImGuiKey.L, + 'M' => ImGuiKey.M, + 'N' => ImGuiKey.N, + 'O' => ImGuiKey.O, + 'P' => ImGuiKey.P, + 'Q' => ImGuiKey.Q, + 'R' => ImGuiKey.R, + 'S' => ImGuiKey.S, + 'T' => ImGuiKey.T, + 'U' => ImGuiKey.U, + 'V' => ImGuiKey.V, + 'W' => ImGuiKey.W, + 'X' => ImGuiKey.X, + 'Y' => ImGuiKey.Y, + 'Z' => ImGuiKey.Z, + VK.VK_F1 => ImGuiKey.F1, + VK.VK_F2 => ImGuiKey.F2, + VK.VK_F3 => ImGuiKey.F3, + VK.VK_F4 => ImGuiKey.F4, + VK.VK_F5 => ImGuiKey.F5, + VK.VK_F6 => ImGuiKey.F6, + VK.VK_F7 => ImGuiKey.F7, + VK.VK_F8 => ImGuiKey.F8, + VK.VK_F9 => ImGuiKey.F9, + VK.VK_F10 => ImGuiKey.F10, + VK.VK_F11 => ImGuiKey.F11, + VK.VK_F12 => ImGuiKey.F12, + _ => ImGuiKey.None, + }; + + /// + /// Maps a to . + /// + /// The ImGui key. + /// The corresponding . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ImGuiKeyToVirtualKey(ImGuiKey key) => key switch + { + ImGuiKey.Tab => VK.VK_TAB, + ImGuiKey.LeftArrow => VK.VK_LEFT, + ImGuiKey.RightArrow => VK.VK_RIGHT, + ImGuiKey.UpArrow => VK.VK_UP, + ImGuiKey.DownArrow => VK.VK_DOWN, + ImGuiKey.PageUp => VK.VK_PRIOR, + ImGuiKey.PageDown => VK.VK_NEXT, + ImGuiKey.Home => VK.VK_HOME, + ImGuiKey.End => VK.VK_END, + ImGuiKey.Insert => VK.VK_INSERT, + ImGuiKey.Delete => VK.VK_DELETE, + ImGuiKey.Backspace => VK.VK_BACK, + ImGuiKey.Space => VK.VK_SPACE, + ImGuiKey.Enter => VK.VK_RETURN, + ImGuiKey.Escape => VK.VK_ESCAPE, + ImGuiKey.Apostrophe => VK.VK_OEM_7, + ImGuiKey.Comma => VK.VK_OEM_COMMA, + ImGuiKey.Minus => VK.VK_OEM_MINUS, + ImGuiKey.Period => VK.VK_OEM_PERIOD, + ImGuiKey.Slash => VK.VK_OEM_2, + ImGuiKey.Semicolon => VK.VK_OEM_1, + ImGuiKey.Equal => VK.VK_OEM_PLUS, + ImGuiKey.LeftBracket => VK.VK_OEM_4, + ImGuiKey.Backslash => VK.VK_OEM_5, + ImGuiKey.RightBracket => VK.VK_OEM_6, + ImGuiKey.GraveAccent => VK.VK_OEM_3, + ImGuiKey.CapsLock => VK.VK_CAPITAL, + ImGuiKey.ScrollLock => VK.VK_SCROLL, + ImGuiKey.NumLock => VK.VK_NUMLOCK, + ImGuiKey.PrintScreen => VK.VK_SNAPSHOT, + ImGuiKey.Pause => VK.VK_PAUSE, + ImGuiKey.Keypad0 => VK.VK_NUMPAD0, + ImGuiKey.Keypad1 => VK.VK_NUMPAD1, + ImGuiKey.Keypad2 => VK.VK_NUMPAD2, + ImGuiKey.Keypad3 => VK.VK_NUMPAD3, + ImGuiKey.Keypad4 => VK.VK_NUMPAD4, + ImGuiKey.Keypad5 => VK.VK_NUMPAD5, + ImGuiKey.Keypad6 => VK.VK_NUMPAD6, + ImGuiKey.Keypad7 => VK.VK_NUMPAD7, + ImGuiKey.Keypad8 => VK.VK_NUMPAD8, + ImGuiKey.Keypad9 => VK.VK_NUMPAD9, + ImGuiKey.KeypadDecimal => VK.VK_DECIMAL, + ImGuiKey.KeypadDivide => VK.VK_DIVIDE, + ImGuiKey.KeypadMultiply => VK.VK_MULTIPLY, + ImGuiKey.KeypadSubtract => VK.VK_SUBTRACT, + ImGuiKey.KeypadAdd => VK.VK_ADD, + ImGuiKey.KeypadEnter => VK.VK_RETURN + 256, + ImGuiKey.LeftShift => VK.VK_LSHIFT, + ImGuiKey.LeftCtrl => VK.VK_LCONTROL, + ImGuiKey.LeftAlt => VK.VK_LMENU, + ImGuiKey.LeftSuper => VK.VK_LWIN, + ImGuiKey.RightShift => VK.VK_RSHIFT, + ImGuiKey.RightCtrl => VK.VK_RCONTROL, + ImGuiKey.RightAlt => VK.VK_RMENU, + ImGuiKey.RightSuper => VK.VK_RWIN, + ImGuiKey.Menu => VK.VK_APPS, + ImGuiKey._0 => '0', + ImGuiKey._1 => '1', + ImGuiKey._2 => '2', + ImGuiKey._3 => '3', + ImGuiKey._4 => '4', + ImGuiKey._5 => '5', + ImGuiKey._6 => '6', + ImGuiKey._7 => '7', + ImGuiKey._8 => '8', + ImGuiKey._9 => '9', + ImGuiKey.A => 'A', + ImGuiKey.B => 'B', + ImGuiKey.C => 'C', + ImGuiKey.D => 'D', + ImGuiKey.E => 'E', + ImGuiKey.F => 'F', + ImGuiKey.G => 'G', + ImGuiKey.H => 'H', + ImGuiKey.I => 'I', + ImGuiKey.J => 'J', + ImGuiKey.K => 'K', + ImGuiKey.L => 'L', + ImGuiKey.M => 'M', + ImGuiKey.N => 'N', + ImGuiKey.O => 'O', + ImGuiKey.P => 'P', + ImGuiKey.Q => 'Q', + ImGuiKey.R => 'R', + ImGuiKey.S => 'S', + ImGuiKey.T => 'T', + ImGuiKey.U => 'U', + ImGuiKey.V => 'V', + ImGuiKey.W => 'W', + ImGuiKey.X => 'X', + ImGuiKey.Y => 'Y', + ImGuiKey.Z => 'Z', + ImGuiKey.F1 => VK.VK_F1, + ImGuiKey.F2 => VK.VK_F2, + ImGuiKey.F3 => VK.VK_F3, + ImGuiKey.F4 => VK.VK_F4, + ImGuiKey.F5 => VK.VK_F5, + ImGuiKey.F6 => VK.VK_F6, + ImGuiKey.F7 => VK.VK_F7, + ImGuiKey.F8 => VK.VK_F8, + ImGuiKey.F9 => VK.VK_F9, + ImGuiKey.F10 => VK.VK_F10, + ImGuiKey.F11 => VK.VK_F11, + ImGuiKey.F12 => VK.VK_F12, + _ => 0, + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsGamepadKey(ImGuiKey key) => (int)key is >= 617 and <= 640; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsModKey(ImGuiKey key) => + key is ImGuiKey.LeftShift + or ImGuiKey.RightShift + or ImGuiKey.ModShift + or ImGuiKey.LeftCtrl + or ImGuiKey.ModCtrl + or ImGuiKey.LeftAlt + or ImGuiKey.RightAlt + or ImGuiKey.ModAlt; + + private static void AddKeyEvent(ImGuiKey key, bool down, int nativeKeycode, int nativeScancode = -1) + { + var io = ImGui.GetIO(); + io.AddKeyEvent(key, down); + io.SetKeyEventNativeData(key, nativeKeycode, nativeScancode); + } + + private static void UpdateKeyModifiers() + { + var io = ImGui.GetIO(); + io.AddKeyEvent(ImGuiKey.ModCtrl, IsVkDown(VK.VK_CONTROL)); + io.AddKeyEvent(ImGuiKey.ModShift, IsVkDown(VK.VK_SHIFT)); + io.AddKeyEvent(ImGuiKey.ModAlt, IsVkDown(VK.VK_MENU)); + io.AddKeyEvent(ImGuiKey.ModSuper, IsVkDown(VK.VK_APPS)); + } + + private static void UpAllKeys() + { + var io = ImGui.GetIO(); + for (var i = (int)ImGuiKey.NamedKey_BEGIN; i < (int)ImGuiKey.NamedKey_END; i++) + io.AddKeyEvent((ImGuiKey)i, false); + } + + private static void UpAllMouseButton() + { + var io = ImGui.GetIO(); + for (var i = 0; i < io.MouseDown.Count; i++) + io.MouseDown[i] = false; + } + + private static bool IsVkDown(int key) => (TerraFX.Interop.Windows.Windows.GetKeyState(key) & 0x8000) != 0; + + private static int GetButton(uint msg, WPARAM wParam) => msg switch + { + WM.WM_LBUTTONUP or WM.WM_LBUTTONDOWN or WM.WM_LBUTTONDBLCLK => 0, + WM.WM_RBUTTONUP or WM.WM_RBUTTONDOWN or WM.WM_RBUTTONDBLCLK => 1, + WM.WM_MBUTTONUP or WM.WM_MBUTTONDOWN or WM.WM_MBUTTONDBLCLK => 2, + WM.WM_XBUTTONUP or WM.WM_XBUTTONDOWN or WM.WM_XBUTTONDBLCLK => + TerraFX.Interop.Windows.Windows.GET_XBUTTON_WPARAM(wParam) == TerraFX.Interop.Windows.Windows.XBUTTON1 ? 3 : 4, + _ => 0, + }; + + private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle) + { + style = (int)(flags.HasFlag(ImGuiViewportFlags.NoDecoration) ? WS.WS_POPUP : WS.WS_OVERLAPPEDWINDOW); + exStyle = + (int)(flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon) ? WS.WS_EX_TOOLWINDOW : (uint)WS.WS_EX_APPWINDOW); + exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP; + if (flags.HasFlag(ImGuiViewportFlags.TopMost)) + exStyle |= WS.WS_EX_TOPMOST; + } +} diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs new file mode 100644 index 0000000000..18fd826511 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs @@ -0,0 +1,1016 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +using ImGuiNET; + +using TerraFX.Interop.Windows; + +using static Dalamud.Interface.ImGuiBackend.Helpers.ImGuiViewportHelpers; + +using static TerraFX.Interop.Windows.Windows; + +using ERROR = TerraFX.Interop.Windows.ERROR; + +namespace Dalamud.Interface.ImGuiBackend.InputHandler; + +/// +/// An implementation of , using Win32 APIs.
+/// Largely a port of https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_win32.cpp, +/// though some changes and wndproc hooking. +///
+[SuppressMessage( + "StyleCop.CSharp.LayoutRules", + "SA1519:Braces should not be omitted from multi-line child statement", + Justification = "Multiple fixed/using scopes")] +internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler +{ + private readonly HWND hWnd; + private readonly HCURSOR[] cursors; + + private readonly WndProcDelegate wndProcDelegate; + private readonly bool[] imguiMouseIsDown; + private readonly nint platformNamePtr; + + private ViewportHandler viewportHandler; + + private long lastTime; + + private nint iniPathPtr; + + private bool disposedValue; + + /// + /// Initializes a new instance of the class. + /// + /// The window handle. + public Win32InputHandler(HWND hWnd) + { + var io = ImGui.GetIO(); + if (ImGui.GetIO().NativePtr->BackendPlatformName is not null) + throw new InvalidOperationException("ImGui backend platform seems to be have been already attached."); + + this.hWnd = hWnd; + + // hook wndproc + // have to hold onto the delegate to keep it in memory for unmanaged code + this.wndProcDelegate = this.WndProcDetour; + + io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors | + ImGuiBackendFlags.HasSetMousePos | + ImGuiBackendFlags.RendererHasViewports | + ImGuiBackendFlags.PlatformHasViewports; + + this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#"); + io.NativePtr->BackendPlatformName = (byte*)this.platformNamePtr; + + var mainViewport = ImGui.GetMainViewport(); + mainViewport.PlatformHandle = mainViewport.PlatformHandleRaw = hWnd; + if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) + this.viewportHandler = new(this); + + this.imguiMouseIsDown = new bool[5]; + + this.cursors = new HCURSOR[9]; + this.cursors[(int)ImGuiMouseCursor.Arrow] = LoadCursorW(default, IDC.IDC_ARROW); + this.cursors[(int)ImGuiMouseCursor.TextInput] = LoadCursorW(default, IDC.IDC_IBEAM); + this.cursors[(int)ImGuiMouseCursor.ResizeAll] = LoadCursorW(default, IDC.IDC_SIZEALL); + this.cursors[(int)ImGuiMouseCursor.ResizeEW] = LoadCursorW(default, IDC.IDC_SIZEWE); + this.cursors[(int)ImGuiMouseCursor.ResizeNS] = LoadCursorW(default, IDC.IDC_SIZENS); + this.cursors[(int)ImGuiMouseCursor.ResizeNESW] = LoadCursorW(default, IDC.IDC_SIZENESW); + this.cursors[(int)ImGuiMouseCursor.ResizeNWSE] = LoadCursorW(default, IDC.IDC_SIZENWSE); + this.cursors[(int)ImGuiMouseCursor.Hand] = LoadCursorW(default, IDC.IDC_HAND); + this.cursors[(int)ImGuiMouseCursor.NotAllowed] = LoadCursorW(default, IDC.IDC_NO); + } + + /// + /// Finalizes an instance of the class. + /// + ~Win32InputHandler() => this.ReleaseUnmanagedResources(); + + private delegate LRESULT WndProcDelegate(HWND hWnd, uint uMsg, WPARAM wparam, LPARAM lparam); + + private delegate BOOL MonitorEnumProcDelegate(HMONITOR monitor, HDC hdc, RECT* rect, LPARAM lparam); + + /// + public bool UpdateCursor { get; set; } = true; + + /// + public string? IniPath + { + get + { + var ptr = (byte*)this.iniPathPtr; + if (ptr is null) + return string.Empty; + var len = 0; + while (ptr![len] != 0) + len++; + return Encoding.UTF8.GetString(ptr, len); + } + + set + { + if (this.iniPathPtr != 0) + Marshal.FreeHGlobal(this.iniPathPtr); + if (!string.IsNullOrEmpty(value)) + { + var e = Encoding.UTF8.GetByteCount(value); + var newAlloc = Marshal.AllocHGlobal(e + 2); + try + { + var span = new Span((void*)newAlloc, e + 2); + span[^1] = span[^2] = 0; + Encoding.UTF8.GetBytes(value, span); + } + catch + { + Marshal.FreeHGlobal(newAlloc); + throw; + } + + this.iniPathPtr = newAlloc; + } + + ImGui.GetIO().NativePtr->IniFilename = (byte*)this.iniPathPtr; + } + } + + /// + public void Dispose() + { + this.ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + /// + public bool IsImGuiCursor(nint hCursor) => this.cursors.Contains((HCURSOR)hCursor); + + /// + public void NewFrame(int targetWidth, int targetHeight) + { + var io = ImGui.GetIO(); + + io.DisplaySize.X = targetWidth; + io.DisplaySize.Y = targetHeight; + io.DisplayFramebufferScale.X = 1f; + io.DisplayFramebufferScale.Y = 1f; + + var frequency = Stopwatch.Frequency; + var currentTime = Stopwatch.GetTimestamp(); + io.DeltaTime = this.lastTime > 0 ? (float)((double)(currentTime - this.lastTime) / frequency) : 1f / 60; + this.lastTime = currentTime; + + this.viewportHandler.UpdateMonitors(); + + this.UpdateMousePos(); + + this.ProcessKeyEventsWorkarounds(); + + // TODO: need to figure out some way to unify all this + // The bottom case works(?) if the caller hooks SetCursor, but otherwise causes fps issues + // The top case more or less works if we use ImGui's software cursor (and ideally hide the + // game's hardware cursor) + // It would be nice if hooking WM_SETCURSOR worked as it 'should' so that external hooking + // wasn't necessary + + // this is what imgui's example does, but it doesn't seem to work for us + // this could be a timing issue.. or their logic could just be wrong for many applications + // var cursor = io.MouseDrawCursor ? ImGuiMouseCursor.None : ImGui.GetMouseCursor(); + // if (_oldCursor != cursor) + // { + // _oldCursor = cursor; + // UpdateMouseCursor(); + // } + + // hacky attempt to make cursors work how I think they 'should' + if ((io.WantCaptureMouse || io.MouseDrawCursor) && this.UpdateCursor) + { + this.UpdateMouseCursor(); + } + + // Similar issue seen with overlapping mouse clicks + // eg, right click and hold on imgui window, drag off, left click and hold + // release right click, release left click -> right click was 'stuck' and imgui + // would become unresponsive + if (!io.WantCaptureMouse) + { + for (var i = 0; i < io.MouseDown.Count; i++) + { + io.MouseDown[i] = false; + } + } + } + + /// + /// Processes window messages. Supports both WndProcA and WndProcW. + /// + /// Handle of the window. + /// Type of window message. + /// wParam. + /// lParam. + /// Return value, if not doing further processing. + public LRESULT? ProcessWndProcW(HWND hWndCurrent, uint msg, WPARAM wParam, LPARAM lParam) + { + if (ImGui.GetCurrentContext() == nint.Zero) + return null; + + var io = ImGui.GetIO(); + + switch (msg) + { + case WM.WM_LBUTTONDOWN: + case WM.WM_LBUTTONDBLCLK: + case WM.WM_RBUTTONDOWN: + case WM.WM_RBUTTONDBLCLK: + case WM.WM_MBUTTONDOWN: + case WM.WM_MBUTTONDBLCLK: + case WM.WM_XBUTTONDOWN: + case WM.WM_XBUTTONDBLCLK: + { + var button = GetButton(msg, wParam); + if (io.WantCaptureMouse) + { + if (!ImGui.IsAnyMouseDown() && GetCapture() == nint.Zero) + SetCapture(hWndCurrent); + + io.MouseDown[button] = true; + this.imguiMouseIsDown[button] = true; + return default(LRESULT); + } + + break; + } + + case WM.WM_LBUTTONUP: + case WM.WM_RBUTTONUP: + case WM.WM_MBUTTONUP: + case WM.WM_XBUTTONUP: + { + var button = GetButton(msg, wParam); + if (io.WantCaptureMouse && this.imguiMouseIsDown[button]) + { + if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent) + ReleaseCapture(); + + io.MouseDown[button] = false; + this.imguiMouseIsDown[button] = false; + return default(LRESULT); + } + + break; + } + + case WM.WM_MOUSEWHEEL: + if (io.WantCaptureMouse) + { + io.MouseWheel += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + return default(LRESULT); + } + + break; + case WM.WM_MOUSEHWHEEL: + if (io.WantCaptureMouse) + { + io.MouseWheelH += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + return default(LRESULT); + } + + break; + case WM.WM_KEYDOWN: + case WM.WM_SYSKEYDOWN: + case WM.WM_KEYUP: + case WM.WM_SYSKEYUP: + { + var isKeyDown = msg is WM.WM_KEYDOWN or WM.WM_SYSKEYDOWN; + if ((int)wParam >= 256) + break; + + // Submit modifiers + UpdateKeyModifiers(); + + // Obtain virtual key code + // (keypad enter doesn't have its own... VK_RETURN with KF_EXTENDED flag means keypad enter, see IM_VK_KEYPAD_ENTER definition for details, it is mapped to ImGuiKey.KeyPadEnter.) + var vk = (int)wParam; + if (vk == VK.VK_RETURN && ((int)lParam & (256 << 16)) > 0) + vk = VK.VK_RETURN + 256; + + // Submit key event + var key = VirtualKeyToImGuiKey(vk); + var scancode = ((int)lParam & 0xff0000) >> 16; + if (key != ImGuiKey.None && io.WantTextInput) + { + AddKeyEvent(key, isKeyDown, vk, scancode); + return nint.Zero; + } + + switch (vk) + { + // Submit individual left/right modifier events + case VK.VK_SHIFT: + // Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in OnProcessKeyEventsWorkarounds() + if (IsVkDown(VK.VK_LSHIFT) == isKeyDown) + AddKeyEvent(ImGuiKey.LeftShift, isKeyDown, VK.VK_LSHIFT, scancode); + + if (IsVkDown(VK.VK_RSHIFT) == isKeyDown) + AddKeyEvent(ImGuiKey.RightShift, isKeyDown, VK.VK_RSHIFT, scancode); + + break; + + case VK.VK_CONTROL: + if (IsVkDown(VK.VK_LCONTROL) == isKeyDown) + AddKeyEvent(ImGuiKey.LeftCtrl, isKeyDown, VK.VK_LCONTROL, scancode); + + if (IsVkDown(VK.VK_RCONTROL) == isKeyDown) + AddKeyEvent(ImGuiKey.RightCtrl, isKeyDown, VK.VK_RCONTROL, scancode); + + break; + + case VK.VK_MENU: + if (IsVkDown(VK.VK_LMENU) == isKeyDown) + AddKeyEvent(ImGuiKey.LeftAlt, isKeyDown, VK.VK_LMENU, scancode); + + if (IsVkDown(VK.VK_RMENU) == isKeyDown) + AddKeyEvent(ImGuiKey.RightAlt, isKeyDown, VK.VK_RMENU, scancode); + + break; + } + + break; + } + + case WM.WM_CHAR: + if (io.WantTextInput) + { + io.AddInputCharacter((uint)wParam); + return nint.Zero; + } + + break; + + // this never seemed to work reasonably, but I'll leave it for now + case WM.WM_SETCURSOR: + if (io.WantCaptureMouse) + { + if (LOWORD(lParam) == HTCLIENT && this.UpdateMouseCursor()) + { + // this message returns 1 to block further processing + // because consistency is no fun + return 1; + } + } + + break; + + case WM.WM_DISPLAYCHANGE: + this.viewportHandler.UpdateMonitors(); + break; + } + + return null; + } + + private void UpdateMousePos() + { + var io = ImGui.GetIO(); + var pt = default(POINT); + + // Depending on if Viewports are enabled, we have to change how we process + // the cursor position. If viewports are enabled, we pass the absolute cursor + // position to ImGui. Otherwise, we use the old method of passing client-local + // mouse position to ImGui. + if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) + { + if (io.WantSetMousePos) + { + SetCursorPos((int)io.MousePos.X, (int)io.MousePos.Y); + } + + if (GetCursorPos(&pt)) + { + io.MousePos.X = pt.x; + io.MousePos.Y = pt.y; + } + else + { + io.MousePos.X = float.MinValue; + io.MousePos.Y = float.MinValue; + } + } + else + { + if (io.WantSetMousePos) + { + pt.x = (int)io.MousePos.X; + pt.y = (int)io.MousePos.Y; + ClientToScreen(this.hWnd, &pt); + SetCursorPos(pt.x, pt.y); + } + + if (GetCursorPos(&pt) && ScreenToClient(this.hWnd, &pt)) + { + io.MousePos.X = pt.x; + io.MousePos.Y = pt.y; + } + else + { + io.MousePos.X = float.MinValue; + io.MousePos.Y = float.MinValue; + } + } + } + + private bool UpdateMouseCursor() + { + var io = ImGui.GetIO(); + if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.NoMouseCursorChange)) + return false; + + var cur = ImGui.GetMouseCursor(); + if (cur == ImGuiMouseCursor.None || io.MouseDrawCursor) + SetCursor(default); + else + SetCursor(this.cursors[(int)cur]); + + return true; + } + + private void ProcessKeyEventsWorkarounds() + { + // Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one. + if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VK.VK_LSHIFT)) + AddKeyEvent(ImGuiKey.LeftShift, false, VK.VK_LSHIFT); + if (ImGui.IsKeyDown(ImGuiKey.RightShift) && !IsVkDown(VK.VK_RSHIFT)) + AddKeyEvent(ImGuiKey.RightShift, false, VK.VK_RSHIFT); + + // Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW). + if (ImGui.IsKeyDown(ImGuiKey.LeftSuper) && !IsVkDown(VK.VK_LWIN)) + AddKeyEvent(ImGuiKey.LeftSuper, false, VK.VK_LWIN); + if (ImGui.IsKeyDown(ImGuiKey.RightSuper) && !IsVkDown(VK.VK_RWIN)) + AddKeyEvent(ImGuiKey.RightSuper, false, VK.VK_RWIN); + + // From ImGui's FAQ: + // Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event + // that your application receive will typically have io.WantCaptureKeyboard == false. Depending on your + // application logic it may or not be inconvenient. + // + // With how the local wndproc works, this causes the key up event to be missed when exiting ImGui text entry + // (eg, from hitting enter or escape. There may be other ways as well) + // This then causes the key to appear 'stuck' down, which breaks subsequent attempts to use the input field. + // This is something of a brute force fix that basically makes key up events irrelevant + // Holding a key will send repeated key down events and (re)set these where appropriate, so this should be ok. + var io = ImGui.GetIO(); + if (!io.WantTextInput) + { + // See: https://github.com/goatcorp/ImGuiScene/pull/13 + // > GetForegroundWindow from winuser.h is a surprisingly expensive function. + var isForeground = GetForegroundWindow() == this.hWnd; + for (var i = (int)ImGuiKey.NamedKey_BEGIN; i < (int)ImGuiKey.NamedKey_END; i++) + { + // Skip raising modifier keys if the game is focused. + // This allows us to raise the keys when one is held and the window becomes unfocused, + // but if we do not skip them, they will only be held down every 4th frame or so. + if (isForeground && (IsGamepadKey((ImGuiKey)i) || IsModKey((ImGuiKey)i))) + continue; + io.AddKeyEvent((ImGuiKey)i, false); + } + } + } + + /// + /// This WndProc is called for ImGuiScene windows. WndProc for main window will be called back from somewhere else. + /// + private LRESULT WndProcDetour(HWND hWndCurrent, uint msg, WPARAM wParam, LPARAM lParam) + { + // Attempt to process the result of this window message + // We will return the result here if we consider the message handled + var processResult = this.ProcessWndProcW(hWndCurrent, msg, wParam, lParam); + + if (processResult != null) return processResult.Value; + + // The message wasn't handled, but it's a platform window + // So we have to handle some messages ourselves + // BUT we might have disposed the context, so check that + if (ImGui.GetCurrentContext() == nint.Zero) + return DefWindowProcW(hWndCurrent, msg, wParam, lParam); + + var viewport = ImGui.FindViewportByPlatformHandle(hWndCurrent); + if (viewport.NativePtr == null) + return DefWindowProcW(hWndCurrent, msg, wParam, lParam); + + switch (msg) + { + case WM.WM_CLOSE: + viewport.PlatformRequestClose = true; + return 0; + case WM.WM_MOVE: + viewport.PlatformRequestMove = true; + return 0; + case WM.WM_SIZE: + viewport.PlatformRequestResize = true; + return 0; + case WM.WM_MOUSEACTIVATE: + // We never want our platform windows to be active, or else Windows will think we + // want messages dispatched with its hWnd. We don't. The only way to activate a platform + // window is via clicking, it does not appear on the taskbar or alt-tab, so we just + // brute force behavior here. + + // Make the game the foreground window. This prevents ImGui windows from becoming + // choppy when users have the "limit FPS" option enabled in-game + SetForegroundWindow(this.hWnd); + + // Also set the window capture to the main window, as focus will not cause + // future messages to be dispatched to the main window unless it is receiving capture + SetCapture(this.hWnd); + + // We still want to return MA_NOACTIVATE + // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mouseactivate + return 0x3; + case WM.WM_NCHITTEST: + // Let mouse pass-through the window. This will allow the backend to set io.MouseHoveredViewport properly (which is OPTIONAL). + // The ImGuiViewportFlags_NoInputs flag is set while dragging a viewport, as want to detect the window behind the one we are dragging. + // If you cannot easily access those viewport flags from your windowing/event code: you may manually synchronize its state e.g. in + // your main loop after calling UpdatePlatformWindows(). Iterate all viewports/platform windows and pass the flag to your windowing system. + if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoInputs)) + { + // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest + return -1; + } + + break; + } + + return DefWindowProcW(hWndCurrent, msg, wParam, lParam); + } + + private void ReleaseUnmanagedResources() + { + if (this.disposedValue) + return; + + this.viewportHandler.Dispose(); + + this.cursors.AsSpan().Clear(); + + if (ImGui.GetIO().NativePtr->BackendPlatformName == (void*)this.platformNamePtr) + ImGui.GetIO().NativePtr->BackendPlatformName = null; + if (this.platformNamePtr != nint.Zero) + Marshal.FreeHGlobal(this.platformNamePtr); + + if (this.iniPathPtr != nint.Zero) + { + ImGui.GetIO().NativePtr->IniFilename = null; + Marshal.FreeHGlobal(this.iniPathPtr); + this.iniPathPtr = nint.Zero; + } + + this.disposedValue = true; + } + + private struct ViewportHandler : IDisposable + { + [SuppressMessage("ReSharper", "CollectionNeverQueried.Local", Justification = "Keeping references alive")] + private readonly List delegateReferences = new(); + + private Win32InputHandler input; + private nint classNamePtr; + + private bool wantUpdateMonitors = true; + + public ViewportHandler(Win32InputHandler input) + { + this.input = input; + this.classNamePtr = Marshal.StringToHGlobalUni("ImGui Platform"); + + var pio = ImGui.GetPlatformIO(); + pio.Platform_CreateWindow = this.RegisterFunctionPointer(this.OnCreateWindow); + pio.Platform_DestroyWindow = this.RegisterFunctionPointer(this.OnDestroyWindow); + pio.Platform_ShowWindow = this.RegisterFunctionPointer(this.OnShowWindow); + pio.Platform_SetWindowPos = this.RegisterFunctionPointer(this.OnSetWindowPos); + pio.Platform_GetWindowPos = this.RegisterFunctionPointer(this.OnGetWindowPos); + pio.Platform_SetWindowSize = this.RegisterFunctionPointer(this.OnSetWindowSize); + pio.Platform_GetWindowSize = this.RegisterFunctionPointer(this.OnGetWindowSize); + pio.Platform_SetWindowFocus = this.RegisterFunctionPointer(this.OnSetWindowFocus); + pio.Platform_GetWindowFocus = this.RegisterFunctionPointer(this.OnGetWindowFocus); + pio.Platform_GetWindowMinimized = + this.RegisterFunctionPointer(this.OnGetWindowMinimized); + pio.Platform_SetWindowTitle = this.RegisterFunctionPointer(this.OnSetWindowTitle); + pio.Platform_SetWindowAlpha = this.RegisterFunctionPointer(this.OnSetWindowAlpha); + pio.Platform_UpdateWindow = this.RegisterFunctionPointer(this.OnUpdateWindow); + // pio.Platform_SetImeInputPos = this.RegisterFunctionPointer(this.OnSetImeInputPos); + // pio.Platform_GetWindowDpiScale = this.RegisterFunctionPointer(this.OnGetWindowDpiScale); + // pio.Platform_ChangedViewport = this.RegisterFunctionPointer(this.OnChangedViewport); + + var wcex = new WNDCLASSEXW + { + cbSize = (uint)sizeof(WNDCLASSEXW), + style = CS.CS_HREDRAW | CS.CS_VREDRAW, + hInstance = GetModuleHandleW(null), + hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND), + lpfnWndProc = (delegate* unmanaged)Marshal + .GetFunctionPointerForDelegate(this.input.wndProcDelegate), + lpszClassName = (ushort*)this.classNamePtr, + }; + + if (RegisterClassExW(&wcex) == 0) + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ?? new("RegisterClassEx Fail"); + + // Register main window handle (which is owned by the main application, not by us) + // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports. + var mainViewport = ImGui.GetMainViewport(); + + var data = (ImGuiViewportDataWin32*)Marshal.AllocHGlobal(Marshal.SizeOf()); + mainViewport.PlatformUserData = (nint)data; + data->Hwnd = this.input.hWnd; + data->HwndOwned = false; + mainViewport.PlatformHandle = this.input.hWnd; + } + + public void Dispose() + { + if (this.input is null) + return; + + var pio = ImGui.GetPlatformIO(); + + if (ImGui.GetPlatformIO().NativePtr->Monitors.Data != 0) + { + // We allocated the platform monitor data in OnUpdateMonitors ourselves, + // so we have to free it ourselves to ImGui doesn't try to, or else it will crash + Marshal.FreeHGlobal(ImGui.GetPlatformIO().NativePtr->Monitors.Data); + ImGui.GetPlatformIO().NativePtr->Monitors = default; + } + + if (this.classNamePtr != 0) + { + UnregisterClassW((ushort*)this.classNamePtr, GetModuleHandleW(null)); + Marshal.FreeHGlobal(this.classNamePtr); + this.classNamePtr = 0; + } + + pio.Platform_CreateWindow = nint.Zero; + pio.Platform_DestroyWindow = nint.Zero; + pio.Platform_ShowWindow = nint.Zero; + pio.Platform_SetWindowPos = nint.Zero; + pio.Platform_GetWindowPos = nint.Zero; + pio.Platform_SetWindowSize = nint.Zero; + pio.Platform_GetWindowSize = nint.Zero; + pio.Platform_SetWindowFocus = nint.Zero; + pio.Platform_GetWindowFocus = nint.Zero; + pio.Platform_GetWindowMinimized = nint.Zero; + pio.Platform_SetWindowTitle = nint.Zero; + pio.Platform_SetWindowAlpha = nint.Zero; + pio.Platform_UpdateWindow = nint.Zero; + // pio.Platform_SetImeInputPos = nint.Zero; + // pio.Platform_GetWindowDpiScale = nint.Zero; + // pio.Platform_ChangedViewport = nint.Zero; + + this.input = null!; + } + + public void UpdateMonitors() + { + if (!this.wantUpdateMonitors || this.input is null) + return; + + this.wantUpdateMonitors = false; + + // Set up platformIO monitor structures + // Here we use a manual ImVector overload, free the existing monitor data, + // and allocate our own, as we are responsible for telling ImGui about monitors + var pio = ImGui.GetPlatformIO(); + var numMonitors = GetSystemMetrics(SM.SM_CMONITORS); + var data = Marshal.AllocHGlobal(Marshal.SizeOf() * numMonitors); + if (pio.NativePtr->Monitors.Data != 0) + Marshal.FreeHGlobal(pio.NativePtr->Monitors.Data); + pio.NativePtr->Monitors = new(numMonitors, numMonitors, data); + + // ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); + // Marshal.FreeHGlobal(platformIO.NativePtr->Monitors.Data); + // int numMonitors = GetSystemMetrics(SystemMetric.SM_CMONITORS); + // nint data = Marshal.AllocHGlobal(Marshal.SizeOf() * numMonitors); + // platformIO.NativePtr->Monitors = new ImVector(numMonitors, numMonitors, data); + + var monitorIndex = -1; + var enumfn = new MonitorEnumProcDelegate( + (hMonitor, _, _, _) => + { + monitorIndex++; + var info = new MONITORINFO { cbSize = (uint)sizeof(MONITORINFO) }; + if (!GetMonitorInfoW(hMonitor, &info)) + return true; + + var monitorLt = new Vector2(info.rcMonitor.left, info.rcMonitor.top); + var monitorRb = new Vector2(info.rcMonitor.right, info.rcMonitor.bottom); + var workLt = new Vector2(info.rcWork.left, info.rcWork.top); + var workRb = new Vector2(info.rcWork.right, info.rcWork.bottom); + // Give ImGui the info for this display + + var imMonitor = ImGui.GetPlatformIO().Monitors[monitorIndex]; + imMonitor.MainPos = monitorLt; + imMonitor.MainSize = monitorRb - monitorLt; + imMonitor.WorkPos = workLt; + imMonitor.WorkSize = workRb - workLt; + imMonitor.DpiScale = 1f; + return true; + }); + EnumDisplayMonitors( + default, + null, + (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(enumfn), + default); + } + + private nint RegisterFunctionPointer(T obj) + { + this.delegateReferences.Add(obj); + return Marshal.GetFunctionPointerForDelegate(obj); + } + + private void OnCreateWindow(ImGuiViewportPtr viewport) + { + var data = (ImGuiViewportDataWin32*)Marshal.AllocHGlobal(Marshal.SizeOf()); + viewport.PlatformUserData = (nint)data; + viewport.Flags = + ImGuiViewportFlags.NoTaskBarIcon | + ImGuiViewportFlags.NoFocusOnClick | + ImGuiViewportFlags.NoFocusOnAppearing | + viewport.Flags; + ViewportFlagsToWin32Styles(viewport.Flags, out data->DwStyle, out data->DwExStyle); + + var parentWindow = default(HWND); + if (viewport.ParentViewportId != 0) + { + var parentViewport = ImGui.FindViewportByID(viewport.ParentViewportId); + parentWindow = (HWND)parentViewport.PlatformHandle; + } + + // Create window + var rect = new RECT + { + left = (int)viewport.Pos.X, + top = (int)viewport.Pos.Y, + right = (int)(viewport.Pos.X + viewport.Size.X), + bottom = (int)(viewport.Pos.Y + viewport.Size.Y), + }; + AdjustWindowRectEx(&rect, (uint)data->DwStyle, false, (uint)data->DwExStyle); + + fixed (char* pwszWindowTitle = "Untitled") + { + data->Hwnd = CreateWindowExW( + (uint)data->DwExStyle, + (ushort*)this.classNamePtr, + (ushort*)pwszWindowTitle, + (uint)data->DwStyle, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + parentWindow, + default, + GetModuleHandleW(null), + default); + } + + data->HwndOwned = true; + viewport.PlatformRequestResize = false; + viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd; + } + + private void OnDestroyWindow(ImGuiViewportPtr viewport) + { + // This is also called on the main viewport for some reason, and we never set that viewport's PlatformUserData + if (viewport.PlatformUserData == nint.Zero) return; + + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + + if (GetCapture() == data->Hwnd) + { + // Transfer capture so if we started dragging from a window that later disappears, we'll still receive the MOUSEUP event. + ReleaseCapture(); + SetCapture(this.input.hWnd); + } + + if (data->Hwnd != nint.Zero && data->HwndOwned) + { + var result = DestroyWindow(data->Hwnd); + if (result == false && GetLastError() == ERROR.ERROR_ACCESS_DENIED) + { + // We are disposing, and we're doing it from a different thread because of course we are + // Just send the window the close message + PostMessageW(data->Hwnd, WM.WM_CLOSE, default, default); + } + } + + data->Hwnd = default; + Marshal.FreeHGlobal(viewport.PlatformUserData); + viewport.PlatformUserData = viewport.PlatformHandle = nint.Zero; + } + + private void OnShowWindow(ImGuiViewportPtr viewport) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + + if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoFocusOnAppearing)) + ShowWindow(data->Hwnd, SW.SW_SHOWNA); + else + ShowWindow(data->Hwnd, SW.SW_SHOW); + } + + private void OnUpdateWindow(ImGuiViewportPtr viewport) + { + // (Optional) Update Win32 style if it changed _after_ creation. + // Generally they won't change unless configuration flags are changed, but advanced uses (such as manually rewriting viewport flags) make this useful. + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + + viewport.Flags = + ImGuiViewportFlags.NoTaskBarIcon | + ImGuiViewportFlags.NoFocusOnClick | + ImGuiViewportFlags.NoFocusOnAppearing | + viewport.Flags; + ViewportFlagsToWin32Styles(viewport.Flags, out var newStyle, out var newExStyle); + + // Only reapply the flags that have been changed from our point of view (as other flags are being modified by Windows) + if (data->DwStyle != newStyle || data->DwExStyle != newExStyle) + { + // (Optional) Update TopMost state if it changed _after_ creation + var topMostChanged = (data->DwExStyle & WS.WS_EX_TOPMOST) != + (newExStyle & WS.WS_EX_TOPMOST); + + var insertAfter = default(HWND); + if (topMostChanged) + { + insertAfter = viewport.Flags.HasFlag(ImGuiViewportFlags.TopMost) + ? HWND.HWND_TOPMOST + : HWND.HWND_NOTOPMOST; + } + + var swpFlag = topMostChanged ? 0 : SWP.SWP_NOZORDER; + + // Apply flags and position (since it is affected by flags) + data->DwStyle = newStyle; + data->DwExStyle = newExStyle; + + _ = SetWindowLongW(data->Hwnd, GWL.GWL_STYLE, data->DwStyle); + _ = SetWindowLongW(data->Hwnd, GWL.GWL_EXSTYLE, data->DwExStyle); + + // Create window + var rect = new RECT + { + left = (int)viewport.Pos.X, + top = (int)viewport.Pos.Y, + right = (int)(viewport.Pos.X + viewport.Size.X), + bottom = (int)(viewport.Pos.Y + viewport.Size.Y), + }; + AdjustWindowRectEx(&rect, (uint)data->DwStyle, false, (uint)data->DwExStyle); + SetWindowPos( + data->Hwnd, + insertAfter, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + (uint)(swpFlag | SWP.SWP_NOACTIVATE | SWP.SWP_FRAMECHANGED)); + + // This is necessary when we alter the style + ShowWindow(data->Hwnd, SW.SW_SHOWNA); + viewport.PlatformRequestMove = viewport.PlatformRequestResize = true; + } + } + + private Vector2* OnGetWindowPos(Vector2* returnStorage, ImGuiViewportPtr viewport) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + var pt = new POINT { x = 0, y = 0 }; + ClientToScreen(data->Hwnd, &pt); + returnStorage->X = pt.x; + returnStorage->Y = pt.y; + return returnStorage; + } + + private void OnSetWindowPos(ImGuiViewportPtr viewport, Vector2 pos) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + var rect = new RECT((int)pos.X, (int)pos.Y, (int)pos.X, (int)pos.Y); + AdjustWindowRectEx(&rect, (uint)data->DwStyle, false, (uint)data->DwExStyle); + SetWindowPos( + data->Hwnd, + default, + rect.left, + rect.top, + 0, + 0, + SWP.SWP_NOZORDER | + SWP.SWP_NOSIZE | + SWP.SWP_NOACTIVATE); + } + + private Vector2* OnGetWindowSize(Vector2* returnStorage, ImGuiViewportPtr viewport) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + RECT rect; + GetClientRect(data->Hwnd, &rect); + returnStorage->X = rect.right - rect.left; + returnStorage->Y = rect.bottom - rect.top; + return returnStorage; + } + + private void OnSetWindowSize(ImGuiViewportPtr viewport, Vector2 size) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + + var rect = new RECT(0, 0, (int)size.X, (int)size.Y); + AdjustWindowRectEx(&rect, (uint)data->DwStyle, false, (uint)data->DwExStyle); + SetWindowPos( + data->Hwnd, + default, + 0, + 0, + rect.right - rect.left, + rect.bottom - rect.top, + SWP.SWP_NOZORDER | + SWP.SWP_NOMOVE | + SWP.SWP_NOACTIVATE); + } + + private void OnSetWindowFocus(ImGuiViewportPtr viewport) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + + BringWindowToTop(data->Hwnd); + SetForegroundWindow(data->Hwnd); + SetFocus(data->Hwnd); + } + + private bool OnGetWindowFocus(ImGuiViewportPtr viewport) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + return GetForegroundWindow() == data->Hwnd; + } + + private bool OnGetWindowMinimized(ImGuiViewportPtr viewport) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + return IsIconic(data->Hwnd); + } + + private void OnSetWindowTitle(ImGuiViewportPtr viewport, string title) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + fixed (char* pwszTitle = title) + SetWindowTextW(data->Hwnd, (ushort*)pwszTitle); + } + + private void OnSetWindowAlpha(ImGuiViewportPtr viewport, float alpha) + { + var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; + var style = GetWindowLongW(data->Hwnd, GWL.GWL_EXSTYLE); + + alpha = Math.Clamp(alpha, 0f, 1f); + if (alpha < 1.0f) + { + style |= WS.WS_EX_LAYERED; + _ = SetWindowLongW(data->Hwnd, GWL.GWL_EXSTYLE, style); + } + else + { + style &= ~WS.WS_EX_LAYERED; + _ = SetWindowLongW(data->Hwnd, GWL.GWL_EXSTYLE, style); + } + + _ = SetLayeredWindowAttributes(data->Hwnd, 0, (byte)(255 * alpha), LWA.LWA_ALPHA); + } + + // TODO: Decode why IME is miserable + // private void OnSetImeInputPos(ImGuiViewportPtr viewport, Vector2 pos) { + // COMPOSITIONFORM cs = new COMPOSITIONFORM( + // 0x20, + // new POINT( + // (int) (pos.X - viewport.Pos.X), + // (int) (pos.Y - viewport.Pos.Y)), + // new RECT(0, 0, 0, 0) + // ); + // var hwnd = viewport.PlatformHandle; + // if (hwnd != nint.Zero) { + // var himc = ImmGetContext(hwnd); + // if (himc != nint.Zero) { + // ImmSetCompositionWindow(himc, ref cs); + // ImmReleaseContext(hwnd, himc); + // } + // } + // } + + // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data-> + private struct ImGuiViewportDataWin32 + { + public HWND Hwnd; + public bool HwndOwned; + public int DwStyle; + public int DwExStyle; + } + } +} diff --git a/Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.ViewportHandler.cs b/Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.ViewportHandler.cs new file mode 100644 index 0000000000..7fcbe432c1 --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.ViewportHandler.cs @@ -0,0 +1,374 @@ +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +using Dalamud.Interface.ImGuiBackend.Helpers; +using Dalamud.Utility; + +using ImGuiNET; + +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +using static TerraFX.Interop.Windows.Windows; + +namespace Dalamud.Interface.ImGuiBackend.Renderers; + +/// +/// Deals with rendering ImGui using DirectX 11. +/// See https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_dx11.cpp for the original implementation. +/// +[SuppressMessage( + "StyleCop.CSharp.LayoutRules", + "SA1519:Braces should not be omitted from multi-line child statement", + Justification = "Multiple fixed/using scopes")] +internal unsafe partial class Dx11Renderer +{ + private class ViewportHandler : IDisposable + { + private readonly Dx11Renderer renderer; + + [SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "Keeping reference alive")] + private readonly ImGuiViewportHelpers.CreateWindowDelegate cwd; + + public ViewportHandler(Dx11Renderer renderer) + { + this.renderer = renderer; + + var pio = ImGui.GetPlatformIO(); + pio.Renderer_CreateWindow = Marshal.GetFunctionPointerForDelegate(this.cwd = this.OnCreateWindow); + pio.Renderer_DestroyWindow = (nint)(delegate* unmanaged)&OnDestroyWindow; + pio.Renderer_SetWindowSize = (nint)(delegate* unmanaged)&OnSetWindowSize; + pio.Renderer_RenderWindow = (nint)(delegate* unmanaged)&OnRenderWindow; + pio.Renderer_SwapBuffers = (nint)(delegate* unmanaged)&OnSwapBuffers; + } + + ~ViewportHandler() => ReleaseUnmanagedResources(); + + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private static void ReleaseUnmanagedResources() + { + var pio = ImGui.GetPlatformIO(); + pio.Renderer_CreateWindow = nint.Zero; + pio.Renderer_DestroyWindow = nint.Zero; + pio.Renderer_SetWindowSize = nint.Zero; + pio.Renderer_RenderWindow = nint.Zero; + pio.Renderer_SwapBuffers = nint.Zero; + } + + [UnmanagedCallersOnly] + private static void OnDestroyWindow(ImGuiViewportPtr viewport) + { + if (viewport.RendererUserData == nint.Zero) + return; + ViewportData.Attach(viewport.RendererUserData).Dispose(); + viewport.RendererUserData = nint.Zero; + } + + [UnmanagedCallersOnly] + private static void OnSetWindowSize(ImGuiViewportPtr viewport, Vector2 size) => + ViewportData.Attach(viewport.RendererUserData).ResizeBuffers((int)size.X, (int)size.Y, true); + + [UnmanagedCallersOnly] + private static void OnRenderWindow(ImGuiViewportPtr viewport, nint v) => + ViewportData.Attach(viewport.RendererUserData).Draw(viewport.DrawData, true); + + [UnmanagedCallersOnly] + private static void OnSwapBuffers(ImGuiViewportPtr viewport, nint v) => + ViewportData.Attach(viewport.RendererUserData).PresentIfSwapChainAvailable(); + + private void OnCreateWindow(ImGuiViewportPtr viewport) + { + // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). + // Some backend will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the HWND. + var hWnd = viewport.PlatformHandleRaw; + if (hWnd == 0) + hWnd = viewport.PlatformHandle; + try + { + viewport.RendererUserData = ViewportData.CreateDComposition(this.renderer, (HWND)hWnd).Handle; + } + catch + { + viewport.RendererUserData = ViewportData.Create(this.renderer, (HWND)hWnd).Handle; + } + } + } + + private sealed class ViewportData : IDisposable + { + private readonly Dx11Renderer parent; + + private GCHandle selfGcHandle; + private ComPtr swapChain; + private ComPtr renderTarget; + private ComPtr renderTargetView; + private ComPtr dcompVisual; + private ComPtr dcompTarget; + + private int width; + private int height; + + public ViewportData( + Dx11Renderer parent, + IDXGISwapChain* swapChain, + int width, + int height, + IDCompositionVisual* dcompVisual, + IDCompositionTarget* dcompTarget) + { + this.parent = parent; + this.swapChain = new(swapChain); + this.width = width; + this.height = height; + if (dcompVisual is not null) + this.dcompVisual = new(dcompVisual); + if (dcompTarget is not null) + this.dcompTarget = new(dcompTarget); + this.selfGcHandle = GCHandle.Alloc(this); + } + + public IDXGISwapChain* SwapChain => this.swapChain; + + public nint Handle => GCHandle.ToIntPtr(this.selfGcHandle); + + private DXGI_FORMAT RtvFormat => this.parent.rtvFormat; + + public static ViewportData Attach(nint handle) => + (ViewportData)GCHandle.FromIntPtr(handle).Target ?? throw new InvalidOperationException(); + + public static ViewportData Create( + Dx11Renderer renderer, + IDXGISwapChain* swapChain, + IDCompositionVisual* dcompVisual, + IDCompositionTarget* dcompTarget) + { + DXGI_SWAP_CHAIN_DESC desc; + swapChain->GetDesc(&desc).ThrowOnError(); + return new( + renderer, + swapChain, + (int)desc.BufferDesc.Width, + (int)desc.BufferDesc.Height, + dcompVisual, + dcompTarget); + } + + public static ViewportData CreateDComposition(Dx11Renderer renderer, HWND hWnd) + { + if (renderer.dcompDevice.IsEmpty()) + throw new NotSupportedException(); + + var mvsd = default(DXGI_SWAP_CHAIN_DESC); + renderer.mainViewport.SwapChain->GetDesc(&mvsd).ThrowOnError(); + + using var dxgiFactory = default(ComPtr); + fixed (Guid* piidFactory = &IID.IID_IDXGIFactory4) + { +#if DEBUG + DirectX.CreateDXGIFactory2( + DXGI.DXGI_CREATE_FACTORY_DEBUG, + piidFactory, + (void**)dxgiFactory.GetAddressOf()).ThrowOnError(); +#else + DirectX.CreateDXGIFactory1(piidFactory, (void**)dxgiFactory.GetAddressOf()).ThrowOnError(); +#endif + } + + RECT rc; + if (!GetWindowRect(hWnd, &rc) || rc.right == rc.left || rc.bottom == rc.top) + rc = new(0, 0, 4, 4); + + using var swapChain1 = default(ComPtr); + var sd1 = new DXGI_SWAP_CHAIN_DESC1 + { + Width = (uint)(rc.right - rc.left), + Height = (uint)(rc.bottom - rc.top), + Format = renderer.rtvFormat, + Stereo = false, + SampleDesc = new(1, 0), + BufferUsage = DXGI.DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount = Math.Max(2u, mvsd.BufferCount), + Scaling = DXGI_SCALING.DXGI_SCALING_STRETCH, + SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + AlphaMode = DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_PREMULTIPLIED, + Flags = 0, + }; + dxgiFactory.Get()->CreateSwapChainForComposition( + (IUnknown*)renderer.device.Get(), + &sd1, + null, + swapChain1.GetAddressOf()).ThrowOnError(); + + if (ReShadePeeler.PeelSwapChain(&swapChain1)) + { + swapChain1.Get()->ResizeBuffers(sd1.BufferCount, sd1.Width, sd1.Height, sd1.Format, sd1.Flags) + .ThrowOnError(); + } + + using var dcTarget = default(ComPtr); + renderer.dcompDevice.Get()->CreateTargetForHwnd(hWnd, BOOL.TRUE, dcTarget.GetAddressOf()); + + using var dcVisual = default(ComPtr); + renderer.dcompDevice.Get()->CreateVisual(dcVisual.GetAddressOf()).ThrowOnError(); + + dcVisual.Get()->SetContent((IUnknown*)swapChain1.Get()).ThrowOnError(); + dcTarget.Get()->SetRoot(dcVisual).ThrowOnError(); + renderer.dcompDevice.Get()->Commit().ThrowOnError(); + + using var swapChain = default(ComPtr); + swapChain1.As(&swapChain).ThrowOnError(); + return Create(renderer, swapChain, dcVisual, dcTarget); + } + + public static ViewportData Create(Dx11Renderer renderer, HWND hWnd) + { + using var dxgiFactory = default(ComPtr); + fixed (Guid* piidFactory = &IID.IID_IDXGIFactory) + { +#if DEBUG + DirectX.CreateDXGIFactory2( + DXGI.DXGI_CREATE_FACTORY_DEBUG, + piidFactory, + (void**)dxgiFactory.GetAddressOf()).ThrowOnError(); +#else + DirectX.CreateDXGIFactory(piidFactory, (void**)dxgiFactory.GetAddressOf()).ThrowOnError(); +#endif + } + + // Create swapchain + using var swapChain = default(ComPtr); + var desc = new DXGI_SWAP_CHAIN_DESC + { + BufferDesc = + { + Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM, + }, + SampleDesc = new(1, 0), + BufferUsage = DXGI.DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount = 1, + OutputWindow = hWnd, + Windowed = true, + SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_DISCARD, + }; + dxgiFactory.Get()->CreateSwapChain((IUnknown*)renderer.device.Get(), &desc, swapChain.GetAddressOf()) + .ThrowOnError(); + + if (ReShadePeeler.PeelSwapChain(&swapChain)) + { + swapChain.Get()->ResizeBuffers( + desc.BufferCount, + desc.BufferDesc.Width, + desc.BufferDesc.Height, + desc.BufferDesc.Format, + desc.Flags) + .ThrowOnError(); + } + + return Create(renderer, swapChain, null, null); + } + + public void Dispose() + { + if (!this.selfGcHandle.IsAllocated) + return; + + this.ResetBuffers(); + this.dcompVisual.Reset(); + this.dcompTarget.Reset(); + this.swapChain.Reset(); + this.selfGcHandle.Free(); + } + + public void Draw(ImDrawDataPtr drawData, bool clearRenderTarget) + { + if (this.width < 1 || this.height < 1) + return; + + this.EnsureRenderTarget(); + this.parent.RenderDrawDataInternal(this.renderTargetView, drawData, clearRenderTarget); + } + + public void PresentIfSwapChainAvailable() + { + if (this.width < 1 || this.height < 1) + return; + + if (!this.swapChain.IsEmpty()) + this.swapChain.Get()->Present(0, 0).ThrowOnError(); + } + + public void ResetBuffers() + { + this.renderTargetView.Reset(); + this.renderTarget.Reset(); + } + + public void ResizeBuffers(int newWidth, int newHeight, bool resizeSwapChain) + { + this.ResetBuffers(); + + this.width = newWidth; + this.height = newHeight; + if (this.width < 1 || this.height < 1) + return; + + if (resizeSwapChain && !this.swapChain.IsEmpty()) + { + DXGI_SWAP_CHAIN_DESC desc; + this.swapChain.Get()->GetDesc(&desc).ThrowOnError(); + this.swapChain.Get()->ResizeBuffers( + desc.BufferCount, + (uint)newWidth, + (uint)newHeight, + DXGI_FORMAT.DXGI_FORMAT_UNKNOWN, + desc.Flags).ThrowOnError(); + } + } + + private void EnsureRenderTarget() + { + if (!this.renderTarget.IsEmpty() && !this.renderTargetView.IsEmpty()) + return; + + this.ResetBuffers(); + + fixed (ID3D11Texture2D** pprt = &this.renderTarget.GetPinnableReference()) + fixed (ID3D11RenderTargetView** pprtv = &this.renderTargetView.GetPinnableReference()) + { + if (this.swapChain.IsEmpty()) + { + var desc = new D3D11_TEXTURE2D_DESC + { + Width = (uint)this.width, + Height = (uint)this.height, + MipLevels = 1, + ArraySize = 1, + Format = this.RtvFormat, + SampleDesc = new(1, 0), + Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT, + BindFlags = (uint)D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE, + CPUAccessFlags = 0, + MiscFlags = (uint)D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_SHARED_NTHANDLE, + }; + this.parent.device.Get()->CreateTexture2D(&desc, null, pprt).ThrowOnError(); + } + else + { + fixed (Guid* piid = &IID.IID_ID3D11Texture2D) + { + this.swapChain.Get()->GetBuffer(0u, piid, (void**)pprt) + .ThrowOnError(); + } + } + + this.parent.device.Get()->CreateRenderTargetView((ID3D11Resource*)*pprt, null, pprtv).ThrowOnError(); + } + } + } +} diff --git a/Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.cs b/Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.cs new file mode 100644 index 0000000000..685389058d --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.cs @@ -0,0 +1,664 @@ +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Dalamud.Interface.ImGuiBackend.Helpers; +using Dalamud.Interface.ImGuiBackend.Helpers.D3D11; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures.TextureWraps.Internal; +using Dalamud.Interface.Utility; +using Dalamud.Utility; + +using ImGuiNET; + +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +namespace Dalamud.Interface.ImGuiBackend.Renderers; + +/// +/// Deals with rendering ImGui using DirectX 11. +/// See https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_dx11.cpp for the original implementation. +/// +[SuppressMessage( + "StyleCop.CSharp.LayoutRules", + "SA1519:Braces should not be omitted from multi-line child statement", + Justification = "Multiple fixed/using scopes")] +internal unsafe partial class Dx11Renderer : IImGuiRenderer +{ + private readonly List fontTextures = new(); + private readonly D3D_FEATURE_LEVEL featureLevel; + private readonly ViewportHandler viewportHandler; + private readonly nint renderNamePtr; + private readonly DXGI_FORMAT rtvFormat; + private readonly ViewportData mainViewport; + + private bool releaseUnmanagedResourceCalled; + + private ComPtr device; + private ComPtr context; + private ComPtr vertexShader; + private ComPtr pixelShader; + private ComPtr sampler; + private ComPtr inputLayout; + private ComPtr vertexConstantBuffer; + private ComPtr blendState; + private ComPtr rasterizerState; + private ComPtr depthStencilState; + private ComPtr vertexBuffer; + private ComPtr indexBuffer; + private int vertexBufferSize; + private int indexBufferSize; + + private ComPtr dcompDevice; + + /// + /// Initializes a new instance of the class. + /// + /// The swap chain. + /// A pointer to an instance of . + /// A pointer to an instance of . + public Dx11Renderer(IDXGISwapChain* swapChain, ID3D11Device* device, ID3D11DeviceContext* context) + { + var io = ImGui.GetIO(); + if (ImGui.GetIO().NativePtr->BackendRendererName is not null) + throw new InvalidOperationException("ImGui backend renderer seems to be have been already attached."); + try + { + DXGI_SWAP_CHAIN_DESC desc; + swapChain->GetDesc(&desc).ThrowOnError(); + this.rtvFormat = desc.BufferDesc.Format; + this.device = new(device); + this.context = new(context); + this.featureLevel = device->GetFeatureLevel(); + + io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasViewports; + + this.renderNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_dx11_c#"); + io.NativePtr->BackendRendererName = (byte*)this.renderNamePtr; + + if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) + { + try + { + fixed (IDCompositionDevice** pp = &this.dcompDevice.GetPinnableReference()) + fixed (Guid* piidDCompositionDevice = &IID.IID_IDCompositionDevice) + DirectX.DCompositionCreateDevice(null, piidDCompositionDevice, (void**)pp).ThrowOnError(); + + ImGuiViewportHelpers.EnableViewportWindowBackgroundAlpha(); + } + catch + { + // don't care; not using DComposition then + } + + this.viewportHandler = new(this); + } + + this.mainViewport = ViewportData.Create(this, swapChain, null, null); + ImGui.GetPlatformIO().Viewports[0].RendererUserData = this.mainViewport.Handle; + } + catch + { + this.ReleaseUnmanagedResources(); + throw; + } + } + + /// + /// Finalizes an instance of the class. + /// + ~Dx11Renderer() => this.ReleaseUnmanagedResources(); + + /// + public void Dispose() + { + this.ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + /// + public void OnNewFrame() + { + this.EnsureDeviceObjects(); + } + + /// + public void OnPreResize() => this.mainViewport.ResetBuffers(); + + /// + public void OnPostResize(int width, int height) => this.mainViewport.ResizeBuffers(width, height, false); + + /// + public void RenderDrawData(ImDrawDataPtr drawData) => + this.mainViewport.Draw(drawData, this.mainViewport.SwapChain == null); + + /// + /// Rebuilds font texture. + /// + public void RebuildFontTexture() + { + foreach (var fontResourceView in this.fontTextures) + fontResourceView.Dispose(); + this.fontTextures.Clear(); + + this.CreateFontsTexture(); + } + + /// + public IDalamudTextureWrap CreateTexture2D( + ReadOnlySpan data, + RawImageSpecification specs, + bool cpuRead, + bool cpuWrite, + bool allowRenderTarget, + [CallerMemberName] string debugName = "") + { + if (cpuRead && cpuWrite) + throw new ArgumentException("cpuRead and cpuWrite cannot be set at the same time."); + + var cpuaf = default(D3D11_CPU_ACCESS_FLAG); + if (cpuRead) + cpuaf |= D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ; + if (cpuWrite) + cpuaf |= D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE; + + D3D11_USAGE usage; + if (cpuRead) + usage = D3D11_USAGE.D3D11_USAGE_STAGING; + else if (cpuWrite) + usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC; + else + usage = D3D11_USAGE.D3D11_USAGE_DEFAULT; + + var texDesc = new D3D11_TEXTURE2D_DESC + { + Width = (uint)specs.Width, + Height = (uint)specs.Height, + MipLevels = 1, + ArraySize = 1, + Format = specs.Format, + SampleDesc = new(1, 0), + Usage = usage, + BindFlags = (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE | + (allowRenderTarget ? D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET : 0)), + CPUAccessFlags = (uint)cpuaf, + MiscFlags = 0, + }; + using var texture = default(ComPtr); + if (data.IsEmpty) + { + Marshal.ThrowExceptionForHR(this.device.Get()->CreateTexture2D(&texDesc, null, texture.GetAddressOf())); + } + else + { + fixed (void* dataPtr = data) + { + var subrdata = new D3D11_SUBRESOURCE_DATA { pSysMem = dataPtr, SysMemPitch = (uint)specs.Pitch }; + Marshal.ThrowExceptionForHR( + this.device.Get()->CreateTexture2D(&texDesc, &subrdata, texture.GetAddressOf())); + } + } + + texture.Get()->SetDebugName($"Texture:{debugName}:SRV"); + + using var srvTemp = default(ComPtr); + var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC( + texture, + D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D); + this.device.Get()->CreateShaderResourceView((ID3D11Resource*)texture.Get(), &srvDesc, srvTemp.GetAddressOf()) + .ThrowOnError(); + srvTemp.Get()->SetDebugName($"Texture:{debugName}:SRV"); + + return new UnknownTextureWrap((IUnknown*)srvTemp.Get(), specs.Width, specs.Height, true); + } + + private void RenderDrawDataInternal( + ID3D11RenderTargetView* renderTargetView, + ImDrawDataPtr drawData, + bool clearRenderTarget) + { + // Avoid rendering when minimized + if (drawData.DisplaySize.X <= 0 || drawData.DisplaySize.Y <= 0) + return; + + using var oldState = new D3D11DeviceContextStateBackup(this.featureLevel, this.context.Get()); + + // Setup desired DX state + this.SetupRenderState(drawData); + + this.context.Get()->OMSetRenderTargets(1, &renderTargetView, null); + if (clearRenderTarget) + { + var color = default(Vector4); + this.context.Get()->ClearRenderTargetView(renderTargetView, (float*)&color); + } + + if (!drawData.Valid || drawData.CmdListsCount == 0) + return; + + var cmdLists = new Span(drawData.NativePtr->CmdLists, drawData.NativePtr->CmdListsCount); + + // Create and grow vertex/index buffers if needed + if (this.vertexBufferSize < drawData.TotalVtxCount) + this.vertexBuffer.Dispose(); + if (this.vertexBuffer.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.vertexBuffer.Attach(buffer); + } + + if (this.indexBufferSize < drawData.TotalIdxCount) + this.indexBuffer.Dispose(); + if (this.indexBuffer.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.indexBuffer.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.context.Get()->Map( + (ID3D11Resource*)this.vertexBuffer.Get(), + 0, + D3D11_MAP.D3D11_MAP_WRITE_DISCARD, + 0, + &vertexData).ThrowOnError(); + this.context.Get()->Map( + (ID3D11Resource*)this.indexBuffer.Get(), + 0, + D3D11_MAP.D3D11_MAP_WRITE_DISCARD, + 0, + &indexData).ThrowOnError(); + + var targetVertices = new Span(vertexData.pData, this.vertexBufferSize); + var targetIndices = new Span(indexData.pData, this.indexBufferSize); + foreach (ref var cmdList in cmdLists) + { + var vertices = new ImVectorWrapper(&cmdList.NativePtr->VtxBuffer); + var indices = new ImVectorWrapper(&cmdList.NativePtr->IdxBuffer); + + vertices.DataSpan.CopyTo(targetVertices); + indices.DataSpan.CopyTo(targetIndices); + + targetVertices = targetVertices[vertices.Length..]; + targetIndices = targetIndices[indices.Length..]; + } + } + finally + { + this.context.Get()->Unmap((ID3D11Resource*)this.vertexBuffer.Get(), 0); + this.context.Get()->Unmap((ID3D11Resource*)this.indexBuffer.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.context.Get()->Map( + (ID3D11Resource*)this.vertexConstantBuffer.Get(), + 0, + D3D11_MAP.D3D11_MAP_WRITE_DISCARD, + 0, + &data).ThrowOnError(); + *(Matrix4x4*)data.pData = Matrix4x4.CreateOrthographicOffCenter( + drawData.DisplayPos.X, + drawData.DisplayPos.X + drawData.DisplaySize.X, + drawData.DisplayPos.Y + drawData.DisplaySize.Y, + drawData.DisplayPos.Y, + 1f, + 0f); + } + finally + { + this.context.Get()->Unmap((ID3D11Resource*)this.vertexConstantBuffer.Get(), 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); + foreach (ref var cmdList in cmdLists) + { + var cmds = new ImVectorWrapper(&cmdList.NativePtr->CmdBuffer); + foreach (ref var cmd in cmds.DataSpan) + { + var clipV4 = cmd.ClipRect - clipOff; + 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) + continue; + + this.context.Get()->RSSetScissorRects(1, &clipRect); + + if (cmd.UserCallback == nint.Zero) + { + // Bind texture and draw + var srv = (ID3D11ShaderResourceView*)cmd.TextureId; + this.context.Get()->PSSetShader(this.pixelShader, null, 0); + this.context.Get()->PSSetSamplers(0, 1, this.sampler.GetAddressOf()); + this.context.Get()->PSSetShaderResources(0, 1, &srv); + this.context.Get()->DrawIndexed( + cmd.ElemCount, + (uint)(cmd.IdxOffset + indexOffset), + (int)(cmd.VtxOffset + vertexOffset)); + } + } + + indexOffset += cmdList.IdxBuffer.Size; + vertexOffset += cmdList.VtxBuffer.Size; + } + } + + /// + /// Builds fonts as necessary, and uploads the built data onto the GPU.
+ /// No-op if it has already been done. + ///
+ private void CreateFontsTexture() + { + if (this.device.IsEmpty()) + throw new ObjectDisposedException(nameof(Dx11Renderer)); + + if (this.fontTextures.Any()) + return; + + var io = ImGui.GetIO(); + if (io.Fonts.Textures.Size == 0) + io.Fonts.Build(); + + for (int textureIndex = 0, textureCount = io.Fonts.Textures.Size; + textureIndex < textureCount; + textureIndex++) + { + // Build texture atlas + io.Fonts.GetTexDataAsRGBA32( + textureIndex, + out byte* fontPixels, + out var width, + out var height, + out var bytespp); + + var tex = this.CreateTexture2D( + new(fontPixels, width * height * bytespp), + new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM, width * bytespp), + false, + false, + false, + $"Font#{textureIndex}"); + io.Fonts.SetTexID(textureIndex, tex.ImGuiHandle); + this.fontTextures.Add(tex); + } + + io.Fonts.ClearTexData(); + } + + /// + /// Initializes the device context's render state to what we would use for rendering ImGui by default. + /// + /// The relevant ImGui draw data. + private void SetupRenderState(ImDrawDataPtr drawData) + { + var ctx = this.context.Get(); + ctx->IASetInputLayout(this.inputLayout); + var buffer = this.vertexBuffer.Get(); + var stride = (uint)sizeof(ImDrawVert); + var offset = 0u; + ctx->IASetVertexBuffers(0, 1, &buffer, &stride, &offset); + ctx->IASetIndexBuffer(this.indexBuffer, DXGI_FORMAT.DXGI_FORMAT_R16_UINT, 0); + ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + var viewport = new D3D11_VIEWPORT(0, 0, drawData.DisplaySize.X, drawData.DisplaySize.Y); + ctx->RSSetState(this.rasterizerState); + ctx->RSSetViewports(1, &viewport); + + var blendColor = default(Vector4); + ctx->OMSetBlendState(this.blendState, (float*)&blendColor, 0xffffffff); + ctx->OMSetDepthStencilState(this.depthStencilState, 0); + + ctx->VSSetShader(this.vertexShader.Get(), null, 0); + buffer = this.vertexConstantBuffer.Get(); + ctx->VSSetConstantBuffers(0, 1, &buffer); + + // PS handled later + + ctx->GSSetShader(null, null, 0); + ctx->HSSetShader(null, null, 0); + ctx->DSSetShader(null, null, 0); + ctx->CSSetShader(null, null, 0); + } + + /// + /// Creates objects from the device as necessary.
+ /// No-op if objects already are built. + ///
+ private void EnsureDeviceObjects() + { + if (this.device.IsEmpty()) + throw new ObjectDisposedException(nameof(Dx11Renderer)); + + var assembly = Assembly.GetExecutingAssembly(); + + // Create the vertex shader + if (this.vertexShader.IsEmpty() || this.inputLayout.IsEmpty()) + { + using var stream = assembly.GetManifestResourceStream("imgui-vertex.hlsl.bytes")!; + var array = ArrayPool.Shared.Rent((int)stream.Length); + stream.ReadExactly(array, 0, (int)stream.Length); + fixed (byte* pArray = array) + fixed (ID3D11VertexShader** ppShader = &this.vertexShader.GetPinnableReference()) + fixed (ID3D11InputLayout** ppInputLayout = &this.inputLayout.GetPinnableReference()) + fixed (void* pszPosition = "POSITION"u8) + fixed (void* pszTexCoord = "TEXCOORD"u8) + fixed (void* pszColor = "COLOR"u8) + { + this.device.Get()->CreateVertexShader(pArray, (nuint)stream.Length, null, ppShader).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, ppInputLayout) + .ThrowOnError(); + } + + ArrayPool.Shared.Return(array); + } + + // Create the pixel shader + if (this.pixelShader.IsEmpty()) + { + using var stream = assembly.GetManifestResourceStream("imgui-frag.hlsl.bytes")!; + var array = ArrayPool.Shared.Rent((int)stream.Length); + stream.ReadExactly(array, 0, (int)stream.Length); + fixed (byte* pArray = array) + fixed (ID3D11PixelShader** ppShader = &this.pixelShader.GetPinnableReference()) + this.device.Get()->CreatePixelShader(pArray, (nuint)stream.Length, null, ppShader).ThrowOnError(); + ArrayPool.Shared.Return(array); + } + + // Create the sampler state + if (this.sampler.IsEmpty()) + { + 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, + }; + + fixed (ID3D11SamplerState** ppSampler = &this.sampler.GetPinnableReference()) + this.device.Get()->CreateSamplerState(&samplerDesc, ppSampler).ThrowOnError(); + } + + // Create the constant buffer + if (this.vertexConstantBuffer.IsEmpty()) + { + var bufferDesc = new D3D11_BUFFER_DESC( + (uint)sizeof(Matrix4x4), + (uint)D3D11_BIND_FLAG.D3D11_BIND_CONSTANT_BUFFER, + D3D11_USAGE.D3D11_USAGE_DYNAMIC, + (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE); + fixed (ID3D11Buffer** ppBuffer = &this.vertexConstantBuffer.GetPinnableReference()) + this.device.Get()->CreateBuffer(&bufferDesc, null, ppBuffer).ThrowOnError(); + } + + // Create the blending setup + if (this.blendState.IsEmpty()) + { + 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, + }, + }, + }; + fixed (ID3D11BlendState** ppBlendState = &this.blendState.GetPinnableReference()) + this.device.Get()->CreateBlendState(&blendStateDesc, ppBlendState).ThrowOnError(); + } + + // Create the rasterizer state + if (this.rasterizerState.IsEmpty()) + { + var rasterizerDesc = new D3D11_RASTERIZER_DESC + { + FillMode = D3D11_FILL_MODE.D3D11_FILL_SOLID, + CullMode = D3D11_CULL_MODE.D3D11_CULL_NONE, + ScissorEnable = true, + DepthClipEnable = true, + }; + fixed (ID3D11RasterizerState** ppRasterizerState = &this.rasterizerState.GetPinnableReference()) + this.device.Get()->CreateRasterizerState(&rasterizerDesc, ppRasterizerState).ThrowOnError(); + } + + // Create the depth-stencil State + if (this.depthStencilState.IsEmpty()) + { + var dsDesc = new D3D11_DEPTH_STENCIL_DESC + { + DepthEnable = false, + DepthWriteMask = D3D11_DEPTH_WRITE_MASK.D3D11_DEPTH_WRITE_MASK_ALL, + DepthFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS, + StencilEnable = false, + StencilReadMask = byte.MaxValue, + StencilWriteMask = byte.MaxValue, + FrontFace = + { + StencilFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, + StencilDepthFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, + StencilPassOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, + StencilFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS, + }, + BackFace = + { + StencilFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, + StencilDepthFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, + StencilPassOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, + StencilFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS, + }, + }; + fixed (ID3D11DepthStencilState** ppDepthStencilState = &this.depthStencilState.GetPinnableReference()) + this.device.Get()->CreateDepthStencilState(&dsDesc, ppDepthStencilState).ThrowOnError(); + } + + this.CreateFontsTexture(); + } + + private void ReleaseUnmanagedResources() + { + if (this.releaseUnmanagedResourceCalled) + return; + this.releaseUnmanagedResourceCalled = true; + + this.mainViewport.Dispose(); + ImGui.GetPlatformIO().Viewports[0].RendererUserData = nint.Zero; + ImGui.DestroyPlatformWindows(); + + this.viewportHandler.Dispose(); + + var io = ImGui.GetIO(); + if (io.NativePtr->BackendRendererName == (void*)this.renderNamePtr) + io.NativePtr->BackendRendererName = null; + if (this.renderNamePtr != 0) + Marshal.FreeHGlobal(this.renderNamePtr); + + foreach (var fontResourceView in this.fontTextures) + fontResourceView.Dispose(); + + foreach (var i in Enumerable.Range(0, io.Fonts.Textures.Size)) + io.Fonts.SetTexID(i, nint.Zero); + + this.device.Reset(); + this.context.Reset(); + this.vertexShader.Reset(); + this.pixelShader.Reset(); + this.sampler.Reset(); + this.inputLayout.Reset(); + this.vertexConstantBuffer.Reset(); + this.blendState.Reset(); + this.rasterizerState.Reset(); + this.depthStencilState.Reset(); + this.vertexBuffer.Reset(); + this.indexBuffer.Reset(); + this.dcompDevice.Reset(); + } +} diff --git a/Dalamud/Interface/ImGuiBackend/Renderers/IImGuiRenderer.cs b/Dalamud/Interface/ImGuiBackend/Renderers/IImGuiRenderer.cs new file mode 100644 index 0000000000..dc9229dc6d --- /dev/null +++ b/Dalamud/Interface/ImGuiBackend/Renderers/IImGuiRenderer.cs @@ -0,0 +1,43 @@ +using System.Runtime.CompilerServices; + +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; + +using ImGuiNET; + +namespace Dalamud.Interface.ImGuiBackend.Renderers; + +/// A simple shared public interface that all ImGui render implementations follow. +internal interface IImGuiRenderer : IDisposable +{ + /// Load an image from a span of bytes of specified format. + /// The data to load. + /// Texture specifications. + /// Whether to support reading from CPU, while disabling reading from GPU. + /// Whether to support writing from CPU, while disabling writing from GPU. + /// Whether to allow rendering to this texture. + /// Name for debugging. + /// A texture, ready to use in ImGui. + IDalamudTextureWrap CreateTexture2D( + ReadOnlySpan data, + RawImageSpecification specs, + bool cpuRead, + bool cpuWrite, + bool allowRenderTarget, + [CallerMemberName] string debugName = ""); + + /// Notifies that the window is about to be resized. + void OnPreResize(); + + /// Notifies that the window has been resized. + /// The new window width. + /// The new window height. + void OnPostResize(int width, int height); + + /// Marks the beginning of a new frame. + void OnNewFrame(); + + /// Renders the draw data. + /// The draw data. + void RenderDrawData(ImDrawDataPtr drawData); +} diff --git a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.hlsl.bytes b/Dalamud/Interface/ImGuiBackend/Renderers/imgui-frag.hlsl.bytes similarity index 100% rename from Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.hlsl.bytes rename to Dalamud/Interface/ImGuiBackend/Renderers/imgui-frag.hlsl.bytes diff --git a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.hlsl.bytes b/Dalamud/Interface/ImGuiBackend/Renderers/imgui-vertex.hlsl.bytes similarity index 100% rename from Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.hlsl.bytes rename to Dalamud/Interface/ImGuiBackend/Renderers/imgui-vertex.hlsl.bytes diff --git a/Dalamud/Interface/ImGuiScene/D3DTextureWrap.cs b/Dalamud/Interface/ImGuiScene/D3DTextureWrap.cs deleted file mode 100644 index b621dbbd47..0000000000 --- a/Dalamud/Interface/ImGuiScene/D3DTextureWrap.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Dalamud.Interface.Textures.TextureWraps; - -using SharpDX.Direct3D11; - -/// -/// DX11 Implementation of . -/// Provides a simple wrapped view of the disposeable resource as well as the handle for ImGui. -/// -public class D3DTextureWrap : IDalamudTextureWrap -{ - // hold onto this directly for easier dispose etc and in case we need it later - private ShaderResourceView _resourceView = null; - - public int Width { get; } - public int Height { get; } - public IntPtr ImGuiHandle => (_resourceView == null) ? IntPtr.Zero : _resourceView.NativePointer; - - public D3DTextureWrap(ShaderResourceView texView, int width, int height) - { - _resourceView = texView; - Width = width; - Height = height; - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - _resourceView?.Dispose(); - _resourceView = null; - - disposedValue = true; - } - } - - ~D3DTextureWrap() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - GC.SuppressFinalize(this); - } - #endregion -} diff --git a/Dalamud/Interface/ImGuiScene/FodyWeavers.xml b/Dalamud/Interface/ImGuiScene/FodyWeavers.xml deleted file mode 100644 index 68a407a252..0000000000 --- a/Dalamud/Interface/ImGuiScene/FodyWeavers.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - SDL2-CS - ImGui.NET - - - stbi - - - \ No newline at end of file diff --git a/Dalamud/Interface/ImGuiScene/FodyWeavers.xsd b/Dalamud/Interface/ImGuiScene/FodyWeavers.xsd deleted file mode 100644 index af5be0b4e5..0000000000 --- a/Dalamud/Interface/ImGuiScene/FodyWeavers.xsd +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of unmanaged 32 bit assembly names to include, delimited with line breaks. - - - - - A list of unmanaged 64 bit assembly names to include, delimited with line breaks. - - - - - The order of preloaded assemblies, delimited with line breaks. - - - - - - This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. - - - - - Controls if .pdbs for reference assemblies are also embedded. - - - - - Controls if runtime assemblies are also embedded. - - - - - Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. - - - - - Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. - - - - - As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. - - - - - Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. - - - - - Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of unmanaged 32 bit assembly names to include, delimited with |. - - - - - A list of unmanaged 64 bit assembly names to include, delimited with |. - - - - - The order of preloaded assemblies, delimited with |. - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/Dalamud/Interface/ImGuiScene/FramerateLimit.cs b/Dalamud/Interface/ImGuiScene/FramerateLimit.cs deleted file mode 100644 index 06227f6d7a..0000000000 --- a/Dalamud/Interface/ImGuiScene/FramerateLimit.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace ImGuiScene -{ - /// - /// Simple encapsulation of framerate limiting behavior, allowing for fully unbounded (no control), - /// Vsync-enabled (sync to monitor refresh), or a specified fixed framerate (vsync disabled, hard time cap) - /// - public class FramerateLimit - { - /// - /// The different methods of limiting framerate. - /// - public enum LimitType - { - /// - /// No limiting at all. - /// - Unbounded, - /// - /// Vsync enabled. Render presentation will be synced to display refresh rate. - /// - Vsync, - /// - /// Restrict rendering to a fixed (maximum) number of frames per second. - /// This will disable vsync regardless of the fps value. - /// - FixedFPS - } - - /// - /// Which type of framerate limiting to apply. - /// - public LimitType Type { get; } - - private readonly int _fps; - /// - /// The current FPS limit. Only valid with . - /// - public int FPS - { - get - { - if (Type != LimitType.FixedFPS) - throw new InvalidOperationException(); - - return _fps; - } - } - - /// - /// Creates a new framerate limit description. - /// - /// Which type of limiting to apply. - /// Used only with , the target frames per second to restrict rendering to. - public FramerateLimit(LimitType limitType, int targetFps = 0) - { - if (limitType == LimitType.FixedFPS && targetFps <= 0) - { - limitType = LimitType.Unbounded; - } - - Type = limitType; - _fps = targetFps; - } - - public override string ToString() - { - var str = Type.ToString(); - if (Type == LimitType.FixedFPS) - { - str += $" ({FPS})"; - } - return str; - } - } -} diff --git a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Custom.cs b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Custom.cs deleted file mode 100644 index 68b47315ee..0000000000 --- a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Custom.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Runtime.InteropServices; - -namespace ImGuiScene.ImGui_Impl { - - // Custom cimgui functions we use for utility purposes - internal static class Custom { - [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] - public static extern void igCustom_ClearStacks(); - } -} diff --git a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/IImGuiInputHandler.cs b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/IImGuiInputHandler.cs deleted file mode 100644 index 7c2d3cfbed..0000000000 --- a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/IImGuiInputHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace ImGuiScene -{ - public interface IImGuiInputHandler : IDisposable - { - void NewFrame(int width, int height); - void SetIniPath(string path); - } -} diff --git a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/ImGui_Input_Impl_Direct.cs b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/ImGui_Input_Impl_Direct.cs deleted file mode 100644 index 3b7fbba3b3..0000000000 --- a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/ImGui_Input_Impl_Direct.cs +++ /dev/null @@ -1,1398 +0,0 @@ -using ImGuiNET; - -using System.Diagnostics; -using System.Linq; -using System.Numerics; -using System.Runtime.InteropServices; -using PInvoke; - -namespace ImGuiScene -{ - // largely a port of https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_win32.cpp, though some changes - // and wndproc hooking - public unsafe class ImGui_Input_Impl_Direct : IImGuiInputHandler - { - private long _lastTime; - - private IntPtr _platformNamePtr; - private IntPtr _iniPathPtr; - private IntPtr _classNamePtr; - private IntPtr _hWnd; - - private User32.WndProc _wndProcDelegate; - private bool[] _imguiMouseIsDown; - - // private ImGuiMouseCursor _oldCursor = ImGuiMouseCursor.None; - private IntPtr[] _cursors; - - public bool UpdateCursor { get; set; } = true; - - public unsafe ImGui_Input_Impl_Direct(IntPtr hWnd) - { - _hWnd = hWnd; - - // hook wndproc - // have to hold onto the delegate to keep it in memory for unmanaged code - _wndProcDelegate = WndProcDetour; - - var io = ImGui.GetIO(); - - io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors | - ImGuiBackendFlags.HasSetMousePos | - ImGuiBackendFlags.RendererHasViewports | - ImGuiBackendFlags.PlatformHasViewports; - - _platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#"); - io.NativePtr->BackendPlatformName = (byte*)_platformNamePtr.ToPointer(); - - ImGuiViewportPtr mainViewport = ImGui.GetMainViewport(); - mainViewport.PlatformHandle = mainViewport.PlatformHandleRaw = hWnd; - if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) - ImGui_ImplWin32_InitPlatformInterface(); - - _imguiMouseIsDown = new bool[5]; - - _cursors = new IntPtr[9]; - _cursors[(int)ImGuiMouseCursor.Arrow] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_ARROW); - _cursors[(int)ImGuiMouseCursor.TextInput] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_IBEAM); - _cursors[(int)ImGuiMouseCursor.ResizeAll] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_SIZEALL); - _cursors[(int)ImGuiMouseCursor.ResizeEW] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_SIZEWE); - _cursors[(int)ImGuiMouseCursor.ResizeNS] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_SIZENS); - _cursors[(int)ImGuiMouseCursor.ResizeNESW] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_SIZENESW); - _cursors[(int)ImGuiMouseCursor.ResizeNWSE] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_SIZENWSE); - _cursors[(int)ImGuiMouseCursor.Hand] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_HAND); - _cursors[(int)ImGuiMouseCursor.NotAllowed] = Win32.LoadCursor(IntPtr.Zero, Cursor.IDC_NO); - } - - public bool IsImGuiCursor(IntPtr hCursor) - { - return _cursors?.Contains(hCursor) ?? false; - } - - public void NewFrame(int targetWidth, int targetHeight) - { - var io = ImGui.GetIO(); - - io.DisplaySize.X = targetWidth; - io.DisplaySize.Y = targetHeight; - io.DisplayFramebufferScale.X = 1f; - io.DisplayFramebufferScale.Y = 1f; - - var frequency = Stopwatch.Frequency; - var currentTime = Stopwatch.GetTimestamp(); - io.DeltaTime = _lastTime > 0 ? (float)((double)(currentTime - _lastTime) / frequency) : 1f / 60; - _lastTime = currentTime; - - UpdateMousePos(); - - ProcessKeyEventsWorkarounds(); - - // TODO: need to figure out some way to unify all this - // The bottom case works(?) if the caller hooks SetCursor, but otherwise causes fps issues - // The top case more or less works if we use ImGui's software cursor (and ideally hide the - // game's hardware cursor) - // It would be nice if hooking WM_SETCURSOR worked as it 'should' so that external hooking - // wasn't necessary - - // this is what imgui's example does, but it doesn't seem to work for us - // this could be a timing issue.. or their logic could just be wrong for many applications - //var cursor = io.MouseDrawCursor ? ImGuiMouseCursor.None : ImGui.GetMouseCursor(); - //if (_oldCursor != cursor) - //{ - // _oldCursor = cursor; - // UpdateMouseCursor(); - //} - - // hacky attempt to make cursors work how I think they 'should' - if ((io.WantCaptureMouse || io.MouseDrawCursor) && UpdateCursor) - { - UpdateMouseCursor(); - } - - // Similar issue seen with overlapping mouse clicks - // eg, right click and hold on imgui window, drag off, left click and hold - // release right click, release left click -> right click was 'stuck' and imgui - // would become unresponsive - if (!io.WantCaptureMouse) - { - for (int i = 0; i < io.MouseDown.Count; i++) - { - io.MouseDown[i] = false; - } - } - } - - public void SetIniPath(string iniPath) - { - // TODO: error/messaging when trying to set after first render? - if (iniPath != null) - { - if (_iniPathPtr != IntPtr.Zero) - { - Marshal.FreeHGlobal(_iniPathPtr); - } - - _iniPathPtr = Marshal.StringToHGlobalAnsi(iniPath); - unsafe - { - ImGui.GetIO().NativePtr->IniFilename = (byte*)_iniPathPtr.ToPointer(); - } - } - } - - private void UpdateMousePos() - { - var io = ImGui.GetIO(); - - // Depending on if Viewports are enabled, we have to change how we process - // the cursor position. If viewports are enabled, we pass the absolute cursor - // position to ImGui. Otherwise, we use the old method of passing client-local - // mouse position to ImGui. - if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) - { - if (io.WantSetMousePos) - { - Win32.SetCursorPos((int)io.MousePos.X, (int)io.MousePos.Y); - } - - if (Win32.GetCursorPos(out Win32.POINT pt)) - { - io.MousePos.X = pt.X; - io.MousePos.Y = pt.Y; - } - else - { - io.MousePos.X = float.MinValue; - io.MousePos.Y = float.MinValue; - } - } - else - { - if (io.WantSetMousePos) - { - var pos = new Win32.POINT { X = (int)io.MousePos.X, Y = (int)io.MousePos.Y }; - Win32.ClientToScreen(_hWnd, ref pos); - Win32.SetCursorPos(pos.X, pos.Y); - } - - if (Win32.GetCursorPos(out Win32.POINT pt) && Win32.ScreenToClient(_hWnd, ref pt)) - { - io.MousePos.X = pt.X; - io.MousePos.Y = pt.Y; - } - else - { - io.MousePos.X = float.MinValue; - io.MousePos.Y = float.MinValue; - } - } - } - - // TODO This is kind of unnecessary unless we REALLY want viewport hovered support - // It seems to mess with the mouse and get it stuck a lot. Do not know why - // private void UpdateMousePos() { - // ImGuiIOPtr io = ImGui.GetIO(); - // - // // Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) - // // (When multi-viewports are enabled, all imgui positions are same as OS positions) - // if (io.WantSetMousePos) { - // POINT pos = new POINT() {x = (int) io.MousePos.X, y = (int) io.MousePos.Y}; - // if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0) - // User32.ClientToScreen(_hWnd, ref pos); - // User32.SetCursorPos(pos.x, pos.y); - // } - // - // io.MousePos = new Vector2(float.NegativeInfinity, float.NegativeInfinity); - // io.MouseHoveredViewport = 0; - // - // // Set imgui mouse position - // if (!User32.GetCursorPos(out POINT mouseScreenPos)) - // return; - // IntPtr focusedHwnd = User32.GetForegroundWindow(); - // if (focusedHwnd != IntPtr.Zero) { - // if (User32.IsChild(focusedHwnd, _hWnd)) - // focusedHwnd = _hWnd; - // if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == ImGuiConfigFlags.ViewportsEnable) { - // // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) - // // This is the position you can get with GetCursorPos(). In theory adding viewport->Pos is also the reverse operation of doing ScreenToClient(). - // ImGuiViewportPtr viewport = ImGui.FindViewportByPlatformHandle(focusedHwnd); - // unsafe { - // if (viewport.NativePtr != null) - // io.MousePos = new Vector2(mouseScreenPos.x, mouseScreenPos.y); - // } - // } else { - // // Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window.) - // // This is the position you can get with GetCursorPos() + ScreenToClient() or from WM_MOUSEMOVE. - // if (focusedHwnd == _hWnd) { - // POINT mouseClientPos = mouseScreenPos; - // User32.ScreenToClient(focusedHwnd, ref mouseClientPos); - // io.MousePos = new Vector2(mouseClientPos.x, mouseClientPos.y); - // } - // } - // } - // - // // (Optional) When using multiple viewports: set io.MouseHoveredViewport to the viewport the OS mouse cursor is hovering. - // // Important: this information is not easy to provide and many high-level windowing library won't be able to provide it correctly, because - // // - This is _ignoring_ viewports with the ImGuiViewportFlags_NoInputs flag (pass-through windows). - // // - This is _regardless_ of whether another viewport is focused or being dragged from. - // // If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, imgui will ignore this field and infer the information by relying on the - // // rectangles and last focused time of every viewports it knows about. It will be unaware of foreign windows that may be sitting between or over your windows. - // IntPtr hovered_hwnd = User32.WindowFromPoint(mouseScreenPos); - // if (hovered_hwnd != IntPtr.Zero) { - // ImGuiViewportPtr viewport = ImGui.FindViewportByPlatformHandle(focusedHwnd); - // unsafe { - // if (viewport.NativePtr != null) - // if ((viewport.Flags & ImGuiViewportFlags.NoInputs) == 0 - // ) // FIXME: We still get our NoInputs window with WM_NCHITTEST/HTTRANSPARENT code when decorated? - // io.MouseHoveredViewport = viewport.ID; - // } - // } - // } - - private bool UpdateMouseCursor() - { - var io = ImGui.GetIO(); - if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.NoMouseCursorChange)) - return false; - - var cur = ImGui.GetMouseCursor(); - if (cur == ImGuiMouseCursor.None || io.MouseDrawCursor) - Win32.SetCursor(IntPtr.Zero); - else - Win32.SetCursor(_cursors[(int)cur]); - - return true; - } - - /// - /// Processes window messages. Supports both WndProcA and WndProcW. - /// - /// Handle of the window. - /// Type of window message. - /// wParam. - /// lParam. - /// Return value, if not doing further processing. - public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParam, void* lParam) - { - if (ImGui.GetCurrentContext() != IntPtr.Zero) - { - var io = ImGui.GetIO(); - - switch (msg) - { - case User32.WindowMessage.WM_LBUTTONDOWN: - case User32.WindowMessage.WM_LBUTTONDBLCLK: - case User32.WindowMessage.WM_RBUTTONDOWN: - case User32.WindowMessage.WM_RBUTTONDBLCLK: - case User32.WindowMessage.WM_MBUTTONDOWN: - case User32.WindowMessage.WM_MBUTTONDBLCLK: - case User32.WindowMessage.WM_XBUTTONDOWN: - case User32.WindowMessage.WM_XBUTTONDBLCLK: { - var button = GetButton(msg, (ulong)wParam); - if (io.WantCaptureMouse) - { - if (!ImGui.IsAnyMouseDown() && Win32.GetCapture() == IntPtr.Zero) - Win32.SetCapture(hWnd); - - io.MouseDown[button] = true; - this._imguiMouseIsDown[button] = true; - return IntPtr.Zero; - } - break; - } - case User32.WindowMessage.WM_LBUTTONUP: - case User32.WindowMessage.WM_RBUTTONUP: - case User32.WindowMessage.WM_MBUTTONUP: - case User32.WindowMessage.WM_XBUTTONUP: { - var button = GetButton(msg, (ulong)wParam); - if (io.WantCaptureMouse && this._imguiMouseIsDown[button]) - { - if (!ImGui.IsAnyMouseDown() && Win32.GetCapture() == hWnd) - Win32.ReleaseCapture(); - - io.MouseDown[button] = false; - this._imguiMouseIsDown[button] = false; - return IntPtr.Zero; - } - break; - } - case User32.WindowMessage.WM_MOUSEWHEEL: - if (io.WantCaptureMouse) - { - io.MouseWheel += (float)Win32.GET_WHEEL_DELTA_WPARAM((ulong)wParam) / - (float)Win32Constants.WHEEL_DELTA; - return IntPtr.Zero; - } - - break; - case User32.WindowMessage.WM_MOUSEHWHEEL: - if (io.WantCaptureMouse) - { - io.MouseWheelH += (float)Win32.GET_WHEEL_DELTA_WPARAM((ulong)wParam) / - (float)Win32Constants.WHEEL_DELTA; - return IntPtr.Zero; - } - - break; - case User32.WindowMessage.WM_KEYDOWN: - case User32.WindowMessage.WM_SYSKEYDOWN: - case User32.WindowMessage.WM_KEYUP: - case User32.WindowMessage.WM_SYSKEYUP: - bool isKeyDown = (msg == User32.WindowMessage.WM_KEYDOWN || msg == User32.WindowMessage.WM_SYSKEYDOWN); - if ((int)wParam < 256) - { - // Submit modifiers - UpdateKeyModifiers(); - - // Obtain virtual key code - // (keypad enter doesn't have its own... VK_RETURN with KF_EXTENDED flag means keypad enter, see IM_VK_KEYPAD_ENTER definition for details, it is mapped to ImGuiKey.KeyPadEnter.) - var vk = (VirtualKey)(int)wParam; - if (((int)wParam == (int)VirtualKey.Return) && ((int)lParam & (256 << 16)) > 0) - vk = (VirtualKey.Return + 256); - - // Submit key event - var key = VirtualKeyToImGuiKey((int)vk); - var scancode = ((int)lParam & 0xff0000) >> 16; - if (key != ImGuiKey.None && io.WantTextInput) { - AddKeyEvent(key, isKeyDown, vk, scancode); - return IntPtr.Zero; - } - - // Submit individual left/right modifier events - if (vk == VirtualKey.Shift) - { - // Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in ImGui_ImplWin32_ProcessKeyEventsWorkarounds() - if (IsVkDown(VirtualKey.LeftShift) == isKeyDown) { AddKeyEvent(ImGuiKey.LeftShift, isKeyDown, VirtualKey.LeftShift, scancode); } - if (IsVkDown(VirtualKey.RightShift) == isKeyDown) { AddKeyEvent(ImGuiKey.RightShift, isKeyDown, VirtualKey.RightShift, scancode); } - } - else if (vk == VirtualKey.Control) - { - if (IsVkDown(VirtualKey.LeftControl) == isKeyDown) { AddKeyEvent(ImGuiKey.LeftCtrl, isKeyDown, VirtualKey.LeftControl, scancode); } - if (IsVkDown(VirtualKey.RightControl) == isKeyDown) { AddKeyEvent(ImGuiKey.RightCtrl, isKeyDown, VirtualKey.RightControl, scancode); } - } - else if (vk == VirtualKey.Menu) - { - if (IsVkDown(VirtualKey.LeftMenu) == isKeyDown) { AddKeyEvent(ImGuiKey.LeftAlt, isKeyDown, VirtualKey.LeftMenu, scancode); } - if (IsVkDown(VirtualKey.RightMenu) == isKeyDown) { AddKeyEvent(ImGuiKey.RightAlt, isKeyDown, VirtualKey.RightMenu, scancode); } - } - } - break; - case User32.WindowMessage.WM_CHAR: - if (io.WantTextInput) - { - io.AddInputCharacter((uint)wParam); - return IntPtr.Zero; - } - break; - // this never seemed to work reasonably, but I'll leave it for now - case User32.WindowMessage.WM_SETCURSOR: - if (io.WantCaptureMouse) - { - if (Win32.LOWORD((ulong)lParam) == Win32Constants.HTCLIENT && UpdateMouseCursor()) - { - // this message returns 1 to block further processing - // because consistency is no fun - return (IntPtr)1; - } - } - break; - // TODO: Decode why IME is miserable - // case User32.WindowMessage.WM_IME_NOTIFY: - // return HandleImeMessage(hWnd, (long) wParam, (long) lParam); - default: - break; - } - } - - // We did not produce a result - return -1 - return null; - } - - private int GetButton(User32.WindowMessage msg, ulong wParam) { - switch (msg) - { - case User32.WindowMessage.WM_LBUTTONUP: - case User32.WindowMessage.WM_LBUTTONDOWN: - case User32.WindowMessage.WM_LBUTTONDBLCLK: - return 0; - case User32.WindowMessage.WM_RBUTTONUP: - case User32.WindowMessage.WM_RBUTTONDOWN: - case User32.WindowMessage.WM_RBUTTONDBLCLK: - return 1; - case User32.WindowMessage.WM_MBUTTONUP: - case User32.WindowMessage.WM_MBUTTONDOWN: - case User32.WindowMessage.WM_MBUTTONDBLCLK: - return 2; - case User32.WindowMessage.WM_XBUTTONUP: - case User32.WindowMessage.WM_XBUTTONDOWN: - case User32.WindowMessage.WM_XBUTTONDBLCLK: - return Win32.GET_XBUTTON_WPARAM(wParam) == Win32Constants.XBUTTON1 ? 3 : 4; - default: - return 0; - } - } - - private void ProcessKeyEventsWorkarounds() - { - // Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one. - if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VirtualKey.LeftShift)) - AddKeyEvent(ImGuiKey.LeftShift, false, VirtualKey.LeftShift); - if (ImGui.IsKeyDown(ImGuiKey.RightShift) && !IsVkDown(VirtualKey.RightShift)) - AddKeyEvent(ImGuiKey.RightShift, false, VirtualKey.RightShift); - - // Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW). - if (ImGui.IsKeyDown(ImGuiKey.LeftSuper) && !IsVkDown(VirtualKey.LeftWindows)) - AddKeyEvent(ImGuiKey.LeftSuper, false, VirtualKey.LeftWindows); - if (ImGui.IsKeyDown(ImGuiKey.RightSuper) && !IsVkDown(VirtualKey.RightWindows)) - AddKeyEvent(ImGuiKey.RightSuper, false, VirtualKey.RightWindows); - - // From ImGui's FAQ: - // Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event - // that your application receive will typically have io.WantCaptureKeyboard == false. Depending on your - // application logic it may or not be inconvenient. - // - // With how the local wndproc works, this causes the key up event to be missed when exiting ImGui text entry - // (eg, from hitting enter or escape. There may be other ways as well) - // This then causes the key to appear 'stuck' down, which breaks subsequent attempts to use the input field. - // This is something of a brute force fix that basically makes key up events irrelevant - // Holding a key will send repeated key down events and (re)set these where appropriate, so this should be ok. - var io = ImGui.GetIO(); - if (!io.WantTextInput) - { - for (int i = (int)ImGuiKey.NamedKey_BEGIN; i < (int)ImGuiKey.NamedKey_END; i++) { - // Skip raising modifier keys if the game is focused. - // This allows us to raise the keys when one is held and the window becomes unfocused, - // but if we do not skip them, they will only be held down every 4th frame or so. - if (User32.GetForegroundWindow() == this._hWnd && - (IsGamepadKey((ImGuiKey) i) || - IsModKey((ImGuiKey) i))) - continue; - io.AddKeyEvent((ImGuiKey) i, false); - } - } - } - - private static void AddKeyEvent(ImGuiKey key, bool down, VirtualKey nativeKeycode, int nativeScancode = -1) { - var io = ImGui.GetIO(); - io.AddKeyEvent(key, down); - io.SetKeyEventNativeData(key, (int)nativeKeycode, nativeScancode); - } - - static void UpdateKeyModifiers() - { - var io = ImGui.GetIO(); - io.AddKeyEvent(ImGuiKey.ModCtrl, IsVkDown(VirtualKey.Control)); - io.AddKeyEvent(ImGuiKey.ModShift, IsVkDown(VirtualKey.Shift)); - io.AddKeyEvent(ImGuiKey.ModAlt, IsVkDown(VirtualKey.Menu)); - io.AddKeyEvent(ImGuiKey.ModSuper, IsVkDown(VirtualKey.Application)); - } - - private static bool IsVkDown(VirtualKey key) { - return (Win32.GetKeyState(key) & 0x8000) != 0; - } - - // TODO: Decode why IME is miserable - // private int HandleImeMessage(IntPtr hWnd, long wParam, long lParam) { - // - // int result = -1; - // // if (io.WantCaptureKeyboard) - // result = (int) User32.DefWindowProc(hWnd, User32.WindowMessage.WM_IME_NOTIFY, (IntPtr) wParam, (IntPtr) lParam); - // System.Diagnostics.Debug.WriteLine($"ime command {(Win32.ImeCommand) wParam} result {result}"); - // - // return result; - // } - - /// - /// This WndProc is called for ImGuiScene windows. WndProc for main window will be called back from somewhere else. - /// - private unsafe IntPtr WndProcDetour(IntPtr hWnd, User32.WindowMessage msg, void* wParam, void* lParam) - { - // Attempt to process the result of this window message - // We will return the result here if we consider the message handled - var processResult = ProcessWndProcW(hWnd, msg, wParam, lParam); - - if (processResult != null) return processResult.Value; - - // The message wasn't handled, but it's a platform window - // So we have to handle some messages ourselves - // BUT we might have disposed the context, so check that - if (ImGui.GetCurrentContext() == IntPtr.Zero) - return User32.DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam); - ImGuiViewportPtr viewport = ImGui.FindViewportByPlatformHandle(hWnd); - - if (viewport.NativePtr != null) - { - switch (msg) - { - case User32.WindowMessage.WM_CLOSE: - viewport.PlatformRequestClose = true; - return IntPtr.Zero; - case User32.WindowMessage.WM_MOVE: - viewport.PlatformRequestMove = true; - break; - case User32.WindowMessage.WM_SIZE: - viewport.PlatformRequestResize = true; - break; - case User32.WindowMessage.WM_MOUSEACTIVATE: - // We never want our platform windows to be active, or else Windows will think we - // want messages dispatched with its hWnd. We don't. The only way to activate a platform - // window is via clicking, it does not appear on the taskbar or alt-tab, so we just - // brute force behavior here. - - // Make the game the foreground window. This prevents ImGui windows from becoming - // choppy when users have the "limit FPS" option enabled in-game - User32.SetForegroundWindow(_hWnd); - - // Also set the window capture to the main window, as focus will not cause - // future messages to be dispatched to the main window unless it is receiving capture - User32.SetCapture(_hWnd); - - // We still want to return MA_NOACTIVATE - // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mouseactivate - return (IntPtr)0x3; - case User32.WindowMessage.WM_NCHITTEST: - // Let mouse pass-through the window. This will allow the backend to set io.MouseHoveredViewport properly (which is OPTIONAL). - // The ImGuiViewportFlags_NoInputs flag is set while dragging a viewport, as want to detect the window behind the one we are dragging. - // If you cannot easily access those viewport flags from your windowing/event code: you may manually synchronize its state e.g. in - // your main loop after calling UpdatePlatformWindows(). Iterate all viewports/platform windows and pass the flag to your windowing system. - if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoInputs)) - { - // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest - return (IntPtr)uint.MaxValue; - } - break; - } - } - - return User32.DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam); - } - - private static void UpAllKeys() { - var io = ImGui.GetIO(); - for (int i = (int)ImGuiKey.NamedKey_BEGIN; i < (int)ImGuiKey.NamedKey_END; i++) - io.AddKeyEvent((ImGuiKey) i, false); - } - - private static void UpAllMouseButton() { - var io = ImGui.GetIO(); - for (int i = 0; i < io.MouseDown.Count; i++) - io.MouseDown[i] = false; - } - - #region large switch statements - // Map VK_xxx to ImGuiKey.xxx. - public static ImGuiKey VirtualKeyToImGuiKey(int key) { - return (VirtualKey)key switch { - VirtualKey.Tab => ImGuiKey.Tab, - VirtualKey.Left => ImGuiKey.LeftArrow, - VirtualKey.Right => ImGuiKey.RightArrow, - VirtualKey.Up => ImGuiKey.UpArrow, - VirtualKey.Down => ImGuiKey.DownArrow, - VirtualKey.Prior => ImGuiKey.PageUp, - VirtualKey.Next => ImGuiKey.PageDown, - VirtualKey.Home => ImGuiKey.Home, - VirtualKey.End => ImGuiKey.End, - VirtualKey.Insert => ImGuiKey.Insert, - VirtualKey.Delete => ImGuiKey.Delete, - VirtualKey.Back => ImGuiKey.Backspace, - VirtualKey.Space => ImGuiKey.Space, - VirtualKey.Return => ImGuiKey.Enter, - VirtualKey.Escape => ImGuiKey.Escape, - VirtualKey.OEM7 => ImGuiKey.Apostrophe, - VirtualKey.OEMComma => ImGuiKey.Comma, - VirtualKey.OEMMinus => ImGuiKey.Minus, - VirtualKey.OEMPeriod => ImGuiKey.Period, - VirtualKey.OEM2 => ImGuiKey.Slash, - VirtualKey.OEM1 => ImGuiKey.Semicolon, - VirtualKey.OEMPlus => ImGuiKey.Equal, - VirtualKey.OEM4 => ImGuiKey.LeftBracket, - VirtualKey.OEM5 => ImGuiKey.Backslash, - VirtualKey.OEM6 => ImGuiKey.RightBracket, - VirtualKey.OEM3 => ImGuiKey.GraveAccent, - VirtualKey.CapsLock => ImGuiKey.CapsLock, - VirtualKey.ScrollLock => ImGuiKey.ScrollLock, - VirtualKey.NumLock => ImGuiKey.NumLock, - VirtualKey.Snapshot => ImGuiKey.PrintScreen, - VirtualKey.Pause => ImGuiKey.Pause, - VirtualKey.Numpad0 => ImGuiKey.Keypad0, - VirtualKey.Numpad1 => ImGuiKey.Keypad1, - VirtualKey.Numpad2 => ImGuiKey.Keypad2, - VirtualKey.Numpad3 => ImGuiKey.Keypad3, - VirtualKey.Numpad4 => ImGuiKey.Keypad4, - VirtualKey.Numpad5 => ImGuiKey.Keypad5, - VirtualKey.Numpad6 => ImGuiKey.Keypad6, - VirtualKey.Numpad7 => ImGuiKey.Keypad7, - VirtualKey.Numpad8 => ImGuiKey.Keypad8, - VirtualKey.Numpad9 => ImGuiKey.Keypad9, - VirtualKey.Decimal => ImGuiKey.KeypadDecimal, - VirtualKey.Divide => ImGuiKey.KeypadDivide, - VirtualKey.Multiply => ImGuiKey.KeypadMultiply, - VirtualKey.Subtract => ImGuiKey.KeypadSubtract, - VirtualKey.Add => ImGuiKey.KeypadAdd, - (VirtualKey.Return + 256) => ImGuiKey.KeypadEnter, - VirtualKey.LeftShift => ImGuiKey.LeftShift, - VirtualKey.LeftControl => ImGuiKey.LeftCtrl, - VirtualKey.LeftMenu => ImGuiKey.LeftAlt, - VirtualKey.LeftWindows => ImGuiKey.LeftSuper, - VirtualKey.RightShift => ImGuiKey.RightShift, - VirtualKey.RightControl => ImGuiKey.RightCtrl, - VirtualKey.RightMenu => ImGuiKey.RightAlt, - VirtualKey.RightWindows => ImGuiKey.RightSuper, - VirtualKey.Application => ImGuiKey.Menu, - VirtualKey.N0 => ImGuiKey._0, - VirtualKey.N1 => ImGuiKey._1, - VirtualKey.N2 => ImGuiKey._2, - VirtualKey.N3 => ImGuiKey._3, - VirtualKey.N4 => ImGuiKey._4, - VirtualKey.N5 => ImGuiKey._5, - VirtualKey.N6 => ImGuiKey._6, - VirtualKey.N7 => ImGuiKey._7, - VirtualKey.N8 => ImGuiKey._8, - VirtualKey.N9 => ImGuiKey._9, - VirtualKey.A => ImGuiKey.A, - VirtualKey.B => ImGuiKey.B, - VirtualKey.C => ImGuiKey.C, - VirtualKey.D => ImGuiKey.D, - VirtualKey.E => ImGuiKey.E, - VirtualKey.F => ImGuiKey.F, - VirtualKey.G => ImGuiKey.G, - VirtualKey.H => ImGuiKey.H, - VirtualKey.I => ImGuiKey.I, - VirtualKey.J => ImGuiKey.J, - VirtualKey.K => ImGuiKey.K, - VirtualKey.L => ImGuiKey.L, - VirtualKey.M => ImGuiKey.M, - VirtualKey.N => ImGuiKey.N, - VirtualKey.O => ImGuiKey.O, - VirtualKey.P => ImGuiKey.P, - VirtualKey.Q => ImGuiKey.Q, - VirtualKey.R => ImGuiKey.R, - VirtualKey.S => ImGuiKey.S, - VirtualKey.T => ImGuiKey.T, - VirtualKey.U => ImGuiKey.U, - VirtualKey.V => ImGuiKey.V, - VirtualKey.W => ImGuiKey.W, - VirtualKey.X => ImGuiKey.X, - VirtualKey.Y => ImGuiKey.Y, - VirtualKey.Z => ImGuiKey.Z, - VirtualKey.F1 => ImGuiKey.F1, - VirtualKey.F2 => ImGuiKey.F2, - VirtualKey.F3 => ImGuiKey.F3, - VirtualKey.F4 => ImGuiKey.F4, - VirtualKey.F5 => ImGuiKey.F5, - VirtualKey.F6 => ImGuiKey.F6, - VirtualKey.F7 => ImGuiKey.F7, - VirtualKey.F8 => ImGuiKey.F8, - VirtualKey.F9 => ImGuiKey.F9, - VirtualKey.F10 => ImGuiKey.F10, - VirtualKey.F11 => ImGuiKey.F11, - VirtualKey.F12 => ImGuiKey.F12, - _ => ImGuiKey.None - }; - } - - // Map ImGuiKey.xxx to VK_xxx. - public static int ImGuiKeyToVirtualKey(ImGuiKey key) { - VirtualKey vk = key switch { - ImGuiKey.Tab => VirtualKey.Tab, - ImGuiKey.LeftArrow => VirtualKey.Left, - ImGuiKey.RightArrow => VirtualKey.Right, - ImGuiKey.UpArrow => VirtualKey.Up, - ImGuiKey.DownArrow => VirtualKey.Down, - ImGuiKey.PageUp => VirtualKey.Prior, - ImGuiKey.PageDown => VirtualKey.Next, - ImGuiKey.Home => VirtualKey.Home, - ImGuiKey.End => VirtualKey.End, - ImGuiKey.Insert => VirtualKey.Insert, - ImGuiKey.Delete => VirtualKey.Delete, - ImGuiKey.Backspace => VirtualKey.Back, - ImGuiKey.Space => VirtualKey.Space, - ImGuiKey.Enter => VirtualKey.Return, - ImGuiKey.Escape => VirtualKey.Escape, - ImGuiKey.Apostrophe => VirtualKey.OEM7, - ImGuiKey.Comma => VirtualKey.OEMComma, - ImGuiKey.Minus => VirtualKey.OEMMinus, - ImGuiKey.Period => VirtualKey.OEMPeriod, - ImGuiKey.Slash => VirtualKey.OEM2, - ImGuiKey.Semicolon => VirtualKey.OEM1, - ImGuiKey.Equal => VirtualKey.OEMPlus, - ImGuiKey.LeftBracket => VirtualKey.OEM4, - ImGuiKey.Backslash => VirtualKey.OEM5, - ImGuiKey.RightBracket => VirtualKey.OEM6, - ImGuiKey.GraveAccent => VirtualKey.OEM3, - ImGuiKey.CapsLock => VirtualKey.CapsLock, - ImGuiKey.ScrollLock => VirtualKey.ScrollLock, - ImGuiKey.NumLock => VirtualKey.NumLock, - ImGuiKey.PrintScreen => VirtualKey.Snapshot, - ImGuiKey.Pause => VirtualKey.Pause, - ImGuiKey.Keypad0 => VirtualKey.Numpad0, - ImGuiKey.Keypad1 => VirtualKey.Numpad1, - ImGuiKey.Keypad2 => VirtualKey.Numpad2, - ImGuiKey.Keypad3 => VirtualKey.Numpad3, - ImGuiKey.Keypad4 => VirtualKey.Numpad4, - ImGuiKey.Keypad5 => VirtualKey.Numpad5, - ImGuiKey.Keypad6 => VirtualKey.Numpad6, - ImGuiKey.Keypad7 => VirtualKey.Numpad7, - ImGuiKey.Keypad8 => VirtualKey.Numpad8, - ImGuiKey.Keypad9 => VirtualKey.Numpad9, - ImGuiKey.KeypadDecimal => VirtualKey.Decimal, - ImGuiKey.KeypadDivide => VirtualKey.Divide, - ImGuiKey.KeypadMultiply => VirtualKey.Multiply, - ImGuiKey.KeypadSubtract => VirtualKey.Subtract, - ImGuiKey.KeypadAdd => VirtualKey.Add, - ImGuiKey.KeypadEnter => (VirtualKey.Return + 256), - ImGuiKey.LeftShift => VirtualKey.LeftShift, - ImGuiKey.LeftCtrl => VirtualKey.LeftControl, - ImGuiKey.LeftAlt => VirtualKey.LeftMenu, - ImGuiKey.LeftSuper => VirtualKey.LeftWindows, - ImGuiKey.RightShift => VirtualKey.RightShift, - ImGuiKey.RightCtrl => VirtualKey.RightControl, - ImGuiKey.RightAlt => VirtualKey.RightMenu, - ImGuiKey.RightSuper => VirtualKey.RightWindows, - ImGuiKey.Menu => VirtualKey.Application, - ImGuiKey._0 => VirtualKey.N0, - ImGuiKey._1 => VirtualKey.N1, - ImGuiKey._2 => VirtualKey.N2, - ImGuiKey._3 => VirtualKey.N3, - ImGuiKey._4 => VirtualKey.N4, - ImGuiKey._5 => VirtualKey.N5, - ImGuiKey._6 => VirtualKey.N6, - ImGuiKey._7 => VirtualKey.N7, - ImGuiKey._8 => VirtualKey.N8, - ImGuiKey._9 => VirtualKey.N9, - ImGuiKey.A => VirtualKey.A, - ImGuiKey.B => VirtualKey.B, - ImGuiKey.C => VirtualKey.C, - ImGuiKey.D => VirtualKey.D, - ImGuiKey.E => VirtualKey.E, - ImGuiKey.F => VirtualKey.F, - ImGuiKey.G => VirtualKey.G, - ImGuiKey.H => VirtualKey.H, - ImGuiKey.I => VirtualKey.I, - ImGuiKey.J => VirtualKey.J, - ImGuiKey.K => VirtualKey.K, - ImGuiKey.L => VirtualKey.L, - ImGuiKey.M => VirtualKey.M, - ImGuiKey.N => VirtualKey.N, - ImGuiKey.O => VirtualKey.O, - ImGuiKey.P => VirtualKey.P, - ImGuiKey.Q => VirtualKey.Q, - ImGuiKey.R => VirtualKey.R, - ImGuiKey.S => VirtualKey.S, - ImGuiKey.T => VirtualKey.T, - ImGuiKey.U => VirtualKey.U, - ImGuiKey.V => VirtualKey.V, - ImGuiKey.W => VirtualKey.W, - ImGuiKey.X => VirtualKey.X, - ImGuiKey.Y => VirtualKey.Y, - ImGuiKey.Z => VirtualKey.Z, - ImGuiKey.F1 => VirtualKey.F1, - ImGuiKey.F2 => VirtualKey.F2, - ImGuiKey.F3 => VirtualKey.F3, - ImGuiKey.F4 => VirtualKey.F4, - ImGuiKey.F5 => VirtualKey.F5, - ImGuiKey.F6 => VirtualKey.F6, - ImGuiKey.F7 => VirtualKey.F7, - ImGuiKey.F8 => VirtualKey.F8, - ImGuiKey.F9 => VirtualKey.F9, - ImGuiKey.F10 => VirtualKey.F10, - ImGuiKey.F11 => VirtualKey.F11, - ImGuiKey.F12 => VirtualKey.F12, - _ => 0 - }; - - return (int)vk; - } - - private static bool IsGamepadKey(ImGuiKey key) { - return (int) key is >= 617 and <= 640; - } - - private static bool IsModKey(ImGuiKey key) { - return key is ImGuiKey.LeftShift - or ImGuiKey.RightShift - or ImGuiKey.ModShift - or ImGuiKey.LeftCtrl - or ImGuiKey.ModCtrl - or ImGuiKey.LeftAlt - or ImGuiKey.RightAlt - or ImGuiKey.ModAlt; - } - - #endregion - - #region IDisposable Support - - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - _cursors = null; - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - if (_platformNamePtr != IntPtr.Zero) - { - unsafe - { - ImGui.GetIO().NativePtr->BackendPlatformName = null; - } - - Marshal.FreeHGlobal(_platformNamePtr); - _platformNamePtr = IntPtr.Zero; - } - - if (_iniPathPtr != IntPtr.Zero) - { - unsafe - { - ImGui.GetIO().NativePtr->IniFilename = null; - } - - Marshal.FreeHGlobal(_iniPathPtr); - _iniPathPtr = IntPtr.Zero; - } - - ImGui_ImplWin32_ShutdownPlatformInterface(); - - disposedValue = true; - } - } - - ~ImGui_Input_Impl_Direct() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false); - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - // Viewport support - private CreateWindowDelegate createWindow; - private DestroyWindowDelegate destroyWindow; - private ShowWindowDelegate showWindow; - private SetWindowPosDelegate setWindowPos; - private GetWindowPosDelegate getWindowPos; - private SetWindowSizeDelegate setWindowSize; - private GetWindowSizeDelegate getWindowSize; - private SetWindowFocusDelegate setWindowFocus; - private GetWindowFocusDelegate getWindowFocus; - private GetWindowMinimizedDelegate getWindowMinimized; - private SetWindowTitleDelegate setWindowTitle; - private SetWindowAlphaDelegate setWindowAlpha; - private UpdateWindowDelegate updateWindow; - // private SetImeInputPosDelegate setImeInputPos; - // private GetWindowDpiScaleDelegate getWindowDpiScale; - // private ChangedViewportDelegate changedViewport; - - private delegate void CreateWindowDelegate(ImGuiViewportPtr viewport); - - private delegate void DestroyWindowDelegate(ImGuiViewportPtr viewport); - - private delegate void ShowWindowDelegate(ImGuiViewportPtr viewport); - - private delegate void UpdateWindowDelegate(ImGuiViewportPtr viewport); - - private delegate Vector2* GetWindowPosDelegate(IntPtr unk, ImGuiViewportPtr viewport); - - private delegate void SetWindowPosDelegate(ImGuiViewportPtr viewport, Vector2 pos); - - private delegate Vector2* GetWindowSizeDelegate(IntPtr unk, ImGuiViewportPtr viewport); - - private delegate void SetWindowSizeDelegate(ImGuiViewportPtr viewport, Vector2 size); - - private delegate void SetWindowFocusDelegate(ImGuiViewportPtr viewport); - - private delegate bool GetWindowFocusDelegate(ImGuiViewportPtr viewport); - - private delegate byte GetWindowMinimizedDelegate(ImGuiViewportPtr viewport); - - private delegate void SetWindowTitleDelegate(ImGuiViewportPtr viewport, string title); - - private delegate void SetWindowAlphaDelegate(ImGuiViewportPtr viewport, float alpha); - - private delegate void SetImeInputPosDelegate(ImGuiViewportPtr viewport, Vector2 pos); - - private delegate float GetWindowDpiScaleDelegate(ImGuiViewportPtr viewport); - - private delegate void ChangedViewportDelegate(ImGuiViewportPtr viewport); - - // private bool wantUpdateMonitors = false; - - private void ImGui_ImplWin32_UpdateMonitors() - { - // Set up platformIO monitor structures - // Here we use a manual ImVector overload, free the existing monitor data, - // and allocate our own, as we are responsible for telling ImGui about monitors - ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); - int numMonitors = User32.GetSystemMetrics(User32.SystemMetric.SM_CMONITORS); - IntPtr data = Marshal.AllocHGlobal(Marshal.SizeOf() * numMonitors); - platformIO.NativePtr->Monitors = new ImVector(numMonitors, numMonitors, data); - - // ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); - // Marshal.FreeHGlobal(platformIO.NativePtr->Monitors.Data); - // int numMonitors = User32.GetSystemMetrics(User32.SystemMetric.SM_CMONITORS); - // IntPtr data = Marshal.AllocHGlobal(Marshal.SizeOf() * numMonitors); - // platformIO.NativePtr->Monitors = new ImVector(numMonitors, numMonitors, data); - - // Store an iterator for the enumeration function - int* iterator = (int*)Marshal.AllocHGlobal(sizeof(int)); - *iterator = 0; - - User32.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, ImGui_ImplWin32_UpdateMonitors_EnumFunc, - new IntPtr(iterator)); - // this.wantUpdateMonitors = false; - } - - private bool ImGui_ImplWin32_UpdateMonitors_EnumFunc(IntPtr nativeMonitor, IntPtr hdc, RECT* LPRECT, - void* LPARAM) - { - // Get and increment iterator - int monitorIndex = *(int*)LPARAM; - *(int*)LPARAM = *(int*)LPARAM + 1; - - User32.MONITORINFO info = new User32.MONITORINFO(); - info.cbSize = Marshal.SizeOf(info); - if (!User32.GetMonitorInfo(nativeMonitor, ref info)) - return true; - - // Give ImGui the info for this display - ImGuiPlatformMonitorPtr imMonitor = ImGui.GetPlatformIO().Monitors[monitorIndex]; - imMonitor.MainPos = new Vector2(info.rcMonitor.left, info.rcMonitor.top); - imMonitor.MainSize = new Vector2(info.rcMonitor.right - info.rcMonitor.left, - info.rcMonitor.bottom - info.rcMonitor.top); - imMonitor.WorkPos = new Vector2(info.rcWork.left, info.rcWork.top); - imMonitor.WorkSize = - new Vector2(info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); - imMonitor.DpiScale = 1f; - - return true; - } - - // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data-> - private struct ImGuiViewportDataWin32 - { - public IntPtr Hwnd; - public bool HwndOwned; - public User32.WindowStyles DwStyle; - public User32.WindowStylesEx DwExStyle; - } - - private void ImGui_ImplWin32_GetWin32StyleFromViewportFlags(ImGuiViewportFlags flags, - ref User32.WindowStyles outStyle, - ref User32.WindowStylesEx outExStyle) - { - if (flags.HasFlag(ImGuiViewportFlags.NoDecoration)) - outStyle = User32.WindowStyles.WS_POPUP; - else - outStyle = User32.WindowStyles.WS_OVERLAPPEDWINDOW; - - if (flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon)) - outExStyle = User32.WindowStylesEx.WS_EX_TOOLWINDOW; - else - outExStyle = User32.WindowStylesEx.WS_EX_APPWINDOW; - - if (flags.HasFlag(ImGuiViewportFlags.TopMost)) - outExStyle |= User32.WindowStylesEx.WS_EX_TOPMOST; - } - - private void ImGui_ImplWin32_CreateWindow(ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataWin32*)Marshal.AllocHGlobal(Marshal.SizeOf()); - viewport.PlatformUserData = (IntPtr)data; - viewport.Flags = - ( - ImGuiViewportFlags.NoTaskBarIcon | - ImGuiViewportFlags.NoFocusOnClick | - ImGuiViewportFlags.NoRendererClear | - ImGuiViewportFlags.NoFocusOnAppearing | - viewport.Flags - ); - ImGui_ImplWin32_GetWin32StyleFromViewportFlags(viewport.Flags, ref data->DwStyle, ref data->DwExStyle); - - IntPtr parentWindow = IntPtr.Zero; - if (viewport.ParentViewportId != 0) - { - ImGuiViewportPtr parentViewport = ImGui.FindViewportByID(viewport.ParentViewportId); - parentWindow = parentViewport.PlatformHandle; - } - - // Create window - var rect = MemUtil.Allocate(); - rect->left = (int)viewport.Pos.X; - rect->top = (int)viewport.Pos.Y; - rect->right = (int)(viewport.Pos.X + viewport.Size.X); - rect->bottom = (int)(viewport.Pos.Y + viewport.Size.Y); - User32.AdjustWindowRectEx(rect, data->DwStyle, false, data->DwExStyle); - - data->Hwnd = User32.CreateWindowEx( - data->DwExStyle, "ImGui Platform", "Untitled", data->DwStyle, - rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, - parentWindow, IntPtr.Zero, Kernel32.GetModuleHandle(null), - IntPtr.Zero); - - // User32.GetWindowThreadProcessId(data->Hwnd, out var windowProcessId); - // var currentThreadId = Kernel32.GetCurrentThreadId(); - // var currentProcessId = Kernel32.GetCurrentProcessId(); - - // Allow transparent windows - // TODO: Eventually... - ImGui_ImplWin32_EnableAlphaCompositing(data->Hwnd); - - data->HwndOwned = true; - viewport.PlatformRequestResize = false; - viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd; - Marshal.FreeHGlobal((IntPtr)rect); - } - - private void ImGui_ImplWin32_DestroyWindow(ImGuiViewportPtr viewport) - { - // This is also called on the main viewport for some reason, and we never set that viewport's PlatformUserData - if (viewport.PlatformUserData == IntPtr.Zero) return; - - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - - if (Win32.GetCapture() == data->Hwnd) - { - // Transfer capture so if we started dragging from a window that later disappears, we'll still receive the MOUSEUP event. - User32.ReleaseCapture(); - User32.SetCapture(_hWnd); - } - - if (data->Hwnd != IntPtr.Zero && data->HwndOwned) - { - var result = User32.DestroyWindow(data->Hwnd); - - if (result == false && Kernel32.GetLastError() == Win32ErrorCode.ERROR_ACCESS_DENIED) - { - // We are disposing, and we're doing it from a different thread because of course we are - // Just send the window the close message - User32.PostMessage(data->Hwnd, User32.WindowMessage.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); - } - } - - data->Hwnd = IntPtr.Zero; - Marshal.FreeHGlobal(viewport.PlatformUserData); - viewport.PlatformUserData = viewport.PlatformHandle = IntPtr.Zero; - } - - private void ImGui_ImplWin32_ShowWindow(ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - - if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoFocusOnAppearing)) - User32.ShowWindow(data->Hwnd, User32.WindowShowStyle.SW_SHOWNA); - else - User32.ShowWindow(data->Hwnd, User32.WindowShowStyle.SW_SHOW); - } - - private void ImGui_ImplWin32_UpdateWindow(ImGuiViewportPtr viewport) - { - // (Optional) Update Win32 style if it changed _after_ creation. - // Generally they won't change unless configuration flags are changed, but advanced uses (such as manually rewriting viewport flags) make this useful. - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - - viewport.Flags = - ( - ImGuiViewportFlags.NoTaskBarIcon | - ImGuiViewportFlags.NoFocusOnClick | - ImGuiViewportFlags.NoRendererClear | - ImGuiViewportFlags.NoFocusOnAppearing | - viewport.Flags - ); - User32.WindowStyles newStyle = 0; - User32.WindowStylesEx newExStyle = 0; - ImGui_ImplWin32_GetWin32StyleFromViewportFlags(viewport.Flags, ref newStyle, ref newExStyle); - - // Only reapply the flags that have been changed from our point of view (as other flags are being modified by Windows) - if (data->DwStyle != newStyle || data->DwExStyle != newExStyle) - { - // (Optional) Update TopMost state if it changed _after_ creation - bool topMostChanged = (data->DwExStyle & User32.WindowStylesEx.WS_EX_TOPMOST) != - (newExStyle & User32.WindowStylesEx.WS_EX_TOPMOST); - - IntPtr insertAfter = IntPtr.Zero; - if (topMostChanged) - { - if (viewport.Flags.HasFlag(ImGuiViewportFlags.TopMost)) - insertAfter = User32.SpecialWindowHandles.HWND_TOPMOST; - else - insertAfter = User32.SpecialWindowHandles.HWND_NOTOPMOST; - } - - User32.SetWindowPosFlags swpFlag = topMostChanged ? 0 : User32.SetWindowPosFlags.SWP_NOZORDER; - - // Apply flags and position (since it is affected by flags) - data->DwStyle = newStyle; - data->DwExStyle = newExStyle; - - User32.SetWindowLong(data->Hwnd, User32.WindowLongIndexFlags.GWL_STYLE, - (User32.SetWindowLongFlags)data->DwStyle); - User32.SetWindowLong(data->Hwnd, User32.WindowLongIndexFlags.GWL_EXSTYLE, - (User32.SetWindowLongFlags)data->DwExStyle); - - // Create window - var rect = MemUtil.Allocate(); - rect->left = (int)viewport.Pos.X; - rect->top = (int)viewport.Pos.Y; - rect->right = (int)(viewport.Pos.X + viewport.Size.X); - rect->bottom = (int)(viewport.Pos.Y + viewport.Size.Y); - User32.AdjustWindowRectEx(rect, data->DwStyle, false, data->DwExStyle); - User32.SetWindowPos(data->Hwnd, insertAfter, - rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, - swpFlag | - User32.SetWindowPosFlags.SWP_NOACTIVATE | - User32.SetWindowPosFlags.SWP_FRAMECHANGED); - - // This is necessary when we alter the style - User32.ShowWindow(data->Hwnd, User32.WindowShowStyle.SW_SHOWNA); - viewport.PlatformRequestMove = viewport.PlatformRequestResize = true; - Marshal.FreeHGlobal((IntPtr)rect); - } - } - - private Vector2* ImGui_ImplWin32_GetWindowPos(IntPtr unk, ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - var vec2 = MemUtil.Allocate(); - - POINT pt = new POINT { x = 0, y = 0 }; - User32.ClientToScreen(data->Hwnd, ref pt); - vec2->X = pt.x; - vec2->Y = pt.y; - - return vec2; - } - - private void ImGui_ImplWin32_SetWindowPos(ImGuiViewportPtr viewport, Vector2 pos) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - - var rect = MemUtil.Allocate(); - rect->left = (int)pos.X; - rect->top = (int)pos.Y; - rect->right = (int)pos.X; - rect->bottom = (int)pos.Y; - - User32.AdjustWindowRectEx(rect, data->DwStyle, false, data->DwExStyle); - User32.SetWindowPos(data->Hwnd, IntPtr.Zero, - rect->left, rect->top, 0, 0, - User32.SetWindowPosFlags.SWP_NOZORDER | - User32.SetWindowPosFlags.SWP_NOSIZE | - User32.SetWindowPosFlags.SWP_NOACTIVATE); - Marshal.FreeHGlobal((IntPtr)rect); - } - - private Vector2* ImGui_ImplWin32_GetWindowSize(IntPtr size, ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - var vec2 = MemUtil.Allocate(); - - User32.GetClientRect(data->Hwnd, out var rect); - vec2->X = rect.right - rect.left; - vec2->Y = rect.bottom - rect.top; - - return vec2; - } - - private void ImGui_ImplWin32_SetWindowSize(ImGuiViewportPtr viewport, Vector2 size) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - - var rect = MemUtil.Allocate(); - rect->left = 0; - rect->top = 0; - rect->right = (int)size.X; - rect->bottom = (int)size.Y; - - User32.AdjustWindowRectEx(rect, data->DwStyle, false, data->DwExStyle); - User32.SetWindowPos(data->Hwnd, IntPtr.Zero, - 0, 0, rect->right - rect->left, rect->bottom - rect->top, - User32.SetWindowPosFlags.SWP_NOZORDER | - User32.SetWindowPosFlags.SWP_NOMOVE | - User32.SetWindowPosFlags.SWP_NOACTIVATE); - Marshal.FreeHGlobal((IntPtr) rect); - } - - private void ImGui_ImplWin32_SetWindowFocus(ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - - Win32.BringWindowToTop(data->Hwnd); - User32.SetForegroundWindow(data->Hwnd); - Win32.SetFocus(data->Hwnd); - } - - private bool ImGui_ImplWin32_GetWindowFocus(ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - return User32.GetForegroundWindow() == data->Hwnd; - } - - private byte ImGui_ImplWin32_GetWindowMinimized(ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - return (byte)(User32.IsIconic(data->Hwnd) ? 1 : 0); - } - - private void ImGui_ImplWin32_SetWindowTitle(ImGuiViewportPtr viewport, string title) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - User32.SetWindowText(data->Hwnd, title); - } - - private void ImGui_ImplWin32_SetWindowAlpha(ImGuiViewportPtr viewport, float alpha) - { - var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; - - if (alpha < 1.0f) - { - User32.WindowStylesEx gwl = - (User32.WindowStylesEx)User32.GetWindowLong(data->Hwnd, User32.WindowLongIndexFlags.GWL_EXSTYLE); - User32.WindowStylesEx style = gwl | User32.WindowStylesEx.WS_EX_LAYERED; - User32.SetWindowLong(data->Hwnd, User32.WindowLongIndexFlags.GWL_EXSTYLE, - (User32.SetWindowLongFlags)style); - Win32.SetLayeredWindowAttributes(data->Hwnd, 0, (byte)(255 * alpha), 0x2); //0x2 = LWA_ALPHA - } - else - { - User32.WindowStylesEx gwl = - (User32.WindowStylesEx)User32.GetWindowLong(data->Hwnd, User32.WindowLongIndexFlags.GWL_EXSTYLE); - User32.WindowStylesEx style = gwl & ~User32.WindowStylesEx.WS_EX_LAYERED; - User32.SetWindowLong(data->Hwnd, User32.WindowLongIndexFlags.GWL_EXSTYLE, - (User32.SetWindowLongFlags)style); - } - } - - // TODO: Decode why IME is miserable - // private void ImGui_ImplWin32_SetImeInputPos(ImGuiViewportPtr viewport, Vector2 pos) { - // Win32.COMPOSITIONFORM cs = new Win32.COMPOSITIONFORM( - // 0x20, - // new Win32.POINT( - // (int) (pos.X - viewport.Pos.X), - // (int) (pos.Y - viewport.Pos.Y)), - // new Win32.RECT(0, 0, 0, 0) - // ); - // var hwnd = viewport.PlatformHandle; - // if (hwnd != IntPtr.Zero) { - // var himc = Win32.ImmGetContext(hwnd); - // if (himc != IntPtr.Zero) { - // Win32.ImmSetCompositionWindow(himc, ref cs); - // Win32.ImmReleaseContext(hwnd, himc); - // } - // } - // } - - // TODO Alpha when it's no longer forced - private void ImGui_ImplWin32_EnableAlphaCompositing(IntPtr hwnd) - { - Win32.DwmIsCompositionEnabled(out bool composition); - - if (!composition) return; - - if (DwmApi.DwmGetColorizationColor(out uint color, out bool opaque) == HResult.Code.S_OK && !opaque) - { - DwmApi.DWM_BLURBEHIND bb = new DwmApi.DWM_BLURBEHIND(); - bb.Enable = true; - bb.dwFlags = DwmApi.DWM_BLURBEHINDFlags.DWM_BB_ENABLE; - bb.hRgnBlur = IntPtr.Zero; - DwmApi.DwmEnableBlurBehindWindow(hwnd, bb); - } - } - - private void ImGui_ImplWin32_InitPlatformInterface() - { - _classNamePtr = Marshal.StringToHGlobalUni("ImGui Platform"); - - User32.WNDCLASSEX wcex = new User32.WNDCLASSEX(); - wcex.cbSize = Marshal.SizeOf(wcex); - wcex.style = User32.ClassStyles.CS_HREDRAW | User32.ClassStyles.CS_VREDRAW; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = Kernel32.GetModuleHandle(null); - wcex.hIcon = IntPtr.Zero; - wcex.hCursor = IntPtr.Zero; - wcex.hbrBackground = new IntPtr(2); // COLOR_BACKGROUND is 1, so... - wcex.lpfnWndProc = _wndProcDelegate; - unsafe - { - wcex.lpszMenuName = null; - wcex.lpszClassName = (char*)_classNamePtr; - } - - wcex.hIconSm = IntPtr.Zero; - User32.RegisterClassEx(ref wcex); - - ImGui_ImplWin32_UpdateMonitors(); - - this.createWindow = ImGui_ImplWin32_CreateWindow; - this.destroyWindow = ImGui_ImplWin32_DestroyWindow; - this.showWindow = ImGui_ImplWin32_ShowWindow; - this.setWindowPos = ImGui_ImplWin32_SetWindowPos; - this.getWindowPos = ImGui_ImplWin32_GetWindowPos; - this.setWindowSize = ImGui_ImplWin32_SetWindowSize; - this.getWindowSize = ImGui_ImplWin32_GetWindowSize; - this.setWindowFocus = ImGui_ImplWin32_SetWindowFocus; - this.getWindowFocus = ImGui_ImplWin32_GetWindowFocus; - this.getWindowMinimized = ImGui_ImplWin32_GetWindowMinimized; - this.setWindowTitle = ImGui_ImplWin32_SetWindowTitle; - this.setWindowAlpha = ImGui_ImplWin32_SetWindowAlpha; - this.updateWindow = ImGui_ImplWin32_UpdateWindow; - // this.setImeInputPos = ImGui_ImplWin32_SetImeInputPos; - - // Register platform interface (will be coupled with a renderer interface) - ImGuiPlatformIOPtr io = ImGui.GetPlatformIO(); - io.Platform_CreateWindow = Marshal.GetFunctionPointerForDelegate(this.createWindow); - io.Platform_DestroyWindow = Marshal.GetFunctionPointerForDelegate(this.destroyWindow); - io.Platform_ShowWindow = Marshal.GetFunctionPointerForDelegate(this.showWindow); - io.Platform_SetWindowPos = Marshal.GetFunctionPointerForDelegate(this.setWindowPos); - io.Platform_GetWindowPos = Marshal.GetFunctionPointerForDelegate(this.getWindowPos); - io.Platform_SetWindowSize = Marshal.GetFunctionPointerForDelegate(this.setWindowSize); - io.Platform_GetWindowSize = Marshal.GetFunctionPointerForDelegate(this.getWindowSize); - io.Platform_SetWindowFocus = Marshal.GetFunctionPointerForDelegate(this.setWindowFocus); - io.Platform_GetWindowFocus = Marshal.GetFunctionPointerForDelegate(this.getWindowFocus); - io.Platform_GetWindowMinimized = Marshal.GetFunctionPointerForDelegate(this.getWindowMinimized); - io.Platform_SetWindowTitle = Marshal.GetFunctionPointerForDelegate(this.setWindowTitle); - io.Platform_SetWindowAlpha = Marshal.GetFunctionPointerForDelegate(this.setWindowAlpha); - io.Platform_UpdateWindow = Marshal.GetFunctionPointerForDelegate(this.updateWindow); - // io.Platform_SetImeInputPos = Marshal.GetFunctionPointerForDelegate(this.setImeInputPos); - - // Register main window handle (which is owned by the main application, not by us) - // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports. - ImGuiViewportPtr mainViewport = ImGui.GetMainViewport(); - - var data = (ImGuiViewportDataWin32*)Marshal.AllocHGlobal(Marshal.SizeOf()); - mainViewport.PlatformUserData = (IntPtr)data; - data->Hwnd = _hWnd; - data->HwndOwned = false; - mainViewport.PlatformHandle = _hWnd; - } - - private void ImGui_ImplWin32_ShutdownPlatformInterface() - { - Marshal.FreeHGlobal(_classNamePtr); - - // We allocated the platform monitor data in ImGui_ImplWin32_UpdateMonitors ourselves, - // so we have to free it ourselves to ImGui doesn't try to, or else it will crash - Marshal.FreeHGlobal(ImGui.GetPlatformIO().NativePtr->Monitors.Data); - ImGui.GetPlatformIO().NativePtr->Monitors = new ImVector(0, 0, IntPtr.Zero); - - User32.UnregisterClass("ImGui Platform", Kernel32.GetModuleHandle(null)); - } - } -} diff --git a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs deleted file mode 100644 index 72036b7bc2..0000000000 --- a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ImGuiScene -{ - /// - /// A simple shared public interface that all ImGui render implementations follow. - /// - public interface IImGuiRenderer - { - // FIXME - probably a better way to do this than params object[] ! - void Init(params object[] initParams); - void Shutdown(); - void NewFrame(); - void RenderDrawData(ImGuiNET.ImDrawDataPtr drawData); - } -} diff --git a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs deleted file mode 100644 index cd63c27e18..0000000000 --- a/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs +++ /dev/null @@ -1,859 +0,0 @@ -using ImGuiNET; -using SharpDX; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using SharpDX.DXGI; -using SharpDX.Mathematics.Interop; - -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Buffer = SharpDX.Direct3D11.Buffer; -using Device = SharpDX.Direct3D11.Device; -using MapFlags = SharpDX.Direct3D11.MapFlags; - -namespace ImGuiScene -{ - /// - /// Currently undocumented because it is a horrible mess. - /// A near-direct port of https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_dx11.cpp - /// State backup follows the general layout of imgui's sample (which is a mess), but has been rather - /// expanded to cover the vast majority of render state, following the example here - /// https://github.com/GPUOpen-LibrariesAndSDKs/CrossfireAPI11/blob/master/amd_lib/src/AMD_SaveRestoreState.cpp - /// Would be nice to organize it better, but it seems to work - /// - public unsafe class ImGui_Impl_DX11 : IImGuiRenderer - { - private IntPtr _renderNamePtr; - private Device _device; - private DeviceContext _deviceContext; - private List _fontResourceViews = new(); - private SamplerState _fontSampler; - private VertexShader _vertexShader; - private PixelShader _pixelShader; - private InputLayout _inputLayout; - private Buffer _vertexConstantBuffer; - private BlendState _blendState; - private RasterizerState _rasterizerState; - private DepthStencilState _depthStencilState; - private Buffer _vertexBuffer; - private Buffer _indexBuffer; - private int _vertexBufferSize; - private int _indexBufferSize; - private VertexBufferBinding _vertexBinding; - // so we don't make a temporary object every frame - private RawColor4 _blendColor = new RawColor4(0, 0, 0, 0); - - // TODO: I'll clean this up better later - private class StateBackup : IDisposable - { - private DeviceContext deviceContext; - - // IA - public InputLayout InputLayout; - public PrimitiveTopology PrimitiveTopology; - public Buffer IndexBuffer; - public Format IndexBufferFormat; - public int IndexBufferOffset; - public Buffer[] VertexBuffers; - public int[] VertexBufferStrides; - public int[] VertexBufferOffsets; - - // RS - public RasterizerState RS; - public Rectangle[] ScissorRects; - public RawViewportF[] Viewports; - - // OM - public BlendState BlendState; - public RawColor4 BlendFactor; - public int SampleMask; - public DepthStencilState DepthStencilState; - public int DepthStencilRef; - public DepthStencilView DepthStencilView; - public RenderTargetView[] RenderTargetViews; - - // VS - public VertexShader VS; - public Buffer[] VSConstantBuffers; - public SamplerState[] VSSamplers; - public ShaderResourceView[] VSResourceViews; - - // HS - public HullShader HS; - public Buffer[] HSConstantBuffers; - public SamplerState[] HSSamplers; - public ShaderResourceView[] HSResourceViews; - - // DS - public DomainShader DS; - public Buffer[] DSConstantBuffers; - public SamplerState[] DSSamplers; - public ShaderResourceView[] DSResourceViews; - - // GS - public GeometryShader GS; - public Buffer[] GSConstantBuffers; - public SamplerState[] GSSamplers; - public ShaderResourceView[] GSResourceViews; - - // PS - public PixelShader PS; - public Buffer[] PSConstantBuffers; - public SamplerState[] PSSamplers; - public ShaderResourceView[] PSResourceViews; - - public ComputeShader CS; - public Buffer[] CSConstantBuffers; - public SamplerState[] CSSamplers; - public ShaderResourceView[] CSResourceViews; - public UnorderedAccessView[] CSUAVs; - - private bool disposedValue = false; // To detect redundant calls - - public StateBackup(DeviceContext deviceContext) - { - this.deviceContext = deviceContext; - - this.ScissorRects = new Rectangle[16]; // I couldn't find D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE as a SharpDX enum - this.Viewports = new RawViewportF[16]; - this.VertexBuffers = new Buffer[InputAssemblerStage.VertexInputResourceSlotCount]; - this.VertexBufferStrides = new int[InputAssemblerStage.VertexInputResourceSlotCount]; - this.VertexBufferOffsets = new int[InputAssemblerStage.VertexInputResourceSlotCount]; - - // IA - this.InputLayout = deviceContext.InputAssembler.InputLayout; - this.deviceContext.InputAssembler.GetIndexBuffer(out this.IndexBuffer, out this.IndexBufferFormat, out this.IndexBufferOffset); - this.PrimitiveTopology = this.deviceContext.InputAssembler.PrimitiveTopology; - this.deviceContext.InputAssembler.GetVertexBuffers(0, InputAssemblerStage.VertexInputResourceSlotCount, this.VertexBuffers, this.VertexBufferStrides, this.VertexBufferOffsets); - - // RS - this.RS = this.deviceContext.Rasterizer.State; - this.deviceContext.Rasterizer.GetScissorRectangles(this.ScissorRects); - this.deviceContext.Rasterizer.GetViewports(this.Viewports); - - // OM - this.BlendState = this.deviceContext.OutputMerger.GetBlendState(out this.BlendFactor, out this.SampleMask); - this.DepthStencilState = this.deviceContext.OutputMerger.GetDepthStencilState(out this.DepthStencilRef); - this.RenderTargetViews = this.deviceContext.OutputMerger.GetRenderTargets(OutputMergerStage.SimultaneousRenderTargetCount, out this.DepthStencilView); - - // VS - this.VS = this.deviceContext.VertexShader.Get(); - this.VSSamplers = this.deviceContext.VertexShader.GetSamplers(0, CommonShaderStage.SamplerSlotCount); - this.VSConstantBuffers = this.deviceContext.VertexShader.GetConstantBuffers(0, CommonShaderStage.ConstantBufferApiSlotCount); - this.VSResourceViews = this.deviceContext.VertexShader.GetShaderResources(0, CommonShaderStage.InputResourceSlotCount); - - // HS - this.HS = this.deviceContext.HullShader.Get(); - this.HSSamplers = this.deviceContext.HullShader.GetSamplers(0, CommonShaderStage.SamplerSlotCount); - this.HSConstantBuffers = this.deviceContext.HullShader.GetConstantBuffers(0, CommonShaderStage.ConstantBufferApiSlotCount); - this.HSResourceViews = this.deviceContext.HullShader.GetShaderResources(0, CommonShaderStage.InputResourceSlotCount); - - // DS - this.DS = this.deviceContext.DomainShader.Get(); - this.DSSamplers = this.deviceContext.DomainShader.GetSamplers(0, CommonShaderStage.SamplerSlotCount); - this.DSConstantBuffers = this.deviceContext.DomainShader.GetConstantBuffers(0, CommonShaderStage.ConstantBufferApiSlotCount); - this.DSResourceViews = this.deviceContext.DomainShader.GetShaderResources(0, CommonShaderStage.InputResourceSlotCount); - - // GS - this.GS = this.deviceContext.GeometryShader.Get(); - this.GSSamplers = this.deviceContext.GeometryShader.GetSamplers(0, CommonShaderStage.SamplerSlotCount); - this.GSConstantBuffers = this.deviceContext.GeometryShader.GetConstantBuffers(0, CommonShaderStage.ConstantBufferApiSlotCount); - this.GSResourceViews = this.deviceContext.GeometryShader.GetShaderResources(0, CommonShaderStage.InputResourceSlotCount); - - // PS - this.PS = this.deviceContext.PixelShader.Get(); - this.PSSamplers = this.deviceContext.PixelShader.GetSamplers(0, CommonShaderStage.SamplerSlotCount); - this.PSConstantBuffers = this.deviceContext.PixelShader.GetConstantBuffers(0, CommonShaderStage.ConstantBufferApiSlotCount); - this.PSResourceViews = this.deviceContext.PixelShader.GetShaderResources(0, CommonShaderStage.InputResourceSlotCount); - - // CS - this.CS = this.deviceContext.ComputeShader.Get(); - this.CSSamplers = this.deviceContext.ComputeShader.GetSamplers(0, CommonShaderStage.SamplerSlotCount); - this.CSConstantBuffers = this.deviceContext.ComputeShader.GetConstantBuffers(0, CommonShaderStage.ConstantBufferApiSlotCount); - this.CSResourceViews = this.deviceContext.ComputeShader.GetShaderResources(0, CommonShaderStage.InputResourceSlotCount); - this.CSUAVs = this.deviceContext.ComputeShader.GetUnorderedAccessViews(0, ComputeShaderStage.UnorderedAccessViewSlotCount); // should be register count and not slot, but the value is correct - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - // IA - this.deviceContext.InputAssembler.InputLayout = this.InputLayout; - this.deviceContext.InputAssembler.SetIndexBuffer(this.IndexBuffer, this.IndexBufferFormat, this.IndexBufferOffset); - this.deviceContext.InputAssembler.PrimitiveTopology = this.PrimitiveTopology; - this.deviceContext.InputAssembler.SetVertexBuffers(0, this.VertexBuffers, this.VertexBufferStrides, this.VertexBufferOffsets); - - // RS - this.deviceContext.Rasterizer.State = this.RS; - this.deviceContext.Rasterizer.SetScissorRectangles(this.ScissorRects); - this.deviceContext.Rasterizer.SetViewports(this.Viewports, this.Viewports.Length); - - // OM - this.deviceContext.OutputMerger.SetBlendState(this.BlendState, this.BlendFactor, this.SampleMask); - this.deviceContext.OutputMerger.SetDepthStencilState(this.DepthStencilState, this.DepthStencilRef); - this.deviceContext.OutputMerger.SetRenderTargets(this.DepthStencilView, this.RenderTargetViews); - - // VS - this.deviceContext.VertexShader.Set(this.VS); - this.deviceContext.VertexShader.SetSamplers(0, this.VSSamplers); - this.deviceContext.VertexShader.SetConstantBuffers(0, this.VSConstantBuffers); - this.deviceContext.VertexShader.SetShaderResources(0, this.VSResourceViews); - - // HS - this.deviceContext.HullShader.Set(this.HS); - this.deviceContext.HullShader.SetSamplers(0, this.HSSamplers); - this.deviceContext.HullShader.SetConstantBuffers(0, this.HSConstantBuffers); - this.deviceContext.HullShader.SetShaderResources(0, this.HSResourceViews); - - // DS - this.deviceContext.DomainShader.Set(this.DS); - this.deviceContext.DomainShader.SetSamplers(0, this.DSSamplers); - this.deviceContext.DomainShader.SetConstantBuffers(0, this.DSConstantBuffers); - this.deviceContext.DomainShader.SetShaderResources(0, this.DSResourceViews); - - // GS - this.deviceContext.GeometryShader.Set(this.GS); - this.deviceContext.GeometryShader.SetSamplers(0, this.GSSamplers); - this.deviceContext.GeometryShader.SetConstantBuffers(0, this.GSConstantBuffers); - this.deviceContext.GeometryShader.SetShaderResources(0, this.GSResourceViews); - - // PS - this.deviceContext.PixelShader.Set(this.PS); - this.deviceContext.PixelShader.SetSamplers(0, this.PSSamplers); - this.deviceContext.PixelShader.SetConstantBuffers(0, this.PSConstantBuffers); - this.deviceContext.PixelShader.SetShaderResources(0, this.PSResourceViews); - - // CS - this.deviceContext.ComputeShader.Set(this.CS); - this.deviceContext.ComputeShader.SetSamplers(0, this.CSSamplers); - this.deviceContext.ComputeShader.SetConstantBuffers(0, this.CSConstantBuffers); - this.deviceContext.ComputeShader.SetShaderResources(0, this.CSResourceViews); - this.deviceContext.ComputeShader.SetUnorderedAccessViews(0, this.CSUAVs); - - // force free these references immediately, or they hang around too long and calls - // to swapchain->ResizeBuffers() will fail due to outstanding references - // We could force free other things too, but nothing else should cause errors - // and these should get gc'd and disposed eventually - foreach (var rtv in this.RenderTargetViews) - { - rtv?.Dispose(); - } - - this.RenderTargetViews = null; - this.DepthStencilView = null; - this.VSResourceViews = null; - this.HSResourceViews = null; - this.DSResourceViews = null; - this.GSResourceViews = null; - this.PSResourceViews = null; - this.CSResourceViews = null; - - disposedValue = true; - } - } - - ~StateBackup() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false); - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - GC.SuppressFinalize(this); - } - } - - public void SetupRenderState(ImDrawDataPtr drawData) - { - // Setup viewport - _deviceContext.Rasterizer.SetViewport(0, 0, drawData.DisplaySize.X, drawData.DisplaySize.Y); - - // Setup shader and vertex buffers - _deviceContext.InputAssembler.InputLayout = _inputLayout; - _vertexBinding.Buffer = _vertexBuffer; - _deviceContext.InputAssembler.SetVertexBuffers(0, _vertexBinding); - _deviceContext.InputAssembler.SetIndexBuffer(_indexBuffer, Format.R16_UInt, 0); - _deviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; - _deviceContext.VertexShader.Set(_vertexShader); - _deviceContext.VertexShader.SetConstantBuffer(0, _vertexConstantBuffer); - _deviceContext.PixelShader.Set(_pixelShader); - _deviceContext.PixelShader.SetSampler(0, _fontSampler); - _deviceContext.GeometryShader.Set(null); - _deviceContext.HullShader.Set(null); - _deviceContext.DomainShader.Set(null); - _deviceContext.ComputeShader.Set(null); - - // Setup blend state - _deviceContext.OutputMerger.BlendState = _blendState; - _deviceContext.OutputMerger.BlendFactor = _blendColor; - _deviceContext.OutputMerger.DepthStencilState = _depthStencilState; - _deviceContext.Rasterizer.State = _rasterizerState; - } - - public void RenderDrawData(ImDrawDataPtr drawData) - { - // Avoid rendering when minimized - if (drawData.DisplaySize.X <= 0 || drawData.DisplaySize.Y <= 0) - { - return; - } - - if (!drawData.Valid || drawData.CmdListsCount == 0) - { - return; - } - - // drawData.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale); - - // Create and grow vertex/index buffers if needed - if (_vertexBuffer == null || _vertexBufferSize < drawData.TotalVtxCount) - { - _vertexBuffer?.Dispose(); - _vertexBufferSize = drawData.TotalVtxCount + 5000; - - _vertexBuffer = new Buffer(_device, new BufferDescription - { - Usage = ResourceUsage.Dynamic, - SizeInBytes = Unsafe.SizeOf() * _vertexBufferSize, - BindFlags = BindFlags.VertexBuffer, - CpuAccessFlags = CpuAccessFlags.Write, - OptionFlags = ResourceOptionFlags.None - }); - - // (Re)make this here rather than every frame - _vertexBinding = new VertexBufferBinding - { - Buffer = _vertexBuffer, - Stride = Unsafe.SizeOf(), - Offset = 0 - }; - } - - if (_indexBuffer == null || _indexBufferSize < drawData.TotalIdxCount) - { - _indexBuffer?.Dispose(); - _indexBufferSize = drawData.TotalIdxCount + 10000; - - _indexBuffer = new Buffer(_device, new BufferDescription - { - Usage = ResourceUsage.Dynamic, - SizeInBytes = sizeof(ushort) * _indexBufferSize, // ImGui.NET doesn't provide an ImDrawIdx, but their sample uses ushort - BindFlags = BindFlags.IndexBuffer, - CpuAccessFlags = CpuAccessFlags.Write - }); - } - - // Upload vertex/index data into a single contiguous GPU buffer - int vertexOffset = 0, indexOffset = 0; - var vertexData = _deviceContext.MapSubresource(_vertexBuffer, 0, MapMode.WriteDiscard, MapFlags.None).DataPointer; - var indexData = _deviceContext.MapSubresource(_indexBuffer, 0, MapMode.WriteDiscard, MapFlags.None).DataPointer; - - for (int n = 0; n < drawData.CmdListsCount; n++) - { - var cmdList = drawData.CmdListsRange[n]; - unsafe - { - System.Buffer.MemoryCopy(cmdList.VtxBuffer.Data.ToPointer(), - (ImDrawVert*)vertexData + vertexOffset, - Unsafe.SizeOf() * _vertexBufferSize, - Unsafe.SizeOf() * cmdList.VtxBuffer.Size); - - System.Buffer.MemoryCopy(cmdList.IdxBuffer.Data.ToPointer(), - (ushort*)indexData + indexOffset, - sizeof(ushort) * _indexBufferSize, - sizeof(ushort) * cmdList.IdxBuffer.Size); - - vertexOffset += cmdList.VtxBuffer.Size; - indexOffset += cmdList.IdxBuffer.Size; - } - } - _deviceContext.UnmapSubresource(_vertexBuffer, 0); - _deviceContext.UnmapSubresource(_indexBuffer, 0); - - // Setup orthographic projection matrix into our constant buffer - // Our visible imgui space lies from drawData.DisplayPos (top left) to drawData.DisplayPos+drawData.DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - var L = drawData.DisplayPos.X; - var R = drawData.DisplayPos.X + drawData.DisplaySize.X; - var T = drawData.DisplayPos.Y; - var B = drawData.DisplayPos.Y + drawData.DisplaySize.Y; - var mvp = new float[] - { - 2f/(R-L), 0, 0, 0, - 0, 2f/(T-B), 0, 0, - 0, 0, 0.5f, 0, - (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1f - }; - - var constantBuffer = _deviceContext.MapSubresource(_vertexConstantBuffer, 0, MapMode.WriteDiscard, MapFlags.None).DataPointer; - unsafe - { - fixed (void* mvpPtr = mvp) - { - System.Buffer.MemoryCopy(mvpPtr, constantBuffer.ToPointer(), 16 * sizeof(float), 16 * sizeof(float)); - } - } - _deviceContext.UnmapSubresource(_vertexConstantBuffer, 0); - - var oldState = new StateBackup(_deviceContext); - - // Setup desired DX state - SetupRenderState(drawData); - - // Render command lists - // (Because we merged all buffers into a single one, we maintain our own offset into them) - vertexOffset = 0; - indexOffset = 0; - var clipOff = drawData.DisplayPos; - for (int n = 0; n < drawData.CmdListsCount; n++) - { - var cmdList = drawData.CmdListsRange[n]; - for (int cmd = 0; cmd < cmdList.CmdBuffer.Size; cmd++) - { - var pcmd = cmdList.CmdBuffer[cmd]; - if (pcmd.UserCallback != IntPtr.Zero) - { - // TODO - throw new NotImplementedException(); - } - else - { - // Apply scissor/clipping rectangle - _deviceContext.Rasterizer.SetScissorRectangle((int)(pcmd.ClipRect.X - clipOff.X), (int)(pcmd.ClipRect.Y - clipOff.Y), (int)(pcmd.ClipRect.Z - clipOff.X), (int)(pcmd.ClipRect.W - clipOff.Y)); - - // Bind texture, Draw - // TODO: might be nice to store samplers for loaded textures so that we can look them up and apply them here - // rather than just always using the font sampler - var textureSrv = ShaderResourceView.FromPointer(pcmd.TextureId); - _deviceContext.PixelShader.SetShaderResource(0, textureSrv); - _deviceContext.DrawIndexed((int)pcmd.ElemCount, (int)(pcmd.IdxOffset + indexOffset), (int)(pcmd.VtxOffset + vertexOffset)); - } - } - - indexOffset += cmdList.IdxBuffer.Size; - vertexOffset += cmdList.VtxBuffer.Size; - } - - oldState.Dispose(); // restores the previous state - oldState = null; - } - - public void CreateFontsTexture() - { - var io = ImGui.GetIO(); - if (io.Fonts.Textures.Size == 0) - io.Fonts.Build(); - - for (int textureIndex = 0, textureCount = io.Fonts.Textures.Size; - textureIndex < textureCount; - textureIndex++) { - - // Build texture atlas - io.Fonts.GetTexDataAsRGBA32(textureIndex, out IntPtr fontPixels, out int fontWidth, out int fontHeight, - out int fontBytesPerPixel); - - // Upload texture to graphics system - var texDesc = new Texture2DDescription { - Width = fontWidth, - Height = fontHeight, - MipLevels = 1, - ArraySize = 1, - Format = Format.R8G8B8A8_UNorm, - SampleDescription = new SampleDescription(1, 0), - Usage = ResourceUsage.Immutable, - BindFlags = BindFlags.ShaderResource, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None - }; - - using var fontTexture = new Texture2D( - _device, texDesc, new DataRectangle(fontPixels, fontWidth * fontBytesPerPixel)); - - // Create texture view - var fontResourceView = new ShaderResourceView(_device, fontTexture, new ShaderResourceViewDescription { - Format = texDesc.Format, - Dimension = ShaderResourceViewDimension.Texture2D, - Texture2D = { MipLevels = texDesc.MipLevels } - }); - - // Store our identifier - _fontResourceViews.Add(fontResourceView); - io.Fonts.SetTexID(textureIndex, fontResourceView.NativePointer); - } - - io.Fonts.ClearTexData(); - - // Create texture sampler - _fontSampler = new SamplerState(_device, new SamplerStateDescription - { - Filter = Filter.MinMagMipLinear, - AddressU = TextureAddressMode.Wrap, - AddressV = TextureAddressMode.Wrap, - AddressW = TextureAddressMode.Wrap, - MipLodBias = 0, - ComparisonFunction = Comparison.Always, - MinimumLod = 0, - MaximumLod = 0 - }); - } - - public bool CreateDeviceObjects() - { - if (_device == null) - { - return false; - } - - if (_fontSampler != null) - { - InvalidateDeviceObjects(); - } - - // Create the vertex shader - byte[] shaderData; - - var assembly = Assembly.GetExecutingAssembly(); - using (var stream = assembly.GetManifestResourceStream("imgui-vertex.hlsl.bytes")) - { - shaderData = new byte[stream.Length]; - stream.Read(shaderData, 0, shaderData.Length); - } - - _vertexShader = new VertexShader(_device, shaderData); - - // Create the input layout - _inputLayout = new InputLayout(_device, shaderData, new[] - { - new InputElement("POSITION", 0, Format.R32G32_Float, 0), - new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0), - new InputElement("COLOR", 0, Format.R8G8B8A8_UNorm, 0) - }); - - // Create the constant buffer - _vertexConstantBuffer = new Buffer(_device, new BufferDescription - { - Usage = ResourceUsage.Dynamic, - BindFlags = BindFlags.ConstantBuffer, - CpuAccessFlags = CpuAccessFlags.Write, - OptionFlags = ResourceOptionFlags.None, - SizeInBytes = 16 * sizeof(float) - }); - - // Create the pixel shader - using (var stream = assembly.GetManifestResourceStream("imgui-frag.hlsl.bytes")) - { - shaderData = new byte[stream.Length]; - stream.Read(shaderData, 0, shaderData.Length); - } - - _pixelShader = new PixelShader(_device, shaderData); - - // Create the blending setup - // ...of course this was setup in a way that can't be done inline - var blendStateDesc = new BlendStateDescription(); - blendStateDesc.AlphaToCoverageEnable = false; - blendStateDesc.RenderTarget[0].IsBlendEnabled = true; - blendStateDesc.RenderTarget[0].SourceBlend = BlendOption.SourceAlpha; - blendStateDesc.RenderTarget[0].DestinationBlend = BlendOption.InverseSourceAlpha; - blendStateDesc.RenderTarget[0].BlendOperation = BlendOperation.Add; - blendStateDesc.RenderTarget[0].SourceAlphaBlend = BlendOption.InverseSourceAlpha; - blendStateDesc.RenderTarget[0].DestinationAlphaBlend = BlendOption.Zero; - blendStateDesc.RenderTarget[0].AlphaBlendOperation = BlendOperation.Add; - blendStateDesc.RenderTarget[0].RenderTargetWriteMask = ColorWriteMaskFlags.All; - _blendState = new BlendState(_device, blendStateDesc); - - // Create the rasterizer state - _rasterizerState = new RasterizerState(_device, new RasterizerStateDescription - { - FillMode = FillMode.Solid, - CullMode = CullMode.None, - IsScissorEnabled = true, - IsDepthClipEnabled = true - }); - - // Create the depth-stencil State - _depthStencilState = new DepthStencilState(_device, new DepthStencilStateDescription - { - IsDepthEnabled = false, - DepthWriteMask = DepthWriteMask.All, - DepthComparison = Comparison.Always, - IsStencilEnabled = false, - FrontFace = - { - FailOperation = StencilOperation.Keep, - DepthFailOperation = StencilOperation.Keep, - PassOperation = StencilOperation.Keep, - Comparison = Comparison.Always - }, - BackFace = - { - FailOperation = StencilOperation.Keep, - DepthFailOperation = StencilOperation.Keep, - PassOperation = StencilOperation.Keep, - Comparison = Comparison.Always - } - }); - - CreateFontsTexture(); - - return true; - } - - // Added to support dynamic rebuilding of the font texture - // for adding fonts after initialization time - public void RebuildFontTexture() - { - _fontSampler?.Dispose(); - foreach (var fontResourceView in this._fontResourceViews) - fontResourceView.Dispose(); - this._fontResourceViews.Clear(); - - CreateFontsTexture(); - } - - public void InvalidateDeviceObjects() - { - if (_device == null) - { - return; - } - - _fontSampler?.Dispose(); - _fontSampler = null; - - foreach (var fontResourceView in this._fontResourceViews) - fontResourceView.Dispose(); - this._fontResourceViews.Clear(); - for (int textureIndex = 0, textureCount = ImGui.GetIO().Fonts.Textures.Size; - textureIndex < textureCount; - textureIndex++) - ImGui.GetIO().Fonts.SetTexID(textureIndex, IntPtr.Zero); - - _indexBuffer?.Dispose(); - _indexBuffer = null; - - _vertexBuffer?.Dispose(); - _vertexBuffer = null; - - _blendState?.Dispose(); - _blendState = null; - - _depthStencilState?.Dispose(); - _depthStencilState = null; - - _rasterizerState?.Dispose(); - _rasterizerState = null; - - _pixelShader?.Dispose(); - _pixelShader = null; - - _vertexConstantBuffer?.Dispose(); - _vertexConstantBuffer = null; - - _inputLayout?.Dispose(); - _inputLayout = null; - - _vertexShader?.Dispose(); - _vertexShader = null; - } - - public void Init(params object[] initParams) - { - ImGui.GetIO().BackendFlags = ImGui.GetIO().BackendFlags | ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasViewports; - - // BackendRendererName is readonly (and null) in ImGui.NET for some reason, but we can hack it via its internal pointer - _renderNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_dx11_c#"); - unsafe - { - ImGui.GetIO().NativePtr->BackendRendererName = (byte*)_renderNamePtr.ToPointer(); - } - - _device = (Device)initParams[0]; - _deviceContext = (DeviceContext)initParams[1]; - - InitPlatformInterface(); - - // SharpDX also doesn't allow reference managment - } - - public void Shutdown() - { - ShutdownPlatformInterface(); - InvalidateDeviceObjects(); - - // we don't own these, so no Dispose() - _device = null; - _deviceContext = null; - - if (_renderNamePtr != IntPtr.Zero) - { - Marshal.FreeHGlobal(_renderNamePtr); - _renderNamePtr = IntPtr.Zero; - } - } - - public void NewFrame() - { - if (_fontSampler == null) - { - CreateDeviceObjects(); - } - } - - /** Viewport support **/ - private struct ImGuiViewportDataDx11 - { - public IntPtr SwapChain; - public IntPtr View; - } - - // Viewport interface - private delegate void CreateWindowDelegate(ImGuiViewportPtr ptr); - private delegate void DestroyWindowDelegate(ImGuiViewportPtr ptr); - private delegate void SetWindowSizeDelegate(ImGuiViewportPtr ptr, Vector2 size); - private delegate void RenderWindowDelegate(ImGuiViewportPtr ptr, IntPtr v); - private delegate void SwapBuffersDelegate(ImGuiViewportPtr ptr, IntPtr v); - - private CreateWindowDelegate _createWindow; - private DestroyWindowDelegate _destroyWindow; - private SetWindowSizeDelegate _setWindowSize; - private RenderWindowDelegate _renderWindow; - private SwapBuffersDelegate _swapBuffers; - - private void InitPlatformInterface() - { - ImGuiPlatformIOPtr ptr = ImGui.GetPlatformIO(); - _createWindow = CreateWindow; - _destroyWindow = DestroyWindow; - _setWindowSize = SetWindowSize; - _renderWindow = RenderWindow; - _swapBuffers = SwapBuffers; - - ptr.Renderer_CreateWindow = Marshal.GetFunctionPointerForDelegate(_createWindow); - ptr.Renderer_DestroyWindow = Marshal.GetFunctionPointerForDelegate(_destroyWindow); - ptr.Renderer_SetWindowSize = Marshal.GetFunctionPointerForDelegate(_setWindowSize); - ptr.Renderer_RenderWindow = Marshal.GetFunctionPointerForDelegate(_renderWindow); - ptr.Renderer_SwapBuffers = Marshal.GetFunctionPointerForDelegate(_swapBuffers); - } - - private void ShutdownPlatformInterface() - { - ImGui.DestroyPlatformWindows(); - } - - // Viewport functions - public void CreateWindow(ImGuiViewportPtr viewport) - { - var data = (ImGuiViewportDataDx11*) Marshal.AllocHGlobal(Marshal.SizeOf()); - - // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). - // Some backend will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the HWND. - IntPtr hWnd = viewport.PlatformHandleRaw; - if (hWnd == IntPtr.Zero) - hWnd = viewport.PlatformHandle; - - // Create swapchain - SwapChainDescription desc = new SwapChainDescription - { - ModeDescription = new ModeDescription - { - Width = 0, - Height = 0, - Format = Format.R8G8B8A8_UNorm, - RefreshRate = new Rational(0, 0) - }, - SampleDescription = new SampleDescription - { - Count = 1, - Quality = 0 - }, - Usage = Usage.RenderTargetOutput, - BufferCount = 1, - OutputHandle = hWnd, - IsWindowed = true, - SwapEffect = SwapEffect.Discard, - Flags = SwapChainFlags.None - }; - - data->SwapChain = CreateSwapChain(desc); - - // Create the render target view - using (var backbuffer = new SwapChain(data->SwapChain).GetBackBuffer(0)) - data->View = new RenderTargetView(_device, backbuffer).NativePointer; - - viewport.RendererUserData = (IntPtr) data; - } - - private IntPtr CreateSwapChain(SwapChainDescription desc) { - - // Create a swapchain using the existing game hardware (I think) - using (var dxgi = _device.QueryInterface()) - using (var adapter = dxgi.Adapter) - using (var factory = adapter.GetParent()) - { - return new SwapChain(factory, _device, desc).NativePointer; - } - } - - public void DestroyWindow(ImGuiViewportPtr viewport) - { - // This is also called on the main viewport for some reason, and we never set that viewport's RendererUserData - if (viewport.RendererUserData == IntPtr.Zero) return; - - var data = (ImGuiViewportDataDx11*) viewport.RendererUserData; - - new SwapChain(data->SwapChain).Dispose(); - new RenderTargetView(data->View).Dispose(); - data->SwapChain = IntPtr.Zero; - data->View = IntPtr.Zero; - - Marshal.FreeHGlobal(viewport.RendererUserData); - viewport.RendererUserData = IntPtr.Zero; - } - - public void SetWindowSize(ImGuiViewportPtr viewport, Vector2 size) - { - var data = (ImGuiViewportDataDx11*)viewport.RendererUserData; - - // Delete our existing view - new RenderTargetView(data->View).Dispose(); - var tmpSwap = new SwapChain(data->SwapChain); - - // Resize buffers and recreate view - tmpSwap.ResizeBuffers(1, (int)size.X, (int)size.Y, Format.Unknown, SwapChainFlags.None); - using (var backbuffer = tmpSwap.GetBackBuffer(0)) - data->View = new RenderTargetView(_device, backbuffer).NativePointer; - } - - public void RenderWindow(ImGuiViewportPtr viewport, IntPtr v) - { - var data = (ImGuiViewportDataDx11*)viewport.RendererUserData; - - var tmpRtv = new RenderTargetView(data->View); - this._deviceContext.OutputMerger.SetTargets(tmpRtv); - if ((viewport.Flags & ImGuiViewportFlags.NoRendererClear) != ImGuiViewportFlags.NoRendererClear) - this._deviceContext.ClearRenderTargetView(tmpRtv, new RawColor4(0f, 0f, 0f, 1f)); - RenderDrawData(viewport.DrawData); - } - - public void SwapBuffers(ImGuiViewportPtr viewport, IntPtr v) - { - var data = (ImGuiViewportDataDx11*)viewport.RendererUserData; - new SwapChain(data->SwapChain).Present(0, PresentFlags.None); - } - } -} diff --git a/Dalamud/Interface/ImGuiScene/RawDX11Scene.cs b/Dalamud/Interface/ImGuiScene/RawDX11Scene.cs deleted file mode 100644 index 299349d51d..0000000000 --- a/Dalamud/Interface/ImGuiScene/RawDX11Scene.cs +++ /dev/null @@ -1,368 +0,0 @@ -using ImGuiNET; -using PInvoke; -using SharpDX; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using SharpDX.DXGI; -using StbiSharp; - -using System.IO; - -using Dalamud.Interface.Textures.TextureWraps; - -using ImGuiScene.ImGui_Impl; -using ImGuizmoNET; -using ImPlotNET; -using Device = SharpDX.Direct3D11.Device; - -namespace ImGuiScene -{ - // This class will likely eventually be unified a bit more with other scenes, but for - // now it should be directly useable - public sealed class RawDX11Scene : IDisposable - { - public Device Device { get; private set; } - public IntPtr WindowHandlePtr { get; private set; } - public SwapChain SwapChain { get; private set; } - - public bool UpdateCursor - { - get => this.imguiInput.UpdateCursor; - set => this.imguiInput.UpdateCursor = value; - } - - private DeviceContext deviceContext; - private RenderTargetView rtv; - - private int targetWidth; - private int targetHeight; - - private ImGui_Impl_DX11 imguiRenderer; - private ImGui_Input_Impl_Direct imguiInput; - - public delegate void BuildUIDelegate(); - public delegate void NewInputFrameDelegate(); - public delegate void NewRenderFrameDelegate(); - - /// - /// User methods invoked every ImGui frame to construct custom UIs. - /// - public BuildUIDelegate OnBuildUI; - - public NewInputFrameDelegate OnNewInputFrame; - public NewRenderFrameDelegate OnNewRenderFrame; - - private string imguiIniPath = null; - public string ImGuiIniPath - { - get { return imguiIniPath; } - set - { - imguiIniPath = value; - imguiInput.SetIniPath(imguiIniPath); - } - } - - public RawDX11Scene(IntPtr nativeSwapChain) - { - this.SwapChain = new SwapChain(nativeSwapChain); - this.Device = SwapChain.GetDevice(); - - Initialize(); - } - - // This ctor will work fine, but it's only usefulness over using just the swapchain version - // is that this one will allow you to pass a different device than the swapchain.GetDevice() would - // return. This is mostly useful for render debugging, where the real d3ddevice is hooked and - // where we would like all our work to be done on that hooked device. - // Because we generally will get the swapchain from the internal present() call, we are getting - // the real d3d swapchain and not a hooked version, so GetDevice() will correspondingly return - // the read device and not a hooked verison. - // By passing in the hooked version explicitly here, we can mostly play nice with debug tools - public RawDX11Scene(IntPtr nativeDevice, IntPtr nativeSwapChain) - { - this.Device = new Device(nativeDevice); - this.SwapChain = new SwapChain(nativeSwapChain); - - Initialize(); - } - - private void Initialize() - { - this.deviceContext = this.Device.ImmediateContext; - - using (var backbuffer = this.SwapChain.GetBackBuffer(0)) - { - this.rtv = new RenderTargetView(this.Device, backbuffer); - } - - // could also do things with GetClientRect() for WindowHandlePtr, not sure if that is necessary - this.targetWidth = this.SwapChain.Description.ModeDescription.Width; - this.targetHeight = this.SwapChain.Description.ModeDescription.Height; - - this.WindowHandlePtr = this.SwapChain.Description.OutputHandle; - - InitializeImGui(); - } - - private void InitializeImGui() - { - this.imguiRenderer = new ImGui_Impl_DX11(); - - var ctx = ImGui.CreateContext(); - ImGuizmo.SetImGuiContext(ctx); - ImPlot.SetImGuiContext(ctx); - ImPlot.CreateContext(); - - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable; - - this.imguiRenderer.Init(this.Device, this.deviceContext); - this.imguiInput = new ImGui_Input_Impl_Direct(WindowHandlePtr); - } - - /// - /// Processes window messages. - /// - /// Handle of the window. - /// Type of window message. - /// wParam. - /// lParam. - /// Return value. - public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParam, void* lParam) { - return this.imguiInput.ProcessWndProcW(hWnd, msg, wParam, lParam); - } - - public void Render() - { - this.deviceContext.OutputMerger.SetRenderTargets(this.rtv); - - this.imguiRenderer.NewFrame(); - this.OnNewRenderFrame?.Invoke(); - this.imguiInput.NewFrame(targetWidth, targetHeight); - this.OnNewInputFrame?.Invoke(); - - ImGui.NewFrame(); - ImGuizmo.BeginFrame(); - - OnBuildUI?.Invoke(); - - ImGui.Render(); - - this.imguiRenderer.RenderDrawData(ImGui.GetDrawData()); - this.deviceContext.OutputMerger.SetRenderTargets((RenderTargetView)null); - ImGui.UpdatePlatformWindows(); - ImGui.RenderPlatformWindowsDefault(); - } - - public void OnPreResize() - { - this.deviceContext.OutputMerger.SetRenderTargets((RenderTargetView)null); - - this.rtv?.Dispose(); - this.rtv = null; - } - - public void OnPostResize(int newWidth, int newHeight) - { - using (var backbuffer = this.SwapChain.GetBackBuffer(0)) - { - this.rtv = new RenderTargetView(this.Device, backbuffer); - } - - this.targetWidth = newWidth; - this.targetHeight = newHeight; - } - - // It is pretty much required that this is called from a handler attached - // to OnNewRenderFrame - public void InvalidateFonts() - { - this.imguiRenderer.RebuildFontTexture(); - } - - // It is pretty much required that this is called from a handler attached - // to OnNewRenderFrame - public void ClearStacksOnContext() { - Custom.igCustom_ClearStacks(); - } - - public bool IsImGuiCursor(IntPtr hCursor) - { - return this.imguiInput.IsImGuiCursor(hCursor); - } - - public IDalamudTextureWrap LoadImage(string path) - { - using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) - using (var ms = new MemoryStream()) - { - fs.CopyTo(ms); - var image = Stbi.LoadFromMemory(ms, 4); - return LoadImage_Internal(image); - } - } - - public IDalamudTextureWrap LoadImage(byte[] imageBytes) - { - using (var ms = new MemoryStream(imageBytes, 0, imageBytes.Length, false, true)) - { - var image = Stbi.LoadFromMemory(ms, 4); - return LoadImage_Internal(image); - } - } - - public unsafe IDalamudTextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels = 4) - { - // StbiSharp doesn't expose a constructor, even just to wrap existing data, which means - // short of something awful like below, or creating another wrapper layer, we can't avoid - // adding divergent code paths into CreateTexture - //var mock = new { Width = width, Height = height, NumChannels = numChannels, Data = imageData }; - //var image = Unsafe.As(mock); - //return LoadImage_Internal(image); - - fixed (void* pixelData = imageData) - { - return CreateTexture(pixelData, width, height, numChannels); - } - } - - private unsafe IDalamudTextureWrap LoadImage_Internal(StbiImage image) - { - fixed (void* pixelData = image.Data) - { - return CreateTexture(pixelData, image.Width, image.Height, image.NumChannels); - } - } - - private unsafe IDalamudTextureWrap CreateTexture(void* pixelData, int width, int height, int bytesPerPixel) - { - ShaderResourceView resView = null; - - var texDesc = new Texture2DDescription - { - Width = width, - Height = height, - MipLevels = 1, - ArraySize = 1, - Format = Format.R8G8B8A8_UNorm, - SampleDescription = new SampleDescription(1, 0), - Usage = ResourceUsage.Immutable, - BindFlags = BindFlags.ShaderResource, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None - }; - - using (var texture = new Texture2D(this.Device, texDesc, new DataRectangle(new IntPtr(pixelData), width * bytesPerPixel))) - { - resView = new ShaderResourceView(this.Device, texture, new ShaderResourceViewDescription - { - Format = texDesc.Format, - Dimension = ShaderResourceViewDimension.Texture2D, - Texture2D = { MipLevels = texDesc.MipLevels } - }); - } - - // no sampler for now because the ImGui implementation we copied doesn't allow for changing it - - return new D3DTextureWrap(resView, width, height); - } - - public byte[] CaptureScreenshot() - { - using (var backBuffer = this.SwapChain.GetBackBuffer(0)) - { - Texture2DDescription desc = backBuffer.Description; - desc.CpuAccessFlags = CpuAccessFlags.Read; - desc.Usage = ResourceUsage.Staging; - desc.OptionFlags = ResourceOptionFlags.None; - desc.BindFlags = BindFlags.None; - - using (var tex = new Texture2D(this.Device, desc)) - { - this.deviceContext.CopyResource(backBuffer, tex); - using (var surf = tex.QueryInterface()) - { - var map = surf.Map(SharpDX.DXGI.MapFlags.Read, out DataStream dataStream); - var pixelData = new byte[surf.Description.Width * surf.Description.Height * surf.Description.Format.SizeOfInBytes()]; - var dataCounter = 0; - - while (dataCounter < pixelData.Length) - { - //var curPixel = dataStream.Read(); - var x = dataStream.Read(); - var y = dataStream.Read(); - var z = dataStream.Read(); - var w = dataStream.Read(); - - pixelData[dataCounter++] = z; - pixelData[dataCounter++] = y; - pixelData[dataCounter++] = x; - pixelData[dataCounter++] = w; - } - - // TODO: test this on a thread - //var gch = GCHandle.Alloc(pixelData, GCHandleType.Pinned); - //using (var bitmap = new Bitmap(surf.Description.Width, surf.Description.Height, map.Pitch, PixelFormat.Format32bppRgb, gch.AddrOfPinnedObject())) - //{ - // bitmap.Save(path); - //} - //gch.Free(); - - surf.Unmap(); - dataStream.Dispose(); - - return pixelData; - } - } - } - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - private void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - this.imguiRenderer?.Shutdown(); - this.imguiInput?.Dispose(); - - ImGui.DestroyContext(); - - this.rtv.Dispose(); - - // Not actually sure how sharpdx does ref management, but hopefully they - // addref when we create our wrappers, so this should just release that count - - // Originally it was thought these lines were needed because it was assumed that SharpDX does - // proper refcounting to handle disposing, but disposing these would cause the game to crash - // on resizing after unloading Dalamud - // this.SwapChain?.Dispose(); - // this.deviceContext?.Dispose(); - // this.Device?.Dispose(); - - disposedValue = true; - } - } - - ~RawDX11Scene() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false); - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - GC.SuppressFinalize(this); - } - #endregion - } -} diff --git a/Dalamud/Interface/ImGuiScene/Win32 Utils/Constants.cs b/Dalamud/Interface/ImGuiScene/Win32 Utils/Constants.cs deleted file mode 100644 index 31d9075fc2..0000000000 --- a/Dalamud/Interface/ImGuiScene/Win32 Utils/Constants.cs +++ /dev/null @@ -1,515 +0,0 @@ -namespace ImGuiScene -{ - // Assorted other win32 constants that are seemingly just #defined - // Not an enum like the others because the types vary and there are overlapping values - internal static class Win32Constants - { - public const short HTTRANSPARENT = -1; - public const short HTCLIENT = 1; - - public const short XBUTTON1 = 1; - - public const int WHEEL_DELTA = 120; - - public const int CURSOR_SHOWING = 1; - public const int CURSOR_SUPPRESSED = 2; - } - - internal enum WindowLongType : int - { - GWL_EXSTYLE = -20, - GWL_HINSTANCE = -6, - GWL_HWNDPARENT = -8, - GWL_ID = -12, - GWL_STYLE = -16, - GWL_USERDATA = -21, - GWL_WNDPROC = -4 - } - - internal enum Cursor : int - { - IDC_ARROW = 32512, - IDC_IBEAM = 32513, - IDC_WAIT = 32514, - IDC_CROSS = 32515, - IDC_UPARROW = 32516, - IDC_SIZE = 32640, - IDC_ICON = 32641, - IDC_SIZENWSE = 32642, - IDC_SIZENESW = 32643, - IDC_SIZEWE = 32644, - IDC_SIZENS = 32645, - IDC_SIZEALL = 32646, - IDC_NO = 32648, - IDC_HAND = 32649, - IDC_APPSTARTING = 32650, - IDC_HELP = 32651 - } - - // taken from https://www.pinvoke.net/default.aspx/Enums/VirtualKeys.html - internal enum VirtualKey : int - { - LeftButton = 0x01, - RightButton = 0x02, - Cancel = 0x03, - MiddleButton = 0x04, - ExtraButton1 = 0x05, - ExtraButton2 = 0x06, - Back = 0x08, - Tab = 0x09, - Clear = 0x0C, - Return = 0x0D, - Shift = 0x10, - Control = 0x11, - Menu = 0x12, - Pause = 0x13, - CapsLock = 0x14, - Kana = 0x15, - Hangeul = 0x15, - Hangul = 0x15, - Junja = 0x17, - Final = 0x18, - Hanja = 0x19, - Kanji = 0x19, - Escape = 0x1B, - Convert = 0x1C, - NonConvert = 0x1D, - Accept = 0x1E, - ModeChange = 0x1F, - Space = 0x20, - Prior = 0x21, - Next = 0x22, - End = 0x23, - Home = 0x24, - Left = 0x25, - Up = 0x26, - Right = 0x27, - Down = 0x28, - Select = 0x29, - Print = 0x2A, - Execute = 0x2B, - Snapshot = 0x2C, - Insert = 0x2D, - Delete = 0x2E, - Help = 0x2F, - N0 = 0x30, - N1 = 0x31, - N2 = 0x32, - N3 = 0x33, - N4 = 0x34, - N5 = 0x35, - N6 = 0x36, - N7 = 0x37, - N8 = 0x38, - N9 = 0x39, - A = 0x41, - B = 0x42, - C = 0x43, - D = 0x44, - E = 0x45, - F = 0x46, - G = 0x47, - H = 0x48, - I = 0x49, - J = 0x4A, - K = 0x4B, - L = 0x4C, - M = 0x4D, - N = 0x4E, - O = 0x4F, - P = 0x50, - Q = 0x51, - R = 0x52, - S = 0x53, - T = 0x54, - U = 0x55, - V = 0x56, - W = 0x57, - X = 0x58, - Y = 0x59, - Z = 0x5A, - LeftWindows = 0x5B, - RightWindows = 0x5C, - Application = 0x5D, - Sleep = 0x5F, - Numpad0 = 0x60, - Numpad1 = 0x61, - Numpad2 = 0x62, - Numpad3 = 0x63, - Numpad4 = 0x64, - Numpad5 = 0x65, - Numpad6 = 0x66, - Numpad7 = 0x67, - Numpad8 = 0x68, - Numpad9 = 0x69, - Multiply = 0x6A, - Add = 0x6B, - Separator = 0x6C, - Subtract = 0x6D, - Decimal = 0x6E, - Divide = 0x6F, - F1 = 0x70, - F2 = 0x71, - F3 = 0x72, - F4 = 0x73, - F5 = 0x74, - F6 = 0x75, - F7 = 0x76, - F8 = 0x77, - F9 = 0x78, - F10 = 0x79, - F11 = 0x7A, - F12 = 0x7B, - F13 = 0x7C, - F14 = 0x7D, - F15 = 0x7E, - F16 = 0x7F, - F17 = 0x80, - F18 = 0x81, - F19 = 0x82, - F20 = 0x83, - F21 = 0x84, - F22 = 0x85, - F23 = 0x86, - F24 = 0x87, - NumLock = 0x90, - ScrollLock = 0x91, - NEC_Equal = 0x92, - Fujitsu_Jisho = 0x92, - Fujitsu_Masshou = 0x93, - Fujitsu_Touroku = 0x94, - Fujitsu_Loya = 0x95, - Fujitsu_Roya = 0x96, - LeftShift = 0xA0, - RightShift = 0xA1, - LeftControl = 0xA2, - RightControl = 0xA3, - LeftMenu = 0xA4, - RightMenu = 0xA5, - BrowserBack = 0xA6, - BrowserForward = 0xA7, - BrowserRefresh = 0xA8, - BrowserStop = 0xA9, - BrowserSearch = 0xAA, - BrowserFavorites = 0xAB, - BrowserHome = 0xAC, - VolumeMute = 0xAD, - VolumeDown = 0xAE, - VolumeUp = 0xAF, - MediaNextTrack = 0xB0, - MediaPrevTrack = 0xB1, - MediaStop = 0xB2, - MediaPlayPause = 0xB3, - LaunchMail = 0xB4, - LaunchMediaSelect = 0xB5, - LaunchApplication1 = 0xB6, - LaunchApplication2 = 0xB7, - OEM1 = 0xBA, - OEMPlus = 0xBB, - OEMComma = 0xBC, - OEMMinus = 0xBD, - OEMPeriod = 0xBE, - OEM2 = 0xBF, - OEM3 = 0xC0, - OEM4 = 0xDB, - OEM5 = 0xDC, - OEM6 = 0xDD, - OEM7 = 0xDE, - OEM8 = 0xDF, - OEMAX = 0xE1, - OEM102 = 0xE2, - ICOHelp = 0xE3, - ICO00 = 0xE4, - ProcessKey = 0xE5, - ICOClear = 0xE6, - Packet = 0xE7, - OEMReset = 0xE9, - OEMJump = 0xEA, - OEMPA1 = 0xEB, - OEMPA2 = 0xEC, - OEMPA3 = 0xED, - OEMWSCtrl = 0xEE, - OEMCUSel = 0xEF, - OEMATTN = 0xF0, - OEMFinish = 0xF1, - OEMCopy = 0xF2, - OEMAuto = 0xF3, - OEMENLW = 0xF4, - OEMBackTab = 0xF5, - ATTN = 0xF6, - CRSel = 0xF7, - EXSel = 0xF8, - EREOF = 0xF9, - Play = 0xFA, - Zoom = 0xFB, - Noname = 0xFC, - PA1 = 0xFD, - OEMClear = 0xFE - } - - // taken from https://gist.github.com/amgine/2395987 - // may be missing some but likely has anything we'd ever need - internal enum WindowsMessage - { - WM_NULL = 0x0000, - WM_CREATE = 0x0001, - WM_DESTROY = 0x0002, - WM_MOVE = 0x0003, - WM_SIZE = 0x0005, - WM_ACTIVATE = 0x0006, - WM_SETFOCUS = 0x0007, - WM_KILLFOCUS = 0x0008, - WM_ENABLE = 0x000A, - WM_SETREDRAW = 0x000B, - WM_SETTEXT = 0x000C, - WM_GETTEXT = 0x000D, - WM_GETTEXTLENGTH = 0x000E, - WM_PAINT = 0x000F, - WM_CLOSE = 0x0010, - WM_QUERYENDSESSION = 0x0011, - WM_QUERYOPEN = 0x0013, - WM_ENDSESSION = 0x0016, - WM_QUIT = 0x0012, - WM_ERASEBKGND = 0x0014, - WM_SYSCOLORCHANGE = 0x0015, - WM_SHOWWINDOW = 0x0018, - WM_WININICHANGE = 0x001A, - WM_SETTINGCHANGE = WM_WININICHANGE, - WM_DEVMODECHANGE = 0x001B, - WM_ACTIVATEAPP = 0x001C, - WM_FONTCHANGE = 0x001D, - WM_TIMECHANGE = 0x001E, - WM_CANCELMODE = 0x001F, - WM_SETCURSOR = 0x0020, - WM_MOUSEACTIVATE = 0x0021, - WM_CHILDACTIVATE = 0x0022, - WM_QUEUESYNC = 0x0023, - WM_GETMINMAXINFO = 0x0024, - WM_PAINTICON = 0x0026, - WM_ICONERASEBKGND = 0x0027, - WM_NEXTDLGCTL = 0x0028, - WM_SPOOLERSTATUS = 0x002A, - WM_DRAWITEM = 0x002B, - WM_MEASUREITEM = 0x002C, - WM_DELETEITEM = 0x002D, - WM_VKEYTOITEM = 0x002E, - WM_CHARTOITEM = 0x002F, - WM_SETFONT = 0x0030, - WM_GETFONT = 0x0031, - WM_SETHOTKEY = 0x0032, - WM_GETHOTKEY = 0x0033, - WM_QUERYDRAGICON = 0x0037, - WM_COMPAREITEM = 0x0039, - WM_GETOBJECT = 0x003D, - WM_COMPACTING = 0x0041, - WM_COMMNOTIFY = 0x0044, - WM_WINDOWPOSCHANGING = 0x0046, - WM_WINDOWPOSCHANGED = 0x0047, - WM_POWER = 0x0048, - WM_COPYDATA = 0x004A, - WM_CANCELJOURNAL = 0x004B, - WM_NOTIFY = 0x004E, - WM_INPUTLANGCHANGEREQUEST = 0x0050, - WM_INPUTLANGCHANGE = 0x0051, - WM_TCARD = 0x0052, - WM_HELP = 0x0053, - WM_USERCHANGED = 0x0054, - WM_NOTIFYFORMAT = 0x0055, - WM_CONTEXTMENU = 0x007B, - WM_STYLECHANGING = 0x007C, - WM_STYLECHANGED = 0x007D, - WM_DISPLAYCHANGE = 0x007E, - WM_GETICON = 0x007F, - WM_SETICON = 0x0080, - WM_NCCREATE = 0x0081, - WM_NCDESTROY = 0x0082, - WM_NCCALCSIZE = 0x0083, - WM_NCHITTEST = 0x0084, - WM_NCPAINT = 0x0085, - WM_NCACTIVATE = 0x0086, - WM_GETDLGCODE = 0x0087, - WM_SYNCPAINT = 0x0088, - - WM_NCMOUSEMOVE = 0x00A0, - WM_NCLBUTTONDOWN = 0x00A1, - WM_NCLBUTTONUP = 0x00A2, - WM_NCLBUTTONDBLCLK = 0x00A3, - WM_NCRBUTTONDOWN = 0x00A4, - WM_NCRBUTTONUP = 0x00A5, - WM_NCRBUTTONDBLCLK = 0x00A6, - WM_NCMBUTTONDOWN = 0x00A7, - WM_NCMBUTTONUP = 0x00A8, - WM_NCMBUTTONDBLCLK = 0x00A9, - WM_NCXBUTTONDOWN = 0x00AB, - WM_NCXBUTTONUP = 0x00AC, - WM_NCXBUTTONDBLCLK = 0x00AD, - - WM_INPUT_DEVICE_CHANGE = 0x00FE, - WM_INPUT = 0x00FF, - - WM_KEYFIRST = 0x0100, - WM_KEYDOWN = 0x0100, - WM_KEYUP = 0x0101, - WM_CHAR = 0x0102, - WM_DEADCHAR = 0x0103, - WM_SYSKEYDOWN = 0x0104, - WM_SYSKEYUP = 0x0105, - WM_SYSCHAR = 0x0106, - WM_SYSDEADCHAR = 0x0107, - WM_UNICHAR = 0x0109, - WM_KEYLAST = 0x0109, - - WM_IME_STARTCOMPOSITION = 0x010D, - WM_IME_ENDCOMPOSITION = 0x010E, - WM_IME_COMPOSITION = 0x010F, - WM_IME_KEYLAST = 0x010F, - - WM_INITDIALOG = 0x0110, - WM_COMMAND = 0x0111, - WM_SYSCOMMAND = 0x0112, - WM_TIMER = 0x0113, - WM_HSCROLL = 0x0114, - WM_VSCROLL = 0x0115, - WM_INITMENU = 0x0116, - WM_INITMENUPOPUP = 0x0117, - WM_MENUSELECT = 0x011F, - WM_MENUCHAR = 0x0120, - WM_ENTERIDLE = 0x0121, - WM_MENURBUTTONUP = 0x0122, - WM_MENUDRAG = 0x0123, - WM_MENUGETOBJECT = 0x0124, - WM_UNINITMENUPOPUP = 0x0125, - WM_MENUCOMMAND = 0x0126, - - WM_CHANGEUISTATE = 0x0127, - WM_UPDATEUISTATE = 0x0128, - WM_QUERYUISTATE = 0x0129, - - WM_CTLCOLORMSGBOX = 0x0132, - WM_CTLCOLOREDIT = 0x0133, - WM_CTLCOLORLISTBOX = 0x0134, - WM_CTLCOLORBTN = 0x0135, - WM_CTLCOLORDLG = 0x0136, - WM_CTLCOLORSCROLLBAR = 0x0137, - WM_CTLCOLORSTATIC = 0x0138, - MN_GETHMENU = 0x01E1, - - WM_MOUSEFIRST = 0x0200, - WM_MOUSEMOVE = 0x0200, - WM_LBUTTONDOWN = 0x0201, - WM_LBUTTONUP = 0x0202, - WM_LBUTTONDBLCLK = 0x0203, - WM_RBUTTONDOWN = 0x0204, - WM_RBUTTONUP = 0x0205, - WM_RBUTTONDBLCLK = 0x0206, - WM_MBUTTONDOWN = 0x0207, - WM_MBUTTONUP = 0x0208, - WM_MBUTTONDBLCLK = 0x0209, - WM_MOUSEWHEEL = 0x020A, - WM_XBUTTONDOWN = 0x020B, - WM_XBUTTONUP = 0x020C, - WM_XBUTTONDBLCLK = 0x020D, - WM_MOUSEHWHEEL = 0x020E, - - WM_PARENTNOTIFY = 0x0210, - WM_ENTERMENULOOP = 0x0211, - WM_EXITMENULOOP = 0x0212, - - WM_NEXTMENU = 0x0213, - WM_SIZING = 0x0214, - WM_CAPTURECHANGED = 0x0215, - WM_MOVING = 0x0216, - - WM_POWERBROADCAST = 0x0218, - - WM_DEVICECHANGE = 0x0219, - - WM_MDICREATE = 0x0220, - WM_MDIDESTROY = 0x0221, - WM_MDIACTIVATE = 0x0222, - WM_MDIRESTORE = 0x0223, - WM_MDINEXT = 0x0224, - WM_MDIMAXIMIZE = 0x0225, - WM_MDITILE = 0x0226, - WM_MDICASCADE = 0x0227, - WM_MDIICONARRANGE = 0x0228, - WM_MDIGETACTIVE = 0x0229, - - WM_MDISETMENU = 0x0230, - WM_ENTERSIZEMOVE = 0x0231, - WM_EXITSIZEMOVE = 0x0232, - WM_DROPFILES = 0x0233, - WM_MDIREFRESHMENU = 0x0234, - - WM_IME_SETCONTEXT = 0x0281, - WM_IME_NOTIFY = 0x0282, - WM_IME_CONTROL = 0x0283, - WM_IME_COMPOSITIONFULL = 0x0284, - WM_IME_SELECT = 0x0285, - WM_IME_CHAR = 0x0286, - WM_IME_REQUEST = 0x0288, - WM_IME_KEYDOWN = 0x0290, - WM_IME_KEYUP = 0x0291, - - WM_MOUSEHOVER = 0x02A1, - WM_MOUSELEAVE = 0x02A3, - WM_NCMOUSEHOVER = 0x02A0, - WM_NCMOUSELEAVE = 0x02A2, - - WM_WTSSESSION_CHANGE = 0x02B1, - - WM_TABLET_FIRST = 0x02c0, - WM_TABLET_LAST = 0x02df, - - WM_CUT = 0x0300, - WM_COPY = 0x0301, - WM_PASTE = 0x0302, - WM_CLEAR = 0x0303, - WM_UNDO = 0x0304, - WM_RENDERFORMAT = 0x0305, - WM_RENDERALLFORMATS = 0x0306, - WM_DESTROYCLIPBOARD = 0x0307, - WM_DRAWCLIPBOARD = 0x0308, - WM_PAINTCLIPBOARD = 0x0309, - WM_VSCROLLCLIPBOARD = 0x030A, - WM_SIZECLIPBOARD = 0x030B, - WM_ASKCBFORMATNAME = 0x030C, - WM_CHANGECBCHAIN = 0x030D, - WM_HSCROLLCLIPBOARD = 0x030E, - WM_QUERYNEWPALETTE = 0x030F, - WM_PALETTEISCHANGING = 0x0310, - WM_PALETTECHANGED = 0x0311, - WM_HOTKEY = 0x0312, - - WM_PRINT = 0x0317, - WM_PRINTCLIENT = 0x0318, - - WM_APPCOMMAND = 0x0319, - - WM_THEMECHANGED = 0x031A, - - WM_CLIPBOARDUPDATE = 0x031D, - - WM_DWMCOMPOSITIONCHANGED = 0x031E, - WM_DWMNCRENDERINGCHANGED = 0x031F, - WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320, - WM_DWMWINDOWMAXIMIZEDCHANGE = 0x0321, - - WM_GETTITLEBARINFOEX = 0x033F, - - WM_HANDHELDFIRST = 0x0358, - WM_HANDHELDLAST = 0x035F, - - WM_AFXFIRST = 0x0360, - WM_AFXLAST = 0x037F, - - WM_PENWINFIRST = 0x0380, - WM_PENWINLAST = 0x038F, - - WM_APP = 0x8000, - - WM_USER = 0x0400, - - WM_REFLECT = WM_USER + 0x1C00, - } -} diff --git a/Dalamud/Interface/ImGuiScene/Win32 Utils/MemUtil.cs b/Dalamud/Interface/ImGuiScene/Win32 Utils/MemUtil.cs deleted file mode 100644 index d87730f5bf..0000000000 --- a/Dalamud/Interface/ImGuiScene/Win32 Utils/MemUtil.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Runtime.InteropServices; - -namespace ImGuiScene -{ - public static unsafe class MemUtil - { - public static T* Allocate() where T : unmanaged { - return (T*)Marshal.AllocHGlobal(Marshal.SizeOf()); - } - - public static void Free(this IntPtr obj) { - Marshal.FreeHGlobal(obj); - } - } -} diff --git a/Dalamud/Interface/ImGuiScene/Win32 Utils/Win32.cs b/Dalamud/Interface/ImGuiScene/Win32 Utils/Win32.cs deleted file mode 100644 index d35162cb5c..0000000000 --- a/Dalamud/Interface/ImGuiScene/Win32 Utils/Win32.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace ImGuiScene -{ - // Even though we are importing PInvoke stuff from Nuget, we still need this class - // for some APIs that do not seem to be exposed in any way through those packages. - // In the future, we may be able to use https://github.com/microsoft/cswin32 - internal class Win32 - { - public enum ImeCommand - { - IMN_CLOSESTATUSWINDOW = 0x0001, - IMN_OPENSTATUSWINDOW = 0x0002, - IMN_CHANGECANDIDATE = 0x0003, - IMN_CLOSECANDIDATE = 0x0004, - IMN_OPENCANDIDATE = 0x0005, - IMN_SETCONVERSIONMODE = 0x0006, - IMN_SETSENTENCEMODE = 0x0007, - IMN_SETOPENSTATUS = 0x0008, - IMN_SETCANDIDATEPOS = 0x0009, - IMN_SETCOMPOSITIONFONT = 0x000A, - IMN_SETCOMPOSITIONWINDOW = 0x000B, - IMN_SETSTATUSWINDOWPOS = 0x000C, - IMN_GUIDELINE = 0x000D, - IMN_PRIVATE = 0x000E - } - - - [StructLayout(LayoutKind.Sequential)] - public struct POINT - { - public int X; - public int Y; - - public POINT(int X, int Y) - { - this.X = X; - this.Y = Y; - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct CURSORINFO - { - public Int32 cbSize; - public Int32 flags; - public IntPtr hCursor; - public POINT ptScreenPos; - } - - [StructLayout(LayoutKind.Sequential)] - public struct RECT - { - public int left; - public int top; - public int right; - public int bottom; - - public RECT(int left, int top, int right, int bottom) - { - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct COMPOSITIONFORM - { - public uint dwStyle; - public POINT ptCurrentPos; - public RECT rcArea; - - public COMPOSITIONFORM(uint dwStyle, POINT ptCurrentPos, RECT rcArea) - { - this.dwStyle = dwStyle; - this.ptCurrentPos = ptCurrentPos; - this.rcArea = rcArea; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort HIWORD(ulong val) - { - // #define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff)) - return (ushort)((val >> 16) & 0xFFFF); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort LOWORD(ulong val) - { - // #define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff)) - return (ushort)(val & 0xFFFF); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort GET_XBUTTON_WPARAM(ulong val) - { - // #define GET_XBUTTON_WPARAM(wParam) (HIWORD(wParam)) - return HIWORD(val); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static short GET_WHEEL_DELTA_WPARAM(ulong val) - { - // #define GET_WHEEL_DELTA_WPARAM(wParam) ((short)HIWORD(wParam)) - return (short)HIWORD(val); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GET_X_LPARAM(ulong val) - { - // #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) - return (int)(short)LOWORD(val); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GET_Y_LPARAM(ulong val) - { - // #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) - return (int)(short)HIWORD(val); - } - - [DllImport("dwmapi.dll")] - public static extern int DwmIsCompositionEnabled(out bool enabled); - [DllImport("user32.dll", SetLastError = true)] - public static extern bool BringWindowToTop(IntPtr hWnd); - [DllImport("user32.dll", SetLastError = true)] - public static extern IntPtr SetFocus(IntPtr hWnd); - [DllImport("user32.dll")] - public static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags); - // [DllImport("Imm32.dll", SetLastError=true)] - // public static extern IntPtr ImmGetContext(IntPtr hWnd); - // [DllImport("Imm32.dll", SetLastError=true)] - // public static extern bool ImmSetCompositionWindow(IntPtr hImc, ref COMPOSITIONFORM lpCompForm); - // [DllImport("Imm32.dll", SetLastError=true)] - // public static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hImc); - - - [DllImport("user32.dll")] - public static extern bool GetCursorPos(out POINT lpPoint); - [DllImport("user32.dll")] - public static extern bool SetCursorPos(int x, int y); - [DllImport("user32.dll")] - public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); - [DllImport("user32.dll")] - public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); - [DllImport("user32.dll")] - public static extern IntPtr GetCapture(); - [DllImport("user32.dll")] - public static extern IntPtr SetCapture(IntPtr hWnd); - [DllImport("user32.dll")] - public static extern bool ReleaseCapture(); - [DllImport("user32.dll")] - public static extern short GetKeyState(VirtualKey nVirtKey); - [DllImport("user32.dll")] - public static extern IntPtr GetCursor(); - [DllImport("user32.dll")] - public static extern IntPtr SetCursor(IntPtr handle); - [DllImport("user32.dll")] - public static extern IntPtr LoadCursor(IntPtr hInstance, Cursor lpCursorName); - [DllImport("user32.dll")] - public static extern int ShowCursor(bool bShow); - [DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)] - public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongType nIndex, IntPtr dwNewLong); - [DllImport("user32.dll", EntryPoint = "CallWindowProcW")] - public static extern long CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, ulong wParam, long lParam); - - [DllImport("user32.dll", EntryPoint = "GetCursorInfo")] - private static extern bool GetCursorInfo_Internal(ref CURSORINFO pci); - - public static bool GetCursorInfo(out CURSORINFO pci) - { - pci = new CURSORINFO - { - cbSize = Marshal.SizeOf(typeof(CURSORINFO)) - }; - - return GetCursorInfo_Internal(ref pci); - } - } -} diff --git a/Dalamud/Interface/ImGuiScene/costura64/stbi.dll b/Dalamud/Interface/ImGuiScene/costura64/stbi.dll deleted file mode 100644 index d36a199bc5..0000000000 Binary files a/Dalamud/Interface/ImGuiScene/costura64/stbi.dll and /dev/null differ diff --git a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.glsl b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.glsl deleted file mode 100644 index 2703e717a9..0000000000 --- a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.glsl +++ /dev/null @@ -1,12 +0,0 @@ -#version 150 - -uniform sampler2D Texture; - -in vec2 Frag_UV; -in vec4 Frag_Color; -out vec4 Out_Color; - -void main() -{ - Out_Color = Frag_Color * texture(Texture, Frag_UV.st); -} \ No newline at end of file diff --git a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.glsl b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.glsl deleted file mode 100644 index c15a7de644..0000000000 --- a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.glsl +++ /dev/null @@ -1,17 +0,0 @@ -#version 150 - -uniform mat4 ProjMtx; - -in vec2 Position; -in vec2 UV; -in vec4 Color; - -out vec2 Frag_UV; -out vec4 Frag_Color; - -void main() -{ - Frag_UV = UV; - Frag_Color = Color; - gl_Position = ProjMtx * vec4(Position.xy, 0, 1); -} diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 26b5c8ce27..16b1279897 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -15,6 +15,7 @@ using Dalamud.Game.Internal.DXGI; using Dalamud.Hooking; using Dalamud.Hooking.WndProcHook; +using Dalamud.Interface.ImGuiBackend; using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.ManagedFontAtlas; @@ -28,12 +29,12 @@ using ImGuiNET; -using ImGuiScene; - using PInvoke; -using SharpDX; -using SharpDX.DXGI; +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +using static TerraFX.Interop.Windows.Windows; // general dev notes, here because it's easiest @@ -66,7 +67,7 @@ internal class InterfaceManager : IInternalDisposableService public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f; private static readonly ModuleLog Log = new("INTERFACE"); - + private readonly ConcurrentBag deferredDisposeTextures = new(); private readonly ConcurrentBag deferredDisposeDisposables = new(); @@ -80,7 +81,7 @@ internal class InterfaceManager : IInternalDisposableService private readonly ConcurrentQueue runAfterImGuiRender = new(); private readonly SwapChainVtableResolver address = new(); - private RawDX11Scene? scene; + private IWin32Backend? scene; private Hook? setCursorHook; private Hook? presentHook; @@ -90,28 +91,29 @@ internal class InterfaceManager : IInternalDisposableService private ILockedImFont? defaultFontResourceLock; // can't access imgui IO before first present call - private bool lastWantCapture = false; + private HWND gameWindowHandle; + private bool lastWantCapture; private bool isOverrideGameCursor = true; - private IntPtr gameWindowHandle; [ServiceManager.ServiceConstructor] private InterfaceManager() { } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private unsafe delegate HRESULT PresentDelegate(IDXGISwapChain* swapChain, uint syncInterval, uint presentFlags); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private unsafe delegate HRESULT ResizeBuffersDelegate( + IDXGISwapChain* swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate IntPtr SetCursorDelegate(IntPtr hCursor); + private delegate HCURSOR SetCursorDelegate(HCURSOR hCursor); /// /// This event gets called each frame to facilitate ImGui drawing. /// - public event RawDX11Scene.BuildUIDelegate? Draw; + public event IImGuiBackend.BuildUiDelegate? Draw; /// /// This event gets called when ResizeBuffers is called. @@ -179,17 +181,7 @@ private InterfaceManager() /// /// Gets the DX11 scene. /// - public RawDX11Scene? Scene => this.scene; - - /// - /// Gets the D3D11 device instance. - /// - public SharpDX.Direct3D11.Device? Device => this.scene?.Device; - - /// - /// Gets the address handle to the main process window. - /// - public IntPtr WindowHandlePtr => this.scene?.WindowHandlePtr ?? IntPtr.Zero; + public IImGuiBackend? Scene => this.scene; /// /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. @@ -222,20 +214,24 @@ public bool OverrideGameCursor /// /// Gets a value indicating the native handle of the game main window. /// - public IntPtr GameWindowHandle + public unsafe HWND GameWindowHandle { get { if (this.gameWindowHandle == 0) { - nint gwh = 0; - while ((gwh = NativeFunctions.FindWindowEx(0, gwh, "FFXIVGAME", 0)) != 0) + var gwh = default(HWND); + fixed (char* pClass = "FFXIVGAME") { - _ = User32.GetWindowThreadProcessId(gwh, out var pid); - if (pid == Environment.ProcessId && User32.IsWindowVisible(gwh)) + while ((gwh = FindWindowExW(default, gwh, (ushort*)pClass, default)) != default) { - this.gameWindowHandle = gwh; - break; + uint pid; + _ = GetWindowThreadProcessId(gwh, &pid); + if (pid == Environment.ProcessId && IsWindowVisible(gwh)) + { + this.gameWindowHandle = gwh; + break; + } } } } @@ -420,27 +416,27 @@ public Task RunAfterImGuiRender(Func func) /// Get video memory information. /// /// The currently used video memory, or null if not available. - public (long Used, long Available)? GetD3dMemoryInfo() + public unsafe (long Used, long Available)? GetD3dMemoryInfo() { - if (this.Device == null) + if (this.scene?.DeviceHandle is 0 or null) return null; - try - { - var dxgiDev = this.Device.QueryInterfaceOrNull(); - var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull(); - if (dxgiAdapter == null) - return null; + using var device = default(ComPtr); + using var adapter = default(ComPtr); + using var adapter4 = default(ComPtr); - var memInfo = dxgiAdapter.QueryVideoMemoryInfo(0, MemorySegmentGroup.Local); - return (memInfo.CurrentUsage, memInfo.CurrentReservation); - } - catch - { - // ignored - } + if (new ComPtr((IUnknown*)this.scene.DeviceHandle).As(&device).FAILED) + return null; + + if (device.Get()->GetAdapter(adapter.GetAddressOf()).FAILED) + return null; - return null; + if (adapter.As(&adapter4).FAILED) + return null; + + var vmi = default(DXGI_QUERY_VIDEO_MEMORY_INFO); + adapter4.Get()->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP.DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &vmi); + return ((long)vmi.CurrentUsage, (long)vmi.CurrentReservation); } /// @@ -449,23 +445,23 @@ public Task RunAfterImGuiRender(Func func) /// public void ClearStacks() { - this.scene?.ClearStacksOnContext(); + ImGuiHelpers.ClearStacksOnContext(); } /// /// Toggle Windows 11 immersive mode on the game window. /// /// Value. - internal void SetImmersiveMode(bool enabled) + internal unsafe void SetImmersiveMode(bool enabled) { if (this.GameWindowHandle == 0) throw new InvalidOperationException("Game window is not yet ready."); - var value = enabled ? 1 : 0; - ((Result)NativeFunctions.DwmSetWindowAttribute( - this.GameWindowHandle, - NativeFunctions.DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, - ref value, - sizeof(int))).CheckError(); + var value = enabled ? 1u : 0u; + DwmSetWindowAttribute( + this.GameWindowHandle, + (uint)DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, + &value, + sizeof(int)).ThrowOnError(); } private static InterfaceManager WhenFontsReady() @@ -480,7 +476,7 @@ private static InterfaceManager WhenFontsReady() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void RenderImGui(RawDX11Scene scene) + private static void RenderImGui(IImGuiBackend backend) { var conf = Service.Get(); @@ -488,22 +484,22 @@ private static void RenderImGui(RawDX11Scene scene) ImGuiHelpers.NewFrame(); // Enable viewports if there are no issues. - if (conf.IsDisableViewport || scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1) + if (conf.IsDisableViewport || backend.IsMainViewportFullScreen() || ImGui.GetPlatformIO().Monitors.Size == 1) ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable; else ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; - scene.Render(); + backend.Render(); } - private void InitScene(IntPtr swapChain) + private unsafe void InitScene(IDXGISwapChain* swapChain) { - RawDX11Scene newScene; + IWin32Backend newBackend; using (Timings.Start("IM Scene Init")) { try { - newScene = new RawDX11Scene(swapChain); + newBackend = new Dx11Win32Backend(swapChain); } catch (DllNotFoundException ex) { @@ -514,7 +510,8 @@ private void InitScene(IntPtr swapChain) IntPtr.Zero, "Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?", "Dalamud Error", - User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR); + User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | + User32.MessageBoxOptions.MB_ICONERROR); if (res == User32.MessageBoxResult.IDYES) { @@ -535,14 +532,18 @@ private void InitScene(IntPtr swapChain) var startInfo = Service.Get().StartInfo; var configuration = Service.Get(); - var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath)!, "dalamudUI.ini")); + var iniFileInfo = new FileInfo( + Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath)!, "dalamudUI.ini")); try { if (iniFileInfo.Length > 1200000) { Log.Warning("dalamudUI.ini was over 1mb, deleting"); - iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName!, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); + iniFileInfo.CopyTo( + Path.Combine( + iniFileInfo.DirectoryName!, + $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); iniFileInfo.Delete(); } } @@ -551,16 +552,18 @@ private void InitScene(IntPtr swapChain) Log.Error(ex, "Could not delete dalamudUI.ini"); } - newScene.UpdateCursor = this.isOverrideGameCursor; - newScene.ImGuiIniPath = iniFileInfo.FullName; - newScene.OnBuildUI += this.Display; - newScene.OnNewInputFrame += this.OnNewInputFrame; + newBackend.UpdateCursor = this.isOverrideGameCursor; + newBackend.IniPath = iniFileInfo.FullName; + newBackend.BuildUi += this.Display; + newBackend.NewInputFrame += this.OnNewInputFrame; StyleModel.TransferOldModels(); - if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name)) + if (configuration.SavedStyles == null || + configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name)) { - configuration.SavedStyles = new List { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic }; + configuration.SavedStyles = new List + { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic }; configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name; } else if (configuration.SavedStyles.Count == 1) @@ -616,15 +619,15 @@ private void InitScene(IntPtr swapChain) Log.Information("[IM] Scene & ImGui setup OK!"); } - this.scene = newScene; + this.scene = newBackend; Service.Provide(new(this)); this.wndProcHookManager.PreWndProc += this.WndProcHookManagerOnPreWndProc; } - private unsafe void WndProcHookManagerOnPreWndProc(WndProcEventArgs args) + private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args) { - var r = this.scene?.ProcessWndProcW(args.Hwnd, (User32.WindowMessage)args.Message, args.WParam, args.LParam); + var r = this.scene?.ProcessWndProcW(args.Hwnd, args.Message, args.WParam, args.LParam); if (r is not null) args.SuppressWithValue(r.Value); } @@ -633,22 +636,24 @@ private unsafe void WndProcHookManagerOnPreWndProc(WndProcEventArgs args) * NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg. * Seems to work fine regardless, I guess, so whatever. */ - private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) + private unsafe HRESULT PresentDetour(IDXGISwapChain* swapChain, uint syncInterval, uint presentFlags) { Debug.Assert(this.presentHook is not null, "How did PresentDetour get called when presentHook is null?"); - Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already"); - - if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) - return this.presentHook!.Original(swapChain, syncInterval, presentFlags); - if (this.scene == null) + if (this.scene is null) + { this.InitScene(swapChain); + if (this.scene is null) + throw new InvalidOperationException("InitScene did not set this.scene?"); + } - Debug.Assert(this.scene is not null, "InitScene did not set the scene field, but did not throw an exception."); + if (!this.scene.IsAttachedToPresentationTarget((nint)swapChain)) + return this.presentHook!.Original(swapChain, syncInterval, presentFlags); - if (!this.dalamudAtlas!.HasBuiltAtlas) + // Do not do anything yet if no font atlas has been built yet. + if (this.dalamudAtlas?.HasBuiltAtlas is not true) { - if (this.dalamudAtlas.BuildTask.Exception != null) + if (this.dalamudAtlas?.BuildTask.Exception != null) { // TODO: Can we do something more user-friendly here? Unload instead? Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts"); @@ -658,23 +663,9 @@ private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFl return this.presentHook!.Original(swapChain, syncInterval, presentFlags); } - this.CumulativePresentCalls++; this.IsMainThreadInPresent = true; - - while (this.runBeforeImGuiRender.TryDequeue(out var action)) - action.InvokeSafely(); - - if (this.address.IsReshade) - { - var pRes = this.presentHook!.Original(swapChain, syncInterval, presentFlags); - - RenderImGui(this.scene!); - this.PostImGuiRender(); - this.IsMainThreadInPresent = false; - - return pRes; - } - + this.CumulativePresentCalls++; + this.PreImGuiRender(); RenderImGui(this.scene!); this.PostImGuiRender(); this.IsMainThreadInPresent = false; @@ -682,6 +673,12 @@ private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFl return this.presentHook!.Original(swapChain, syncInterval, presentFlags); } + private void PreImGuiRender() + { + while (this.runBeforeImGuiRender.TryDequeue(out var action)) + action.InvokeSafely(); + } + private void PostImGuiRender() { while (this.runAfterImGuiRender.TryDequeue(out var action)) @@ -711,7 +708,7 @@ private void PostImGuiRender() [ServiceManager.CallWhenServicesReady( "InterfaceManager accepts event registration and stuff even when the game window is not ready.")] - private void ContinueConstruction( + private unsafe void ContinueConstruction( TargetSigScanner sigScanner, FontAtlasFactory fontAtlasFactory) { @@ -731,13 +728,14 @@ private void ContinueConstruction( GlyphMaxAdvanceX = DefaultFontSizePx, }))); this.IconFontFixedWidthHandle = (FontHandle)this.dalamudAtlas.NewDelegateFontHandle( - e => e.OnPreBuild(tk => tk.AddDalamudAssetFont( - DalamudAsset.FontAwesomeFreeSolid, - new() - { - SizePx = Service.Get().DefaultFontSpec.SizePx, - GlyphRanges = new ushort[] { 0x20, 0x20, 0x00 }, - }))); + e => e.OnPreBuild( + tk => tk.AddDalamudAssetFont( + DalamudAsset.FontAwesomeFreeSolid, + new() + { + SizePx = Service.Get().DefaultFontSpec.SizePx, + GlyphRanges = [0x20, 0x20, 0x00], + }))); this.MonoFontHandle = (FontHandle)this.dalamudAtlas.NewDelegateFontHandle( e => e.OnPreBuild( tk => tk.AddDalamudAssetFont( @@ -769,10 +767,7 @@ private void ContinueConstruction( () => { // Update the ImGui default font. - unsafe - { - ImGui.GetIO().NativePtr->FontDefault = fontLocked.ImFont; - } + ImGui.GetIO().NativePtr->FontDefault = fontLocked.ImFont; // Update the reference to the resources of the default font. this.defaultFontResourceLock?.Dispose(); @@ -783,7 +778,7 @@ private void ContinueConstruction( }); }; } - + // This will wait for scene on its own. We just wait for this.dalamudAtlas.BuildTask in this.InitScene. _ = this.dalamudAtlas.BuildFontsAsync(); @@ -799,9 +794,16 @@ private void ContinueConstruction( Log.Error(ex, "Could not enable immersive mode"); } - this.setCursorHook = Hook.FromImport(null, "user32.dll", "SetCursor", 0, this.SetCursorDetour); + this.setCursorHook = Hook.FromImport( + null, + "user32.dll", + "SetCursor", + 0, + this.SetCursorDetour); this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour); - this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour); + this.resizeBuffersHook = Hook.FromAddress( + this.address.ResizeBuffers, + this.ResizeBuffersDetour); Log.Verbose("===== S W A P C H A I N ====="); Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}"); @@ -812,39 +814,43 @@ private void ContinueConstruction( this.resizeBuffersHook.Enable(); } - private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags) + private unsafe HRESULT ResizeBuffersDetour( + IDXGISwapChain* swapChain, + uint bufferCount, + uint width, + uint height, + uint newFormat, + uint swapChainFlags) { #if DEBUG - Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); + Log.Verbose( + $"Calling resizebuffers swap@{(nint)swapChain:X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); #endif this.ResizeBuffers?.InvokeSafely(); - // We have to ensure we're working with the main swapchain, - // as viewports might be resizing as well - if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) + // We have to ensure we're working with the main swapchain, as other viewports might be resizing as well. + if (this.scene?.IsAttachedToPresentationTarget((nint)swapChain) is not true) return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); this.scene?.OnPreResize(); var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); - if (ret.ToInt64() == 0x887A0001) - { + if (ret == DXGI.DXGI_ERROR_INVALID_CALL) Log.Error("invalid call to resizeBuffers"); - } this.scene?.OnPostResize((int)width, (int)height); return ret; } - private IntPtr SetCursorDetour(IntPtr hCursor) + private HCURSOR SetCursorDetour(HCURSOR hCursor) { if (this.lastWantCapture && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) - return IntPtr.Zero; + return default; return this.setCursorHook?.IsDisposed is not false - ? User32.SetCursor(new(hCursor, false)).DangerousGetHandle() + ? SetCursor(hCursor) : this.setCursorHook.Original(hCursor); } diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs index ef92ffd659..3633a8f958 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Dalamud.Interface.GameFonts; -using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Logging.Internal; @@ -318,8 +317,8 @@ public DalamudFontAtlas( if (this.disposed) return; - r.Result.OnNewRenderFrame += this.ImGuiSceneOnNewRenderFrame; - this.disposables.Add(() => r.Result.OnNewRenderFrame -= this.ImGuiSceneOnNewRenderFrame); + r.Result.NewRenderFrame += this.ImGuiSceneOnNewRenderFrame; + this.disposables.Add(() => r.Result.NewRenderFrame -= this.ImGuiSceneOnNewRenderFrame); } if (this.AutoRebuildMode == FontAtlasAutoRebuildMode.OnNewFrame) diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs index c084d88e27..e518c2c77c 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs @@ -10,6 +10,7 @@ using Dalamud.Game; using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.GameFonts; +using Dalamud.Interface.ImGuiBackend; using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.TextureWraps; @@ -19,8 +20,6 @@ using ImGuiNET; -using ImGuiScene; - using Lumina.Data.Files; using TerraFX.Interop.DirectX; @@ -151,9 +150,9 @@ private FontAtlasFactory( public TextureManager TextureManager => Service.Get(); /// - /// Gets the async task for inside . + /// Gets the async task for inside . /// - public Task SceneTask { get; } + public Task SceneTask { get; } /// /// Gets the default glyph ranges (glyph ranges of ). diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Drawer.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Drawer.cs index 7fb79311ad..d1b0171972 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.Drawer.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.Drawer.cs @@ -60,7 +60,7 @@ public void Dispose() /// The device. public void Setup(ID3D11Device* device) { - var assembly = typeof(ImGuiScene.ImGui_Impl_DX11).Assembly; + var assembly = typeof(SimpleDrawerImpl).Assembly; // Create the vertex shader if (this.vertexShader.IsEmpty() || this.inputLayout.IsEmpty()) diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs index c9ee5d20e2..ef85de53ee 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs @@ -58,7 +58,7 @@ internal sealed partial class TextureManager private unsafe TextureManager(InterfaceManager.InterfaceManagerWithScene withScene) { using var failsafe = new DisposeSafety.ScopedFinalizer(); - failsafe.Add(this.device = new((ID3D11Device*)withScene.Manager.Device!.NativePointer)); + failsafe.Add(this.device = new((ID3D11Device*)withScene.Manager.Scene!.DeviceHandle)); failsafe.Add(this.dynamicPriorityTextureLoader = new(Math.Max(1, Environment.ProcessorCount - 1))); failsafe.Add(this.sharedTextureManager = new(this)); failsafe.Add(this.wicManager = new(this)); diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 1413f33471..e199cea2e8 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -19,8 +19,6 @@ using Serilog; -using SharpDX.Direct3D11; - namespace Dalamud.Interface; /// @@ -120,7 +118,7 @@ public interface IUiBuilder /// /// Gets the game's active Direct3D device. /// - Device Device { get; } + SharpDX.Direct3D11.Device Device { get; } /// /// Gets the game's main window handle. @@ -259,6 +257,8 @@ public sealed class UiBuilder : IDisposable, IUiBuilder private IFontHandle? monoFontHandle; private IFontHandle? iconFontFixedWidthHandle; + private SharpDX.Direct3D11.Device? sdxDevice; + /// /// Initializes a new instance of the class and registers it. /// You do not have to call this manually. @@ -418,12 +418,13 @@ internal UiBuilder(LocalPlugin plugin, string namespaceName) /// /// Gets the game's active Direct3D device. /// - public Device Device => this.InterfaceManagerWithScene!.Device!; + public SharpDX.Direct3D11.Device Device => + this.sdxDevice ??= new(this.InterfaceManagerWithScene!.Scene!.DeviceHandle); /// /// Gets the game's main window handle. /// - public IntPtr WindowHandlePtr => this.InterfaceManagerWithScene!.WindowHandlePtr; + public nint WindowHandlePtr => this.InterfaceManagerWithScene!.GameWindowHandle; /// /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden. diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index b09b294112..f32c4225d5 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -8,13 +8,12 @@ using System.Text.Unicode; using Dalamud.Configuration.Internal; -using Dalamud.Game.ClientState.Keys; +using Dalamud.Interface.ImGuiBackend.InputHandler; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Utility.Raii; using ImGuiNET; -using ImGuiScene; using VirtualKey = Dalamud.Game.ClientState.Keys.VirtualKey; @@ -23,7 +22,7 @@ namespace Dalamud.Interface.Utility; /// /// Class containing various helper methods for use with ImGui inside Dalamud. /// -public static class ImGuiHelpers +public static partial class ImGuiHelpers { /// /// Gets the main viewport. @@ -373,7 +372,7 @@ public static unsafe void CopyGlyphsAcrossFonts( /// The ImGuiKey that corresponds to this VirtualKey, or ImGuiKey.None otherwise. public static ImGuiKey VirtualKeyToImGuiKey(VirtualKey key) { - return ImGui_Input_Impl_Direct.VirtualKeyToImGuiKey((int)key); + return Win32InputHandler.VirtualKeyToImGuiKey((int)key); } /// @@ -383,7 +382,7 @@ public static ImGuiKey VirtualKeyToImGuiKey(VirtualKey key) /// The VirtualKey that corresponds to this ImGuiKey, or VirtualKey.NO_KEY otherwise. public static VirtualKey ImGuiKeyToVirtualKey(ImGuiKey key) { - return (VirtualKey)ImGui_Input_Impl_Direct.ImGuiKeyToVirtualKey(key); + return (VirtualKey)Win32InputHandler.ImGuiKeyToVirtualKey(key); } /// @@ -587,6 +586,12 @@ internal static unsafe int FindViewportId(nint hwnd) return -1; } + /// + /// Clears the stack in the current ImGui context. + /// + [LibraryImport("cimgui", EntryPoint = "igCustom_ClearStacks")] + internal static partial void ClearStacksOnContext(); + /// /// Attempts to validate that is valid. /// diff --git a/Dalamud/Utility/TexFileExtensions.cs b/Dalamud/Utility/TexFileExtensions.cs index ec8e10b3c2..9d6f152c5c 100644 --- a/Dalamud/Utility/TexFileExtensions.cs +++ b/Dalamud/Utility/TexFileExtensions.cs @@ -1,10 +1,12 @@ using System.Runtime.CompilerServices; +using Dalamud.Interface.ImGuiBackend.Renderers; using Dalamud.Memory; -using ImGuiScene; using Lumina.Data.Files; +using TerraFX.Interop.DirectX; + namespace Dalamud.Utility; /// @@ -13,7 +15,9 @@ namespace Dalamud.Utility; public static class TexFileExtensions { /// - /// Returns the image data formatted for . + /// Returns the image data formatted for , + /// using .
+ /// Consider using with . ///
/// The TexFile to format. /// The formatted image data.