From 3cbbc859f185f476e21f3cdbe375f2b64c3f13c6 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Fri, 12 Jul 2024 17:27:35 +0900 Subject: [PATCH] Rewrite ImGuiScene --- Dalamud/Dalamud.csproj | 12 +- .../Internal/DXGI/SwapChainVtableResolver.cs | 142 +- Dalamud/Interface/DragDrop/DragDropManager.cs | 2 +- .../ImGuiBackend/Dx11Win32Backend.cs | 222 +++ .../D3D11/D3D11DeviceContextStateBackup.cs | 655 ++++++++ .../ImGuiBackend/Helpers/D3D11/Extensions.cs | 26 + .../Helpers/ImGuiViewportHelpers.cs | 165 ++ .../ImGuiBackend/Helpers/ReShadePeeler.cs | 134 ++ .../Interface/ImGuiBackend/IImGuiBackend.cs | 72 + .../Interface/ImGuiBackend/IWin32Backend.cs | 15 + .../InputHandler/IImGuiInputHandler.cs | 22 + ...Win32InputHandler.StaticLookupFunctions.cs | 309 ++++ .../InputHandler/Win32InputHandler.cs | 1016 ++++++++++++ .../Renderers/Dx11Renderer.ViewportHandler.cs | 374 +++++ .../ImGuiBackend/Renderers/Dx11Renderer.cs | 664 ++++++++ .../ImGuiBackend/Renderers/IImGuiRenderer.cs | 43 + .../Renderers}/imgui-frag.hlsl.bytes | Bin .../Renderers}/imgui-vertex.hlsl.bytes | Bin .../Interface/ImGuiScene/D3DTextureWrap.cs | 60 - Dalamud/Interface/ImGuiScene/FodyWeavers.xml | 12 - Dalamud/Interface/ImGuiScene/FodyWeavers.xsd | 141 -- .../Interface/ImGuiScene/FramerateLimit.cs | 75 - .../Interface/ImGuiScene/ImGui_Impl/Custom.cs | 10 - .../ImGui_Impl/Input/IImGuiInputHandler.cs | 8 - .../Input/ImGui_Input_Impl_Direct.cs | 1398 ----------------- .../ImGui_Impl/Renderers/IImGuiRenderer.cs | 14 - .../ImGui_Impl/Renderers/ImGui_Impl_DX11.cs | 859 ---------- Dalamud/Interface/ImGuiScene/RawDX11Scene.cs | 368 ----- .../ImGuiScene/Win32 Utils/Constants.cs | 515 ------ .../ImGuiScene/Win32 Utils/MemUtil.cs | 15 - .../Interface/ImGuiScene/Win32 Utils/Win32.cs | 184 --- .../Interface/ImGuiScene/costura64/stbi.dll | Bin 90112 -> 0 bytes .../resources/shaders/imgui-frag.glsl | 12 - .../resources/shaders/imgui-vertex.glsl | 17 - .../Interface/Internal/InterfaceManager.cs | 256 +-- .../FontAtlasFactory.Implementation.cs | 5 +- .../Internals/FontAtlasFactory.cs | 7 +- .../Internal/TextureManager.Drawer.cs | 2 +- .../Textures/Internal/TextureManager.cs | 2 +- Dalamud/Interface/UiBuilder.cs | 11 +- Dalamud/Interface/Utility/ImGuiHelpers.cs | 15 +- Dalamud/Utility/TexFileExtensions.cs | 8 +- 42 files changed, 3897 insertions(+), 3970 deletions(-) create mode 100644 Dalamud/Interface/ImGuiBackend/Dx11Win32Backend.cs create mode 100644 Dalamud/Interface/ImGuiBackend/Helpers/D3D11/D3D11DeviceContextStateBackup.cs create mode 100644 Dalamud/Interface/ImGuiBackend/Helpers/D3D11/Extensions.cs create mode 100644 Dalamud/Interface/ImGuiBackend/Helpers/ImGuiViewportHelpers.cs create mode 100644 Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs create mode 100644 Dalamud/Interface/ImGuiBackend/IImGuiBackend.cs create mode 100644 Dalamud/Interface/ImGuiBackend/IWin32Backend.cs create mode 100644 Dalamud/Interface/ImGuiBackend/InputHandler/IImGuiInputHandler.cs create mode 100644 Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs create mode 100644 Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs create mode 100644 Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.ViewportHandler.cs create mode 100644 Dalamud/Interface/ImGuiBackend/Renderers/Dx11Renderer.cs create mode 100644 Dalamud/Interface/ImGuiBackend/Renderers/IImGuiRenderer.cs rename Dalamud/Interface/{ImGuiScene/resources/shaders => ImGuiBackend/Renderers}/imgui-frag.hlsl.bytes (100%) rename Dalamud/Interface/{ImGuiScene/resources/shaders => ImGuiBackend/Renderers}/imgui-vertex.hlsl.bytes (100%) delete mode 100644 Dalamud/Interface/ImGuiScene/D3DTextureWrap.cs delete mode 100644 Dalamud/Interface/ImGuiScene/FodyWeavers.xml delete mode 100644 Dalamud/Interface/ImGuiScene/FodyWeavers.xsd delete mode 100644 Dalamud/Interface/ImGuiScene/FramerateLimit.cs delete mode 100644 Dalamud/Interface/ImGuiScene/ImGui_Impl/Custom.cs delete mode 100644 Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/IImGuiInputHandler.cs delete mode 100644 Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/ImGui_Input_Impl_Direct.cs delete mode 100644 Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs delete mode 100644 Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs delete mode 100644 Dalamud/Interface/ImGuiScene/RawDX11Scene.cs delete mode 100644 Dalamud/Interface/ImGuiScene/Win32 Utils/Constants.cs delete mode 100644 Dalamud/Interface/ImGuiScene/Win32 Utils/MemUtil.cs delete mode 100644 Dalamud/Interface/ImGuiScene/Win32 Utils/Win32.cs delete mode 100644 Dalamud/Interface/ImGuiScene/costura64/stbi.dll delete mode 100644 Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.glsl delete mode 100644 Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.glsl 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 d36a199bc5b676b3adf5546c54a194f8ab9db080..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 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 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.