diff --git a/.gitmodules b/.gitmodules index dd184b54e..d379480d9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "lib/ImGuiScene"] - path = lib/ImGuiScene - url = https://github.com/goatcorp/ImGuiScene [submodule "lib/FFXIVClientStructs"] path = lib/FFXIVClientStructs url = https://github.com/aers/FFXIVClientStructs @@ -10,3 +7,6 @@ [submodule "lib/TsudaKageyu-minhook"] path = lib/TsudaKageyu-minhook url = https://github.com/TsudaKageyu/minhook.git +[submodule "lib/ImGui.NET"] + path = lib/ImGui.NET + url = https://github.com/goatcorp/ImGui.NET.git diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 626788a7a..b9bc63cd1 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -40,13 +40,7 @@ false - - false - - - false - - + false diff --git a/Dalamud.sln b/Dalamud.sln index 22cc59a8d..67c15dd2d 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -27,11 +27,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Tes EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.CorePlugin", "Dalamud.CorePlugin\Dalamud.CorePlugin.csproj", "{4AFDB34A-7467-4D41-B067-53BC4101D9D0}" EndProject @@ -81,14 +77,6 @@ Global {0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64 {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64 {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64 - {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64 - {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64 - {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64 - {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64 - {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64 - {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64 - {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64 - {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64 @@ -127,8 +115,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {0483026E-C6CE-4B1A-AA68-46544C08140B} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} - {C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} - {2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} {C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} {3620414C-7DFC-423E-929F-310E19F5D930} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} {A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 2b575f617..775345d33 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -79,11 +79,18 @@ + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -95,12 +102,17 @@ + + + + + + + - - - + diff --git a/Dalamud/Interface/ImGuiScene/D3DTextureWrap.cs b/Dalamud/Interface/ImGuiScene/D3DTextureWrap.cs new file mode 100644 index 000000000..b621dbbd4 --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/D3DTextureWrap.cs @@ -0,0 +1,60 @@ +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 new file mode 100644 index 000000000..68a407a25 --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/FodyWeavers.xml @@ -0,0 +1,12 @@ + + + + + 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 new file mode 100644 index 000000000..af5be0b4e --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/FodyWeavers.xsd @@ -0,0 +1,141 @@ + + + + + + + + + + + + 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 new file mode 100644 index 000000000..06227f6d7 --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/FramerateLimit.cs @@ -0,0 +1,75 @@ +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 new file mode 100644 index 000000000..68b47315e --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Custom.cs @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..7c2d3cfbe --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/IImGuiInputHandler.cs @@ -0,0 +1,8 @@ +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 new file mode 100644 index 000000000..3b7fbba3b --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Input/ImGui_Input_Impl_Direct.cs @@ -0,0 +1,1398 @@ +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 new file mode 100644 index 000000000..72036b7bc --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/IImGuiRenderer.cs @@ -0,0 +1,14 @@ +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 new file mode 100644 index 000000000..cd63c27e1 --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/ImGui_Impl/Renderers/ImGui_Impl_DX11.cs @@ -0,0 +1,859 @@ +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 new file mode 100644 index 000000000..299349d51 --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/RawDX11Scene.cs @@ -0,0 +1,368 @@ +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 new file mode 100644 index 000000000..31d9075fc --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/Win32 Utils/Constants.cs @@ -0,0 +1,515 @@ +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 new file mode 100644 index 000000000..d87730f5b --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/Win32 Utils/MemUtil.cs @@ -0,0 +1,15 @@ +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 new file mode 100644 index 000000000..d35162cb5 --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/Win32 Utils/Win32.cs @@ -0,0 +1,184 @@ +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 new file mode 100644 index 000000000..d36a199bc Binary files /dev/null and b/Dalamud/Interface/ImGuiScene/costura64/stbi.dll differ diff --git a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.glsl b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.glsl new file mode 100644 index 000000000..2703e717a --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.glsl @@ -0,0 +1,12 @@ +#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-frag.hlsl.bytes b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.hlsl.bytes new file mode 100644 index 000000000..2a9fbf5a3 Binary files /dev/null and b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-frag.hlsl.bytes differ diff --git a/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.glsl b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.glsl new file mode 100644 index 000000000..c15a7de64 --- /dev/null +++ b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.glsl @@ -0,0 +1,17 @@ +#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/ImGuiScene/resources/shaders/imgui-vertex.hlsl.bytes b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.hlsl.bytes new file mode 100644 index 000000000..572a04538 Binary files /dev/null and b/Dalamud/Interface/ImGuiScene/resources/shaders/imgui-vertex.hlsl.bytes differ diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 885f73067..b09b29411 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -16,6 +16,8 @@ using Dalamud.Interface.Utility.Raii; using ImGuiNET; using ImGuiScene; +using VirtualKey = Dalamud.Game.ClientState.Keys.VirtualKey; + namespace Dalamud.Interface.Utility; /// diff --git a/lib/ImGui.NET b/lib/ImGui.NET new file mode 160000 index 000000000..b104b3520 --- /dev/null +++ b/lib/ImGui.NET @@ -0,0 +1 @@ +Subproject commit b104b3520d500366a6ee34c743c20ccf112d7a6e diff --git a/lib/ImGuiScene b/lib/ImGuiScene deleted file mode 160000 index 2f37349ff..000000000 --- a/lib/ImGuiScene +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2f37349ffd778561a1103a650683116c43edc86c