using System; using System.Diagnostics; using System.Runtime.InteropServices; using ImGuiNET; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; using ErrorCode = OpenTK.Windowing.GraphicsLibraryFramework.ErrorCode; using Vector2 = System.Numerics.Vector2; // ReSharper disable PossibleNullReferenceException namespace Pow3r { internal sealed unsafe partial class Program { private Renderer _renderer = Renderer.Veldrid; [UnmanagedCallersOnly] private static byte* GetClipboardTextCallback(void* userData) { return GLFW.GetClipboardStringRaw((Window*) userData); } [UnmanagedCallersOnly] private static void SetClipboardTextCallback(void* userData, byte* text) { GLFW.SetClipboardStringRaw((Window*) userData, text); } private static readonly GLFWCallbacks.ErrorCallback ErrorCallback = GlfwErrorCallback; private static void GlfwErrorCallback(ErrorCode error, string description) { Console.WriteLine($"{error}: {description}"); } private bool[] _mouseJustPressed = new bool[5]; private bool _fullscreen; private int _monitorIdx; private bool _vsync = true; private GameWindow _window; private readonly Stopwatch _stopwatch = new(); private readonly Cursor*[] _cursors = new Cursor*[9]; private readonly float[] _frameTimings = new float[180]; private int _frameTimeIdx = 0; private int _tps = 60; private void Run(string[] args) { for (var i = 0; i < args.Length; i++) { if (args[i] == "--renderer") { _renderer = Enum.Parse(args[++i]); } else if (args[i] == "--veldrid") { _vdRenderer = Enum.Parse(args[++i]); } else if (args[i] == "--fullscreen") { _fullscreen = true; } else if (args[i] == "--monitor-idx") { _monitorIdx = int.Parse(args[++i]); } else if (args[i] == "--no-vsync") { _vsync = false; } else if (args[i] == "--help") { Console.WriteLine("--renderer "); Console.WriteLine("--veldrid "); Console.WriteLine("--no-vsync"); Console.WriteLine("--fullscreen"); Console.WriteLine("--monitor-idx"); Console.WriteLine("--help"); return; } else { Console.WriteLine($"unknown arg \"{args[i]}\""); return; } } Console.WriteLine($"Renderer: {_renderer}"); if (_renderer == Renderer.Veldrid) Console.WriteLine($"Veldrid API: {_vdRenderer}"); Console.WriteLine($"Fullscreen: {_fullscreen}"); Console.WriteLine($"VSync: {_vsync}"); //NativeLibrary.Load("nvapi64.dll"); GLFW.Init(); GLFW.SetErrorCallback(ErrorCallback); // var sw = Stopwatch.StartNew(); GLFW.WindowHint(WindowHintBool.SrgbCapable, true); var windowSettings = new NativeWindowSettings { Size = (1280, 720), WindowState = WindowState.Maximized, StartVisible = false, Title = "Pow3r" }; var openGLBased = _renderer == Renderer.OpenGL || (_renderer == Renderer.Veldrid && _vdRenderer == VeldridRenderer.OpenGL); if (openGLBased) { windowSettings.API = ContextAPI.OpenGL; if (_renderer == Renderer.Veldrid) { windowSettings.Profile = ContextProfile.Core; windowSettings.APIVersion = new Version(4, 6); windowSettings.Flags = ContextFlags.ForwardCompatible; } else { windowSettings.Profile = ContextProfile.Any; windowSettings.APIVersion = new Version(1, 5); } #if DEBUG windowSettings.Flags |= ContextFlags.Debug; #endif } else { windowSettings.API = ContextAPI.NoAPI; } _window = new GameWindow(GameWindowSettings.Default, windowSettings); // Console.WriteLine(sw.ElapsedMilliseconds); if (_fullscreen) { var monitors = GLFW.GetMonitors(); var monitor = monitors[_monitorIdx]; var monitorMode = GLFW.GetVideoMode(monitor); GLFW.SetWindowMonitor( _window.WindowPtr, monitor, 0, 0, monitorMode->Width, monitorMode->Height, monitorMode->RefreshRate); } if (openGLBased) { _window.VSync = _vsync ? VSyncMode.On : VSyncMode.Off; } var context = ImGui.CreateContext(); ImGui.SetCurrentContext(context); ImGui.StyleColorsDark(); var io = ImGui.GetIO(); io.Fonts.AddFontDefault(); delegate* unmanaged getClipboardCallback = &GetClipboardTextCallback; io.GetClipboardTextFn = (IntPtr) getClipboardCallback; delegate* unmanaged setClipboardCallback = &SetClipboardTextCallback; io.GetClipboardTextFn = (IntPtr) setClipboardCallback; io.ClipboardUserData = (IntPtr) _window.WindowPtr; io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos; io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors; io.KeyMap[(int) ImGuiKey.Tab] = (int) Keys.Tab; io.KeyMap[(int) ImGuiKey.LeftArrow] = (int) Keys.Left; io.KeyMap[(int) ImGuiKey.RightArrow] = (int) Keys.Right; io.KeyMap[(int) ImGuiKey.UpArrow] = (int) Keys.Up; io.KeyMap[(int) ImGuiKey.DownArrow] = (int) Keys.Down; io.KeyMap[(int) ImGuiKey.PageUp] = (int) Keys.PageUp; io.KeyMap[(int) ImGuiKey.PageDown] = (int) Keys.PageDown; io.KeyMap[(int) ImGuiKey.Home] = (int) Keys.Home; io.KeyMap[(int) ImGuiKey.End] = (int) Keys.End; io.KeyMap[(int) ImGuiKey.Delete] = (int) Keys.Delete; io.KeyMap[(int) ImGuiKey.Backspace] = (int) Keys.Backspace; io.KeyMap[(int) ImGuiKey.Enter] = (int) Keys.Enter; io.KeyMap[(int) ImGuiKey.Escape] = (int) Keys.Escape; io.KeyMap[(int) ImGuiKey.A] = (int) Keys.A; io.KeyMap[(int) ImGuiKey.C] = (int) Keys.C; io.KeyMap[(int) ImGuiKey.V] = (int) Keys.V; io.KeyMap[(int) ImGuiKey.X] = (int) Keys.X; io.KeyMap[(int) ImGuiKey.Y] = (int) Keys.Y; io.KeyMap[(int) ImGuiKey.Z] = (int) Keys.Z; _cursors[(int) ImGuiMouseCursor.Arrow] = GLFW.CreateStandardCursor(CursorShape.Arrow); _cursors[(int) ImGuiMouseCursor.TextInput] = GLFW.CreateStandardCursor(CursorShape.IBeam); _cursors[(int) ImGuiMouseCursor.ResizeNS] = GLFW.CreateStandardCursor(CursorShape.VResize); _cursors[(int) ImGuiMouseCursor.ResizeEW] = GLFW.CreateStandardCursor(CursorShape.HResize); _cursors[(int) ImGuiMouseCursor.Hand] = GLFW.CreateStandardCursor(CursorShape.Hand); _cursors[(int) ImGuiMouseCursor.ResizeAll] = GLFW.CreateStandardCursor(CursorShape.Arrow); _cursors[(int) ImGuiMouseCursor.ResizeNESW] = GLFW.CreateStandardCursor(CursorShape.Arrow); _cursors[(int) ImGuiMouseCursor.ResizeNWSE] = GLFW.CreateStandardCursor(CursorShape.Arrow); _cursors[(int) ImGuiMouseCursor.NotAllowed] = GLFW.CreateStandardCursor(CursorShape.Arrow); InitRenderer(); _window.MouseDown += OnMouseDown; _window.TextInput += WindowOnTextInput; _window.MouseWheel += WindowOnMouseWheel; _window.KeyDown += args => KeyCallback(args, true); _window.KeyUp += args => KeyCallback(args, false); _stopwatch.Start(); LoadFromDisk(); _window.IsVisible = true; var lastTick = TimeSpan.Zero; var lastFrame = TimeSpan.Zero; var curTime = TimeSpan.Zero; while (!GLFW.WindowShouldClose(_window.WindowPtr)) { NativeWindow.ProcessWindowEvents(false); var tickSpan = TimeSpan.FromSeconds(1f / _tps); while (curTime - lastTick > tickSpan) { lastTick += tickSpan; Tick((float) tickSpan.TotalSeconds); } _frameTimeIdx = (_frameTimeIdx + 1) % _frameTimings.Length; var dt = curTime - lastFrame; lastFrame = curTime; _frameTimings[_frameTimeIdx] = (float) dt.TotalMilliseconds; FrameUpdate((float) dt.TotalSeconds); Render(); curTime = _stopwatch.Elapsed; } SaveToDisk(); } private static void KeyCallback(KeyboardKeyEventArgs obj, bool down) { var io = ImGui.GetIO(); if (obj.Key ==Keys.Unknown) return; var keyInt = (int) obj.Key; io.KeysDown[keyInt] = down; io.KeyCtrl = io.KeysDown[(int) Keys.LeftControl] || io.KeysDown[(int) Keys.RightControl]; io.KeyShift = io.KeysDown[(int) Keys.LeftShift] || io.KeysDown[(int) Keys.RightShift]; io.KeyAlt = io.KeysDown[(int) Keys.LeftAlt] || io.KeysDown[(int) Keys.RightAlt]; } private static void WindowOnMouseWheel(MouseWheelEventArgs obj) { var io = ImGui.GetIO(); io.MouseWheelH += obj.OffsetX; io.MouseWheel += obj.OffsetY; } private static void WindowOnTextInput(TextInputEventArgs obj) { var io = ImGui.GetIO(); io.AddInputCharacter((uint) obj.Unicode); } private void OnMouseDown(MouseButtonEventArgs obj) { var button = (int) obj.Button; if (obj.IsPressed && button < _mouseJustPressed.Length) _mouseJustPressed[button] = true; } private void FrameUpdate(float dt) { //var sw = Stopwatch.StartNew(); var io = ImGui.GetIO(); GLFW.GetFramebufferSize(_window.WindowPtr, out var fbW, out var fbH); GLFW.GetWindowSize(_window.WindowPtr, out var wW, out var wH); io.DisplaySize = new Vector2(wW, wH); io.DisplayFramebufferScale = new Vector2(fbW / (float) wW, fbH / (float) wH); io.DeltaTime = dt; UpdateMouseState(io); UpdateCursorState(io); //Console.WriteLine($"INPUT: {sw.Elapsed.TotalMilliseconds}"); ImGui.NewFrame(); DoUI(dt); } private void UpdateCursorState(ImGuiIOPtr io) { var cursor = ImGui.GetMouseCursor(); if (cursor == ImGuiMouseCursor.None) { GLFW.SetInputMode(_window.WindowPtr, CursorStateAttribute.Cursor, CursorModeValue.CursorHidden); } else { GLFW.SetCursor(_window.WindowPtr, _cursors[(int) cursor]); GLFW.SetInputMode(_window.WindowPtr, CursorStateAttribute.Cursor, CursorModeValue.CursorNormal); } } private void UpdateMouseState(ImGuiIOPtr io) { for (var i = 0; i < io.MouseDown.Count; i++) { io.MouseDown[i] = _mouseJustPressed[i] || GLFW.GetMouseButton(_window.WindowPtr, (MouseButton) i) == InputAction.Press; _mouseJustPressed[i] = false; } var oldMousePos = io.MousePos; io.MousePos = new Vector2(float.PositiveInfinity, float.PositiveInfinity); var focused = _window.IsFocused; if (focused) { if (io.WantSetMousePos) { GLFW.SetCursorPos(_window.WindowPtr, oldMousePos.X, oldMousePos.Y); } else { GLFW.GetCursorPos(_window.WindowPtr, out var x, out var y); io.MousePos = new Vector2((float) x, (float) y); } } } private void InitRenderer() { switch (_renderer) { case Renderer.OpenGL: InitOpenGL(); break; case Renderer.Veldrid: InitVeldrid(); break; } } private void Render() { ImGui.Render(); switch (_renderer) { case Renderer.OpenGL: RenderOpenGL(); break; case Renderer.Veldrid: RenderVeldrid(); break; } } private static void Main(string[] args) { new Program().Run(args); } public enum Renderer { OpenGL, Veldrid } } }