feat: OpenTK 4.x OpenGL backend — replaces Silk.NET, default renderer

- Deleted Engine.Graphics.OpenGL (Silk.NET) project
- Created Engine.Graphics.OpenTK with OpenTK 4.9.4
- OpenTKWindow: GameWindow + ProcessEvents() (non-blocking), SwapBuffers()
- OpenTKInputState: KeyboardState/MouseState → Engine.Core.Key
- OpenTKRenderer: raw OpenGL 3.3 Core, managed arrays (no unsafe),
  OpenTK.Mathematics.Matrix4 (column-major, no transpose needed),
  shadow FBO with depth texture, PCF 3x3, full PBR shader
- OpenTKRenderContext + OpenTKBackendRegistrar ('opentk')
- Program.cs: default backend is 'opentk'
- 66/66 tests, 0 errors, 1600 FPS (VSync off)
This commit is contained in:
emil28092005
2026-06-17 21:24:33 +03:00
parent 5687e97a09
commit 270806a189
14 changed files with 684 additions and 737 deletions
@@ -0,0 +1,104 @@
using System.Collections.Generic;
using Engine.Core;
using OpenTK.Windowing.GraphicsLibraryFramework;
using EngineKey = Engine.Core.Key;
using OpenTKKey = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
namespace Engine.Graphics.OpenTK;
/// <summary>
/// OpenTK input-backed implementation of IInputState.
/// </summary>
public sealed class OpenTKInputState : IInputState
{
private readonly HashSet<EngineKey> _keysDown = new();
private readonly HashSet<EngineKey> _keysPressed = new();
private readonly HashSet<EngineKey> _keysReleased = new();
private readonly HashSet<EngineKey> _prevDown = new();
public int MouseX { get; private set; }
public int MouseY { get; private set; }
public bool MouseLeft { get; private set; }
public bool MouseRight { get; private set; }
public bool MouseMiddle { get; private set; }
public float MouseWheelDelta { get; private set; }
public void Update(KeyboardState kb, MouseState ms)
{
MouseX = (int)ms.Position.X;
MouseY = (int)ms.Position.Y;
MouseLeft = ms.IsButtonDown(MouseButton.Left);
MouseRight = ms.IsButtonDown(MouseButton.Right);
MouseMiddle = ms.IsButtonDown(MouseButton.Middle);
MouseWheelDelta = (float)ms.ScrollDelta.Y;
// Compute pressed/released edges
_keysPressed.Clear();
_keysReleased.Clear();
var currentKeys = new HashSet<EngineKey>();
foreach (var openTkKey in AllKeys)
{
if (kb.IsKeyDown(openTkKey))
{
var k = MapKey(openTkKey);
if (k == EngineKey.Unknown) continue;
currentKeys.Add(k);
if (!_prevDown.Contains(k))
_keysPressed.Add(k);
}
}
foreach (var k in _prevDown)
if (!currentKeys.Contains(k))
_keysReleased.Add(k);
_keysDown.Clear();
_keysDown.UnionWith(currentKeys);
_prevDown.Clear();
_prevDown.UnionWith(currentKeys);
}
public void BeginFrame()
{
_keysPressed.Clear();
_keysReleased.Clear();
MouseWheelDelta = 0;
}
public bool IsKeyDown(EngineKey key) => _keysDown.Contains(key);
public bool IsKeyPressed(EngineKey key) => _keysPressed.Contains(key);
public bool IsKeyReleased(EngineKey key) => _keysReleased.Contains(key);
private static readonly OpenTKKey[] AllKeys =
{
OpenTKKey.W, OpenTKKey.A, OpenTKKey.S, OpenTKKey.D,
OpenTKKey.Q, OpenTKKey.E, OpenTKKey.F,
OpenTKKey.LeftShift,
OpenTKKey.Space, OpenTKKey.Escape, OpenTKKey.Enter,
OpenTKKey.Tab, OpenTKKey.Backspace,
OpenTKKey.Up, OpenTKKey.Down, OpenTKKey.Left, OpenTKKey.Right,
};
private static EngineKey MapKey(OpenTKKey key) => key switch
{
OpenTKKey.W => EngineKey.W,
OpenTKKey.A => EngineKey.A,
OpenTKKey.S => EngineKey.S,
OpenTKKey.D => EngineKey.D,
OpenTKKey.Q => EngineKey.Q,
OpenTKKey.E => EngineKey.E,
OpenTKKey.F => EngineKey.F,
OpenTKKey.LeftShift => EngineKey.LeftShift,
OpenTKKey.Space => EngineKey.Space,
OpenTKKey.Escape => EngineKey.Escape,
OpenTKKey.Enter => EngineKey.Enter,
OpenTKKey.Tab => EngineKey.Tab,
OpenTKKey.Backspace => EngineKey.Backspace,
OpenTKKey.Up => EngineKey.Up,
OpenTKKey.Down => EngineKey.Down,
OpenTKKey.Left => EngineKey.Left,
OpenTKKey.Right => EngineKey.Right,
_ => EngineKey.Unknown,
};
}