1 Commits

Author SHA1 Message Date
NepuShiro 96e90a09d9 This works :) 2026-05-19 11:43:24 -05:00
4 changed files with 741 additions and 329 deletions
+1
View File
@@ -15,6 +15,7 @@
<PackageReference Include="Hexa.NET.ImGui" Version="2.2.9" /> <PackageReference Include="Hexa.NET.ImGui" Version="2.2.9" />
<PackageReference Include="Hexa.NET.ImGui.Widgets" Version="1.2.18" /> <PackageReference Include="Hexa.NET.ImGui.Widgets" Version="1.2.18" />
<PackageReference Include="Hexa.NET.ImPlot" Version="2.2.9" /> <PackageReference Include="Hexa.NET.ImPlot" Version="2.2.9" />
<PackageReference Include="Hexa.NET.OpenGL3" Version="1.1.0" />
<PackageReference Include="Hexa.NET.Utilities" Version="2.2.12" /> <PackageReference Include="Hexa.NET.Utilities" Version="2.2.12" />
<PackageReference Include="SDL3-CS" Version="3.2.18" /> <PackageReference Include="SDL3-CS" Version="3.2.18" />
<PackageReference Include="SDL3-CS.Native" Version="3.2.18" /> <PackageReference Include="SDL3-CS.Native" Version="3.2.18" />
+95 -58
View File
@@ -2,6 +2,7 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Hexa.NET.ImGui; using Hexa.NET.ImGui;
using Hexa.NET.OpenGL;
using SDL3; using SDL3;
namespace SDL3_TestingSuite.SDL3; namespace SDL3_TestingSuite.SDL3;
@@ -16,7 +17,7 @@ public unsafe static class ImGuiSDL3Platform
{ {
public nint Window; public nint Window;
public uint WindowID; public uint WindowID;
public nint Renderer; // public nint Renderer;
public ulong Time; public ulong Time;
public nint ClipboardTextData; public nint ClipboardTextData;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
@@ -60,15 +61,19 @@ public unsafe static class ImGuiSDL3Platform
public static PlatformData GetPlatformData() => ImGui.GetCurrentContext().Handle != null ? ImGuiUserData<PlatformData>.Get(ImGui.GetIO().BackendPlatformUserData)! : null!; public static PlatformData GetPlatformData() => ImGui.GetCurrentContext().Handle != null ? ImGuiUserData<PlatformData>.Get(ImGui.GetIO().BackendPlatformUserData)! : null!;
public static SDL3Window? Parent; public static SDL3Window? Parent;
private static GL _gl;
private static nint _glContext;
public static bool Init(nint window, nint renderer, SDL3Window? parentWindow = null) public static bool Init(GL gl, nint ctx, nint window, SDL3Window? parentWindow = null)
{ {
_gl = gl;
_glContext = ctx;
Parent = parentWindow; Parent = parentWindow;
ImGuiIOPtr io = ImGui.GetIO(); ImGuiIOPtr io = ImGui.GetIO();
PlatformData data = new PlatformData PlatformData data = new PlatformData
{ {
Window = window, Window = window,
Renderer = renderer, // Renderer = renderer,
WindowID = SDL.GetWindowID(window) WindowID = SDL.GetWindowID(window)
}; };
@@ -128,7 +133,7 @@ public unsafe static class ImGuiSDL3Platform
SDL.SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "0"); SDL.SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "0");
if ((io.BackendFlags & ImGuiBackendFlags.PlatformHasViewports) != 0) if ((io.BackendFlags & ImGuiBackendFlags.PlatformHasViewports) != 0)
InitMultiViewportSupport(window); InitMultiViewportSupport(ctx, window);
return true; return true;
} }
@@ -162,7 +167,7 @@ public unsafe static class ImGuiSDL3Platform
io.AddMousePosEvent(-float.MaxValue, -float.MaxValue); io.AddMousePosEvent(-float.MaxValue, -float.MaxValue);
} }
if (data.MouseCanReportHoveredViewport && ImGui.GetDragDropPayload().Equals(nint.Zero)) if (data.MouseCanReportHoveredViewport && ImGui.GetDragDropPayload().IsNull)
{ {
io.BackendFlags |= ImGuiBackendFlags.HasMouseHoveredViewport; io.BackendFlags |= ImGuiBackendFlags.HasMouseHoveredViewport;
} }
@@ -322,7 +327,7 @@ public unsafe static class ImGuiSDL3Platform
// - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue on X11 we we wait until mouse has moved to begin capture. // - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue on X11 we we wait until mouse has moved to begin capture.
if (bd.MouseCaptureMode == MouseCaptureMode.Enabled) if (bd.MouseCaptureMode == MouseCaptureMode.Enabled)
{ {
SDL.CaptureMouse(bd.MouseButtonsDown != 0 ? true : false); SDL.CaptureMouse(bd.MouseButtonsDown != 0);
} }
else if (bd.MouseCaptureMode == MouseCaptureMode.EnabledAfterDrag) else if (bd.MouseCaptureMode == MouseCaptureMode.EnabledAfterDrag)
{ {
@@ -332,7 +337,7 @@ public unsafe static class ImGuiSDL3Platform
if (ImGui.IsMouseDragging((ImGuiMouseButton)buttonN, 1.0f)) if (ImGui.IsMouseDragging((ImGuiMouseButton)buttonN, 1.0f))
wantCapture = true; wantCapture = true;
} }
SDL.CaptureMouse(wantCapture ? true : false); SDL.CaptureMouse(wantCapture);
} }
nint focusedWindow = SDL.GetKeyboardFocus(); nint focusedWindow = SDL.GetKeyboardFocus();
@@ -379,7 +384,7 @@ public unsafe static class ImGuiSDL3Platform
{ {
uint mouseViewportID = 0; uint mouseViewportID = 0;
ImGuiViewportPtr? mouseViewport = GetViewportForWindowId(bd.MouseWindowID); ImGuiViewportPtr? mouseViewport = GetViewportForWindowId(bd.MouseWindowID);
if (mouseViewport != null) if (mouseViewport != null && !mouseViewport.Value.IsNull)
mouseViewportID = mouseViewport.Value.ID; mouseViewportID = mouseViewport.Value.ID;
io.AddMouseViewportEvent(mouseViewportID); io.AddMouseViewportEvent(mouseViewportID);
} }
@@ -729,22 +734,17 @@ public unsafe static class ImGuiSDL3Platform
io.AddKeyEvent(ImGuiKey.ModSuper, (mod & SDL.Keymod.GUI) != 0); io.AddKeyEvent(ImGuiKey.ModSuper, (mod & SDL.Keymod.GUI) != 0);
} }
public class ImGui_ImplSDL3_ViewportData : ViewPortData { }
public class ViewPortData public class ViewPortData
{ {
public nint Window; public nint Window;
public nint Renderer;
public nint ParentWindow; public nint ParentWindow;
public uint WindowID; // Stored in ImGuiViewport.PlatformHandle. Use SDL.GetWindowFromID() to get SDL.Window* from Uint32 WindowID. public uint WindowID; // Stored in ImGuiViewport.PlatformHandle. Use SDL.GetWindowFromID() to get SDL.Window* from Uint32 WindowID.
public bool WindowOwned; public bool WindowOwned;
public bool RendererOwned; public nint GLContext;
} }
private static void InitMultiViewportSupport(nint window) private static void InitMultiViewportSupport(nint _gl, nint window)
{ {
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
platformIO.PlatformCreateWindow = DelegateStorage.CreateWindowDelegate.GetPtrForDelegate(); platformIO.PlatformCreateWindow = DelegateStorage.CreateWindowDelegate.GetPtrForDelegate();
@@ -760,6 +760,8 @@ public unsafe static class ImGuiSDL3Platform
platformIO.PlatformSetWindowTitle = DelegateStorage.SetWindowTitleDelegate.GetPtrForDelegate(); platformIO.PlatformSetWindowTitle = DelegateStorage.SetWindowTitleDelegate.GetPtrForDelegate();
platformIO.PlatformSetWindowAlpha = DelegateStorage.SetWindowAlphaDelegate.GetPtrForDelegate(); platformIO.PlatformSetWindowAlpha = DelegateStorage.SetWindowAlphaDelegate.GetPtrForDelegate();
platformIO.PlatformUpdateWindow = DelegateStorage.UpdateWindowDelegate.GetPtrForDelegate(); platformIO.PlatformUpdateWindow = DelegateStorage.UpdateWindowDelegate.GetPtrForDelegate();
platformIO.PlatformRenderWindow = DelegateStorage.RenderWindowDelegate.GetPtrForDelegate();
platformIO.PlatformSwapBuffers = DelegateStorage.SwapBuffersDelegate.GetPtrForDelegate();
platformIO.PlatformGetWindowFramebufferScale = DelegateStorage.GetWindowFramebufferScaleDelegate.GetPtrForDelegate(); platformIO.PlatformGetWindowFramebufferScale = DelegateStorage.GetWindowFramebufferScaleDelegate.GetPtrForDelegate();
ImGuiViewportPtr mainViewport = ImGui.GetMainViewport(); ImGuiViewportPtr mainViewport = ImGui.GetMainViewport();
@@ -767,10 +769,9 @@ public unsafe static class ImGuiSDL3Platform
ViewPortData vd = new ViewPortData ViewPortData vd = new ViewPortData
{ {
Window = window, Window = window,
Renderer = GetPlatformData().Renderer,
WindowID = SDL.GetWindowID(window), WindowID = SDL.GetWindowID(window),
WindowOwned = false, WindowOwned = false,
RendererOwned = false GLContext = _gl
}; };
mainViewport.PlatformUserData = ImGuiUserData<ViewPortData>.Store(vd); mainViewport.PlatformUserData = ImGuiUserData<ViewPortData>.Store(vd);
@@ -892,8 +893,19 @@ public unsafe static class ImGuiSDL3Platform
// // // //
// // // // // // // // // // // // // // // // // // // // // // // // // // // //
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void CreateWindowDelegateFn(ImGuiViewportPtr ctx); internal delegate void ViewportCallback(ImGuiViewportPtr ctx);
internal static readonly CreateWindowDelegateFn CreateWindowDelegate = CreateWindow; [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void ViewportCallbackSetVec2(ImGuiViewportPtr ctx, Vector2 vec);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate Vector2 ViewportCallbackGetVec2(ImGuiViewportPtr ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate bool ViewportCallbackBool(ImGuiViewportPtr ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void ViewportCallbackSetString(ImGuiViewportPtr ctx, string title);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void ViewportCallbackSetFloat(ImGuiViewportPtr ctx, float alpha);
internal static readonly ViewportCallback CreateWindowDelegate = CreateWindow;
private static void CreateWindow(ImGuiViewportPtr viewport) private static void CreateWindow(ImGuiViewportPtr viewport)
{ {
PlatformData bd = GetPlatformData(); PlatformData bd = GetPlatformData();
@@ -901,8 +913,21 @@ public unsafe static class ImGuiSDL3Platform
ViewPortData vd = new ViewPortData(); ViewPortData vd = new ViewPortData();
viewport.PlatformUserData = ImGuiUserData<ViewPortData>.Store(vd); viewport.PlatformUserData = ImGuiUserData<ViewPortData>.Store(vd);
bool useOpengl = _glContext != nint.Zero;
nint backupContext = nint.Zero;
if (useOpengl)
{
backupContext = SDL.GLGetCurrentContext();
SDL.GLSetAttribute(SDL.GLAttr.ShareWithCurrentContext, 1);
}
SDL.WindowFlags flags = SDL.WindowFlags.Hidden; SDL.WindowFlags flags = SDL.WindowFlags.Hidden;
if (useOpengl)
flags |= SDL.WindowFlags.OpenGL;
else if (bd.UseVulkan)
flags |= SDL.WindowFlags.Vulkan;
flags |= SDL.GetWindowFlags(bd.Window) & SDL.WindowFlags.HighPixelDensity; flags |= SDL.GetWindowFlags(bd.Window) & SDL.WindowFlags.HighPixelDensity;
if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoDecoration)) if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoDecoration))
@@ -915,6 +940,7 @@ public unsafe static class ImGuiSDL3Platform
flags |= SDL.WindowFlags.AlwaysOnTop; flags |= SDL.WindowFlags.AlwaysOnTop;
vd.Window = SDL.CreateWindow("Untitled", (int)viewport.Size.X, (int)viewport.Size.Y, flags); vd.Window = SDL.CreateWindow("Untitled", (int)viewport.Size.X, (int)viewport.Size.Y, flags);
vd.WindowID = SDL.GetWindowID(vd.Window);
ImGuiViewportPtr? parentViewport = viewport.ParentViewportId != 0 ? ImGui.FindViewportByID(viewport.ParentViewportId) : null; ImGuiViewportPtr? parentViewport = viewport.ParentViewportId != 0 ? ImGui.FindViewportByID(viewport.ParentViewportId) : null;
if (parentViewport != null && parentViewport.Value.Handle != null) if (parentViewport != null && parentViewport.Value.Handle != null)
@@ -926,6 +952,15 @@ public unsafe static class ImGuiSDL3Platform
SDL.SetWindowPosition(vd.Window, (int)viewport.Pos.X, (int)viewport.Pos.Y); SDL.SetWindowPosition(vd.Window, (int)viewport.Pos.X, (int)viewport.Pos.Y);
vd.WindowOwned = true; vd.WindowOwned = true;
if (useOpengl)
{
vd.GLContext = SDL.GLCreateContext(vd.Window);
SDL.GLSetSwapInterval(0);
}
if (useOpengl && backupContext != nint.Zero)
{
SDL.GLMakeCurrent(bd.Window, backupContext);
}
SetupPlatformHandles(viewport, vd.Window); SetupPlatformHandles(viewport, vd.Window);
@@ -940,19 +975,24 @@ public unsafe static class ImGuiSDL3Platform
#endif #endif
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallback DestroyWindowDelegate = DestroyWindow;
internal delegate void DestroyWindowDelegateFn(ImGuiViewportPtr ctx);
internal static readonly DestroyWindowDelegateFn DestroyWindowDelegate = DestroyWindow;
private static void DestroyWindow(ImGuiViewportPtr viewport) private static void DestroyWindow(ImGuiViewportPtr viewport)
{ {
ViewPortData? vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData); ViewPortData? vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData);
if (vd != null) if (vd != null)
{ {
if (vd.GLContext != nint.Zero && vd.WindowOwned)
{
SDL.GLDestroyContext(vd.GLContext);
}
if (vd.Window != nint.Zero && vd.WindowOwned) if (vd.Window != nint.Zero && vd.WindowOwned)
{ {
SDL.DestroyWindow(vd.Window); SDL.DestroyWindow(vd.Window);
} }
vd.GLContext = nint.Zero;
vd.Window = nint.Zero;
} }
ImGuiUserData<ViewPortData>.Free(viewport.PlatformUserData); ImGuiUserData<ViewPortData>.Free(viewport.PlatformUserData);
@@ -962,9 +1002,7 @@ public unsafe static class ImGuiSDL3Platform
viewport.PlatformHandleRaw = null; viewport.PlatformHandleRaw = null;
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallback ShowWindowDelegate = ShowWindow;
internal delegate void ShowWindowDelegateFn(ImGuiViewportPtr ctx);
internal static readonly ShowWindowDelegateFn ShowWindowDelegate = ShowWindow;
private static void ShowWindow(ImGuiViewportPtr viewport) private static void ShowWindow(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -978,9 +1016,7 @@ public unsafe static class ImGuiSDL3Platform
SDL.SetHint(SDL.Hints.WindowActivateWhenShown, oldHint); SDL.SetHint(SDL.Hints.WindowActivateWhenShown, oldHint);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallback UpdateWindowDelegate = UpdateWindow;
internal delegate void UpdateWindowDelegateFn(ImGuiViewportPtr ctx);
internal static readonly UpdateWindowDelegateFn UpdateWindowDelegate = UpdateWindow;
private static void UpdateWindow(ImGuiViewportPtr viewport) private static void UpdateWindow(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -995,9 +1031,28 @@ public unsafe static class ImGuiSDL3Platform
} }
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallback RenderWindowDelegate = RenderWindow;
internal delegate void SetWindowPosDelegateFn(ImGuiViewportPtr ctx, Vector2 pos); private static void RenderWindow(ImGuiViewportPtr viewport)
internal static readonly SetWindowPosDelegateFn SetWindowPosDelegate = SetWindowPos; {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
if (vd.GLContext != nint.Zero)
SDL.GLMakeCurrent(vd.Window, vd.GLContext);
}
internal static readonly ViewportCallback SwapBuffersDelegate = SwapBuffers;
private static void SwapBuffers(ImGuiViewportPtr viewport)
{
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
if (vd.GLContext != nint.Zero)
{
SDL.GLMakeCurrent(vd.Window, vd.GLContext);
SDL.GLSwapWindow(vd.Window);
}
}
internal static readonly ViewportCallbackSetVec2 SetWindowPosDelegate = SetWindowPos;
private static void SetWindowPos(ImGuiViewportPtr viewport, Vector2 pos) private static void SetWindowPos(ImGuiViewportPtr viewport, Vector2 pos)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1005,9 +1060,7 @@ public unsafe static class ImGuiSDL3Platform
SDL.SetWindowPosition(vd.Window, (int)pos.X, (int)pos.Y); SDL.SetWindowPosition(vd.Window, (int)pos.X, (int)pos.Y);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackGetVec2 GetWindowPosDelegate = GetWindowPos;
internal delegate Vector2 GetWindowPosDelegateFn(ImGuiViewportPtr ctx);
internal static readonly GetWindowPosDelegateFn GetWindowPosDelegate = GetWindowPos;
private static Vector2 GetWindowPos(ImGuiViewportPtr viewport) private static Vector2 GetWindowPos(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1015,9 +1068,7 @@ public unsafe static class ImGuiSDL3Platform
return new Vector2(x, y); return new Vector2(x, y);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackSetVec2 SetWindowSizeDelegate = SetWindowSize;
internal delegate void SetWindowSizeDelegateFn(ImGuiViewportPtr ctx, Vector2 size);
internal static readonly SetWindowSizeDelegateFn SetWindowSizeDelegate = SetWindowSize;
private static void SetWindowSize(ImGuiViewportPtr viewport, Vector2 size) private static void SetWindowSize(ImGuiViewportPtr viewport, Vector2 size)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1025,9 +1076,7 @@ public unsafe static class ImGuiSDL3Platform
SDL.SetWindowSize(vd.Window, (int)size.X, (int)size.Y); SDL.SetWindowSize(vd.Window, (int)size.X, (int)size.Y);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackGetVec2 GetWindowSizeDelegate = GetWindowSize;
internal delegate Vector2 GetWindowSizeDelegateFn(ImGuiViewportPtr ctx);
internal static readonly GetWindowSizeDelegateFn GetWindowSizeDelegate = GetWindowSize;
private static Vector2 GetWindowSize(ImGuiViewportPtr viewport) private static Vector2 GetWindowSize(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1037,9 +1086,7 @@ public unsafe static class ImGuiSDL3Platform
return new Vector2(w, h); return new Vector2(w, h);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackGetVec2 GetWindowFramebufferScaleDelegate = GetWindowFramebufferScale;
internal delegate Vector2 GetWindowFramebufferScaleDelegateFn(ImGuiViewportPtr ctx);
internal static readonly GetWindowFramebufferScaleDelegateFn GetWindowFramebufferScaleDelegate = GetWindowFramebufferScale;
private static Vector2 GetWindowFramebufferScale(ImGuiViewportPtr viewport) private static Vector2 GetWindowFramebufferScale(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1049,9 +1096,7 @@ public unsafe static class ImGuiSDL3Platform
return framebufferScale; return framebufferScale;
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallback SetWindowFocusDelegate = SetWindowFocus;
internal delegate void SetWindowFocusDelegateFn(ImGuiViewportPtr ctx);
internal static readonly SetWindowFocusDelegateFn SetWindowFocusDelegate = SetWindowFocus;
private static void SetWindowFocus(ImGuiViewportPtr viewport) private static void SetWindowFocus(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1059,9 +1104,7 @@ public unsafe static class ImGuiSDL3Platform
SDL.RaiseWindow(vd.Window); SDL.RaiseWindow(vd.Window);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackBool GetWindowFocusDelegate = GetWindowFocus;
internal delegate bool GetWindowFocusDelegateFn(ImGuiViewportPtr ctx);
internal static readonly GetWindowFocusDelegateFn GetWindowFocusDelegate = GetWindowFocus;
private static bool GetWindowFocus(ImGuiViewportPtr viewport) private static bool GetWindowFocus(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1069,9 +1112,7 @@ public unsafe static class ImGuiSDL3Platform
return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.InputFocus) != 0; return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.InputFocus) != 0;
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackBool GetWindowMinimizedDelegate = GetWindowMinimized;
internal delegate bool GetWindowMinimizedDelegateFn(ImGuiViewportPtr ctx);
internal static readonly GetWindowMinimizedDelegateFn GetWindowMinimizedDelegate = GetWindowMinimized;
private static bool GetWindowMinimized(ImGuiViewportPtr viewport) private static bool GetWindowMinimized(ImGuiViewportPtr viewport)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1079,9 +1120,7 @@ public unsafe static class ImGuiSDL3Platform
return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.Minimized) != 0; return (SDL.GetWindowFlags(vd.Window) & SDL.WindowFlags.Minimized) != 0;
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackSetString SetWindowTitleDelegate = SetWindowTitle;
internal delegate void SetWindowTitleDelegateFn(ImGuiViewportPtr ctx, string title);
internal static readonly SetWindowTitleDelegateFn SetWindowTitleDelegate = SetWindowTitle;
private static void SetWindowTitle(ImGuiViewportPtr viewport, string title) private static void SetWindowTitle(ImGuiViewportPtr viewport, string title)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
@@ -1089,9 +1128,7 @@ public unsafe static class ImGuiSDL3Platform
SDL.SetWindowTitle(vd.Window, title); SDL.SetWindowTitle(vd.Window, title);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal static readonly ViewportCallbackSetFloat SetWindowAlphaDelegate = SetWindowAlpha;
internal delegate void SetWindowAlphaDelegateFn(ImGuiViewportPtr ctx, float alpha);
internal static readonly SetWindowAlphaDelegateFn SetWindowAlphaDelegate = SetWindowAlpha;
private static void SetWindowAlpha(ImGuiViewportPtr viewport, float alpha) private static void SetWindowAlpha(ImGuiViewportPtr viewport, float alpha)
{ {
ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!; ViewPortData vd = ImGuiUserData<ViewPortData>.Get(viewport.PlatformUserData)!;
+550 -253
View File
@@ -1,377 +1,674 @@
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Hexa.NET.ImGui; using Hexa.NET.ImGui;
using SDL3; using Hexa.NET.OpenGL;
namespace SDL3_TestingSuite.SDL3; namespace SDL3_TestingSuite.SDL3;
/// <summary>
/// Implementation of ImGui for SDL3 Renderer backend.
/// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdlrenderer3.h
/// </summary>
public unsafe static class ImGuiSDL3Renderer public unsafe static class ImGuiSDL3Renderer
{ {
public class RendererData public class RendererData
{ {
public nint Renderer; // Main viewport's renderer public uint GlVersion; // e.g. 320 for GL 3.2
public ImVector<SDL.FColor> ColorBuffer; public string GlslVersionString = ""; // e.g. "#version 330 core\n"
public bool GlProfileIsES2;
public bool GlProfileIsES3;
public bool GlProfileIsCompat;
public int GlProfileMask;
public int MaxTextureSize;
// Render State public uint ShaderHandle;
public SDL.ScaleMode CurrentScaleMode; public int AttribLocationTex;
public int AttribLocationProjMtx;
public uint AttribLocationVtxPos;
public uint AttribLocationVtxUV;
public uint AttribLocationVtxColor;
public uint VboHandle;
public uint ElementsHandle;
public bool HasPolygonMode;
public bool HasBindSampler;
public bool HasClipOrigin;
public uint[] TexSamplers = new uint[2]; // [0]=linear, [1]=nearest
public bool UseTexParameterToSetSampler;
public int NextSampler; // GL_LINEAR or GL_NEAREST
public GL Gl = null!;
} }
private struct BackupSDLRendererState public static RendererData? GetRendererData() => ImGui.GetCurrentContext().Handle != null ? ImGuiUserData<RendererData>.Get(ImGui.GetIO().BackendRendererUserData) : null;
public static bool Init(GL gl, string? glslVersion = null)
{ {
public SDL.Rect Viewport;
public bool ClipEnabled;
public SDL.Rect ClipRect;
}
public static RendererData GetRendererData() => ImGui.GetCurrentContext().Handle != null ? ImGuiUserData<RendererData>.Get(ImGui.GetIO().BackendRendererUserData)! : null!;
public static SDL3Window? Parent;
public static bool Init(nint renderer, SDL3Window? parentWindow = null)
{
Parent = parentWindow;
ImGuiIOPtr io = ImGui.GetIO(); ImGuiIOPtr io = ImGui.GetIO();
Debug.Assert(io.BackendRendererUserData == null, "Already initialised a renderer backend!");
RendererData bd = new RendererData(); RendererData bd = new RendererData { Gl = gl };
io.BackendRendererUserData = ImGuiUserData<RendererData>.Store(bd); io.BackendRendererUserData = ImGuiUserData<RendererData>.Store(bd);
io.BackendRendererName = (byte*)Marshal.StringToHGlobalAnsi("NepImGuiSDL3Renderer"); io.BackendRendererName = (byte*)Marshal.StringToHGlobalAnsi("imgui_impl_opengl3_cs");
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; // We can honor the ImDrawCmd.VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags.RendererHasTextures; // We can honor ImGuiPlatformIO.Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags.RendererHasViewports;
bd.Renderer = renderer; string? glVersionStr = Marshal.PtrToStringAnsi((nint)gl.GetString(GLStringName.Version));
if (glVersionStr != null && glVersionStr.StartsWith("OpenGL ES 2", StringComparison.Ordinal))
{
bd.GlVersion = 200;
bd.GlProfileIsES2 = true;
}
else
{
gl.GetIntegerv(GLGetPName.MajorVersion, out int major);
gl.GetIntegerv(GLGetPName.MinorVersion, out int minor);
if (major == 0 && minor == 0 && glVersionStr != null)
{
string[] parts = glVersionStr.Split('.');
if (parts.Length >= 2)
{
int.TryParse(parts[0], out major);
int.TryParse(new string(parts[1].TakeWhile(char.IsDigit).ToArray()), out minor);
}
}
bd.GlVersion = (uint)(major * 100 + minor * 10);
if (glVersionStr != null && glVersionStr.StartsWith("OpenGL ES 3", StringComparison.Ordinal))
bd.GlProfileIsES3 = true;
gl.GetIntegerv(GLGetPName.MaxTextureSize, out bd.MaxTextureSize);
if (!bd.GlProfileIsES3 && bd.GlVersion >= 320)
{
gl.GetIntegerv(GLGetPName.ContextProfileMask, out bd.GlProfileMask);
}
}
if (bd.GlVersion >= 320)
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; // large meshes
io.BackendFlags |= ImGuiBackendFlags.RendererHasTextures; // dynamic font atlas
io.BackendFlags |= ImGuiBackendFlags.RendererHasViewports; // multi-viewport
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
platformIO.RendererCreateWindow = DelegateStorage.RendererCreateWindowDelegate.GetPtrForDelegate(); platformIO.RendererTextureMaxWidth = bd.MaxTextureSize;
platformIO.RendererDestroyWindow = DelegateStorage.RendererDestroyWindowDelegate.GetPtrForDelegate(); platformIO.RendererTextureMaxHeight = bd.MaxTextureSize;
platformIO.RendererRenderWindow = DelegateStorage.RendererRenderWindowDelegate.GetPtrForDelegate();
platformIO.RendererSwapBuffers = DelegateStorage.RendererSwapBuffersDelegate.GetPtrForDelegate(); if (glslVersion == null)
{
if (bd.GlProfileIsES2) glslVersion = "#version 100";
else if (bd.GlProfileIsES3) glslVersion = "#version 300 es";
else if (OperatingSystem.IsMacOS()) glslVersion = "#version 150";
else glslVersion = "#version 130";
}
bd.GlslVersionString = glslVersion + "\n";
bd.HasPolygonMode = !bd.GlProfileIsES2 && !bd.GlProfileIsES3;
bd.HasBindSampler = bd.GlVersion >= 330 || bd.GlProfileIsES3;
bd.HasClipOrigin = bd.GlVersion >= 450;
if (!bd.HasClipOrigin && !bd.GlProfileIsES2 && !bd.GlProfileIsES3)
{
gl.GetIntegerv(GLGetPName.NumExtensions, out int numExt);
for (uint i = 0; i < numExt; i++)
{
string? ext = Marshal.PtrToStringAnsi((nint)gl.GetStringi(GLStringName.Extensions, i));
if (ext == "GL_ARB_clip_control")
{
bd.HasClipOrigin = true;
break;
}
}
}
InitMultiViewportSupport();
return true; return true;
} }
public static void Dispose() public static void Dispose()
{ {
RendererData? bd = GetRendererData();
Debug.Assert(bd != null, "No renderer backend to shut down, or already shut down?");
ImGuiIOPtr io = ImGui.GetIO(); ImGuiIOPtr io = ImGui.GetIO();
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
ShutdownMultiViewportSupport();
DestroyDeviceObjects();
if (io.BackendRendererName != null) if (io.BackendRendererName != null)
{
Marshal.FreeHGlobal((nint)io.BackendRendererName); Marshal.FreeHGlobal((nint)io.BackendRendererName);
}
ImGuiUserData<RendererData>.Free(io.BackendRendererUserData); ImGuiUserData<RendererData>.Free(io.BackendRendererUserData);
io.BackendRendererName = null; io.BackendRendererName = null;
io.BackendRendererUserData = null; io.BackendRendererUserData = null;
io.BackendFlags &= ~(ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasTextures | ImGuiBackendFlags.RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasTextures | ImGuiBackendFlags.RendererHasViewports);
platformIO.RendererTextureMaxWidth = 0;
platformIO.RendererTextureMaxHeight = 0;
platformIO.RendererRenderState = null;
}
public static void SetupRenderState(nint renderer)
{
// Clear out any viewports and cliprect set by the user
// FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process.
SDL.SetRenderViewport(renderer, nint.Zero);
SDL.SetRenderClipRect(renderer, nint.Zero);
} }
public static void NewFrame() public static void NewFrame()
{ {
RendererData bd = GetRendererData(); RendererData? bd = GetRendererData();
Debug.Assert(bd != null, "Context or backend not initialized! Did you call ImGuiSDL3Renderer.Init()?"); Debug.Assert(bd != null, "Context or backend not initialised! Did you call ImGuiOpenGL3Renderer.Init()?");
if (bd.ShaderHandle == 0)
{
bool ok = CreateDeviceObjects();
Debug.Assert(ok, "ImGuiOpenGL3Renderer.CreateDeviceObjects() failed!");
}
} }
public static void RenderDrawData(ImDrawDataPtr drawData, nint renderer) public static void RenderDrawData(ImDrawDataPtr drawData)
{ {
// Skip if no data to render if (drawData.Handle == null) return;
if (drawData.Handle == null || drawData.CmdListsCount == 0)
return;
SDL.GetRenderScale(renderer, out float renderScaleX, out float renderScaleY); int fbWidth = (int)(drawData.DisplaySize.X * drawData.FramebufferScale.X);
Vector2 renderScale = new Vector2(renderScaleX == 1.0f ? drawData.FramebufferScale.X : 1.0f, renderScaleY == 1.0f ? drawData.FramebufferScale.Y : 1.0f); int fbHeight = (int)(drawData.DisplaySize.Y * drawData.FramebufferScale.Y);
if (fbWidth <= 0 || fbHeight <= 0) return;
int fbWidth = (int)(drawData.DisplaySize.X * renderScale.X); RendererData bd = GetRendererData()!;
int fbHeight = (int)(drawData.DisplaySize.Y * renderScale.Y); GL gl = bd.Gl;
if (fbWidth <= 0 || fbHeight <= 0)
return;
if (drawData.Textures.Size > 0)
{
for (int i = 0; i < drawData.Textures.Size; i++) for (int i = 0; i < drawData.Textures.Size; i++)
{ {
ImTextureDataPtr texture = drawData.Textures[i]; ImTextureDataPtr tex = drawData.Textures[i];
if (texture.Handle != null) if (tex.Status != ImTextureStatus.Ok)
{ UpdateTexture(tex);
UpdateTexture(texture, renderer);
} }
} }
// Backup SDL renderer state gl.GetIntegerv(GLGetPName.ActiveTexture, out int lastActiveTexture);
BackupSDLRendererState old = new BackupSDLRendererState gl.ActiveTexture(GLTextureUnit.Texture0);
gl.GetIntegerv(GLGetPName.CurrentProgram, out int lastProgram);
gl.GetIntegerv(GLGetPName.TextureBinding2D, out int lastTexture);
gl.GetIntegerv(GLGetPName.SamplerBinding, out int lastSampler);
gl.GetIntegerv(GLGetPName.ArrayBufferBinding, out int lastArrayBuffer);
gl.GetIntegerv(GLGetPName.VertexArrayBinding, out int lastVertexArray);
int[] lastPolygonMode = new int[2];
if (bd.HasPolygonMode) gl.GetIntegerv(GLGetPName.PolygonMode, lastPolygonMode);
int[] lastViewport = new int[4];
gl.GetIntegerv(GLGetPName.Viewport, lastViewport);
int[] lastScissorBox = new int[4];
gl.GetIntegerv(GLGetPName.ScissorBox, lastScissorBox);
gl.GetIntegerv(GLGetPName.BlendSrcRgb, out int lastBlendSrcRgb);
gl.GetIntegerv(GLGetPName.BlendDstRgb, out int lastBlendDstRgb);
gl.GetIntegerv(GLGetPName.BlendSrcAlpha, out int lastBlendSrcAlpha);
gl.GetIntegerv(GLGetPName.BlendDstAlpha, out int lastBlendDstAlpha);
gl.GetIntegerv(GLGetPName.BlendEquationRgb, out int lastBlendEqRgb);
gl.GetIntegerv(GLGetPName.BlendEquationAlpha, out int lastBlendEqAlpha);
bool lastBlend = gl.IsEnabled(GLEnableCap.Blend);
bool lastCullFace = gl.IsEnabled(GLEnableCap.CullFace);
bool lastDepthTest = gl.IsEnabled(GLEnableCap.DepthTest);
bool lastStencilTest = gl.IsEnabled(GLEnableCap.StencilTest);
bool lastScissorTest = gl.IsEnabled(GLEnableCap.ScissorTest);
bool lastPrimRestart = bd.GlVersion >= 310 && !bd.GlProfileIsES3 && gl.IsEnabled(GLEnableCap.PrimitiveRestart);
uint vao = 0;
gl.GenVertexArrays(1, ref vao);
SetupRenderState(drawData, fbWidth, fbHeight, vao);
Vector2 clipOff = drawData.DisplayPos;
Vector2 clipScale = drawData.FramebufferScale;
for (int listIdx = 0; listIdx < drawData.CmdListsCount; listIdx++)
{ {
ClipEnabled = SDL.RenderClipEnabled(renderer) ImDrawListPtr drawList = drawData.CmdLists[listIdx];
};
SDL.GetRenderViewport(renderer, out old.Viewport);
SDL.GetRenderClipRect(renderer, out old.ClipRect);
// Set up render state int vtxSize = drawList.VtxBuffer.Size * sizeof(ImDrawVert);
SetupRenderState(renderer); int idxSize = drawList.IdxBuffer.Size * sizeof(ushort);
// Set render state in platform IO gl.BufferData(GLBufferTargetARB.ArrayBuffer, vtxSize, drawList.VtxBuffer.Data, GLBufferUsageARB.StreamDraw);
ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO();
platformIo.RendererRenderState = (void*)renderer;
Vector2 clipOffset = drawData.DisplayPos; gl.BufferData(GLBufferTargetARB.ElementArrayBuffer, idxSize, drawList.IdxBuffer.Data, GLBufferUsageARB.StreamDraw);
// Render command lists for (int cmdIdx = 0; cmdIdx < drawList.CmdBuffer.Size; cmdIdx++)
for (int n = 0; n < drawData.CmdListsCount; n++)
{ {
ImDrawListPtr drawList = drawData.CmdLists[n]; ImDrawCmd cmd = drawList.CmdBuffer[cmdIdx];
for (int cmdIndex = 0; cmdIndex < drawList.CmdBuffer.Size; cmdIndex++)
{
ImDrawCmd cmd = drawList.CmdBuffer[cmdIndex];
if (cmd.UserCallback != null) if (cmd.UserCallback != null)
{ {
if (cmd.UserCallback == DelegateStorage.DrawCallbackResetRenderStateDelegate.GetPtrForDelegate())
{ nint cbPtr = (nint)cmd.UserCallback;
SetupRenderState(renderer); if (cbPtr == (nint)DelegateStorage.DrawCallbackResetRenderStateDelegate.GetPtrForDelegate())
} SetupRenderState(drawData, fbWidth, fbHeight, vao);
else if (cbPtr == (nint)DelegateStorage.DrawCallbackSetSamplerLinearDelegate.GetPtrForDelegate())
ApplySamplerLinear(bd);
else if (cbPtr == (nint)DelegateStorage.DrawCallbackSetSamplerNearestDelegate.GetPtrForDelegate())
ApplySamplerNearest(bd);
else else
{ {
// this is cursed af and idk if it even works :) ImDrawCmdPtr cmdPtr = new ImDrawCmdPtr { Handle = &cmd };
ImDrawCmdPtr cmdPtr = new ImDrawCmdPtr
{
Handle = (ImDrawCmd*)Unsafe.AsPointer(ref cmd)
};
((delegate* unmanaged[Cdecl]<ImDrawListPtr, ImDrawCmdPtr, void>)cmd.UserCallback)(drawList, cmdPtr); ((delegate* unmanaged[Cdecl]<ImDrawListPtr, ImDrawCmdPtr, void>)cmd.UserCallback)(drawList, cmdPtr);
} }
continue;
}
float clipMinX = (cmd.ClipRect.X - clipOff.X) * clipScale.X;
float clipMinY = (cmd.ClipRect.Y - clipOff.Y) * clipScale.Y;
float clipMaxX = (cmd.ClipRect.Z - clipOff.X) * clipScale.X;
float clipMaxY = (cmd.ClipRect.W - clipOff.Y) * clipScale.Y;
if (clipMaxX <= clipMinX || clipMaxY <= clipMinY) continue;
gl.Scissor((int)clipMinX, fbHeight - (int)clipMaxY, (int)(clipMaxX - clipMinX), (int)(clipMaxY - clipMinY));
gl.BindTexture(GLTextureTarget.Texture2D, (uint)(nint)cmd.GetTexID());
if (!bd.HasBindSampler && bd.UseTexParameterToSetSampler)
{
gl.TexParameterf(GLTextureTarget.Texture2D, GLTextureParameterName.MinFilter, bd.NextSampler);
gl.TexParameterf(GLTextureTarget.Texture2D, GLTextureParameterName.MagFilter, bd.NextSampler);
}
if (bd.GlVersion >= 320)
{
gl.DrawElementsBaseVertex(GLPrimitiveType.Triangles, (int)cmd.ElemCount, GLDrawElementsType.UnsignedShort, (void*)(cmd.IdxOffset * sizeof(ushort)), (int)cmd.VtxOffset);
} }
else else
{ {
// Apply clipping rectangle gl.DrawElements(GLPrimitiveType.Triangles, (int)cmd.ElemCount, GLDrawElementsType.UnsignedShort, (void*)(cmd.IdxOffset * sizeof(ushort)));
Vector4 clipRect = cmd.ClipRect;
Vector2 clipMin = new Vector2((clipRect.X - clipOffset.X) * renderScale.X, (clipRect.Y - clipOffset.Y) * renderScale.Y);
Vector2 clipMax = new Vector2((clipRect.Z - clipOffset.X) * renderScale.X, (clipRect.W - clipOffset.Y) * renderScale.Y);
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);
if (clipMax.X <= clipMin.X || clipMax.Y <= clipMin.Y)
continue;
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)
};
SDL.SetRenderClipRect(renderer, r);
// Convert ImGui vertices to SDL vertices
uint indexOffset = cmd.IdxOffset;
uint vertexOffset = cmd.VtxOffset;
uint elemCount = cmd.ElemCount;
SDL.Vertex[] vertices = new SDL.Vertex[elemCount];
int[] indices = new int[elemCount];
for (int i = 0; i < elemCount; i++)
{
ushort idx = drawList.IdxBuffer[(int)indexOffset + i];
int vertIdx = (int)(vertexOffset + idx);
ImDrawVert srcVert = drawList.VtxBuffer[vertIdx];
uint col = srcVert.Col;
byte colR = (byte)((col >> 0) & 0xFF);
byte colG = (byte)((col >> 8) & 0xFF);
byte colB = (byte)((col >> 16) & 0xFF);
byte colA = (byte)((col >> 24) & 0xFF);
vertices[i] = new SDL.Vertex
{
Position = new SDL.FPoint
{
X = (srcVert.Pos.X - clipOffset.X) * renderScale.X,
Y = (srcVert.Pos.Y - clipOffset.Y) * renderScale.Y
},
Color = new SDL.FColor
{
R = colR / 255f,
G = colG / 255f,
B = colB / 255f,
A = colA / 255f
},
TexCoord = new SDL.FPoint
{
X = srcVert.Uv.X,
Y = srcVert.Uv.Y
} }
}
}
gl.DeleteVertexArray(vao);
if (lastProgram == 0 || gl.IsProgram((uint)lastProgram))
gl.UseProgram((uint)lastProgram);
gl.BindTexture(GLTextureTarget.Texture2D, (uint)lastTexture);
if (bd.HasBindSampler) gl.BindSampler(0, (uint)lastSampler);
gl.ActiveTexture((GLTextureUnit)lastActiveTexture);
gl.BindVertexArray((uint)lastVertexArray);
gl.BindBuffer(GLBufferTargetARB.ArrayBuffer, (uint)lastArrayBuffer);
gl.BlendEquationSeparate((GLBlendEquationModeEXT)lastBlendEqRgb, (GLBlendEquationModeEXT)lastBlendEqAlpha);
gl.BlendFuncSeparate((GLBlendingFactor)lastBlendSrcRgb, (GLBlendingFactor)lastBlendDstRgb, (GLBlendingFactor)lastBlendSrcAlpha, (GLBlendingFactor)lastBlendDstAlpha);
SetCap(gl, GLEnableCap.Blend, lastBlend);
SetCap(gl, GLEnableCap.CullFace, lastCullFace);
SetCap(gl, GLEnableCap.DepthTest, lastDepthTest);
SetCap(gl, GLEnableCap.StencilTest, lastStencilTest);
SetCap(gl, GLEnableCap.ScissorTest, lastScissorTest);
if (bd.GlVersion >= 310 && !bd.GlProfileIsES3)
SetCap(gl, GLEnableCap.PrimitiveRestart, lastPrimRestart);
if (bd.HasPolygonMode)
{
if (bd.GlVersion <= 310 || bd.GlProfileIsCompat)
{
gl.PolygonMode(GLTriangleFace.Front, (GLPolygonMode)lastPolygonMode[0]);
gl.PolygonMode(GLTriangleFace.Back, (GLPolygonMode)lastPolygonMode[1]);
}
else
{
gl.PolygonMode(GLTriangleFace.FrontAndBack, (GLPolygonMode)lastPolygonMode[0]);
}
}
gl.Viewport(lastViewport[0], lastViewport[1], lastViewport[2], lastViewport[3]);
gl.Scissor(lastScissorBox[0], lastScissorBox[1], lastScissorBox[2], lastScissorBox[3]);
}
private static void SetupRenderState(ImDrawDataPtr drawData, int fbWidth, int fbHeight, uint vao)
{
RendererData bd = GetRendererData()!;
GL gl = bd.Gl;
gl.Enable(GLEnableCap.Blend);
gl.BlendEquation(GLBlendEquationModeEXT.FuncAdd);
gl.BlendFuncSeparate(GLBlendingFactor.SrcAlpha, GLBlendingFactor.OneMinusSrcAlpha, GLBlendingFactor.One, GLBlendingFactor.OneMinusSrcAlpha);
gl.Disable(GLEnableCap.CullFace);
gl.Disable(GLEnableCap.DepthTest);
gl.Disable(GLEnableCap.StencilTest);
gl.Enable(GLEnableCap.ScissorTest);
if (bd.GlVersion >= 310 && !bd.GlProfileIsES3)
gl.Disable(GLEnableCap.PrimitiveRestart);
if (bd.HasPolygonMode)
gl.PolygonMode(GLTriangleFace.FrontAndBack, GLPolygonMode.Fill);
gl.Viewport(0, 0, fbWidth, fbHeight);
float L = drawData.DisplayPos.X;
float R = drawData.DisplayPos.X + drawData.DisplaySize.X;
float T = drawData.DisplayPos.Y;
float B = drawData.DisplayPos.Y + drawData.DisplaySize.Y;
if (bd.HasClipOrigin)
{
gl.GetIntegerv(GLGetPName.ClipPlane0, out int clipOrigin);
if (clipOrigin == (int)GLEnum.UpperLeft)
{
(T, B) = (B, T);
}
}
float[] ortho =
{
2.0f / (R - L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (T - B), 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, 0.0f,
(R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f,
}; };
indices[i] = i; gl.UseProgram(bd.ShaderHandle);
gl.Uniform1f(bd.AttribLocationTex, 0);
gl.UniformMatrix4fv(bd.AttribLocationProjMtx, 1, false, ortho);
if (bd.HasBindSampler)
gl.BindSampler(0, bd.TexSamplers[0]); // linear
gl.BindVertexArray(vao);
gl.BindBuffer(GLBufferTargetARB.ArrayBuffer, bd.VboHandle);
gl.BindBuffer(GLBufferTargetARB.ElementArrayBuffer, bd.ElementsHandle);
gl.EnableVertexAttribArray(bd.AttribLocationVtxPos);
gl.VertexAttribPointer(bd.AttribLocationVtxPos, 2, GLVertexAttribPointerType.Float, false, sizeof(ImDrawVert), (void*)0);
gl.EnableVertexAttribArray(bd.AttribLocationVtxUV);
gl.VertexAttribPointer(bd.AttribLocationVtxUV, 2, GLVertexAttribPointerType.Float, false, sizeof(ImDrawVert), (void*)8);
gl.EnableVertexAttribArray(bd.AttribLocationVtxColor);
gl.VertexAttribPointer(bd.AttribLocationVtxColor, 4, GLVertexAttribPointerType.UnsignedByte, true, sizeof(ImDrawVert), (void*)16);
} }
// Get texture
ImTextureID texId = cmd.GetTexID(); public static void UpdateTexture(ImTextureDataPtr tex)
if (!SDL.RenderGeometry(renderer, texId, vertices, vertices.Length, indices, indices.Length))
{ {
Program.Logger.Log($"Failed to render ImGui draw command: {SDL.GetError()}"); RendererData bd = GetRendererData()!;
} GL gl = bd.Gl;
}
}
}
// Reset render state
platformIo.RendererRenderState = null;
// Restore renderer state
SDL.SetRenderViewport(renderer, old.Viewport);
SDL.SetRenderClipRect(renderer, old.ClipEnabled ? old.ClipRect : new SDL.Rect());
}
private static void UpdateTexture(ImTextureDataPtr tex, nint renderer)
{
if (tex.Status == ImTextureStatus.WantDestroy) if (tex.Status == ImTextureStatus.WantDestroy)
{ {
if (tex.TexID != ImTextureID.Null) if (tex.TexID != ImTextureID.Null)
SDL.DestroyTexture(tex.TexID); {
uint glId = (uint)(nint)tex.TexID;
gl.DeleteTexture(glId);
}
tex.SetTexID(ImTextureID.Null); tex.SetTexID(ImTextureID.Null);
tex.SetStatus(ImTextureStatus.Destroyed); tex.SetStatus(ImTextureStatus.Destroyed);
return; return;
} }
bool needsCreate = tex.TexID == ImTextureID.Null || SDL.GetRendererFromTexture(tex.TexID) != renderer; gl.PixelStoref(GLPixelStoreParameter.UnpackRowLength, 0);
if (tex.Status == ImTextureStatus.WantCreate || needsCreate) gl.PixelStoref(GLPixelStoreParameter.UnpackAlignment, 1);
if (tex.Status == ImTextureStatus.WantCreate)
{ {
if (tex.TexID != ImTextureID.Null) Debug.Assert(tex.Format == ImTextureFormat.Rgba32);
SDL.DestroyTexture(tex.TexID);
// Create texture on the renderer that is actually drawing this viewport. gl.GetIntegerv(GLGetPName.TextureBinding2D, out int lastTex);
// SDL textures are renderer-owned and cannot be shared across renderer instances. uint glTexId = 0;
nint sdlTexture = SDL.CreateTexture(renderer, SDL.PixelFormat.ABGR8888, SDL.TextureAccess.Static, tex.Width, tex.Height); gl.GenTextures(1, ref glTexId);
SDL.UpdateTexture(sdlTexture, nint.Zero, (nint)tex.GetPixels(), tex.GetPitch()); gl.BindTexture(GLTextureTarget.Texture2D, glTexId);
SDL.SetTextureBlendMode(sdlTexture, SDL.BlendMode.Blend); gl.TexParameterf(GLTextureTarget.Texture2D, GLTextureParameterName.MinFilter, (int)GLEnum.Linear);
SDL.SetTextureScaleMode(sdlTexture, SDL.ScaleMode.Linear); gl.TexParameterf(GLTextureTarget.Texture2D, GLTextureParameterName.MagFilter, (int)GLEnum.Linear);
gl.TexParameterf(GLTextureTarget.Texture2D, GLTextureParameterName.WrapS, (int)GLEnum.ClampToEdge);
gl.TexParameterf(GLTextureTarget.Texture2D, GLTextureParameterName.WrapT, (int)GLEnum.ClampToEdge);
gl.TexImage2D(GLTextureTarget.Texture2D, 0, GLInternalFormat.Rgba, tex.Width, tex.Height, 0, GLPixelFormat.Rgba, GLPixelType.UnsignedByte, tex.GetPixels());
tex.SetTexID(sdlTexture); tex.SetTexID((nint)glTexId);
tex.SetStatus(ImTextureStatus.Ok); tex.SetStatus(ImTextureStatus.Ok);
gl.BindTexture(GLTextureTarget.Texture2D, (uint)lastTex);
return; return;
} }
if (tex.Status == ImTextureStatus.WantUpdates) if (tex.Status == ImTextureStatus.WantUpdates)
{ {
// Update selected blocks. We only ever write to textures regions which have never been used before! gl.GetIntegerv(GLGetPName.TextureBinding2D, out int lastTex);
// This backend choose to use tex.Updates[] but you can use tex.UpdateRect to upload a single region. uint glTexId = (uint)(nint)tex.TexID;
nint sdlTexture = tex.TexID; gl.BindTexture(GLTextureTarget.Texture2D, glTexId);
for (int i = 0; i < tex.Updates.Size; i++) for (int i = 0; i < tex.Updates.Size; i++)
{ {
ImTextureRect r = tex.Updates[i]; ImTextureRect r = tex.Updates[i];
SDL.Rect sdlR = new SDL.Rect { X = r.X, Y = r.Y, W = r.W, H = r.H }; gl.PixelStoref(GLPixelStoreParameter.UnpackRowLength, tex.Width);
SDL.UpdateTexture(sdlTexture, sdlR, (nint)tex.GetPixelsAt(r.X, r.Y), tex.GetPitch()); gl.TexSubImage2D(GLTextureTarget.Texture2D, 0, r.X, r.Y, r.W, r.H, GLPixelFormat.Rgba, GLPixelType.UnsignedByte, tex.GetPixelsAt(r.X, r.Y));
} }
gl.PixelStoref(GLPixelStoreParameter.UnpackRowLength, 0);
tex.SetStatus(ImTextureStatus.Ok); tex.SetStatus(ImTextureStatus.Ok);
gl.BindTexture(GLTextureTarget.Texture2D, (uint)lastTex);
} }
} }
public static void CreateDeviceObjects() { }
public static bool CreateDeviceObjects()
{
RendererData bd = GetRendererData()!;
GL gl = bd.Gl;
gl.GetIntegerv(GLGetPName.TextureBinding2D, out int lastTexture);
gl.GetIntegerv(GLGetPName.ArrayBufferBinding, out int lastArrayBuffer);
gl.GetIntegerv(GLGetPName.VertexArrayBinding, out int lastVertexArray);
int glslVersion = 130;
{
string vstr = bd.GlslVersionString.TrimStart().TrimStart('#');
if (vstr.StartsWith("version "))
int.TryParse(vstr["version ".Length..].Split(' ')[0], out glslVersion);
}
const string vertSrc120 = "uniform mat4 ProjMtx;\n" + "attribute vec2 Position;\n" + "attribute vec2 UV;\n" + "attribute vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main() {\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n";
const string fragSrc120 = "#ifdef GL_ES\n precision mediump float;\n#endif\n" + "uniform sampler2D Texture;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main() {\n" + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" + "}\n";
const string vertSrc130 = "uniform mat4 ProjMtx;\n" + "in vec2 Position;\nin vec2 UV;\nin vec4 Color;\n" + "out vec2 Frag_UV;\nout vec4 Frag_Color;\n" + "void main() {\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n";
const string fragSrc130 = "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\nin vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main() {\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n";
const string vertSrc300es = "precision highp float;\n" + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\nout vec4 Frag_Color;\n" + "void main() {\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n";
const string fragSrc300es = "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\nin vec4 Frag_Color;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main() {\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n";
const string vertSrc410 = "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\nout vec4 Frag_Color;\n" + "void main() {\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n";
const string fragSrc410 = "in vec2 Frag_UV;\nin vec4 Frag_Color;\n" + "uniform sampler2D Texture;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main() {\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n";
(string vertSrc, string fragSrc) = glslVersion switch
{
< 130 => (vertSrc120, fragSrc120),
300 => (vertSrc300es, fragSrc300es),
>= 410 => (vertSrc410, fragSrc410),
_ => (vertSrc130, fragSrc130),
};
string fullVert = bd.GlslVersionString + vertSrc;
string fullFrag = bd.GlslVersionString + fragSrc;
uint vert = gl.CreateShader(GLShaderType.VertexShader);
gl.ShaderSource(vert, fullVert);
gl.CompileShader(vert);
if (!CheckShader(gl, vert, "vertex shader")) return false;
uint frag = gl.CreateShader(GLShaderType.FragmentShader);
gl.ShaderSource(frag, fullFrag);
gl.CompileShader(frag);
if (!CheckShader(gl, frag, "fragment shader")) return false;
bd.ShaderHandle = gl.CreateProgram();
gl.AttachShader(bd.ShaderHandle, vert);
gl.AttachShader(bd.ShaderHandle, frag);
gl.LinkProgram(bd.ShaderHandle);
if (!CheckProgram(gl, bd.ShaderHandle, "shader program")) return false;
gl.DetachShader(bd.ShaderHandle, vert);
gl.DetachShader(bd.ShaderHandle, frag);
gl.DeleteShader(vert);
gl.DeleteShader(frag);
bd.AttribLocationTex = gl.GetUniformLocation(bd.ShaderHandle, "Texture");
bd.AttribLocationProjMtx = gl.GetUniformLocation(bd.ShaderHandle, "ProjMtx");
bd.AttribLocationVtxPos = (uint)gl.GetAttribLocation(bd.ShaderHandle, "Position");
bd.AttribLocationVtxUV = (uint)gl.GetAttribLocation(bd.ShaderHandle, "UV");
bd.AttribLocationVtxColor = (uint)gl.GetAttribLocation(bd.ShaderHandle, "Color");
gl.GenBuffers(1, ref bd.VboHandle);
gl.GenBuffers(1, ref bd.ElementsHandle);
if (bd.HasBindSampler)
{
gl.GenSamplers(2, bd.TexSamplers);
for (int i = 0; i < 2; i++)
{
int filter = i == 0 ? (int)GLEnum.Linear : (int)GLEnum.Nearest;
gl.SamplerParameteri(bd.TexSamplers[i], GLSamplerParameterI.TextureMinFilter, filter);
gl.SamplerParameteri(bd.TexSamplers[i], GLSamplerParameterI.TextureMagFilter, filter);
gl.SamplerParameteri(bd.TexSamplers[i], GLSamplerParameterI.TextureWrapS, (int)GLEnum.ClampToEdge);
gl.SamplerParameteri(bd.TexSamplers[i], GLSamplerParameterI.TextureWrapT, (int)GLEnum.ClampToEdge);
}
}
gl.BindTexture(GLTextureTarget.Texture2D, (uint)lastTexture);
gl.BindBuffer(GLBufferTargetARB.ArrayBuffer, (uint)lastArrayBuffer);
gl.BindVertexArray((uint)lastVertexArray);
return true;
}
public static void DestroyDeviceObjects() public static void DestroyDeviceObjects()
{ {
ImVector<ImTextureDataPtr> texures = ImGui.GetPlatformIO().Textures; RendererData? bd = GetRendererData();
for (int i = 0; i < texures.Size; i++) if (bd == null) return;
GL gl = bd.Gl;
if (bd.HasBindSampler && bd.TexSamplers[0] != 0)
{ {
ImTextureDataPtr texture = texures[i]; gl.DeleteSamplers(2, bd.TexSamplers);
texture.Status = ImTextureStatus.WantDestroy; bd.TexSamplers[0] = bd.TexSamplers[1] = 0;
UpdateTexture(texture, GetRendererData().Renderer); }
if (bd.VboHandle != 0)
{
gl.DeleteBuffer(bd.VboHandle);
bd.VboHandle = 0;
}
if (bd.ElementsHandle != 0)
{
gl.DeleteBuffer(bd.ElementsHandle);
bd.ElementsHandle = 0;
}
if (bd.ShaderHandle != 0)
{
gl.DeleteProgram(bd.ShaderHandle);
bd.ShaderHandle = 0;
}
ImVector<ImTextureDataPtr> textures = ImGui.GetPlatformIO().Textures;
for (int i = 0; i < textures.Size; i++)
{
ImTextureDataPtr tex = textures[i];
if (tex.RefCount == 1)
{
tex.SetStatus(ImTextureStatus.WantDestroy);
UpdateTexture(tex);
}
} }
} }
// @formatter:off — disable formatter after this line
private static void InitMultiViewportSupport()
{
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
platformIO.RendererRenderWindow = DelegateStorage.RendererRenderWindowDelegate.GetPtrForDelegate();
}
private static void ShutdownMultiViewportSupport()
{
ImGui.DestroyPlatformWindows();
}
private static void ApplySamplerLinear(RendererData bd)
{
if (bd.HasBindSampler) bd.Gl.BindSampler(0, bd.TexSamplers[0]);
else
{
bd.UseTexParameterToSetSampler = true;
bd.NextSampler = (int)GLEnum.Linear;
}
}
private static void ApplySamplerNearest(RendererData bd)
{
if (bd.HasBindSampler) bd.Gl.BindSampler(0, bd.TexSamplers[1]);
else
{
bd.UseTexParameterToSetSampler = true;
bd.NextSampler = (int)GLEnum.Nearest;
}
}
private static bool CheckShader(GL gl, uint handle, string desc)
{
gl.GetShaderiv(handle, GLShaderParameterName.CompileStatus, out int status);
gl.GetShaderiv(handle, GLShaderParameterName.InfoLogLength, out int logLen);
if (status == 0)
{
string log = logLen > 1 ? gl.GetShaderInfoLog(handle) : "";
Console.Error.WriteLine($"[ImGui OpenGL3] Failed to compile {desc}:\n{log}");
}
return status != 0;
}
private static bool CheckProgram(GL gl, uint handle, string desc)
{
gl.GetProgramiv(handle, GLProgramPropertyARB.LinkStatus, out int status);
gl.GetProgramiv(handle, GLProgramPropertyARB.InfoLogLength, out int logLen);
if (status == 0)
{
string log = logLen > 1 ? gl.GetProgramInfoLog(handle) : "";
Console.Error.WriteLine($"[ImGui OpenGL3] Failed to link {desc}:\n{log}");
}
return status != 0;
}
private static void SetCap(GL gl, GLEnableCap cap, bool enabled)
{
if (enabled) gl.Enable(cap);
else gl.Disable(cap);
}
public static class DelegateStorage public static class DelegateStorage
{ {
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void DrawCallbackResetRenderStateFn(ImDrawListPtr drawList, ImDrawCmdPtr cmd);
internal static readonly DrawCallbackResetRenderStateFn DrawCallbackResetRenderStateDelegate = DrawCallbackResetRenderState;
private static void DrawCallbackResetRenderState(ImDrawListPtr drawList, ImDrawCmdPtr cmd) { } // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void RendererCreateWindowFn(ImGuiViewportPtr viewport); internal delegate void DrawCallbackFn(ImDrawListPtr drawList, ImDrawCmdPtr cmd);
internal static readonly RendererCreateWindowFn RendererCreateWindowDelegate = RendererCreateWindow;
private static void RendererCreateWindow(ImGuiViewportPtr viewport)
{
ImGuiSDL3Platform.ViewPortData? vd = ImGuiUserData<ImGuiSDL3Platform.ViewPortData>.Get(viewport.PlatformUserData);
if (vd == null || vd.Window == nint.Zero)
return;
if (vd.Renderer == nint.Zero) internal static readonly DrawCallbackFn DrawCallbackResetRenderStateDelegate = DrawCallbackResetRenderState;
private static void DrawCallbackResetRenderState(ImDrawListPtr _, ImDrawCmdPtr __) { } // Intentionally empty.
internal static readonly DrawCallbackFn DrawCallbackSetSamplerLinearDelegate = DrawCallbackSetSamplerLinear;
private static void DrawCallbackSetSamplerLinear(ImDrawListPtr _, ImDrawCmdPtr __)
{ {
vd.Renderer = SDL.CreateRenderer(vd.Window, null); RendererData? bd = GetRendererData();
vd.RendererOwned = true; if (bd != null) ApplySamplerLinear(bd);
} }
internal static readonly DrawCallbackFn DrawCallbackSetSamplerNearestDelegate = DrawCallbackSetSamplerNearest;
private static void DrawCallbackSetSamplerNearest(ImDrawListPtr _, ImDrawCmdPtr __)
{
RendererData? bd = GetRendererData();
if (bd != null) ApplySamplerNearest(bd);
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void RendererDestroyWindowFn(ImGuiViewportPtr viewport); internal delegate void RendererRenderWindowFn(ImGuiViewportPtr viewport, void* renderArg);
internal static readonly RendererDestroyWindowFn RendererDestroyWindowDelegate = RendererDestroyWindow;
private static void RendererDestroyWindow(ImGuiViewportPtr viewport)
{
ImGuiSDL3Platform.ViewPortData? vd = ImGuiUserData<ImGuiSDL3Platform.ViewPortData>.Get(viewport.PlatformUserData);
if (vd == null)
return;
if (vd.RendererOwned && vd.Renderer != nint.Zero)
{
SDL.DestroyRenderer(vd.Renderer);
}
vd.Renderer = nint.Zero;
vd.RendererOwned = false;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void RendererRenderWindowFn(ImGuiViewportPtr viewport);
internal static readonly RendererRenderWindowFn RendererRenderWindowDelegate = RendererRenderWindow; internal static readonly RendererRenderWindowFn RendererRenderWindowDelegate = RendererRenderWindow;
private static void RendererRenderWindow(ImGuiViewportPtr viewport) private static void RendererRenderWindow(ImGuiViewportPtr viewport, void* renderArg)
{ {
ImGuiSDL3Platform.ViewPortData? vd = ImGuiUserData<ImGuiSDL3Platform.ViewPortData>.Get(viewport.PlatformUserData); if ((viewport.Flags & ImGuiViewportFlags.NoRendererClear) == 0)
if (vd == null || vd.Renderer == nint.Zero)
{ {
return; RendererData? bd = GetRendererData();
if (bd != null)
{
bd.Gl.ClearColor(0f, 0f, 0f, 1f);
bd.Gl.Clear(GLClearBufferMask.ColorBufferBit);
}
}
RenderDrawData(viewport.DrawData);
}
} }
if (Parent != null)
{
SDL.SetRenderDrawColorFloat(vd.Renderer, Parent.ClearColor.X, Parent.ClearColor.Y, Parent.ClearColor.Z, 0f);
SDL.RenderClear(vd.Renderer);
}
RenderDrawData(viewport.DrawData, vd.Renderer);
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void RendererSwapBuffersFn(ImGuiViewportPtr viewport);
internal static readonly RendererSwapBuffersFn RendererSwapBuffersDelegate = RendererSwapBuffers;
private static void RendererSwapBuffers(ImGuiViewportPtr viewport)
{
ImGuiSDL3Platform.ViewPortData? vd = ImGuiUserData<ImGuiSDL3Platform.ViewPortData>.Get(viewport.PlatformUserData);
if (vd == null || vd.Renderer == nint.Zero)
return;
SDL.RenderPresent(vd.Renderer);
}
}
// @formatter:on — enable formatter after this line
} }
+91 -14
View File
@@ -2,6 +2,8 @@
using Hexa.NET.ImGui; using Hexa.NET.ImGui;
using Hexa.NET.ImGuizmo; using Hexa.NET.ImGuizmo;
using Hexa.NET.ImPlot; using Hexa.NET.ImPlot;
using Hexa.NET.OpenGL;
using HexaGen.Runtime;
using SDL3; using SDL3;
namespace SDL3_TestingSuite.SDL3; namespace SDL3_TestingSuite.SDL3;
@@ -9,7 +11,6 @@ namespace SDL3_TestingSuite.SDL3;
public sealed unsafe class SDL3Window : IDisposable public sealed unsafe class SDL3Window : IDisposable
{ {
public readonly nint Window; public readonly nint Window;
public readonly nint Renderer;
public readonly Vector4 DefaultClearColor = new Vector4(0.06f, 0.06f, 0.06f, 1f); public readonly Vector4 DefaultClearColor = new Vector4(0.06f, 0.06f, 0.06f, 1f);
@@ -30,17 +31,37 @@ public sealed unsafe class SDL3Window : IDisposable
private readonly FileSystemWatcher? _watcher; private readonly FileSystemWatcher? _watcher;
private readonly GL _gl;
public SDL3Window(string name, int posX, int posY, int width, int height, SDL.WindowFlags flags = SDL.WindowFlags.Resizable | SDL.WindowFlags.HighPixelDensity) public SDL3Window(string name, int posX, int posY, int width, int height, SDL.WindowFlags flags = SDL.WindowFlags.Resizable | SDL.WindowFlags.HighPixelDensity)
{ {
if (!SDL.Init(SDL.InitFlags.Events | SDL.InitFlags.Video | SDL.InitFlags.Gamepad)) if (!SDL.Init(SDL.InitFlags.Events | SDL.InitFlags.Video | SDL.InitFlags.Gamepad))
throw new Exception($"SDL_Init failed: {SDL.GetError()}"); throw new Exception($"SDL_Init failed: {SDL.GetError()}");
SDL.GLSetAttribute(SDL.GLAttr.ContextFlags, (int)SDL.GLContextFlag.ForwardCompatible);
SDL.GLSetAttribute(SDL.GLAttr.ContextProfileMask, (int)SDL.GLProfile.Core);
SDL.GLSetAttribute(SDL.GLAttr.ContextMajorVersion, 3);
SDL.GLSetAttribute(SDL.GLAttr.ContextMinorVersion, 3);
SDL.SetHint(SDL.Hints.IMEImplementedUI, "1");
// Create window & renderer // Create window & renderer
if (!SDL.CreateWindowAndRenderer(name, width, height, flags, out Window, out Renderer)) SDL.GLSetAttribute(SDL.GLAttr.DoubleBuffer, 1);
SDL.GLSetAttribute(SDL.GLAttr.DepthSize, 24);
SDL.GLSetAttribute(SDL.GLAttr.StencilSize, 8);
Window = SDL.CreateWindow(name, width, height, flags | SDL.WindowFlags.OpenGL);
if (Window == nint.Zero)
throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}"); throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}");
nint glContext = SDL.GLCreateContext(Window);
if (glContext == nint.Zero)
throw new Exception($"SDL_GL_CreateContext failed: {SDL.GetError()}");
SDL.GLMakeCurrent(Window, glContext);
SDL.GLSetSwapInterval(1);
SDL.SetWindowPosition(Window, posX, posY); SDL.SetWindowPosition(Window, posX, posY);
SDL.SetRenderVSync(Renderer, 1); // SDL.SetRenderVSync(Renderer, 1);
SDL.ShowWindow(Window); SDL.ShowWindow(Window);
// Create ImGui context // Create ImGui context
@@ -92,9 +113,11 @@ public sealed unsafe class SDL3Window : IDisposable
Console.WriteLine(e); Console.WriteLine(e);
} }
_gl = new GL(new BindingsContext(Window, glContext));
// Init platform and renderer // Init platform and renderer
ImGuiSDL3Platform.Init(Window, Renderer, this); ImGuiSDL3Platform.Init(_gl, glContext, Window, this);
ImGuiSDL3Renderer.Init(Renderer, this); ImGuiSDL3Renderer.Init(_gl, "#version 330");
} }
public bool ShouldClose; public bool ShouldClose;
@@ -103,6 +126,8 @@ public sealed unsafe class SDL3Window : IDisposable
{ {
while (!_disposed) while (!_disposed)
{ {
SDL.PumpEvents();
if (ImGui.GetIO().WantTextInput && !SDL.TextInputActive(Window)) if (ImGui.GetIO().WantTextInput && !SDL.TextInputActive(Window))
SDL.StartTextInput(Window); SDL.StartTextInput(Window);
else if (!ImGui.GetIO().WantTextInput && SDL.TextInputActive(Window)) else if (!ImGui.GetIO().WantTextInput && SDL.TextInputActive(Window))
@@ -122,6 +147,10 @@ public sealed unsafe class SDL3Window : IDisposable
} }
} }
_gl.MakeCurrent();
_gl.ClearColor(ClearColor.X, ClearColor.Y, ClearColor.Z, ClearColor.W);
_gl.Clear(GLClearBufferMask.ColorBufferBit);
ImGuiSDL3Platform.NewFrame(); ImGuiSDL3Platform.NewFrame();
ImGuiSDL3Renderer.NewFrame(); ImGuiSDL3Renderer.NewFrame();
ImGui.NewFrame(); ImGui.NewFrame();
@@ -132,11 +161,9 @@ public sealed unsafe class SDL3Window : IDisposable
ImGui.Render(); ImGui.Render();
SDL.SetRenderScale(Renderer, io.DisplayFramebufferScale.X, io.DisplayFramebufferScale.Y); _gl.MakeCurrent();
SDL.SetRenderDrawColorFloat(Renderer, ClearColor.X, ClearColor.Y, ClearColor.Z, ClearColor.W);
SDL.RenderClear(Renderer);
ImGuiSDL3Renderer.RenderDrawData(ImGui.GetDrawData(), Renderer); ImGuiSDL3Renderer.RenderDrawData(ImGui.GetDrawData());
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
{ {
@@ -144,7 +171,8 @@ public sealed unsafe class SDL3Window : IDisposable
ImGui.RenderPlatformWindowsDefault(); ImGui.RenderPlatformWindowsDefault();
} }
SDL.RenderPresent(Renderer); _gl.MakeCurrent();
_gl.SwapBuffers();
if (ShouldClose) if (ShouldClose)
{ {
@@ -203,10 +231,10 @@ public sealed unsafe class SDL3Window : IDisposable
_imPlotContext = null; _imPlotContext = null;
} }
if (Renderer != nint.Zero) // if (Renderer != nint.Zero)
{ // {
SDL.DestroyRenderer(Renderer); // SDL.DestroyRenderer(Renderer);
} // }
if (Window != nint.Zero) if (Window != nint.Zero)
{ {
@@ -215,3 +243,52 @@ public sealed unsafe class SDL3Window : IDisposable
} }
} }
public unsafe class BindingsContext : IGLContext
{
private readonly nint window;
private readonly nint context;
public BindingsContext(nint window, nint context)
{
this.window = window;
this.context = context;
}
public nint Handle => window;
public bool IsCurrent => SDL.GLGetCurrentContext() == context;
public void Dispose() { }
public nint GetProcAddress(string procName)
{
return (nint)SDL.GLGetProcAddress(procName).GetPtrForDelegate();
}
public bool IsExtensionSupported(string extensionName)
{
return SDL.GLExtensionSupported(extensionName);
}
public void MakeCurrent()
{
SDL.GLMakeCurrent(window, context);
}
public void SwapBuffers()
{
SDL.GLSwapWindow(window);
}
public void SwapInterval(int interval)
{
SDL.GLSetSwapInterval(interval);
}
public bool TryGetProcAddress(string procName, out nint procAddress)
{
procAddress = (nint)SDL.GLGetProcAddress(procName).GetPtrForDelegate();
return procAddress != 0;
}
}