fix: wire complete ImGui window input
This commit is contained in:
+153
-12
@@ -83,6 +83,8 @@ class Program
|
|||||||
|
|
||||||
var lastWidth = window.Width;
|
var lastWidth = window.Width;
|
||||||
var lastHeight = window.Height;
|
var lastHeight = window.Height;
|
||||||
|
var lastDrawableWidth = window.DrawableWidth;
|
||||||
|
var lastDrawableHeight = window.DrawableHeight;
|
||||||
var frames = 0;
|
var frames = 0;
|
||||||
var lastFpsTime = 0.0;
|
var lastFpsTime = 0.0;
|
||||||
var timing = new Timing();
|
var timing = new Timing();
|
||||||
@@ -132,13 +134,17 @@ class Program
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (window.Width != lastWidth || window.Height != lastHeight)
|
if (window.Width != lastWidth || window.Height != lastHeight ||
|
||||||
|
window.DrawableWidth != lastDrawableWidth || window.DrawableHeight != lastDrawableHeight)
|
||||||
{
|
{
|
||||||
lastWidth = window.Width;
|
lastWidth = window.Width;
|
||||||
lastHeight = window.Height;
|
lastHeight = window.Height;
|
||||||
if (lastWidth > 0 && lastHeight > 0)
|
lastDrawableWidth = window.DrawableWidth;
|
||||||
|
lastDrawableHeight = window.DrawableHeight;
|
||||||
|
if (lastWidth > 0 && lastHeight > 0 &&
|
||||||
|
lastDrawableWidth > 0 && lastDrawableHeight > 0)
|
||||||
{
|
{
|
||||||
renderContext.Resize(lastWidth, lastHeight);
|
renderContext.Resize(lastDrawableWidth, lastDrawableHeight);
|
||||||
ref var cam = ref cameraEntity.Ensure<Camera>();
|
ref var cam = ref cameraEntity.Ensure<Camera>();
|
||||||
cam.AspectRatio = (float)lastWidth / lastHeight;
|
cam.AspectRatio = (float)lastWidth / lastHeight;
|
||||||
cameraEntity.Set(cam);
|
cameraEntity.Set(cam);
|
||||||
@@ -215,14 +221,21 @@ class Program
|
|||||||
|
|
||||||
if (hasImGui)
|
if (hasImGui)
|
||||||
{
|
{
|
||||||
|
var displayWidth = Math.Max(window.Width, 1);
|
||||||
|
var displayHeight = Math.Max(window.Height, 1);
|
||||||
|
var drawableWidth = Math.Max(window.DrawableWidth, 1);
|
||||||
|
var drawableHeight = Math.Max(window.DrawableHeight, 1);
|
||||||
|
var framebufferScaleX = (float)drawableWidth / displayWidth;
|
||||||
|
var framebufferScaleY = (float)drawableHeight / displayHeight;
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
io.DisplaySize = new System.Numerics.Vector2(window.Width, window.Height);
|
UpdateImGuiInput(io, input);
|
||||||
io.MousePos = new System.Numerics.Vector2(input.MouseX, input.MouseY);
|
|
||||||
io.MouseDown[0] = input.MouseLeft;
|
|
||||||
io.MouseDown[1] = input.MouseRight;
|
|
||||||
io.MouseDown[2] = input.MouseMiddle;
|
|
||||||
|
|
||||||
renderer.BeginImGuiFrame();
|
renderer.BeginImGuiFrame(
|
||||||
|
displayWidth,
|
||||||
|
displayHeight,
|
||||||
|
framebufferScaleX,
|
||||||
|
framebufferScaleY,
|
||||||
|
(float)timing.DeltaTime);
|
||||||
|
|
||||||
ImGui.Begin("Cortex Engine Debug");
|
ImGui.Begin("Cortex Engine Debug");
|
||||||
ImGui.Text($"FPS: {frames}");
|
ImGui.Text($"FPS: {frames}");
|
||||||
@@ -388,7 +401,7 @@ class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
var paramsText = sb.ToString();
|
var paramsText = sb.ToString();
|
||||||
ImGui.SetClipboardText(paramsText);
|
window.SetClipboardText(paramsText);
|
||||||
Console.WriteLine("[App] Parameters copied to clipboard:");
|
Console.WriteLine("[App] Parameters copied to clipboard:");
|
||||||
Console.WriteLine(paramsText);
|
Console.WriteLine(paramsText);
|
||||||
}
|
}
|
||||||
@@ -406,8 +419,8 @@ class Program
|
|||||||
recordingPath = $"Videos/cortex_{timestamp}.mp4";
|
recordingPath = $"Videos/cortex_{timestamp}.mp4";
|
||||||
Directory.CreateDirectory("Videos");
|
Directory.CreateDirectory("Videos");
|
||||||
|
|
||||||
var w = window.Width;
|
var w = window.DrawableWidth;
|
||||||
var h = window.Height;
|
var h = window.DrawableHeight;
|
||||||
var outputPath = recordingPath!;
|
var outputPath = recordingPath!;
|
||||||
|
|
||||||
// ArgumentList avoids shell-specific quoting rules and works
|
// ArgumentList avoids shell-specific quoting rules and works
|
||||||
@@ -540,6 +553,134 @@ class Program
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void UpdateImGuiInput(ImGuiIOPtr io, IInputState input)
|
||||||
|
{
|
||||||
|
io.AddMousePosEvent(input.MouseX, input.MouseY);
|
||||||
|
|
||||||
|
foreach (var inputEvent in input.Events)
|
||||||
|
{
|
||||||
|
switch (inputEvent.Type)
|
||||||
|
{
|
||||||
|
case InputEventType.KeyDown:
|
||||||
|
case InputEventType.KeyUp:
|
||||||
|
if (TryMapImGuiKey(inputEvent.Key, out var imguiKey))
|
||||||
|
io.AddKeyEvent(imguiKey, inputEvent.Type == InputEventType.KeyDown);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputEventType.TextInput:
|
||||||
|
if (!string.IsNullOrEmpty(inputEvent.Text))
|
||||||
|
io.AddInputCharactersUTF8(inputEvent.Text);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputEventType.MouseButtonDown:
|
||||||
|
case InputEventType.MouseButtonUp:
|
||||||
|
io.AddMouseButtonEvent(
|
||||||
|
inputEvent.MouseButton,
|
||||||
|
inputEvent.Type == InputEventType.MouseButtonDown);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputEventType.MouseWheel:
|
||||||
|
io.AddMouseWheelEvent(inputEvent.WheelX, inputEvent.WheelY);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputEventType.FocusGained:
|
||||||
|
io.AddFocusEvent(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputEventType.FocusLost:
|
||||||
|
io.AddFocusEvent(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImGui uses separate virtual modifier keys for shortcuts and navigation.
|
||||||
|
io.AddKeyEvent(ImGuiKey.ModCtrl,
|
||||||
|
input.IsKeyDown(Key.LeftControl) || input.IsKeyDown(Key.RightControl));
|
||||||
|
io.AddKeyEvent(ImGuiKey.ModShift,
|
||||||
|
input.IsKeyDown(Key.LeftShift) || input.IsKeyDown(Key.RightShift));
|
||||||
|
io.AddKeyEvent(ImGuiKey.ModAlt,
|
||||||
|
input.IsKeyDown(Key.LeftAlt) || input.IsKeyDown(Key.RightAlt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryMapImGuiKey(Key key, out ImGuiKey imguiKey)
|
||||||
|
{
|
||||||
|
imguiKey = key switch
|
||||||
|
{
|
||||||
|
Key.Space => ImGuiKey.Space,
|
||||||
|
Key.Escape => ImGuiKey.Escape,
|
||||||
|
Key.Enter => ImGuiKey.Enter,
|
||||||
|
Key.Tab => ImGuiKey.Tab,
|
||||||
|
Key.Backspace => ImGuiKey.Backspace,
|
||||||
|
Key.Insert => ImGuiKey.Insert,
|
||||||
|
Key.Delete => ImGuiKey.Delete,
|
||||||
|
Key.Home => ImGuiKey.Home,
|
||||||
|
Key.End => ImGuiKey.End,
|
||||||
|
Key.PageUp => ImGuiKey.PageUp,
|
||||||
|
Key.PageDown => ImGuiKey.PageDown,
|
||||||
|
Key.Left => ImGuiKey.LeftArrow,
|
||||||
|
Key.Right => ImGuiKey.RightArrow,
|
||||||
|
Key.Up => ImGuiKey.UpArrow,
|
||||||
|
Key.Down => ImGuiKey.DownArrow,
|
||||||
|
Key.A => ImGuiKey.A,
|
||||||
|
Key.B => ImGuiKey.B,
|
||||||
|
Key.C => ImGuiKey.C,
|
||||||
|
Key.D => ImGuiKey.D,
|
||||||
|
Key.E => ImGuiKey.E,
|
||||||
|
Key.F => ImGuiKey.F,
|
||||||
|
Key.G => ImGuiKey.G,
|
||||||
|
Key.H => ImGuiKey.H,
|
||||||
|
Key.I => ImGuiKey.I,
|
||||||
|
Key.J => ImGuiKey.J,
|
||||||
|
Key.K => ImGuiKey.K,
|
||||||
|
Key.L => ImGuiKey.L,
|
||||||
|
Key.M => ImGuiKey.M,
|
||||||
|
Key.N => ImGuiKey.N,
|
||||||
|
Key.O => ImGuiKey.O,
|
||||||
|
Key.P => ImGuiKey.P,
|
||||||
|
Key.Q => ImGuiKey.Q,
|
||||||
|
Key.R => ImGuiKey.R,
|
||||||
|
Key.S => ImGuiKey.S,
|
||||||
|
Key.T => ImGuiKey.T,
|
||||||
|
Key.U => ImGuiKey.U,
|
||||||
|
Key.V => ImGuiKey.V,
|
||||||
|
Key.W => ImGuiKey.W,
|
||||||
|
Key.X => ImGuiKey.X,
|
||||||
|
Key.Y => ImGuiKey.Y,
|
||||||
|
Key.Z => ImGuiKey.Z,
|
||||||
|
Key.Zero => ImGuiKey._0,
|
||||||
|
Key.One => ImGuiKey._1,
|
||||||
|
Key.Two => ImGuiKey._2,
|
||||||
|
Key.Three => ImGuiKey._3,
|
||||||
|
Key.Four => ImGuiKey._4,
|
||||||
|
Key.Five => ImGuiKey._5,
|
||||||
|
Key.Six => ImGuiKey._6,
|
||||||
|
Key.Seven => ImGuiKey._7,
|
||||||
|
Key.Eight => ImGuiKey._8,
|
||||||
|
Key.Nine => ImGuiKey._9,
|
||||||
|
Key.F1 => ImGuiKey.F1,
|
||||||
|
Key.F2 => ImGuiKey.F2,
|
||||||
|
Key.F3 => ImGuiKey.F3,
|
||||||
|
Key.F4 => ImGuiKey.F4,
|
||||||
|
Key.F5 => ImGuiKey.F5,
|
||||||
|
Key.F6 => ImGuiKey.F6,
|
||||||
|
Key.F7 => ImGuiKey.F7,
|
||||||
|
Key.F8 => ImGuiKey.F8,
|
||||||
|
Key.F9 => ImGuiKey.F9,
|
||||||
|
Key.F10 => ImGuiKey.F10,
|
||||||
|
Key.F11 => ImGuiKey.F11,
|
||||||
|
Key.F12 => ImGuiKey.F12,
|
||||||
|
Key.LeftShift => ImGuiKey.LeftShift,
|
||||||
|
Key.RightShift => ImGuiKey.RightShift,
|
||||||
|
Key.LeftControl => ImGuiKey.LeftCtrl,
|
||||||
|
Key.RightControl => ImGuiKey.RightCtrl,
|
||||||
|
Key.LeftAlt => ImGuiKey.LeftAlt,
|
||||||
|
Key.RightAlt => ImGuiKey.RightAlt,
|
||||||
|
_ => ImGuiKey.None,
|
||||||
|
};
|
||||||
|
|
||||||
|
return imguiKey != ImGuiKey.None;
|
||||||
|
}
|
||||||
|
|
||||||
static int ParseMcpPort(string[] args)
|
static int ParseMcpPort(string[] args)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < args.Length; i++)
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ public interface IInputState
|
|||||||
bool MouseMiddle { get; }
|
bool MouseMiddle { get; }
|
||||||
float MouseWheelDelta { get; }
|
float MouseWheelDelta { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Events received since the last <see cref="BeginFrame"/> call.
|
||||||
|
/// Backends that do not expose an event queue may return an empty list.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<InputEvent> Events => Array.Empty<InputEvent>();
|
||||||
|
|
||||||
bool IsKeyDown(Key key);
|
bool IsKeyDown(Key key);
|
||||||
bool IsKeyPressed(Key key);
|
bool IsKeyPressed(Key key);
|
||||||
bool IsKeyReleased(Key key);
|
bool IsKeyReleased(Key key);
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ public interface IWindow : IDisposable
|
|||||||
{
|
{
|
||||||
int Width { get; }
|
int Width { get; }
|
||||||
int Height { get; }
|
int Height { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of the drawable Vulkan framebuffer in physical pixels. This can
|
||||||
|
/// differ from <see cref="Width"/>/<see cref="Height"/> on high-DPI displays.
|
||||||
|
/// </summary>
|
||||||
|
int DrawableWidth => Width;
|
||||||
|
int DrawableHeight => Height;
|
||||||
|
|
||||||
bool ShouldClose { get; }
|
bool ShouldClose { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -27,6 +35,11 @@ public interface IWindow : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Places UTF-8 text on the platform clipboard when supported.
|
||||||
|
/// </summary>
|
||||||
|
void SetClipboardText(string text) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Native window handle (e.g. <c>SDL_Window*</c>). Used by backends that need
|
/// Native window handle (e.g. <c>SDL_Window*</c>). Used by backends that need
|
||||||
/// the raw OS handle for surface creation. Returns 0 if not applicable.
|
/// the raw OS handle for surface creation. Returns 0 if not applicable.
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend-agnostic input event captured during the current frame.
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct InputEvent(
|
||||||
|
InputEventType Type,
|
||||||
|
Key Key = Key.Unknown,
|
||||||
|
bool IsRepeat = false,
|
||||||
|
int MouseButton = -1,
|
||||||
|
float WheelX = 0,
|
||||||
|
float WheelY = 0,
|
||||||
|
string? Text = null);
|
||||||
|
|
||||||
|
public enum InputEventType
|
||||||
|
{
|
||||||
|
KeyDown,
|
||||||
|
KeyUp,
|
||||||
|
TextInput,
|
||||||
|
MouseButtonDown,
|
||||||
|
MouseButtonUp,
|
||||||
|
MouseWheel,
|
||||||
|
FocusGained,
|
||||||
|
FocusLost,
|
||||||
|
}
|
||||||
@@ -7,11 +7,12 @@ namespace Engine.Core;
|
|||||||
/// SDL3-backed implementation of <see cref="IInputState"/>.
|
/// SDL3-backed implementation of <see cref="IInputState"/>.
|
||||||
/// Populated by polling SDL events via <see cref="ProcessEvent"/> once per frame.
|
/// Populated by polling SDL events via <see cref="ProcessEvent"/> once per frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class InputMapping : IInputState
|
public sealed unsafe class InputMapping : IInputState
|
||||||
{
|
{
|
||||||
private readonly HashSet<Key> _keysPressed = new();
|
private readonly HashSet<Key> _keysPressed = new();
|
||||||
private readonly HashSet<Key> _keysDown = new();
|
private readonly HashSet<Key> _keysDown = new();
|
||||||
private readonly HashSet<Key> _keysReleased = new();
|
private readonly HashSet<Key> _keysReleased = new();
|
||||||
|
private readonly List<InputEvent> _events = new();
|
||||||
|
|
||||||
public int MouseX { get; private set; }
|
public int MouseX { get; private set; }
|
||||||
public int MouseY { get; private set; }
|
public int MouseY { get; private set; }
|
||||||
@@ -19,11 +20,13 @@ public sealed class InputMapping : IInputState
|
|||||||
public bool MouseRight { get; private set; }
|
public bool MouseRight { get; private set; }
|
||||||
public bool MouseMiddle { get; private set; }
|
public bool MouseMiddle { get; private set; }
|
||||||
public float MouseWheelDelta { get; private set; }
|
public float MouseWheelDelta { get; private set; }
|
||||||
|
public IReadOnlyList<InputEvent> Events => _events;
|
||||||
|
|
||||||
public void BeginFrame()
|
public void BeginFrame()
|
||||||
{
|
{
|
||||||
_keysPressed.Clear();
|
_keysPressed.Clear();
|
||||||
_keysReleased.Clear();
|
_keysReleased.Clear();
|
||||||
|
_events.Clear();
|
||||||
MouseWheelDelta = 0;
|
MouseWheelDelta = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +41,10 @@ public sealed class InputMapping : IInputState
|
|||||||
if (!_keysDown.Contains(key))
|
if (!_keysDown.Contains(key))
|
||||||
_keysPressed.Add(key);
|
_keysPressed.Add(key);
|
||||||
_keysDown.Add(key);
|
_keysDown.Add(key);
|
||||||
|
_events.Add(new InputEvent(
|
||||||
|
InputEventType.KeyDown,
|
||||||
|
Key: key,
|
||||||
|
IsRepeat: evt.key.repeat));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +54,7 @@ public sealed class InputMapping : IInputState
|
|||||||
if (key == Key.Unknown) break;
|
if (key == Key.Unknown) break;
|
||||||
_keysDown.Remove(key);
|
_keysDown.Remove(key);
|
||||||
_keysReleased.Add(key);
|
_keysReleased.Add(key);
|
||||||
|
_events.Add(new InputEvent(InputEventType.KeyUp, Key: key));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,14 +65,46 @@ public sealed class InputMapping : IInputState
|
|||||||
|
|
||||||
case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN:
|
case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||||
SetMouseButton(evt.button.button, true);
|
SetMouseButton(evt.button.button, true);
|
||||||
|
if (evt.button.button is >= 1 and <= 3)
|
||||||
|
_events.Add(new InputEvent(
|
||||||
|
InputEventType.MouseButtonDown,
|
||||||
|
MouseButton: evt.button.button - 1));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP:
|
case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP:
|
||||||
SetMouseButton(evt.button.button, false);
|
SetMouseButton(evt.button.button, false);
|
||||||
|
if (evt.button.button is >= 1 and <= 3)
|
||||||
|
_events.Add(new InputEvent(
|
||||||
|
InputEventType.MouseButtonUp,
|
||||||
|
MouseButton: evt.button.button - 1));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_EventType.SDL_EVENT_MOUSE_WHEEL:
|
case SDL_EventType.SDL_EVENT_MOUSE_WHEEL:
|
||||||
MouseWheelDelta += evt.wheel.y;
|
MouseWheelDelta += evt.wheel.y;
|
||||||
|
_events.Add(new InputEvent(
|
||||||
|
InputEventType.MouseWheel,
|
||||||
|
WheelX: evt.wheel.x,
|
||||||
|
WheelY: evt.wheel.y));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_EventType.SDL_EVENT_TEXT_INPUT:
|
||||||
|
{
|
||||||
|
var text = SDL3.PtrToStringUTF8(evt.text.text);
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
_events.Add(new InputEvent(InputEventType.TextInput, Text: text));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SDL_EventType.SDL_EVENT_WINDOW_FOCUS_GAINED:
|
||||||
|
_events.Add(new InputEvent(InputEventType.FocusGained));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_EventType.SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||||
|
_keysDown.Clear();
|
||||||
|
MouseLeft = false;
|
||||||
|
MouseRight = false;
|
||||||
|
MouseMiddle = false;
|
||||||
|
_events.Add(new InputEvent(InputEventType.FocusLost));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,25 @@ public sealed unsafe class Sdl3Window : IWindow
|
|||||||
|
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
|
public int DrawableWidth { get; private set; }
|
||||||
|
public int DrawableHeight { get; private set; }
|
||||||
public bool ShouldClose { get; private set; }
|
public bool ShouldClose { get; private set; }
|
||||||
public IInputState Input => _input;
|
public IInputState Input => _input;
|
||||||
public nint Handle => (nint)_window;
|
public nint Handle => (nint)_window;
|
||||||
|
|
||||||
public void Close() => ShouldClose = true;
|
public void Close() => ShouldClose = true;
|
||||||
|
|
||||||
|
public void SetClipboardText(string text)
|
||||||
|
{
|
||||||
|
SDL3.SDL_SetClipboardText(text);
|
||||||
|
}
|
||||||
|
|
||||||
public Sdl3Window(string title, int width, int height, bool vulkanSurface = true)
|
public Sdl3Window(string title, int width, int height, bool vulkanSurface = true)
|
||||||
{
|
{
|
||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
|
DrawableWidth = width;
|
||||||
|
DrawableHeight = height;
|
||||||
|
|
||||||
if (!SDL3.SDL_Init(SDL_InitFlags.SDL_INIT_VIDEO))
|
if (!SDL3.SDL_Init(SDL_InitFlags.SDL_INIT_VIDEO))
|
||||||
throw new InvalidOperationException($"SDL_Init failed: {SDL3.SDL_GetError()}");
|
throw new InvalidOperationException($"SDL_Init failed: {SDL3.SDL_GetError()}");
|
||||||
@@ -49,6 +58,8 @@ public sealed unsafe class Sdl3Window : IWindow
|
|||||||
|
|
||||||
SDL3.SDL_ShowWindow(_window);
|
SDL3.SDL_ShowWindow(_window);
|
||||||
SDL3.SDL_RaiseWindow(_window);
|
SDL3.SDL_RaiseWindow(_window);
|
||||||
|
UpdateDrawableSize();
|
||||||
|
SDL3.SDL_StartTextInput(_window);
|
||||||
|
|
||||||
// On Wayland, the compositor needs an event round-trip before mapping
|
// On Wayland, the compositor needs an event round-trip before mapping
|
||||||
// the window. Pump events to flush the show request without blocking.
|
// the window. Pump events to flush the show request without blocking.
|
||||||
@@ -80,6 +91,16 @@ public sealed unsafe class Sdl3Window : IWindow
|
|||||||
case SDL_EventType.SDL_EVENT_WINDOW_RESIZED:
|
case SDL_EventType.SDL_EVENT_WINDOW_RESIZED:
|
||||||
Width = evt.window.data1;
|
Width = evt.window.data1;
|
||||||
Height = evt.window.data2;
|
Height = evt.window.data2;
|
||||||
|
UpdateDrawableSize();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_EventType.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||||
|
DrawableWidth = evt.window.data1;
|
||||||
|
DrawableHeight = evt.window.data2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_EventType.SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
|
||||||
|
UpdateDrawableSize();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_EventType.SDL_EVENT_KEY_DOWN:
|
case SDL_EventType.SDL_EVENT_KEY_DOWN:
|
||||||
@@ -104,11 +125,25 @@ public sealed unsafe class Sdl3Window : IWindow
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateDrawableSize()
|
||||||
|
{
|
||||||
|
if (_window == null) return;
|
||||||
|
|
||||||
|
var pixelWidth = Width;
|
||||||
|
var pixelHeight = Height;
|
||||||
|
if (SDL3.SDL_GetWindowSizeInPixels(_window, &pixelWidth, &pixelHeight))
|
||||||
|
{
|
||||||
|
DrawableWidth = pixelWidth;
|
||||||
|
DrawableHeight = pixelHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed) return;
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
|
SDL3.SDL_StopTextInput(_window);
|
||||||
SDL3.SDL_DestroyWindow(_window);
|
SDL3.SDL_DestroyWindow(_window);
|
||||||
SDL3.SDL_Quit();
|
SDL3.SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -447,11 +447,13 @@ internal sealed unsafe class VulkanImGui : IDisposable
|
|||||||
_indexMemory = im;
|
_indexMemory = im;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NewFrame()
|
public void NewFrame(float displayWidth, float displayHeight,
|
||||||
|
float framebufferScaleX, float framebufferScaleY, float deltaTime)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
io.DisplaySize = new System.Numerics.Vector2(1280, 720);
|
io.DisplaySize = new Vector2(displayWidth, displayHeight);
|
||||||
io.DeltaTime = 0.016f;
|
io.DisplayFramebufferScale = new Vector2(framebufferScaleX, framebufferScaleY);
|
||||||
|
io.DeltaTime = MathF.Max(deltaTime, 1f / 1000f);
|
||||||
ImGui.NewFrame();
|
ImGui.NewFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,13 +522,21 @@ internal sealed unsafe class VulkanImGui : IDisposable
|
|||||||
{
|
{
|
||||||
var pcmd = list.CmdBuffer[i];
|
var pcmd = list.CmdBuffer[i];
|
||||||
var clip = pcmd.ClipRect;
|
var clip = pcmd.ClipRect;
|
||||||
|
var framebufferScale = io.DisplayFramebufferScale;
|
||||||
|
var clipMinX = MathF.Max(clip.X * framebufferScale.X, 0f);
|
||||||
|
var clipMinY = MathF.Max(clip.Y * framebufferScale.Y, 0f);
|
||||||
|
var clipMaxX = MathF.Min(clip.Z * framebufferScale.X, width);
|
||||||
|
var clipMaxY = MathF.Min(clip.W * framebufferScale.Y, height);
|
||||||
|
if (clipMaxX <= clipMinX || clipMaxY <= clipMinY)
|
||||||
|
continue;
|
||||||
|
|
||||||
var scissor = new VkRect2D
|
var scissor = new VkRect2D
|
||||||
{
|
{
|
||||||
Offset = new VkOffset2D { X = (int)clip.X, Y = (int)clip.Y },
|
Offset = new VkOffset2D { X = (int)clipMinX, Y = (int)clipMinY },
|
||||||
Extent = new VkExtent2D
|
Extent = new VkExtent2D
|
||||||
{
|
{
|
||||||
Width = (uint)(clip.Z - clip.X),
|
Width = (uint)(clipMaxX - clipMinX),
|
||||||
Height = (uint)(clip.W - clip.Y),
|
Height = (uint)(clipMaxY - clipMinY),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Vk.vkCmdSetScissor(cmd, 0, 1, &scissor);
|
Vk.vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ internal sealed class VulkanRenderContext : IRenderContext{
|
|||||||
};
|
};
|
||||||
|
|
||||||
_swapchain = new VulkanSwapchain(_ctx.Device, _ctx.PhysicalDevice, _ctx.Surface,
|
_swapchain = new VulkanSwapchain(_ctx.Device, _ctx.PhysicalDevice, _ctx.Surface,
|
||||||
surfaceFormat, window.Width, window.Height, _ctx);
|
surfaceFormat, window.DrawableWidth, window.DrawableHeight, _ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRenderer CreateRenderer()
|
public IRenderer CreateRenderer()
|
||||||
|
|||||||
@@ -179,9 +179,11 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginImGuiFrame()
|
public void BeginImGuiFrame(float displayWidth, float displayHeight,
|
||||||
|
float framebufferScaleX, float framebufferScaleY, float deltaTime)
|
||||||
{
|
{
|
||||||
_imGui?.NewFrame();
|
_imGui?.NewFrame(displayWidth, displayHeight,
|
||||||
|
framebufferScaleX, framebufferScaleY, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndImGuiFrame()
|
public void EndImGuiFrame()
|
||||||
@@ -296,7 +298,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
|
|
||||||
// A minimized window has no drawable surface. Keep the frame fence signaled and wait
|
// A minimized window has no drawable surface. Keep the frame fence signaled and wait
|
||||||
// for the window event loop to provide a non-zero extent before trying to acquire.
|
// for the window event loop to provide a non-zero extent before trying to acquire.
|
||||||
if (_window.Width <= 0 || _window.Height <= 0)
|
if (_window.DrawableWidth <= 0 || _window.DrawableHeight <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Read captured frame from previous render (GPU has finished by now)
|
// Read captured frame from previous render (GPU has finished by now)
|
||||||
@@ -324,7 +326,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
// from this frame would leave the next WaitForFrame blocked forever.
|
// from this frame would leave the next WaitForFrame blocked forever.
|
||||||
if (acquireResult == VkResult.ErrorOutOfDateKHR)
|
if (acquireResult == VkResult.ErrorOutOfDateKHR)
|
||||||
{
|
{
|
||||||
RecreateSwapchain(_window.Width, _window.Height);
|
RecreateSwapchain(_window.DrawableWidth, _window.DrawableHeight);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,7 +659,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
|
|
||||||
if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR)
|
if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR)
|
||||||
{
|
{
|
||||||
RecreateSwapchain(_window.Width, _window.Height);
|
RecreateSwapchain(_window.DrawableWidth, _window.DrawableHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
_frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
|
_frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
|
||||||
|
|||||||
@@ -17,8 +17,11 @@ public interface IRenderer : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called before RenderWorld to begin a new ImGui frame.
|
/// Called before RenderWorld to begin a new ImGui frame.
|
||||||
/// Null if the backend doesn't support ImGui.
|
/// Null if the backend doesn't support ImGui.
|
||||||
|
/// <paramref name="displayWidth"/> and <paramref name="displayHeight"/> are
|
||||||
|
/// logical window coordinates; framebuffer scale maps them to pixels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void BeginImGuiFrame() { }
|
void BeginImGuiFrame(float displayWidth, float displayHeight,
|
||||||
|
float framebufferScaleX, float framebufferScaleY, float deltaTime) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called after RenderWorld to render ImGui draw data.
|
/// Called after RenderWorld to render ImGui draw data.
|
||||||
|
|||||||
Reference in New Issue
Block a user