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