Skip to content

Instantly share code, notes, and snippets.

@PumpkinPaul
Created July 17, 2025 14:17
Show Gist options
  • Save PumpkinPaul/b2e655ecb5719802736f42e71d13bf39 to your computer and use it in GitHub Desktop.
Save PumpkinPaul/b2e655ecb5719802736f42e71d13bf39 to your computer and use it in GitHub Desktop.
// 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