Created
July 17, 2025 14:17
-
-
Save PumpkinPaul/b2e655ecb5719802736f42e71d13bf39 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// vertext shader | |
#pragma pack_matrix( row_major ) | |
cbuffer UniformBlock : register(b0, space1) | |
{ | |
float4x4 Transform; | |
}; | |
struct Input | |
{ | |
float2 Position : TEXCOORD0; | |
float2 TexCoord : TEXCOORD1; | |
float4 Color : TEXCOORD2; | |
}; | |
struct Output | |
{ | |
float4 Position : SV_Position; | |
float2 TexCoord : TEXCOORD0; | |
float4 Color : TEXCOORD1; | |
}; | |
Output main(Input input) | |
{ | |
Output output; | |
float4 position = float4(input.Position, 0.0, 1.0); | |
output.Position = mul(position, Transform); | |
output.TexCoord = input.TexCoord; | |
output.Color = input.Color; | |
return output; | |
} | |
// Fragment Shader | |
Texture2D<float4> Texture : register(t0, space2); | |
SamplerState Sampler : register(s0, space2); | |
float4 main( | |
float2 TexCoord : TEXCOORD0, | |
float4 Color : TEXCOORD1 | |
) : SV_Target0 | |
{ | |
float4 color = Texture.Sample(Sampler, TexCoord); | |
return color *= Color; | |
} | |
// ImGuiMoonworksWindow | |
using Hexa.NET.ImGui; | |
using MoonWorks; | |
using MoonWorks.Graphics; | |
using System.Runtime.InteropServices; | |
using static SDL3.SDL; | |
namespace Pathogen.Game.Editor; | |
public class ImGuiWindow : IDisposable | |
{ | |
readonly GCHandle _gcHandle; | |
readonly GraphicsDevice _graphicsDevice; | |
readonly Window _window; | |
bool _isDisposed; | |
public Window Window { get => _window; } | |
public unsafe ImGuiWindow( | |
GraphicsDevice graphicsDevice, | |
ImGuiViewport* vp, | |
SDL_WindowFlags flags | |
) | |
{ | |
_graphicsDevice = graphicsDevice; | |
_window = new Window( | |
new WindowCreateInfo( | |
"No Title Yet", | |
(uint)vp->Size.X, | |
(uint)vp->Size.Y, | |
ScreenMode.Windowed | |
), | |
flags | |
); | |
_graphicsDevice.ClaimWindow(_window); | |
_gcHandle = GCHandle.Alloc(this); | |
vp->PlatformUserData = (void*)(IntPtr)_gcHandle; | |
} | |
public unsafe ImGuiWindow( | |
ImGuiViewport* vp, | |
Window window | |
) | |
{ | |
_window = window; | |
_gcHandle = GCHandle.Alloc(this); | |
vp->PlatformUserData = (void*)(IntPtr)_gcHandle; | |
} | |
public void Dispose() | |
{ | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (_isDisposed) | |
return; | |
_isDisposed = true; | |
if (disposing) | |
{ | |
_graphicsDevice?.UnclaimWindow(_window); | |
_window?.Dispose(); | |
} | |
if (_gcHandle.IsAllocated) | |
_gcHandle.Free(); | |
} | |
} | |
// ImGuiMoonWorksHelper | |
public class ImGuiHelper | |
{ | |
readonly Window _window; | |
readonly GraphicsDevice _graphicsDevice; | |
TitleStorage _titleStorage; | |
readonly Inputs _inputs; | |
readonly DebugTextureStorage TextureStorage; | |
Texture FontTexture; | |
uint VertexCount = 0; | |
uint IndexCount = 0; | |
Buffer ImGuiVertexBuffer = null; | |
Buffer ImGuiIndexBuffer = null; | |
readonly GraphicsPipeline ImGuiPipeline; | |
readonly Shader ImGuiVertexShader; | |
readonly Shader ImGuiFragmentShader; | |
readonly Sampler ImGuiSampler; | |
readonly ResourceUploader BufferUploader; | |
readonly Dictionary<ImGuiMouseCursor, IntPtr> _mouseCursors = []; | |
ImGuiMouseCursor _lastCursor = ImGuiMouseCursor.None; | |
// Support for text input in floating windows. | |
nint _lastFocusWindowHandle = 0; | |
Dictionary<nint, Window> _windows = []; | |
Window _focusWindow; | |
readonly KeyCode[] _allKeys = Enum.GetValues<KeyCode>(); | |
public unsafe ImGuiHelper( | |
Window window, | |
GraphicsDevice graphicsDevice, | |
TitleStorage titleStorage, | |
Inputs inputs | |
) | |
{ | |
_window = window; | |
_graphicsDevice = graphicsDevice; | |
_inputs = inputs; | |
TextureStorage = new DebugTextureStorage(); | |
var guiContext = ImGui.CreateContext(null); | |
ImGui.SetCurrentContext(guiContext); | |
ImPlot.SetImGuiContext(guiContext); | |
var plotContext = ImPlot.CreateContext(); | |
ImPlot.SetCurrentContext(plotContext); | |
var io = ImGui.GetIO(); | |
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; | |
io.DisplaySize = new Vector2(window.Width, window.Height); | |
io.DisplayFramebufferScale = Vector2.One; | |
InitMouseCursors(io); | |
InitViewportsSupport(io); | |
io.Fonts.Build(); | |
Inputs.TextInput += c => | |
{ | |
if (c == '\t') | |
return; | |
io.AddInputCharacter(c); | |
}; | |
ImGuiVertexShader = ShaderCross.Create( | |
graphicsDevice, | |
titleStorage, | |
GetHLSLPath("ImGui.vert"), | |
"main", | |
ShaderCross.ShaderFormat.HLSL, | |
ShaderStage.Vertex | |
); | |
ImGuiFragmentShader = ShaderCross.Create( | |
graphicsDevice, | |
titleStorage, | |
GetHLSLPath("ImGui.frag"), | |
"main", | |
ShaderCross.ShaderFormat.HLSL, | |
ShaderStage.Fragment | |
); | |
ImGuiSampler = Samplers.LinearClamp; | |
ImGuiPipeline = GraphicsPipeline.Create( | |
graphicsDevice, | |
new GraphicsPipelineCreateInfo | |
{ | |
TargetInfo = new GraphicsPipelineTargetInfo | |
{ | |
ColorTargetDescriptions = [ | |
new ColorTargetDescription | |
{ | |
Format = window.SwapchainFormat, | |
BlendState = ColorTargetBlendState.NonPremultipliedAlphaBlend | |
} | |
] | |
}, | |
DepthStencilState = DepthStencilState.Disable, | |
VertexShader = ImGuiVertexShader, | |
FragmentShader = ImGuiFragmentShader, | |
VertexInputState = VertexInputState.CreateSingleBinding<Position2DTextureColorVertex>(), | |
PrimitiveType = PrimitiveType.TriangleList, | |
RasterizerState = RasterizerState.CW_CullNone, | |
MultisampleState = MultisampleState.None | |
} | |
); | |
BufferUploader = new ResourceUploader(graphicsDevice); | |
BuildFontAtlas(); | |
} | |
private void InitMouseCursors(ImGuiIOPtr io) | |
{ | |
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors; | |
_mouseCursors.Add(ImGuiMouseCursor.Arrow, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_DEFAULT)); | |
_mouseCursors.Add(ImGuiMouseCursor.TextInput, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_TEXT)); | |
_mouseCursors.Add(ImGuiMouseCursor.ResizeAll, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_MOVE)); | |
_mouseCursors.Add(ImGuiMouseCursor.ResizeNs, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_NS_RESIZE)); | |
_mouseCursors.Add(ImGuiMouseCursor.ResizeEw, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_EW_RESIZE)); | |
_mouseCursors.Add(ImGuiMouseCursor.ResizeNesw, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_NESW_RESIZE)); | |
_mouseCursors.Add(ImGuiMouseCursor.ResizeNwse, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_NWSE_RESIZE)); | |
_mouseCursors.Add(ImGuiMouseCursor.Hand, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_POINTER)); | |
_mouseCursors.Add(ImGuiMouseCursor.NotAllowed, SDL_CreateSystemCursor(SDL_SystemCursor.SDL_SYSTEM_CURSOR_NOT_ALLOWED)); | |
} | |
unsafe void BuildFontAtlas() | |
{ | |
var textureUploader = new ResourceUploader(_graphicsDevice); | |
var io = ImGui.GetIO(); | |
byte* pixels; | |
int width; | |
int height; | |
int bytesPerPixel; | |
io.Fonts.GetTexDataAsRGBA32( | |
&pixels, | |
&width, | |
&height, | |
&bytesPerPixel | |
); | |
var pixelSpan = new ReadOnlySpan<Color>(pixels, width * height); | |
FontTexture = textureUploader.CreateTexture2D( | |
pixelSpan, | |
TextureFormat.R8G8B8A8Unorm, | |
TextureUsageFlags.Sampler, | |
(uint)width, | |
(uint)height); | |
textureUploader.Upload(); | |
textureUploader.Dispose(); | |
io.Fonts.SetTexID(new ImTextureID(FontTexture.Handle)); | |
io.Fonts.ClearTexData(); | |
TextureStorage.Add(FontTexture); | |
} | |
unsafe void InitViewportsSupport(ImGuiIOPtr io) | |
{ | |
var backendData = new BackendData | |
{ | |
WindowHandle = _window.Handle | |
}; | |
nint backendDataPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(backendData)); | |
Marshal.StructureToPtr(backendData, backendDataPtr, false); | |
io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; | |
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; | |
io.BackendFlags |= ImGuiBackendFlags.PlatformHasViewports; | |
io.BackendFlags |= ImGuiBackendFlags.RendererHasViewports; | |
io.BackendPlatformUserData = (void*)backendDataPtr; | |
var platformIO = ImGui.GetPlatformIO(); | |
platformIO.PlatformCreateWindow = (void*)Marshal.GetFunctionPointerForDelegate<PlatformCreateWindow>(CreateWindow); | |
platformIO.PlatformDestroyWindow = (void*)Marshal.GetFunctionPointerForDelegate<PlatformDestroyWindow>(DestroyWindow); | |
platformIO.PlatformShowWindow = (void*)Marshal.GetFunctionPointerForDelegate<PlatformShowWindow>(ShowWindow); | |
platformIO.PlatformSetWindowPos = (void*)Marshal.GetFunctionPointerForDelegate<PlatformSetWindowPos>(SetWindowPos); | |
platformIO.PlatformGetWindowPos = (void*)Marshal.GetFunctionPointerForDelegate<PlatformGetWindowPos>(GetWindowPos); | |
platformIO.PlatformSetWindowSize = (void*)Marshal.GetFunctionPointerForDelegate<PlatformSetWindowSize>(SetWindowSize); | |
platformIO.PlatformGetWindowSize = (void*)Marshal.GetFunctionPointerForDelegate<PlatformGetWindowSize>(GetWindowSize); | |
platformIO.PlatformSetWindowFocus = (void*)Marshal.GetFunctionPointerForDelegate<PlatformSetWindowFocus>(SetWindowFocus); | |
platformIO.PlatformGetWindowFocus = (void*)Marshal.GetFunctionPointerForDelegate<PlatformGetWindowFocus>(GetWindowFocus); | |
platformIO.PlatformGetWindowMinimized = (void*)Marshal.GetFunctionPointerForDelegate<PlatformGetWindowMinimized>(GetWindowMinimized); | |
platformIO.PlatformSetWindowTitle = (void*)Marshal.GetFunctionPointerForDelegate<PlatformSetWindowTitle>(SetWindowTitle); | |
platformIO.PlatformSetWindowAlpha = (void*)Marshal.GetFunctionPointerForDelegate<PlatformSetWindowAlpha>(SetWindowAlpha); | |
platformIO.PlatformSetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate<PlatformSetClipboardTextFn>(SetClipboardText); | |
platformIO.PlatformGetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate<PlatformGetClipboardTextFn>(GetClipboardText); | |
platformIO.PlatformClipboardUserData = null; | |
ImGuiViewport* mainViewport = ImGui.GetMainViewport().Handle; | |
var target = new ImGuiWindow(mainViewport, _window); | |
mainViewport->PlatformHandle = (void*)target.Window.Handle; | |
mainViewport->PlatformHandleRaw = (void*)target.Window.Handle; | |
_windows[target.Window.Handle] = target.Window; | |
_focusWindow = target.Window; | |
UpdateMonitors(); | |
} | |
private static void ShutdownPlatformInterface() | |
{ | |
ImGui.DestroyPlatformWindows(); | |
} | |
/// <summary> | |
/// SDL Data | |
/// </summary> | |
private unsafe struct BackendData | |
{ | |
public nint WindowHandle; | |
//public Renderer* Renderer; | |
public ulong Time; | |
public byte* ClipboardTextData; | |
public bool UseVulkan; | |
public bool WantUpdateMonitors; | |
public uint MouseWindowID; | |
public int MouseButtonsDown; | |
//public Cursor** MouseCursors; | |
//public Cursor* LastMouseCursor; | |
public int MouseLastLeaveFrame; | |
public bool MouseCanUseGlobalState; | |
public bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state. | |
//public UnsafeList<Pointer<GameController>> Gamepads; | |
//public GamepadMode GamepadMode; | |
public bool WantUpdateGamepadsList; | |
} | |
static unsafe BackendData* GetBackendData() | |
{ | |
return !ImGui.GetCurrentContext().IsNull ? (BackendData*)ImGui.GetIO().BackendPlatformUserData : null; | |
} | |
static unsafe ImGuiWindow GetViewportTarget(ImGuiViewport* viewport) | |
{ | |
return (ImGuiWindow)GCHandle.FromIntPtr((nint)viewport->PlatformUserData).Target; | |
} | |
private unsafe void CreateWindow(ImGuiViewport* viewport) | |
{ | |
BackendData* bd = GetBackendData(); | |
SDL_WindowFlags windowFlags = 0; | |
windowFlags |= SDL_WindowFlags.SDL_WINDOW_VULKAN; | |
windowFlags |= SDL_WindowFlags.SDL_WINDOW_HIDDEN; | |
windowFlags |= SDL_GetWindowFlags(bd->WindowHandle) & SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY; | |
windowFlags |= (viewport->Flags & ImGuiViewportFlags.NoDecoration) != 0 ? SDL_WindowFlags.SDL_WINDOW_BORDERLESS : 0; | |
windowFlags |= (viewport->Flags & ImGuiViewportFlags.NoDecoration) != 0 ? 0 : SDL_WindowFlags.SDL_WINDOW_RESIZABLE; | |
windowFlags |= (viewport->Flags & ImGuiViewportFlags.NoTaskBarIcon) != 0 ? SDL_WindowFlags.SDL_WINDOW_UTILITY : 0; | |
windowFlags |= (viewport->Flags & ImGuiViewportFlags.TopMost) != 0 ? SDL_WindowFlags.SDL_WINDOW_ALWAYS_ON_TOP : 0; | |
var target = new ImGuiWindow(_graphicsDevice, viewport, windowFlags); | |
target.Window.SetPosition((int)viewport->Pos.X, (int)viewport->Pos.Y); | |
viewport->PlatformHandle = (void*)target.Window.Handle; | |
viewport->PlatformHandleRaw = (void*)target.Window.Handle; | |
_windows[target.Window.Handle] = target.Window; | |
} | |
unsafe void DestroyWindow(ImGuiViewport* viewport) | |
{ | |
var target = GetViewportTarget(viewport); | |
if (target != null) | |
{ | |
target.Dispose(); | |
_windows.Remove(target.Window.Handle); | |
} | |
viewport->PlatformUserData = null; | |
viewport->PlatformHandle = null; | |
} | |
static unsafe void ShowWindow(ImGuiViewport* viewport) | |
{ | |
var target = GetViewportTarget(viewport); | |
SDL_ShowWindow(target.Window.Handle); | |
} | |
static unsafe Vector2* GetWindowPos(Vector2* size, ImGuiViewport* viewport) | |
{ | |
var target = GetViewportTarget(viewport); | |
SDL_GetWindowPosition(target.Window.Handle, out var x, out var y); | |
*size = new Vector2(x, y); | |
return size; | |
} | |
static unsafe void SetWindowPos(ImGuiViewport* viewport, Vector2 pos) | |
{ | |
var target = GetViewportTarget(viewport); | |
target.Window.SetPosition((int)pos.X, (int)pos.Y); | |
} | |
static unsafe Vector2* GetWindowSize(Vector2* size, ImGuiViewport* viewport) | |
{ | |
var target = GetViewportTarget(viewport); | |
SDL_GetWindowSize(target.Window.Handle, out var w, out var h); | |
*size = new Vector2(w, h); | |
return size; | |
} | |
static unsafe void SetWindowSize(ImGuiViewport* viewport, Vector2 size) | |
{ | |
var target = GetViewportTarget(viewport); | |
SDL_SetWindowSize(target.Window.Handle, (int)size.X, (int)size.Y); | |
} | |
static unsafe void SetWindowTitle(ImGuiViewport* viewport, byte* title) | |
{ | |
var windowTitle = CallerOwnedStringMarshaller.ConvertToManaged(title); | |
var target = GetViewportTarget(viewport); | |
SDL_SetWindowTitle(target.Window.Handle, windowTitle); | |
} | |
static unsafe void SetWindowAlpha(ImGuiViewport* viewport, float alpha) | |
{ | |
var target = GetViewportTarget(viewport); | |
SDL_SetWindowOpacity(target.Window.Handle, alpha); | |
} | |
static unsafe void SetWindowFocus(ImGuiViewport* viewport) | |
{ | |
var target = GetViewportTarget(viewport); | |
SDL_RaiseWindow(target.Window.Handle); | |
} | |
static unsafe byte GetWindowFocus(ImGuiViewport* viewport) | |
{ | |
var target = GetViewportTarget(viewport); | |
var focused = (SDL_GetWindowFlags(target.Window.Handle) & SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS) != 0; | |
return (byte)(focused ? 1 : 0); | |
} | |
static unsafe byte GetWindowMinimized(ImGuiViewport* viewport) | |
{ | |
var target = GetViewportTarget(viewport); | |
var minimized = (SDL_GetWindowFlags(target.Window.Handle) & SDL_WindowFlags.SDL_WINDOW_MINIMIZED) != 0; | |
return (byte)(minimized ? 1 : 0); | |
} | |
static unsafe byte* GetClipboardText(ImGuiContext* data) | |
{ | |
BackendData* bd = GetBackendData(); | |
if (bd->ClipboardTextData != null) | |
SDL_free((nint)bd->ClipboardTextData); | |
bd->ClipboardTextData = AnsiStringMarshaller.ConvertToUnmanaged(SDL_GetClipboardText()); | |
return bd->ClipboardTextData; | |
} | |
static unsafe void SetClipboardText(ImGuiContext* data, byte* text) | |
{ | |
var clipboardText = CallerOwnedStringMarshaller.ConvertToManaged(text); | |
SDL_SetClipboardText(clipboardText); | |
} | |
public void NewFrame() | |
{ | |
ImGui.NewFrame(); | |
} | |
public void EndFrame() | |
{ | |
ImGui.EndFrame(); | |
if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) | |
ImGui.UpdatePlatformWindows(); | |
} | |
public void Update(TimeSpan elapsedTime) | |
{ | |
if (IsVisible == false) | |
return; | |
var io = ImGui.GetIO(); | |
// Use global mouse coordinates when using multiple viewports | |
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) | |
{ | |
SDL_GetGlobalMouseState(out var x, out var y); | |
io.AddMousePosEvent(x, y); | |
// Text input for focused window | |
var focusWindowHandle = SDL_GetMouseFocus(); | |
if (focusWindowHandle != 0 && _lastFocusWindowHandle != focusWindowHandle) | |
{ | |
_lastFocusWindowHandle = focusWindowHandle; | |
_focusWindow = _windows[focusWindowHandle]; | |
} | |
} | |
else | |
{ | |
io.AddMousePosEvent(_inputs.Mouse.X, _inputs.Mouse.Y); | |
} | |
if (io.WantCaptureKeyboard) | |
_focusWindow.StartTextInput(); | |
else if (io.WantCaptureKeyboard == false) | |
_focusWindow.StopTextInput(); | |
io.AddMouseButtonEvent(0, _inputs.Mouse.LeftButton.IsDown); | |
io.AddMouseButtonEvent(1, _inputs.Mouse.RightButton.IsDown); | |
io.AddMouseButtonEvent(2, _inputs.Mouse.MiddleButton.IsDown); | |
io.AddMouseWheelEvent(0f, _inputs.Mouse.Wheel); | |
io.KeyShift = _inputs.Keyboard.IsDown(KeyCode.LeftShift) || _inputs.Keyboard.IsDown(KeyCode.RightShift); | |
io.KeyCtrl = _inputs.Keyboard.IsDown(KeyCode.LeftControl) || _inputs.Keyboard.IsDown(KeyCode.RightControl); | |
io.KeyAlt = _inputs.Keyboard.IsDown(KeyCode.LeftAlt) || _inputs.Keyboard.IsDown(KeyCode.RightAlt); | |
io.KeySuper = _inputs.Keyboard.IsDown(KeyCode.LeftMeta) || _inputs.Keyboard.IsDown(KeyCode.RightMeta); | |
io.AddKeyEvent(ImGuiKey.A, _inputs.Keyboard.IsDown(KeyCode.A)); | |
io.AddKeyEvent(ImGuiKey.Z, _inputs.Keyboard.IsDown(KeyCode.Z)); | |
io.AddKeyEvent(ImGuiKey.Y, _inputs.Keyboard.IsDown(KeyCode.Y)); | |
io.AddKeyEvent(ImGuiKey.X, _inputs.Keyboard.IsDown(KeyCode.X)); | |
io.AddKeyEvent(ImGuiKey.C, _inputs.Keyboard.IsDown(KeyCode.C)); | |
io.AddKeyEvent(ImGuiKey.V, _inputs.Keyboard.IsDown(KeyCode.V)); | |
io.AddKeyEvent(ImGuiKey.Tab, _inputs.Keyboard.IsDown(KeyCode.Tab)); | |
io.AddKeyEvent(ImGuiKey.LeftArrow, _inputs.Keyboard.IsDown(KeyCode.Left)); | |
io.AddKeyEvent(ImGuiKey.RightArrow, _inputs.Keyboard.IsDown(KeyCode.Right)); | |
io.AddKeyEvent(ImGuiKey.UpArrow, _inputs.Keyboard.IsDown(KeyCode.Up)); | |
io.AddKeyEvent(ImGuiKey.DownArrow, _inputs.Keyboard.IsDown(KeyCode.Down)); | |
io.AddKeyEvent(ImGuiKey.Enter, _inputs.Keyboard.IsDown(KeyCode.Return)); | |
io.AddKeyEvent(ImGuiKey.Escape, _inputs.Keyboard.IsDown(KeyCode.Escape)); | |
io.AddKeyEvent(ImGuiKey.Delete, _inputs.Keyboard.IsDown(KeyCode.Delete)); | |
io.AddKeyEvent(ImGuiKey.Backspace, _inputs.Keyboard.IsDown(KeyCode.Backspace)); | |
io.AddKeyEvent(ImGuiKey.Home, _inputs.Keyboard.IsDown(KeyCode.Home)); | |
io.AddKeyEvent(ImGuiKey.End, _inputs.Keyboard.IsDown(KeyCode.End)); | |
io.AddKeyEvent(ImGuiKey.PageDown, _inputs.Keyboard.IsDown(KeyCode.PageDown)); | |
io.AddKeyEvent(ImGuiKey.PageUp, _inputs.Keyboard.IsDown(KeyCode.PageUp)); | |
foreach (var key in _allKeys) | |
{ | |
if (TryMapKeyCode(key, out ImGuiKey imguikey)) | |
io.AddKeyEvent(imguikey, _inputs.Keyboard.IsDown(key)); | |
} | |
UpdateMouseCursor(); | |
UpdateMonitors(); | |
} | |
private static bool TryMapKeyCode(KeyCode key, out ImGuiKey imguikey) | |
{ | |
imguikey = key switch | |
{ | |
// Could add more stuff here if needed | |
KeyCode.Backspace => ImGuiKey.Backspace, | |
KeyCode.Tab => ImGuiKey.Tab, | |
KeyCode.Return => ImGuiKey.Enter, | |
KeyCode.CapsLock => ImGuiKey.CapsLock, | |
KeyCode.Escape => ImGuiKey.Escape, | |
KeyCode.Space => ImGuiKey.Space, | |
KeyCode.PageUp => ImGuiKey.PageUp, | |
KeyCode.PageDown => ImGuiKey.PageDown, | |
KeyCode.End => ImGuiKey.End, | |
KeyCode.Home => ImGuiKey.Home, | |
KeyCode.Left => ImGuiKey.LeftArrow, | |
KeyCode.Right => ImGuiKey.RightArrow, | |
KeyCode.Up => ImGuiKey.UpArrow, | |
KeyCode.Down => ImGuiKey.DownArrow, | |
KeyCode.PrintScreen => ImGuiKey.PrintScreen, | |
KeyCode.Insert => ImGuiKey.Insert, | |
KeyCode.Delete => ImGuiKey.Delete, | |
>= KeyCode.D1 and <= KeyCode.D0 => ImGuiKey.Keypad0 + (key - KeyCode.D1), | |
>= KeyCode.A and <= KeyCode.Z => ImGuiKey.A + (key - KeyCode.A), | |
>= KeyCode.Keypad1 and <= KeyCode.Keypad0 => ImGuiKey.Keypad1 + (key - KeyCode.Keypad1), | |
KeyCode.KeypadMultiply => ImGuiKey.KeypadMultiply, | |
KeyCode.KeypadPlus => ImGuiKey.KeypadAdd, | |
KeyCode.KeypadMinus => ImGuiKey.KeypadSubtract, | |
KeyCode.KeypadDivide => ImGuiKey.KeypadDivide, | |
KeyCode.KeypadEnter => ImGuiKey.KeypadEnter, | |
>= KeyCode.F1 and <= KeyCode.F12 => ImGuiKey.F1 + (key - KeyCode.F1), | |
//KeyCode.NumLockClear => ImGuiKey.NumLock, | |
KeyCode.ScrollLock => ImGuiKey.ScrollLock, | |
KeyCode.LeftShift => ImGuiKey.ModShift, | |
KeyCode.LeftControl => ImGuiKey.ModCtrl, | |
KeyCode.LeftAlt => ImGuiKey.ModAlt, | |
KeyCode.Semicolon => ImGuiKey.Semicolon, | |
KeyCode.Backslash => ImGuiKey.Backslash, | |
_ => ImGuiKey.None, | |
}; | |
return imguikey != ImGuiKey.None; | |
} | |
/// <summary> | |
/// FIXME: Note that doesn't update with DPI/Scaling change only as SDL2 doesn't have an event for it (SDL3 has). | |
/// </summary> | |
static unsafe void UpdateMonitors() | |
{ | |
var bd = GetBackendData(); | |
ImGuiPlatformIO* platform_io = ImGui.GetPlatformIO(); | |
ImVector<ImGuiPlatformMonitor>* monitors = &platform_io->Monitors; | |
monitors->Resize(0); | |
bd->WantUpdateMonitors = false; | |
var displays = SDL_GetDisplays(out var display_count); | |
for (int n = 0; n < display_count; n++) | |
{ | |
var displayId = (uint)n + 1; | |
// Warning: the validity of monitor DPI information on Windows depends on the application DPI awareness settings, which generally needs to be set in the manifest or at runtime. | |
ImGuiPlatformMonitor monitor = default; | |
SDL_GetDisplayBounds(displayId, out SDL_Rect r); | |
monitor.MainPos = new Vector2(r.x, r.y); | |
monitor.MainSize = new Vector2(r.w, r.h); | |
SDL_GetDisplayUsableBounds(displayId, out r); | |
monitor.WorkPos = new(r.x, r.y); | |
monitor.WorkSize = new(r.w, r.h); | |
monitor.DpiScale = SDL_GetDisplayContentScale(displayId); | |
monitors->PushBack(monitor); | |
} | |
} | |
void UpdateMouseCursor() | |
{ | |
var io = ImGui.GetIO(); | |
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0) | |
return; | |
var cursor = ImGui.GetMouseCursor(); | |
if (_lastCursor == cursor) | |
return; | |
if (io.MouseDrawCursor || cursor == ImGuiMouseCursor.None) | |
SDL_HideCursor(); | |
else | |
{ | |
SDL_ShowCursor(); | |
SDL_SetCursor(_mouseCursors[cursor]); | |
} | |
_lastCursor = cursor; | |
} | |
public unsafe void Draw() | |
{ | |
if (IsVisible == false) | |
return; | |
ImGui.Render(); | |
// Render the main viewport | |
var commandBuffer = _graphicsDevice.AcquireCommandBuffer(); | |
Texture swapchainTexture = commandBuffer.AcquireSwapchainTexture(_window); | |
if (swapchainTexture != null) | |
Render(commandBuffer, swapchainTexture, ImGui.GetDrawData()); | |
_graphicsDevice.Submit(commandBuffer); | |
// Render any extra viewports if the feature if enabled | |
if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0) | |
return; | |
var platformIO = ImGui.GetPlatformIO(); | |
for (var i = 1; i < platformIO.Viewports.Size; i++) | |
{ | |
var viewport = platformIO.Viewports[i]; | |
if ((viewport.Flags & ImGuiViewportFlags.IsMinimized) != 0) | |
continue; | |
var target = GetViewportTarget(viewport); | |
var windowCommandBuffer = _graphicsDevice.AcquireCommandBuffer(); | |
var windowTexture = windowCommandBuffer.AcquireSwapchainTexture(target.Window); | |
if (windowTexture != null) | |
Render(windowCommandBuffer, windowTexture, viewport.DrawData); | |
_graphicsDevice.Submit(windowCommandBuffer); | |
} | |
} | |
void Render(CommandBuffer commandBuffer, Texture texture, ImDrawDataPtr drawDataPtr) | |
{ | |
UploadBuffers(drawDataPtr); | |
RenderDrawData(drawDataPtr, commandBuffer, texture); | |
} | |
unsafe void UploadBuffers(ImDrawDataPtr drawDataPtr) | |
{ | |
if (drawDataPtr.TotalVtxCount == 0) | |
return; | |
if (drawDataPtr.TotalVtxCount > VertexCount) | |
{ | |
ImGuiVertexBuffer?.Dispose(); | |
VertexCount = (uint)(drawDataPtr.TotalVtxCount); | |
ImGuiVertexBuffer = Buffer.Create<Position2DTextureColorVertex>( | |
_graphicsDevice, | |
BufferUsageFlags.Vertex, | |
VertexCount | |
); | |
} | |
if (drawDataPtr.TotalIdxCount > IndexCount) | |
{ | |
ImGuiIndexBuffer?.Dispose(); | |
IndexCount = (uint)(drawDataPtr.TotalIdxCount); | |
ImGuiIndexBuffer = Buffer.Create<ushort>( | |
_graphicsDevice, | |
BufferUsageFlags.Index, | |
IndexCount | |
); | |
} | |
uint vertexOffset = 0; | |
uint indexOffset = 0; | |
for (var n = 0; n < drawDataPtr.CmdListsCount; n += 1) | |
{ | |
var cmdList = drawDataPtr.CmdLists[n]; | |
BufferUploader.SetBufferData( | |
ImGuiVertexBuffer, | |
vertexOffset, | |
new ReadOnlySpan<Position2DTextureColorVertex>(cmdList.VtxBuffer.Data, cmdList.VtxBuffer.Size), | |
n == 0 | |
); | |
BufferUploader.SetBufferData( | |
ImGuiIndexBuffer, | |
indexOffset, | |
new ReadOnlySpan<ushort>(cmdList.IdxBuffer.Data, cmdList.IdxBuffer.Size), | |
n == 0 | |
); | |
vertexOffset += (uint)cmdList.VtxBuffer.Size; | |
indexOffset += (uint)cmdList.IdxBuffer.Size; | |
} | |
BufferUploader.Upload(); | |
} | |
void RenderDrawData( | |
ImDrawDataPtr drawDataPtr, | |
CommandBuffer commandBuffer, | |
Texture renderTexture | |
) | |
{ | |
if (drawDataPtr.CmdListsCount == 0) | |
return; | |
var renderPass = commandBuffer.BeginRenderPass( | |
new ColorTargetInfo(renderTexture, LoadOp.Load) | |
); | |
renderPass.BindGraphicsPipeline(ImGuiPipeline); | |
renderPass.SetViewport(new Viewport | |
{ | |
X = 0, | |
Y = 0, | |
W = Math.Max(1, drawDataPtr.DisplaySize.X), | |
H = Math.Max(1, drawDataPtr.DisplaySize.Y), | |
MaxDepth = 1, | |
MinDepth = 0 | |
}); | |
commandBuffer.PushVertexUniformData( | |
new VertexUniforms( | |
Matrix4x4.CreateOrthographicOffCenter( | |
drawDataPtr.DisplayPos.X, | |
drawDataPtr.DisplayPos.X + drawDataPtr.DisplaySize.X, | |
drawDataPtr.DisplayPos.Y + drawDataPtr.DisplaySize.Y, | |
drawDataPtr.DisplayPos.Y, | |
-1, | |
1 | |
) | |
) | |
); | |
renderPass.BindVertexBuffers(ImGuiVertexBuffer); | |
renderPass.BindIndexBuffer(ImGuiIndexBuffer, IndexElementSize.Sixteen); | |
uint vertexOffset = 0; | |
uint indexOffset = 0; | |
// Will project scissor/clipping rectangles into framebuffer space | |
var clipOffset = drawDataPtr.DisplayPos; // (0,0) unless using multi-viewports | |
var clipScale = drawDataPtr.FramebufferScale; // (1,1) unless using retina display which are often (2,2) | |
for (int n = 0; n < drawDataPtr.CmdListsCount; n += 1) | |
{ | |
var cmdList = drawDataPtr.CmdLists[n]; | |
for (int cmdIndex = 0; cmdIndex < cmdList.CmdBuffer.Size; cmdIndex += 1) | |
{ | |
var drawCmd = cmdList.CmdBuffer[cmdIndex]; | |
renderPass.BindFragmentSamplers( | |
new TextureSamplerBinding(TextureStorage.GetTexture(drawCmd.TextureId), ImGuiSampler) | |
); | |
// Project scissor/clipping rectangles into framebuffer space | |
var clipMin = new Vector2( | |
(drawCmd.ClipRect.X - clipOffset.X) * clipScale.X, | |
(drawCmd.ClipRect.Y - clipOffset.Y) * clipScale.Y | |
); | |
var clipMax = new Vector2( | |
(drawCmd.ClipRect.Z - clipOffset.X) * clipScale.X, | |
(drawCmd.ClipRect.W - clipOffset.Y) * clipScale.Y | |
); | |
// Clamp to viewport as vkCmdSetScissor() won't accept values that are off bounds | |
clipMin.X = Math.Max(0, clipMin.X); | |
clipMin.Y = Math.Max(0, clipMin.Y); | |
clipMax.X = Math.Min(drawDataPtr.DisplaySize.X, clipMax.X); | |
clipMax.Y = Math.Min(drawDataPtr.DisplaySize.Y, clipMax.Y); | |
if (clipMax.X <= clipMin.X || clipMax.Y <= clipMin.Y) | |
continue; | |
renderPass.SetScissor( | |
new Rect | |
{ | |
X = (int)clipMin.X, | |
Y = (int)clipMin.Y, | |
W = (int)(clipMax.X - clipMin.X), | |
H = (int)(clipMax.Y - clipMin.Y) | |
} | |
); | |
renderPass.DrawIndexedPrimitives( | |
drawCmd.ElemCount, | |
1, | |
indexOffset, | |
(int)vertexOffset, | |
0 | |
); | |
indexOffset += drawCmd.ElemCount; | |
} | |
vertexOffset += (uint)cmdList.VtxBuffer.Size; | |
} | |
commandBuffer.EndRenderPass(renderPass); | |
} | |
public void RegisterTexture(Texture texture) | |
{ | |
TextureStorage.Add(texture); | |
} | |
[StructLayout(LayoutKind.Explicit, Size = 64)] | |
public record struct VertexUniforms( | |
[field:FieldOffset(0)] | |
Matrix4x4 Transform | |
); | |
public struct Position2DTextureColorVertex( | |
Vector2 position, | |
Vector2 texcoord, | |
Color color | |
) : IVertexType | |
{ | |
public Vector2 Position = position; | |
public Vector2 TexCoord = texcoord; | |
public Color Color = color; | |
public static VertexElementFormat[] Formats => | |
[ | |
VertexElementFormat.Float2, | |
VertexElementFormat.Float2, | |
VertexElementFormat.Ubyte4Norm | |
]; | |
public static uint[] Offsets => | |
[ | |
0, | |
8, | |
16 | |
]; | |
} | |
public class DebugTextureStorage | |
{ | |
readonly Dictionary<ImTextureID, WeakReference<Texture>> PointerToTexture = []; | |
public nint Add(Texture texture) | |
{ | |
var texId = new ImTextureID(texture.Handle); | |
_ = PointerToTexture.TryAdd(texId, new WeakReference<Texture>(texture)); | |
return texture.Handle; | |
} | |
public Texture GetTexture(ImTextureID texId) | |
{ | |
if (!PointerToTexture.TryGetValue(texId, out WeakReference<Texture> result)) | |
{ | |
return null; | |
} | |
if (!result.TryGetTarget(out var texture)) | |
{ | |
PointerToTexture.Remove(texId); | |
return null; | |
} | |
return texture; | |
} | |
} | |
public static string GetHLSLPath(string shaderName) => $"Content/Shaders/HLSL/{shaderName}.hlsl"; | |
public bool IsVisible { get; set; } = false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment