switch to SDL3+BepisLoader

This commit is contained in:
art0007i
2025-10-01 22:21:23 +02:00
parent cdfcc46019
commit 0d3fb60b4c
21 changed files with 1439 additions and 289 deletions
+427
View File
@@ -0,0 +1,427 @@
using ImGuiNET;
using SDL3;
namespace ResoniteImGuiLib.SDL3;
/// <summary>
/// Implementation of SDL3 platform backend for ImGui.
/// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdl3.h
/// </summary>
public class ImGuiSDL3 : IDisposable
{
public readonly IntPtr Window;
public readonly IntPtr Renderer;
public readonly uint WindowId;
private uint _mouseWindowId;
private int _mousePendingLeaveFrame;
private IntPtr[] _mouseCursors = new IntPtr[(int)ImGuiMouseCursor.COUNT];
private IntPtr _mouseLastCursor = -1;
private int _mouseButtonsDown;
// I don't think we actually need these since it works just fine without them
//
// [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
// public delegate IntPtr Platform_GetClipboardTextFn(IntPtr ctx);
//
// [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
// public delegate void Platform_SetClipboardTextFn(IntPtr ctx, IntPtr text);
//
// private static Platform_GetClipboardTextFn getClipboardDelegate = GetClipboardText;
// private static Platform_SetClipboardTextFn setClipboardDelegate = SetClipboardText;
//
// private static IntPtr getClipboardPtr;
// private static IntPtr setClipboardPtr;
//
// private static IntPtr clipboardTextPtr = IntPtr.Zero;
public ImGuiSDL3(IntPtr window, IntPtr renderer)
{
ImGuiIOPtr io = ImGui.GetIO();
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
Window = window;
WindowId = SDL.GetWindowID(Window);
Renderer = renderer;
// I don't think we actually need these since it works just fine without them
//
// ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO();
// platformIo.Platform_SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(setClipboardDelegate);
// platformIo.Platform_GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(getClipboardDelegate);
// getClipboardPtr = platformIo.Platform_GetClipboardTextFn;
// setClipboardPtr = platformIo.Platform_SetClipboardTextFn;
_mouseCursors[(int)ImGuiMouseCursor.Arrow] = SDL.CreateSystemCursor(SDL.SystemCursor.Default);
_mouseCursors[(int)ImGuiMouseCursor.TextInput] = SDL.CreateSystemCursor(SDL.SystemCursor.Text);
_mouseCursors[(int)ImGuiMouseCursor.ResizeAll] = SDL.CreateSystemCursor(SDL.SystemCursor.Move);
_mouseCursors[(int)ImGuiMouseCursor.ResizeNS] = SDL.CreateSystemCursor(SDL.SystemCursor.NSResize);
_mouseCursors[(int)ImGuiMouseCursor.ResizeEW] = SDL.CreateSystemCursor(SDL.SystemCursor.EWResize);
_mouseCursors[(int)ImGuiMouseCursor.ResizeNESW] = SDL.CreateSystemCursor(SDL.SystemCursor.NESWResize);
_mouseCursors[(int)ImGuiMouseCursor.ResizeNWSE] = SDL.CreateSystemCursor(SDL.SystemCursor.NWSEResize);
_mouseCursors[(int)ImGuiMouseCursor.Hand] = SDL.CreateSystemCursor(SDL.SystemCursor.Pointer);
_mouseCursors[(int)ImGuiMouseCursor.NotAllowed] = SDL.CreateSystemCursor(SDL.SystemCursor.NotAllowed);
ImGuiViewportPtr viewport = ImGui.GetMainViewport();
SetupPlatformHandles(viewport, window);
}
public void Dispose()
{
foreach (IntPtr cursor in _mouseCursors)
SDL.DestroyCursor(cursor);
}
public void NewFrame()
{
ImGuiIOPtr io = ImGui.GetIO();
SDL.GetWindowSize(Window, out int w, out int h);
if (SDL.GetWindowFlags(Window).HasFlag(SDL.WindowFlags.Minimized))
{
w = h = 0;
}
SDL.GetWindowSizeInPixels(Window, out int displayW, out int displayH);
io.DisplaySize = new System.Numerics.Vector2(w, h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = new System.Numerics.Vector2((float)displayW / w, (float)displayH / h);
if (_mousePendingLeaveFrame > 0 && _mousePendingLeaveFrame >= ImGui.GetFrameCount())
{
_mouseWindowId = 0;
_mousePendingLeaveFrame = 0;
io.AddMousePosEvent(-float.MaxValue, -float.MaxValue);
}
UpdateMouseData();
UpdateMouseCursor();
}
public unsafe bool ProcessEvent(SDL.Event e)
{
ImGuiIOPtr io = ImGui.GetIO();
switch ((SDL.EventType)e.Type)
{
case SDL.EventType.MouseMotion:
if (GetViewportForWindowId(e.Motion.WindowID) == null)
return false;
io.AddMouseSourceEvent(e.Motion.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse);
io.AddMousePosEvent(e.Motion.X, e.Motion.Y);
return true;
case SDL.EventType.MouseWheel:
if (GetViewportForWindowId(e.Wheel.WindowID) == null)
return false;
float wheelX = -e.Wheel.X;
float wheelY = e.Wheel.Y;
io.AddMouseSourceEvent(e.Wheel.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse);
io.AddMouseWheelEvent(wheelX, wheelY);
return true;
case SDL.EventType.MouseButtonDown:
case SDL.EventType.MouseButtonUp:
if (GetViewportForWindowId(e.Button.WindowID) == null)
return false;
int mouseButton = e.Button.Button switch
{
SDL.ButtonLeft => 0,
SDL.ButtonRight => 1,
SDL.ButtonMiddle => 2,
SDL.ButtonX1 => 3,
SDL.ButtonX2 => 4,
_ => -1
};
if (mouseButton == -1) break;
io.AddMouseSourceEvent(e.Button.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse);
io.AddMouseButtonEvent(mouseButton, (SDL.EventType)e.Type == SDL.EventType.MouseButtonDown);
_mouseButtonsDown = (SDL.EventType)e.Type == SDL.EventType.MouseButtonDown ? _mouseButtonsDown | 1 << mouseButton : _mouseButtonsDown & ~(1 << mouseButton);
return true;
case SDL.EventType.TextInput:
if (GetViewportForWindowId(e.Text.WindowID) == null)
return false;
ImGuiNative.ImGuiIO_AddInputCharactersUTF8(io, (byte*)e.Text.Text);
return true;
case SDL.EventType.KeyDown:
case SDL.EventType.KeyUp:
if (GetViewportForWindowId(e.Key.WindowID) == null)
return false;
UpdateKeyModifiers(e.Key.Mod);
ImGuiKey imguiKey = KeyEventToImGui(e.Key.Key, e.Key.Scancode);
bool pressed = (SDL.EventType)e.Type == SDL.EventType.KeyDown;
io.AddKeyEvent(imguiKey, pressed);
io.SetKeyEventNativeData(imguiKey, (int)e.Key.Key, (int)e.Key.Scancode, (int)e.Key.Scancode);
return true;
case SDL.EventType.WindowMouseEnter:
if (GetViewportForWindowId(e.Window.WindowID) == null)
return false;
_mouseWindowId = e.Window.WindowID;
_mousePendingLeaveFrame = 0;
return true;
case SDL.EventType.WindowMouseLeave:
if (GetViewportForWindowId(e.Window.WindowID) == null)
return false;
_mousePendingLeaveFrame = ImGui.GetFrameCount() + 1;
return true;
case SDL.EventType.WindowFocusGained:
case SDL.EventType.WindowFocusLost:
if (GetViewportForWindowId(e.Window.WindowID) == null)
return false;
io.AddFocusEvent((SDL.EventType)e.Type == SDL.EventType.WindowFocusGained);
return true;
default:
break;
}
return false;
}
private void UpdateMouseData()
{
ImGuiIOPtr io = ImGui.GetIO();
IntPtr focusedWindow = SDL.GetKeyboardFocus();
bool isAppFocused = focusedWindow == Window;
if (isAppFocused)
{
if (io.WantSetMousePos)
{
SDL.WarpMouseInWindow(Window, (int)io.MousePos.X, (int)io.MousePos.Y);
}
}
}
private void UpdateMouseCursor()
{
ImGuiIOPtr io = ImGui.GetIO();
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0)
return;
ImGuiMouseCursor imguiCursor = ImGui.GetMouseCursor();
if (io.MouseDrawCursor || imguiCursor == ImGuiMouseCursor.None)
{
SDL.HideCursor();
}
else
{
IntPtr expectedCursor = _mouseCursors[(int)imguiCursor];
if (_mouseLastCursor != expectedCursor)
{
SDL.SetCursor(expectedCursor);
_mouseLastCursor = expectedCursor;
}
SDL.ShowCursor();
}
}
private ImGuiKey KeyEventToImGui(SDL.Keycode keycoade, SDL.Scancode scancode)
{
switch (scancode)
{
case SDL.Scancode.Kp0: return ImGuiKey.Keypad0;
case SDL.Scancode.Kp1: return ImGuiKey.Keypad1;
case SDL.Scancode.Kp2: return ImGuiKey.Keypad2;
case SDL.Scancode.Kp3: return ImGuiKey.Keypad3;
case SDL.Scancode.Kp4: return ImGuiKey.Keypad4;
case SDL.Scancode.Kp5: return ImGuiKey.Keypad5;
case SDL.Scancode.Kp6: return ImGuiKey.Keypad6;
case SDL.Scancode.Kp7: return ImGuiKey.Keypad7;
case SDL.Scancode.Kp8: return ImGuiKey.Keypad8;
case SDL.Scancode.Kp9: return ImGuiKey.Keypad9;
case SDL.Scancode.KpPeriod: return ImGuiKey.KeypadDecimal;
case SDL.Scancode.KpDivide: return ImGuiKey.KeypadDivide;
case SDL.Scancode.KpMultiply: return ImGuiKey.KeypadMultiply;
case SDL.Scancode.KpMinus: return ImGuiKey.KeypadSubtract;
case SDL.Scancode.KpPlus: return ImGuiKey.KeypadAdd;
case SDL.Scancode.KpEnter: return ImGuiKey.KeypadEnter;
case SDL.Scancode.KpEquals: return ImGuiKey.KeypadEqual;
default: break;
}
switch (keycoade)
{
case SDL.Keycode.Tab: return ImGuiKey.Tab;
case SDL.Keycode.Left: return ImGuiKey.LeftArrow;
case SDL.Keycode.Right: return ImGuiKey.RightArrow;
case SDL.Keycode.Up: return ImGuiKey.UpArrow;
case SDL.Keycode.Down: return ImGuiKey.DownArrow;
case SDL.Keycode.Pageup: return ImGuiKey.PageUp;
case SDL.Keycode.Pagedown: return ImGuiKey.PageDown;
case SDL.Keycode.Home: return ImGuiKey.Home;
case SDL.Keycode.End: return ImGuiKey.End;
case SDL.Keycode.Insert: return ImGuiKey.Insert;
case SDL.Keycode.Delete: return ImGuiKey.Delete;
case SDL.Keycode.Backspace: return ImGuiKey.Backspace;
case SDL.Keycode.Space: return ImGuiKey.Space;
case SDL.Keycode.Return: return ImGuiKey.Enter;
case SDL.Keycode.Escape: return ImGuiKey.Escape;
case SDL.Keycode.Apostrophe: return ImGuiKey.Apostrophe;
case SDL.Keycode.Comma: return ImGuiKey.Comma;
case SDL.Keycode.Minus: return ImGuiKey.Minus;
case SDL.Keycode.Period: return ImGuiKey.Period;
case SDL.Keycode.Slash: return ImGuiKey.Slash;
case SDL.Keycode.Semicolon: return ImGuiKey.Semicolon;
case SDL.Keycode.Equals: return ImGuiKey.Equal;
case SDL.Keycode.LeftBracket: return ImGuiKey.LeftBracket;
case SDL.Keycode.Backslash: return ImGuiKey.Backslash;
case SDL.Keycode.RightBracket: return ImGuiKey.RightBracket;
case SDL.Keycode.Grave: return ImGuiKey.GraveAccent;
case SDL.Keycode.Capslock: return ImGuiKey.CapsLock;
case SDL.Keycode.ScrollLock: return ImGuiKey.ScrollLock;
case SDL.Keycode.NumLockClear: return ImGuiKey.NumLock;
case SDL.Keycode.PrintScreen: return ImGuiKey.PrintScreen;
case SDL.Keycode.Pause: return ImGuiKey.Pause;
case SDL.Keycode.LCtrl: return ImGuiKey.LeftCtrl;
case SDL.Keycode.LShift: return ImGuiKey.LeftShift;
case SDL.Keycode.LAlt: return ImGuiKey.LeftAlt;
case SDL.Keycode.LGui: return ImGuiKey.LeftSuper;
case SDL.Keycode.RCtrl: return ImGuiKey.RightCtrl;
case SDL.Keycode.RShift: return ImGuiKey.RightShift;
case SDL.Keycode.RAlt: return ImGuiKey.RightAlt;
case SDL.Keycode.RGUI: return ImGuiKey.RightSuper;
case SDL.Keycode.Application: return ImGuiKey.Menu;
case SDL.Keycode.Alpha0: return ImGuiKey._0;
case SDL.Keycode.Alpha1: return ImGuiKey._1;
case SDL.Keycode.Alpha2: return ImGuiKey._2;
case SDL.Keycode.Alpha3: return ImGuiKey._3;
case SDL.Keycode.Alpha4: return ImGuiKey._4;
case SDL.Keycode.Alpha5: return ImGuiKey._5;
case SDL.Keycode.Alpha6: return ImGuiKey._6;
case SDL.Keycode.Alpha7: return ImGuiKey._7;
case SDL.Keycode.Alpha8: return ImGuiKey._8;
case SDL.Keycode.Alpha9: return ImGuiKey._9;
case SDL.Keycode.A: return ImGuiKey.A;
case SDL.Keycode.B: return ImGuiKey.B;
case SDL.Keycode.C: return ImGuiKey.C;
case SDL.Keycode.D: return ImGuiKey.D;
case SDL.Keycode.E: return ImGuiKey.E;
case SDL.Keycode.F: return ImGuiKey.F;
case SDL.Keycode.G: return ImGuiKey.G;
case SDL.Keycode.H: return ImGuiKey.H;
case SDL.Keycode.I: return ImGuiKey.I;
case SDL.Keycode.J: return ImGuiKey.J;
case SDL.Keycode.K: return ImGuiKey.K;
case SDL.Keycode.L: return ImGuiKey.L;
case SDL.Keycode.M: return ImGuiKey.M;
case SDL.Keycode.N: return ImGuiKey.N;
case SDL.Keycode.O: return ImGuiKey.O;
case SDL.Keycode.P: return ImGuiKey.P;
case SDL.Keycode.Q: return ImGuiKey.Q;
case SDL.Keycode.R: return ImGuiKey.R;
case SDL.Keycode.S: return ImGuiKey.S;
case SDL.Keycode.T: return ImGuiKey.T;
case SDL.Keycode.U: return ImGuiKey.U;
case SDL.Keycode.V: return ImGuiKey.V;
case SDL.Keycode.W: return ImGuiKey.W;
case SDL.Keycode.X: return ImGuiKey.X;
case SDL.Keycode.Y: return ImGuiKey.Y;
case SDL.Keycode.Z: return ImGuiKey.Z;
case SDL.Keycode.F1: return ImGuiKey.F1;
case SDL.Keycode.F2: return ImGuiKey.F2;
case SDL.Keycode.F3: return ImGuiKey.F3;
case SDL.Keycode.F4: return ImGuiKey.F4;
case SDL.Keycode.F5: return ImGuiKey.F5;
case SDL.Keycode.F6: return ImGuiKey.F6;
case SDL.Keycode.F7: return ImGuiKey.F7;
case SDL.Keycode.F8: return ImGuiKey.F8;
case SDL.Keycode.F9: return ImGuiKey.F9;
case SDL.Keycode.F10: return ImGuiKey.F10;
case SDL.Keycode.F11: return ImGuiKey.F11;
case SDL.Keycode.F12: return ImGuiKey.F12;
case SDL.Keycode.F13: return ImGuiKey.F13;
case SDL.Keycode.F14: return ImGuiKey.F14;
case SDL.Keycode.F15: return ImGuiKey.F15;
case SDL.Keycode.F16: return ImGuiKey.F16;
case SDL.Keycode.F17: return ImGuiKey.F17;
case SDL.Keycode.F18: return ImGuiKey.F18;
case SDL.Keycode.F19: return ImGuiKey.F19;
case SDL.Keycode.F20: return ImGuiKey.F20;
case SDL.Keycode.F21: return ImGuiKey.F21;
case SDL.Keycode.F22: return ImGuiKey.F22;
case SDL.Keycode.F23: return ImGuiKey.F23;
case SDL.Keycode.F24: return ImGuiKey.F24;
case SDL.Keycode.AcBack: return ImGuiKey.AppBack;
case SDL.Keycode.AcForward: return ImGuiKey.AppForward;
default: break;
}
return ImGuiKey.None;
}
public static IntPtr Data() => ImGui.GetIO().BackendPlatformUserData;
// I don't think we actually need these since it works just fine without them
//
// public static IntPtr GetClipboardText(IntPtr ctx)
// {
// if (clipboardTextPtr != IntPtr.Zero)
// {
// Marshal.FreeHGlobal(clipboardTextPtr);
// clipboardTextPtr = IntPtr.Zero;
// }
//
// string text = SDL.GetClipboardText();
// clipboardTextPtr = Marshal.StringToHGlobalAnsi(text ?? string.Empty);
// return clipboardTextPtr;
// }
//
// public static void SetClipboardText(IntPtr ctx, IntPtr text)
// {
// string managedText = Marshal.PtrToStringAnsi(text);
// if (managedText != null)
// {
// SDL.SetClipboardText(managedText);
// }
// }
// !! This is sorta fucked
// public static void UpdateKeyModifiers(SDL.Keymod keymods)
// {
// ImGuiIOPtr io = ImGui.GetIO();
// io.KeyCtrl = keymods.HasFlag(SDL.Keymod.LCtrl) || keymods.HasFlag(SDL.Keymod.RCtrl);
// io.KeyShift = keymods.HasFlag(SDL.Keymod.LShift) || keymods.HasFlag(SDL.Keymod.RShift);
// io.KeyAlt = keymods.HasFlag(SDL.Keymod.LAlt) || keymods.HasFlag(SDL.Keymod.RAlt);
// io.KeySuper = keymods.HasFlag(SDL.Keymod.LGUI) || keymods.HasFlag(SDL.Keymod.RGUI);
// }
private static void UpdateKeyModifiers(SDL.Keymod mod)
{
ImGuiIOPtr io = ImGui.GetIO();
io.AddKeyEvent(ImGuiKey.ModCtrl, (mod & SDL.Keymod.Ctrl) != 0);
io.AddKeyEvent(ImGuiKey.ModShift, (mod & SDL.Keymod.Shift) != 0);
io.AddKeyEvent(ImGuiKey.ModAlt, (mod & SDL.Keymod.Alt) != 0);
io.AddKeyEvent(ImGuiKey.ModSuper, (mod & SDL.Keymod.GUI) != 0);
}
public static ImGuiViewportPtr? GetViewportForWindowId(uint id)
{
ImGuiViewportPtr viewport = ImGui.GetMainViewport();
return viewport.ID == id ? ImGui.GetMainViewport() : null;
}
private static void SetupPlatformHandles(ImGuiViewportPtr viewport, IntPtr window)
{
viewport.PlatformHandle = window;
viewport.PlatformHandleRaw = 0;
#if _WIN32 && !__WINTR__
SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowWin32HWNDPointer, 0);
#elif __APPLE__ && SDL_VIDEO_DRIVER_COCOA
SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowCocoaWindow, 0);
#endif
}
}
+302
View File
@@ -0,0 +1,302 @@
using System.Numerics;
using ImGuiNET;
using SDL3;
namespace ResoniteImGuiLib.SDL3;
/// <summary>
/// Implementation of ImGui for SDL3 Renderer backend.
/// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdlrenderer3.h
/// </summary>
public class ImGuiSDL3Renderer : IDisposable
{
struct BackupSDLRendererState
{
public SDL.Rect Viewport;
public bool ViewportEnabled;
public bool ClipEnabled;
public SDL.Rect ClipRect;
}
public SDL.Rect DefaultClipRect = new SDL.Rect();
public readonly IntPtr Renderer;
private IntPtr _fontTexture = IntPtr.Zero;
public ImGuiSDL3Renderer(IntPtr renderer)
{
Renderer = renderer;
ImGuiIOPtr io = ImGui.GetIO();
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
}
public void Dispose()
{
ImGuiIOPtr io = ImGui.GetIO();
if (_fontTexture != IntPtr.Zero)
{
io.Fonts.SetTexID(IntPtr.Zero);
SDL.DestroyTexture(_fontTexture);
_fontTexture = IntPtr.Zero;
}
}
public void NewFrame()
{
if (_fontTexture == IntPtr.Zero)
{
CreateDeviceObjects();
}
}
public void RenderDrawData(ImDrawDataPtr drawData)
{
// Skip if no data to render
unsafe
{
if (drawData.NativePtr == null || drawData.CmdListsCount == 0)
return;
}
// Get display size & framebuffer scale
Vector2 renderScale = new Vector2(drawData.FramebufferScale.X, drawData.FramebufferScale.Y);
int fbWidth = (int)(drawData.DisplaySize.X * renderScale.X);
int fbHeight = (int)(drawData.DisplaySize.Y * renderScale.Y);
if (fbWidth <= 0 || fbHeight <= 0)
return;
// Backup SDL renderer state
BackupSDLRendererState oldState = BackupRendererState();
// Set up render state
SetupRenderState();
// Set render state in platform IO
ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO();
platformIo.Renderer_RenderState = Renderer;
Vector2 clipOffset = drawData.DisplayPos;
// Render command lists
for (int n = 0; n < drawData.CmdListsCount; n++)
{
ImDrawListPtr cmdList = drawData.CmdLists[n];
for (int cmdIndex = 0; cmdIndex < cmdList.CmdBuffer.Size; cmdIndex++)
{
ImDrawCmdPtr cmd = cmdList.CmdBuffer[cmdIndex];
if (cmd.UserCallback != IntPtr.Zero)
{
// User callback not implemented
continue;
}
// Apply clipping rectangle
Vector4 clipRect = cmd.ClipRect;
SDL.Rect r = CalculateClipRect(clipRect, clipOffset, renderScale, fbWidth, fbHeight);
SDL.SetRenderClipRect(Renderer, r);
// Get texture
IntPtr texId = cmd.GetTexID();
// Convert ImGui vertices to SDL vertices
if (!RenderDrawCommand(cmdList, cmd, texId, renderScale))
{
Console.WriteLine($"Failed to render ImGui draw command: {SDL.GetError()}");
}
}
}
// Reset render state
platformIo.Renderer_RenderState = IntPtr.Zero;
// Restore renderer state
RestoreRendererState(oldState);
}
private bool RenderDrawCommand(ImDrawListPtr drawList, ImDrawCmdPtr cmd, IntPtr texId, Vector2 scale)
{
// Get indices and vertices for this command
int indexOffset = (int)cmd.IdxOffset;
int vertexOffset = (int)cmd.VtxOffset;
int elemCount = (int)cmd.ElemCount;
// Create SDL vertices just for the vertices used by this command
// Determine the vertex range by looking at the indices
int minVertexIdx = int.MaxValue;
int maxVertexIdx = 0;
for (int i = 0; i < elemCount; i++)
{
ushort idx = drawList.IdxBuffer[indexOffset + i];
minVertexIdx = Math.Min(minVertexIdx, idx);
maxVertexIdx = Math.Max(maxVertexIdx, idx);
}
// Adjust for the vertex offset
minVertexIdx += vertexOffset;
maxVertexIdx += vertexOffset;
// Calculate the number of vertices we need
int numVertices = maxVertexIdx - minVertexIdx + 1;
// Create arrays for the vertices and indices we're going to use
SDL.Vertex[] vertices = new SDL.Vertex[numVertices];
int[] indices = new int[elemCount];
// Convert only the vertices we need
for (int i = 0; i < numVertices; i++)
{
int vertIdx = minVertexIdx + i;
ImDrawVertPtr srcVert = drawList.VtxBuffer[vertIdx];
// Convert from ImGui ABGR color to SDL RGBA color
uint col = srcVert.col;
byte r = (byte)(col >> 0 & 0xFF); // Extract R from the least significant byte
byte g = (byte)(col >> 8 & 0xFF); // Extract G
byte b = (byte)(col >> 16 & 0xFF); // Extract B
byte a = (byte)(col >> 24 & 0xFF); // Extract A from the most significant byte
vertices[i] = new SDL.Vertex
{
Position = new SDL.FPoint { X = srcVert.pos.X, Y = srcVert.pos.Y },
Color = new SDL.FColor { R = r / 255f, G = g / 255f, B = b / 255f, A = a / 255f },
TexCoord = new SDL.FPoint { X = srcVert.uv.X, Y = srcVert.uv.Y }
};
}
// Adjust indices to be relative to our new vertex array
for (int i = 0; i < elemCount; i++)
{
ushort originalIdx = drawList.IdxBuffer[indexOffset + i];
indices[i] = (ushort)(originalIdx - (minVertexIdx - vertexOffset));
}
// Call your SDL.RenderGeometry wrapper with the managed arrays
return SDL.RenderGeometry(
Renderer,
texId,
vertices,
numVertices,
indices,
elemCount
);
}
private void SetupRenderState()
{
SDL.SetRenderViewport(Renderer, 0);
SDL.SetRenderClipRect(Renderer, IntPtr.Zero);
SDL.SetRenderDrawBlendMode(Renderer, SDL.BlendMode.Blend);
}
private BackupSDLRendererState BackupRendererState()
{
BackupSDLRendererState state = new BackupSDLRendererState
{
ViewportEnabled = SDL.RenderViewportSet(Renderer),
ClipEnabled = SDL.RenderClipEnabled(Renderer)
};
SDL.GetRenderViewport(Renderer, out state.Viewport);
SDL.GetRenderClipRect(Renderer, out state.ClipRect);
return state;
}
private void RestoreRendererState(BackupSDLRendererState state)
{
if (state.ViewportEnabled)
SDL.SetRenderViewport(Renderer, state.Viewport);
else
SDL.SetRenderViewport(Renderer, IntPtr.Zero);
if (state.ClipEnabled)
SDL.SetRenderClipRect(Renderer, state.ClipRect);
else
SDL.SetRenderClipRect(Renderer, IntPtr.Zero);
}
private SDL.Rect CalculateClipRect(Vector4 clipRect, Vector2 clipOffset, Vector2 scale, int fbWidth, int fbHeight)
{
Vector2 clipMin = new Vector2((clipRect.X - clipOffset.X) * scale.X, (clipRect.Y - clipOffset.Y) * scale.Y);
Vector2 clipMax = new Vector2((clipRect.Z - clipOffset.X) * scale.X, (clipRect.W - clipOffset.Y) * scale.Y);
// Clamp to framebuffer bounds
clipMin.X = Math.Max(0, clipMin.X);
clipMin.Y = Math.Max(0, clipMin.Y);
clipMax.X = Math.Min(fbWidth, clipMax.X);
clipMax.Y = Math.Min(fbHeight, clipMax.Y);
SDL.Rect r = new SDL.Rect
{
X = (int)clipMin.X,
Y = (int)clipMin.Y,
W = (int)(clipMax.X - clipMin.X),
H = (int)(clipMax.Y - clipMin.Y)
};
return r;
}
private unsafe bool CreateDeviceObjects()
{
ImGuiIOPtr io = ImGui.GetIO();
io.Fonts.AddFontDefault();
// TODO: Load custom fonts from "Fonts" directory
//string fontsPath = Path.Combine(Plugin.AssemblyDirectory, "Fonts");
//if (Path.Exists(fontsPath))
//{
// string[] fonts = Directory.GetFiles(fontsPath, "*.ttf");
// if (fonts.Length > 0)
// {
// foreach (string font in fonts)
// {
// io.Fonts.AddFontFromFileTTF(font, 20f);
// }
// }
//}
// Build texture atlas
io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out int width, out int height);
// Create surface from pixel data
IntPtr surface = SDL.CreateSurfaceFrom(width, height, SDL.PixelFormat.RGBA8888, (IntPtr)pixels, width * 4);
if (surface == IntPtr.Zero)
{
SDL.LogError(SDL.LogCategory.Application, $"Failed to create font surface: {SDL.GetError()}");
return false;
}
// Create texture
_fontTexture = SDL.CreateTextureFromSurface(Renderer, surface);
if (_fontTexture == IntPtr.Zero)
{
SDL.LogError(SDL.LogCategory.Application, $"Failed to create font texture: {SDL.GetError()}");
return false;
}
// Update texture directly without converting pixel format
if (!SDL.UpdateTexture(_fontTexture, IntPtr.Zero, (IntPtr)pixels, width * 4))
{
SDL.LogError(SDL.LogCategory.Application, $"Failed to update font texture: {SDL.GetError()}");
return false;
}
// Ensure proper blending for font rendering
SDL.SetTextureBlendMode(_fontTexture, SDL.BlendMode.Blend);
// Use nearest neighbor filtering for crisp font rendering at small sizes
SDL.SetTextureScaleMode(_fontTexture, SDL.ScaleMode.Linear);
// Store our identifier
io.Fonts.SetTexID(_fontTexture);
SDL.DestroySurface(surface);
io.Fonts.ClearTexData();
return true;
}
}
+220
View File
@@ -0,0 +1,220 @@
using CodeGenerationConfig;
using Elements.Core;
using ImGuiNET;
using SDL3;
using System.Diagnostics;
namespace ResoniteImGuiLib.SDL3;
public class SDL3Window : IDisposable
{
public readonly IntPtr Window;
public readonly IntPtr Device;
public readonly ImGuiSDL3 Platform;
public readonly ImGuiSDL3Renderer Renderer;
internal readonly Queue<SDL.Event> EventQueue = new();
public event Action<int4>? WindowRectModified;
public color? ClearColor = null;
public Action? RenderCallback { get; set; }
private readonly Stopwatch _timer = Stopwatch.StartNew();
private TimeSpan _time = TimeSpan.Zero;
private SDL.Rect _screenClipRect;
private IntPtr _imGuiContext;
public SDL3Window(string name, int posX, int posY, int width, int height)
{
if (!SDL.Init(SDL.InitFlags.Video))
throw new Exception($"SDL_Init failed: {SDL.GetError()}");
// Create window & renderer
const SDL.WindowFlags windowFlags = SDL.WindowFlags.Resizable;
if (!SDL.CreateWindowAndRenderer(name, width, height, windowFlags, out Window, out Device))
throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}");
SDL.SetWindowTitle(Window, name);
SDL.SetWindowSize(Window, width, height);
SDL.SetWindowPosition(Window, posX, posY);
// Enable VSync
SDL.SetRenderVSync(Device, 1);
// Setup screen clip rect
SetupScreenClipRect();
// Create ImGui context
_imGuiContext = ImGui.CreateContext();
ImGui.SetCurrentContext(_imGuiContext);
ImGuiIOPtr io = ImGui.GetIO();
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard | ImGuiConfigFlags.DockingEnable;
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
// Init platform and renderer
Platform = new ImGuiSDL3(Window, Device);
Renderer = new ImGuiSDL3Renderer(Device);
//ImGuiManager.TriggerImGuiRecreated();
}
private bool _disposed;
~SDL3Window()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
_disposed = true;
try
{
if (Window != IntPtr.Zero)
{
SDL.GetWindowPosition(Window, out int x, out int y);
SDL.GetWindowSize(Window, out int w, out int h);
WindowRectModified?.Invoke(new(x, y, w, h));
}
}
catch
{
// Ignore if SDL window is already invalid
}
if (disposing)
{
Renderer?.Dispose();
Platform?.Dispose();
RenderCallback = null;
}
if (_imGuiContext != IntPtr.Zero)
{
ImGui.SetCurrentContext(_imGuiContext);
ImGui.DestroyContext();
_imGuiContext = IntPtr.Zero;
}
if (Device != IntPtr.Zero)
{
SDL.DestroyRenderer(Device);
}
if (Window != IntPtr.Zero)
{
SDL.DestroyWindow(Window);
}
}
public void RunOneFrame()
{
if (_disposed) return;
ImGui.SetCurrentContext(_imGuiContext);
ImGui.GetIO().DeltaTime = (float) (_timer.Elapsed - _time).TotalSeconds;
_time = _timer.Elapsed;
PollEvents();
Update();
var clear = ClearColor ?? Plugin.DefaultBackgroundColor.Value;
SDL.SetRenderDrawColor(Device, (byte) (clear.R * 255), (byte) (clear.G * 255), (byte) (clear.B * 255), (byte) (clear.A * 255));
Render();
if (_shouldClose || Plugin.CancellationToken.IsCancellationRequested)
{
Dispose();
}
ImGui.SetCurrentContext(IntPtr.Zero);
}
private bool _shouldClose;
private void PollEvents()
{
if (ImGui.GetIO().WantTextInput && !SDL.TextInputActive(Window))
SDL.StartTextInput(Window);
else if (!ImGui.GetIO().WantTextInput && SDL.TextInputActive(Window))
SDL.StopTextInput(Window);
while (EventQueue.TryDequeue(out var ev))
{
Platform.ProcessEvent(ev);
switch ((SDL.EventType) ev.Type)
{
// TODO: reimplement these events
case SDL.EventType.WindowFocusGained:
//ImGuiManager.TriggerImGuiFocusChanged(true);
break;
case SDL.EventType.WindowFocusLost:
//ImGuiManager.TriggerImGuiFocusChanged(false);
break;
case SDL.EventType.Terminating:
case SDL.EventType.WindowCloseRequested:
case SDL.EventType.Quit:
_shouldClose = true;
break;
case SDL.EventType.WindowResized:
SetupScreenClipRect();
SDL.GetWindowPosition(Window, out var x, out var y);
WindowRectModified?.Invoke(new(x, y, ev.Window.Data1, ev.Window.Data2));
break;
case SDL.EventType.WindowMoved:
SDL.GetWindowSize(Window, out var h, out var w);
WindowRectModified?.Invoke(new(ev.Window.Data1, ev.Window.Data2, w, h));
break;
}
}
}
private void Update()
{
Platform.NewFrame();
Renderer.NewFrame();
ImGui.NewFrame();
RenderCallback?.Invoke();
ImGui.EndFrame();
}
private void Render()
{
SDL.RenderClear(Device);
// Reset the clip rect to the screen size
SDL.SetRenderClipRect(Device, _screenClipRect);
// Render ImGui
ImGui.Render();
Renderer.RenderDrawData(ImGui.GetDrawData());
SDL.RenderPresent(Device);
}
private void SetupScreenClipRect()
{
SDL.GetWindowSize(Window, out int w, out int h);
_screenClipRect = new SDL.Rect
{
X = 0,
Y = 0,
W = w,
H = h
};
}
}