using System.Numerics; using Hexa.NET.ImGui; using Hexa.NET.ImGuizmo; using Hexa.NET.ImPlot; using Hexa.NET.OpenGL; using HexaGen.Runtime; using SDL3; namespace SDL3_TestingSuite.SDL3; public sealed unsafe class SDL3Window : IDisposable { public readonly nint Window; public readonly Vector4 DefaultClearColor = new Vector4(0.06f, 0.06f, 0.06f, 1f); private Vector4? _clearColor; public Vector4 ClearColor { get => _clearColor ?? DefaultClearColor; set => _clearColor = value; } public event Action? RenderCallback; private ImGuiContextPtr _imGuiContext; private ImPlotContextPtr _imPlotContext; public bool Disposed => _disposed; private bool _disposed; 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) { if (!SDL.Init(SDL.InitFlags.Events | SDL.InitFlags.Video | SDL.InitFlags.Gamepad)) 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 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()}"); 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.SetRenderVSync(Renderer, 1); SDL.ShowWindow(Window); // Create ImGui context _imGuiContext = ImGui.CreateContext(); _imPlotContext = ImPlot.CreateContext(); ImPlot.SetImGuiContext(_imGuiContext); ImGuizmo.SetImGuiContext(_imGuiContext); ImGuiIOPtr io = ImGui.GetIO(); io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard | ImGuiConfigFlags.NavEnableGamepad | ImGuiConfigFlags.DockingEnable; io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines; io.Fonts.AddFontDefault(); try { string fontsPath = Path.Combine(AppContext.BaseDirectory, "Fonts"); if (!Path.Exists(fontsPath)) Directory.CreateDirectory(fontsPath); _watcher = new FileSystemWatcher(fontsPath); _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime; _watcher.Created += (_, b) => { if (!File.Exists(b.FullPath)) return; if (Path.GetExtension(b.FullPath) != ".ttf") return; ImGui.GetIO().AddFont(b.FullPath); }; _watcher.Deleted += (_, b) => { if (Path.GetExtension(b.FullPath) != ".ttf") return; ImGui.GetIO().RemoveFont(b.Name); }; _watcher.IncludeSubdirectories = false; _watcher.EnableRaisingEvents = true; string[] fonts = Directory.GetFiles(fontsPath, "*.ttf", SearchOption.AllDirectories); foreach (string font in fonts) { io.AddFont(font); } } catch (Exception e) { Console.WriteLine(e); } _gl = new GL(new BindingsContext(Window, glContext)); // Init platform and renderer ImGuiSDL3Platform.Init(_gl, glContext, Window, this); ImGuiSDL3Renderer.Init(_gl, "#version 330"); } public bool ShouldClose; public void Run() { while (!_disposed) { SDL.PumpEvents(); if (ImGui.GetIO().WantTextInput && !SDL.TextInputActive(Window)) SDL.StartTextInput(Window); else if (!ImGui.GetIO().WantTextInput && SDL.TextInputActive(Window)) SDL.StopTextInput(Window); while (SDL.PollEvent(out SDL.Event ev)) { ImGuiSDL3Platform.ProcessEvent(ev); switch ((SDL.EventType)ev.Type) { case SDL.EventType.Terminating: case SDL.EventType.WindowCloseRequested: case SDL.EventType.Quit: ShouldClose = true; break; } } _gl.MakeCurrent(); _gl.ClearColor(ClearColor.X, ClearColor.Y, ClearColor.Z, ClearColor.W); _gl.Clear(GLClearBufferMask.ColorBufferBit); ImGuiSDL3Platform.NewFrame(); ImGuiSDL3Renderer.NewFrame(); ImGui.NewFrame(); RenderCallback?.Invoke(); ImGuiIOPtr io = ImGui.GetIO(); ImGui.Render(); _gl.MakeCurrent(); ImGuiSDL3Renderer.RenderDrawData(ImGui.GetDrawData()); if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) { ImGui.UpdatePlatformWindows(); ImGui.RenderPlatformWindowsDefault(); } _gl.MakeCurrent(); _gl.SwapBuffers(); if (ShouldClose) { Dispose(); break; } } if (!_disposed) Dispose(); } ~SDL3Window() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) return; _disposed = true; if (disposing) { if (_imGuiContext.Handle != null) { ImGui.SetCurrentContext(_imGuiContext); } if (_imPlotContext.Handle != null) { ImPlot.SetCurrentContext(_imPlotContext); } ImGuiSDL3Renderer.Dispose(); ImGuiSDL3Platform.Dispose(); RenderCallback = null; } if (_imGuiContext.Handle != null) { ImGui.SetCurrentContext(null); ImGui.DestroyContext(_imGuiContext); _imGuiContext = null; } if (_imPlotContext.Handle != null) { ImPlot.SetCurrentContext(null); ImPlot.DestroyContext(_imPlotContext); _imPlotContext = null; } // if (Renderer != nint.Zero) // { // SDL.DestroyRenderer(Renderer); // } if (Window != nint.Zero) { SDL.DestroyWindow(Window); } } } 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; } }