From 129aae563e2531b29b90bff1ffb110234bef2718 Mon Sep 17 00:00:00 2001 From: Syntriax Date: Wed, 28 Jan 2026 22:38:17 +0300 Subject: [PATCH] feat: added basic window, input & drawing --- Engine | 2 +- Platforms/Desktop/Desktop.csproj | 2 + .../CameraController2D.cs | 31 ++ .../ISdl3EventProcessor.cs | 8 + .../Engine.Integration.SDL3/Sdl3Camera2D.cs | 86 ++++++ .../Engine.Integration.SDL3/Sdl3GameInputs.cs | 134 +++++++++ .../Sdl3KeyboardInputs.cs | 87 ++++++ .../Sdl3TriangleBatch.cs | 269 ++++++++++++++++++ .../{SdlWindow.cs => Sdl3Window.cs} | 106 ++----- .../Sdl3WindowManager.cs | 71 +++++ .../Windowing/IWindow.cs | 1 - Platforms/Desktop/Program.cs | 53 +++- 12 files changed, 754 insertions(+), 96 deletions(-) create mode 100644 Platforms/Desktop/Engine.Integration.SDL3/CameraController2D.cs create mode 100644 Platforms/Desktop/Engine.Integration.SDL3/ISdl3EventProcessor.cs create mode 100644 Platforms/Desktop/Engine.Integration.SDL3/Sdl3Camera2D.cs create mode 100644 Platforms/Desktop/Engine.Integration.SDL3/Sdl3GameInputs.cs create mode 100644 Platforms/Desktop/Engine.Integration.SDL3/Sdl3KeyboardInputs.cs create mode 100644 Platforms/Desktop/Engine.Integration.SDL3/Sdl3TriangleBatch.cs rename Platforms/Desktop/Engine.Integration.SDL3/{SdlWindow.cs => Sdl3Window.cs} (67%) create mode 100644 Platforms/Desktop/Engine.Integration.SDL3/Sdl3WindowManager.cs diff --git a/Engine b/Engine index ad444de..b9f3227 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit ad444decbb7fad331278415d2437c9a01df694ab +Subproject commit b9f3227f737a61be2860b998ca9ed99e268dcbf5 diff --git a/Platforms/Desktop/Desktop.csproj b/Platforms/Desktop/Desktop.csproj index e41705f..6d52c57 100644 --- a/Platforms/Desktop/Desktop.csproj +++ b/Platforms/Desktop/Desktop.csproj @@ -10,6 +10,7 @@ MyUniverse + true app.manifest Icon.ico @@ -28,6 +29,7 @@ + diff --git a/Platforms/Desktop/Engine.Integration.SDL3/CameraController2D.cs b/Platforms/Desktop/Engine.Integration.SDL3/CameraController2D.cs new file mode 100644 index 0000000..039eb52 --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/CameraController2D.cs @@ -0,0 +1,31 @@ +using Engine.Core; + +using SDL3; + +namespace Engine.Integration.SDL3; + +public class CameraController2D : Behaviour2D, IFirstFrameUpdate, IUpdate +{ + private Sdl3KeyboardInputs keyboardInputs = null!; + private ICamera2D camera = null!; + + public void FirstActiveFrame() + { + keyboardInputs = Universe.FindRequiredBehaviour(); + camera = BehaviourController.GetRequiredBehaviourInParent(); + } + + public void Update() + { + if (keyboardInputs.IsPressed(SDL.Keycode.W)) Transform.Position += Transform.Up * Universe.Time.DeltaTime * 100f; + if (keyboardInputs.IsPressed(SDL.Keycode.A)) Transform.Position += Transform.Left * Universe.Time.DeltaTime * 100f; + if (keyboardInputs.IsPressed(SDL.Keycode.S)) Transform.Position += Transform.Down * Universe.Time.DeltaTime * 100f; + if (keyboardInputs.IsPressed(SDL.Keycode.D)) Transform.Position += Transform.Right * Universe.Time.DeltaTime * 100f; + + if (keyboardInputs.IsPressed(SDL.Keycode.Q)) Transform.Rotation += Universe.Time.DeltaTime * 45f; + if (keyboardInputs.IsPressed(SDL.Keycode.E)) Transform.Rotation -= Universe.Time.DeltaTime * 45f; + + if (keyboardInputs.IsPressed(SDL.Keycode.R)) camera.Zoom += Universe.Time.DeltaTime * 15f; + if (keyboardInputs.IsPressed(SDL.Keycode.F)) camera.Zoom -= Universe.Time.DeltaTime * 15f; + } +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/ISdl3EventProcessor.cs b/Platforms/Desktop/Engine.Integration.SDL3/ISdl3EventProcessor.cs new file mode 100644 index 0000000..578f8bd --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/ISdl3EventProcessor.cs @@ -0,0 +1,8 @@ +using SDL3; + +namespace Engine.Integration.SDL3; + +public interface ISdl3EventProcessor +{ + void Process(SDL.Event sdlEvent); +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/Sdl3Camera2D.cs b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3Camera2D.cs new file mode 100644 index 0000000..9750799 --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3Camera2D.cs @@ -0,0 +1,86 @@ +using Engine.Core; + +namespace Engine.Integration.SDL3; + +public class Sdl3Camera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw +{ + public Event OnViewMatrixChanged { get; } = new(); + public Event OnProjectionMatrixChanged { get; } = new(); + public Event OnViewportChanged { get; } = new(); + public Event OnZoomChanged { get; } = new(); + + private IWindow window = null!; + public ITransform2D Transform { get; private set; } = null!; + + public Matrix4x4 ProjectionMatrix + { + get; + private set + { + if (field == value) + return; + + field = value; + OnProjectionMatrixChanged.Invoke(this); + } + } = Matrix4x4.Identity; + + public Matrix4x4 ViewMatrix + { + get; + private set + { + if (field == value) + return; + + field = value; + OnViewMatrixChanged.Invoke(this); + } + } = Matrix4x4.Identity; + + public float Zoom + { + get; + set + { + float newValue = Math.Max(0.1f, value); + + if (field == newValue) + return; + + field = newValue; + OnZoomChanged.Invoke(this); + } + } = 1f; + + // TODO This causes delay since OnPreDraw calls assuming this is called in in Update + public Vector2D ScreenToWorldPosition(Vector2D screenPosition) + { + Vector2D worldPosition = Vector2D.Transform(screenPosition, Transform); + return worldPosition; + } + + public Vector2D WorldToScreenPosition(Vector2D worldPosition) + { + Vector2D screenPosition = Vector2D.Transform(worldPosition, Transform); + return screenPosition; + } + + public void LastActiveFrame() => Transform = null!; + public void FirstActiveFrame() + { + window = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour(); + Transform = BehaviourController.GetRequiredBehaviour(); + } + + public void PreDraw() + { + ProjectionMatrix = Matrix4x4.CreateOrthographicViewCentered(window.Size.X, window.Size.Y); + + ViewMatrix = Matrix4x4.Identity + .ApplyScale(Transform.Scale.X.Max(Transform.Scale.Y)) + .ApplyScale(Zoom) + .ApplyRotationZ(-Transform.Rotation * Math.DegreeToRadian) + .ApplyTranslation(new Vector3D(-Transform.Position.X, -Transform.Position.Y, 0f)); + } +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/Sdl3GameInputs.cs b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3GameInputs.cs new file mode 100644 index 0000000..74b1482 --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3GameInputs.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; + +using Engine.Core; +using Engine.Systems.Input; + +using MyUniverse.Shared.Behaviours.Abstract; + +using SDL3; + +namespace Engine.Integration.SDL3; + +public class Sdl3GameInputs : Behaviour, IGameInputs, IFirstFrameUpdate +{ + public IButtonInputs.InputEvent OnAnyButtonPressed { get; } = new(); + public IButtonInputs.InputEvent OnAnyButtonReleased { get; } = new(); + + private readonly Dictionary.InputEvent> OnPressed = new(256); + private readonly Dictionary.InputEvent> OnReleased = new(256); + + private readonly Dictionary keyMappingReversed = []; + private readonly Dictionary keyMapping = new() { + { SDL.Keycode.Space, IGameInputs.Button.Interact } + }; + + private Sdl3KeyboardInputs keyboardInputs = null!; + + public void FirstActiveFrame() + { + keyboardInputs = BehaviourController.GetOrAddBehaviour(); + keyboardInputs.OnAnyButtonPressed.AddListener(AnyPressCallback); + keyboardInputs.OnAnyButtonReleased.AddListener(AnyReleaseCallback); + + foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping) + keyboardInputs.RegisterOnPress(key, PressCallback); + + foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping) + keyboardInputs.RegisterOnRelease(key, ReleaseCallback); + } + + public void ExitUniverse(IUniverse universe) + { + keyboardInputs.OnAnyButtonPressed.RemoveListener(AnyPressCallback); + keyboardInputs.OnAnyButtonReleased.RemoveListener(AnyReleaseCallback); + + foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping) + keyboardInputs.UnregisterOnPress(key, PressCallback); + + foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping) + keyboardInputs.UnregisterOnRelease(key, ReleaseCallback); + } + + private void PressCallback(IButtonInputs sender, IButtonInputs.ButtonCallbackArguments args) + { + if (!keyMapping.TryGetValue(args.Button, out IGameInputs.Button button)) + return; + + if (!OnPressed.TryGetValue(button, out IButtonInputs.InputEvent? @event)) + return; + + @event.Invoke(this, new(button)); + } + + private void ReleaseCallback(IButtonInputs sender, IButtonInputs.ButtonCallbackArguments args) + { + if (!keyMapping.TryGetValue(args.Button, out IGameInputs.Button button)) + return; + + if (!OnReleased.TryGetValue(button, out IButtonInputs.InputEvent? @event)) + return; + + @event.Invoke(this, new(button)); + } + + private void AnyReleaseCallback(IButtonInputs sender, IButtonInputs.ButtonCallbackArguments args) + { + if (keyMapping.TryGetValue(args.Button, out IGameInputs.Button button)) + OnAnyButtonPressed?.Invoke(this, new(button)); + } + + private void AnyPressCallback(IButtonInputs sender, IButtonInputs.ButtonCallbackArguments args) + { + if (keyMapping.TryGetValue(args.Button, out IGameInputs.Button button)) + OnAnyButtonPressed?.Invoke(this, new(button)); + } + + public void RegisterOnPress(IGameInputs.Button button, IButtonInputs.InputEvent.EventHandler callback) + { + if (!OnPressed.TryGetValue(button, out IButtonInputs.InputEvent? @event)) + { + @event = new(); + OnPressed.Add(button, @event); + } + + @event.AddListener(callback); + } + + public void UnregisterOnPress(IGameInputs.Button button, IButtonInputs.InputEvent.EventHandler callback) + { + if (OnPressed.TryGetValue(button, out IButtonInputs.InputEvent? @event)) + @event.RemoveListener(callback); + } + + public void RegisterOnRelease(IGameInputs.Button button, IButtonInputs.InputEvent.EventHandler callback) + { + if (!OnReleased.TryGetValue(button, out IButtonInputs.InputEvent? @event)) + { + @event = new(); + OnReleased.Add(button, @event); + } + + @event.AddListener(callback); + } + + public void UnregisterOnRelease(IGameInputs.Button button, IButtonInputs.InputEvent.EventHandler callback) + { + if (OnReleased.TryGetValue(button, out IButtonInputs.InputEvent? @event)) + @event.RemoveListener(callback); + } + + public bool IsPressed(IGameInputs.Button button) + { + if (!keyMappingReversed.TryGetValue(button, out SDL.Keycode key)) + throw new($"{Enum.GetName(button)} is not mapped correctly"); + + return keyboardInputs.IsPressed(key); + } + + public Sdl3GameInputs() + { + foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping) + keyMappingReversed.Add(button, key); + } +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/Sdl3KeyboardInputs.cs b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3KeyboardInputs.cs new file mode 100644 index 0000000..cd75dff --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3KeyboardInputs.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; + +using Engine.Core; +using Engine.Systems.Input; + +using SDL3; + +namespace Engine.Integration.SDL3; + +public class Sdl3KeyboardInputs : Behaviour, ISdl3EventProcessor, IButtonInputs +{ + public IButtonInputs.InputEvent OnAnyButtonPressed { get; } = new(); + public IButtonInputs.InputEvent OnAnyButtonReleased { get; } = new(); + + private readonly Dictionary.InputEvent> OnPressed = new(256); + private readonly Dictionary.InputEvent> OnReleased = new(256); + + private readonly FastList currentlyPressedKeys = []; + + public void RegisterOnPress(SDL.Keycode key, IButtonInputs.InputEvent.EventHandler callback) + { + if (!OnPressed.TryGetValue(key, out IButtonInputs.InputEvent? delegateCallback)) + { + delegateCallback = new(); + OnPressed.Add(key, delegateCallback); + } + + delegateCallback.AddListener(callback); + } + + public void UnregisterOnPress(SDL.Keycode key, IButtonInputs.InputEvent.EventHandler callback) + { + if (OnPressed.TryGetValue(key, out IButtonInputs.InputEvent? delegateCallback)) + delegateCallback.RemoveListener(callback); + } + + public void RegisterOnRelease(SDL.Keycode key, IButtonInputs.InputEvent.EventHandler callback) + { + if (!OnReleased.TryGetValue(key, out IButtonInputs.InputEvent? delegateCallback)) + { + delegateCallback = new(); + OnReleased.Add(key, delegateCallback); + } + + delegateCallback.AddListener(callback); + } + + public void UnregisterOnRelease(SDL.Keycode key, IButtonInputs.InputEvent.EventHandler callback) + { + if (OnReleased.TryGetValue(key, out IButtonInputs.InputEvent? delegateCallback)) + delegateCallback.RemoveListener(callback); + } + + public bool IsPressed(SDL.Keycode button) => currentlyPressedKeys.Contains(button); + + public void Process(SDL.Event sdlEvent) + { + switch ((SDL.EventType)sdlEvent.Type) + { + case SDL.EventType.KeyDown: + SDL.Keycode currentlyPressedKey = sdlEvent.Key.Key; + if (IsPressed(currentlyPressedKey)) + return; + + currentlyPressedKeys.Add(currentlyPressedKey); + + if (OnPressed.TryGetValue(currentlyPressedKey, out IButtonInputs.InputEvent? pressCallback)) + pressCallback?.Invoke(this, new(currentlyPressedKey)); + + OnAnyButtonPressed?.Invoke(this, new(currentlyPressedKey)); + return; + + case SDL.EventType.KeyUp: + SDL.Keycode previouslyPressedKey = sdlEvent.Key.Key; + if (!IsPressed(previouslyPressedKey)) + return; + + currentlyPressedKeys.Remove(previouslyPressedKey); + + if (OnReleased.TryGetValue(previouslyPressedKey, out IButtonInputs.InputEvent? releaseCallback)) + releaseCallback?.Invoke(this, new(previouslyPressedKey)); + + OnAnyButtonReleased?.Invoke(this, new(previouslyPressedKey)); + return; + } + } +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/Sdl3TriangleBatch.cs b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3TriangleBatch.cs new file mode 100644 index 0000000..3dd64e0 --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3TriangleBatch.cs @@ -0,0 +1,269 @@ +using System; +using Engine.Core; +using Engine.Systems.Graphics; +using SDL3; +using Silk.NET.OpenGL; + +namespace Engine.Integration.SDL3; + +public class Sdl3TriangleBatch : Behaviour, ITriangleBatch, IFirstFrameUpdate +{ + private GL gl = null!; + private readonly uint MaxVertices = 1024; + private uint vao; + private uint vbo; + private uint shader; + + private uint vertexIndex = 0; + private VertexPositionColor[] vertices = new VertexPositionColor[1024]; + + private Matrix4x4 view = Matrix4x4.Identity; + private Matrix4x4 projection = Matrix4x4.Identity; + private Sdl3Window sdl3window = null!; + private ICamera? camera = null!; + private nint glContext; + + public void FirstActiveFrame() + { + sdl3window = BehaviourController.GetRequiredBehaviourInParent(); + camera = BehaviourController.GetRequiredBehaviourInParent(); + nint window = sdl3window.Window; + + SDL.GLSetAttribute(SDL.GLAttr.ContextMajorVersion, 3); + SDL.GLSetAttribute(SDL.GLAttr.ContextMinorVersion, 3); + SDL.GLSetAttribute(SDL.GLAttr.ContextProfileMask, (int)SDL.GLProfile.Core); + SDL.GLSetAttribute(SDL.GLAttr.DoubleBuffer, 1); + + glContext = SDL.GLCreateContext(window); + if (glContext == nint.Zero) + throw new Exception(SDL.GetError()); + + SDL.GLMakeCurrent(window, glContext); + + gl = GL.GetApi(procName => SDL.GLGetProcAddress(procName)); + gl.Viewport(0, 0, (uint)sdl3window.Size.X, (uint)sdl3window.Size.Y); + vertices = new VertexPositionColor[MaxVertices]; + + // --- GL objects --- + vao = gl.GenVertexArray(); + vbo = gl.GenBuffer(); + + gl.BindVertexArray(vao); + gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo); + + unsafe + { + gl.BufferData( + BufferTargetARB.ArrayBuffer, + (nuint)(MaxVertices * sizeof(VertexPositionColor)), + null, + BufferUsageARB.DynamicDraw + ); + + // position + gl.EnableVertexAttribArray(0); + gl.VertexAttribPointer( + 0, + 3, + VertexAttribPointerType.Float, + false, + (uint)sizeof(VertexPositionColor), + (void*)0 + ); + + // color + gl.EnableVertexAttribArray(1); + gl.VertexAttribPointer( + 1, + 4, + VertexAttribPointerType.Float, + false, + (uint)sizeof(VertexPositionColor), + (void*)(3 * sizeof(float)) + ); + + gl.BindVertexArray(0); + } + + shader = CreateShader(); + } + + public void Draw(Triangle triangle, ColorRGBA colorRGBA) + { + if (vertexIndex + 3 >= vertices.Length) + Flush(); + + Vector2D A = triangle.A; + Vector2D B = triangle.B; + Vector2D C = triangle.C; + + Vector4D c = new Vector4D(colorRGBA.R, colorRGBA.G, colorRGBA.B, colorRGBA.A) / 256f; + + vertices[vertexIndex++] = new() + { + Position = new Vector3D(A.X, A.Y, 0f), + Color = c + }; + + vertices[vertexIndex++] = new() + { + Position = new Vector3D(B.X, B.Y, 0f), + Color = c + }; + + vertices[vertexIndex++] = new() + { + Position = new Vector3D(C.X, C.Y, 0f), + Color = c + }; + } + + public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null) + { + this.view = view ?? camera?.ViewMatrix ?? Matrix4x4.Identity; + + if (projection != null) + this.projection = projection.Value; + else + this.projection = camera?.ProjectionMatrix ?? Matrix4x4.CreateOrthographicViewCentered(sdl3window.Size.X, sdl3window.Size.Y); + + vertexIndex = 0; + SDL.GLMakeCurrent(sdl3window.Window, glContext); + gl.ClearColor(sdl3window.BackgroundColor.R / 256f, sdl3window.BackgroundColor.G / 256f, sdl3window.BackgroundColor.B / 256f, 1f); + gl.Clear(ClearBufferMask.ColorBufferBit); + } + + public void End() + { + Flush(); + + SDL.GLSwapWindow(sdl3window.Window); + } + + private void Flush() + { + if (vertexIndex == 0) + return; + + gl.UseProgram(shader); + + int viewLoc = gl.GetUniformLocation(shader, "uView"); + int projLoc = gl.GetUniformLocation(shader, "uProjection"); + + unsafe + { + Span temp = stackalloc float[16]; + + CopyTo(view.Transposed, temp); + fixed (float* v = temp) + gl.UniformMatrix4(viewLoc, 1, false, v); + + CopyTo(projection.Transposed, temp); + fixed (float* p = temp) + gl.UniformMatrix4(projLoc, 1, false, p); + + gl.BindVertexArray(vao); + gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo); + + fixed (VertexPositionColor* ptr = vertices) + { + gl.BufferSubData( + BufferTargetARB.ArrayBuffer, + 0, + (nuint)(vertexIndex * sizeof(VertexPositionColor)), + ptr + ); + } + } + + gl.DrawArrays(PrimitiveType.Triangles, 0, vertexIndex); + + vertexIndex = 0; + } + + public static void CopyTo(Matrix4x4 m, Span destination) + { + if (destination.Length < 16) + throw new ArgumentException("Destination span must have at least 16 elements."); + + destination[0] = m.M11; + destination[1] = m.M12; + destination[2] = m.M13; + destination[3] = m.M14; + + destination[4] = m.M21; + destination[5] = m.M22; + destination[6] = m.M23; + destination[7] = m.M24; + + destination[8] = m.M31; + destination[9] = m.M32; + destination[10] = m.M33; + destination[11] = m.M34; + + destination[12] = m.M41; + destination[13] = m.M42; + destination[14] = m.M43; + destination[15] = m.M44; + } + + private uint CreateShader() + { + const string vertexSrc = """ + #version 330 core + layout (location = 0) in vec3 aPosition; + layout (location = 1) in vec4 aColor; + + uniform mat4 uView; + uniform mat4 uProjection; + + out vec4 vColor; + + void main() + { + gl_Position = uProjection * uView * vec4(aPosition, 1.0); + vColor = aColor; + } + """; + + const string fragmentSrc = """ + #version 330 core + in vec4 vColor; + out vec4 FragColor; + + void main() + { + FragColor = vColor; + } + """; + + uint vs = CompileShader(ShaderType.VertexShader, vertexSrc); + uint fs = CompileShader(ShaderType.FragmentShader, fragmentSrc); + + uint program = gl.CreateProgram(); + gl.AttachShader(program, vs); + gl.AttachShader(program, fs); + gl.LinkProgram(program); + + gl.DeleteShader(vs); + gl.DeleteShader(fs); + + return program; + } + + private uint CompileShader(ShaderType type, string src) + { + uint shader = gl.CreateShader(type); + gl.ShaderSource(shader, src); + gl.CompileShader(shader); + return shader; + } + + struct VertexPositionColor + { + public Vector3D Position; + public Vector4D Color; + + public const int SizeInBytes = (3 + 4) * sizeof(float); + } +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3Window.cs similarity index 67% rename from Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs rename to Platforms/Desktop/Engine.Integration.SDL3/Sdl3Window.cs index beeb3e0..8e84024 100644 --- a/Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs +++ b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3Window.cs @@ -6,7 +6,7 @@ using SDL3; namespace Engine.Integration.SDL3; -public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniverse, IExitUniverse +public class Sdl3Window : Behaviour, IWindow, IDisposable, IEnterUniverse, IExitUniverse { public Event OnStateChanged { get; } = new(); public Event OnFocusStateChanged { get; } = new(); @@ -19,26 +19,7 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver public Event OnSizeChanged { get; } = new(); public Event OnBackgroundColorChanged { get; } = new(); - private IntPtr window = IntPtr.Zero; - private IntPtr renderer = IntPtr.Zero; - - private ulong nextRenderTick = 0; - private ulong renderInterval = (ulong)(1f / 60f * 1000f); - - public uint TargetFps - { - get => field; - set - { - if (value == field) - return; - - uint previousTargetFps = field; - field = value; - renderInterval = value == 0 ? 0 : (ulong)(1f / value * 1000f); - OnTargetFpsChanged.Invoke(this, new(previousTargetFps)); - } - } = 0; + public IntPtr Window { get; private set; } = IntPtr.Zero; public WindowState State { @@ -63,7 +44,7 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver public WindowFocusState FocusState { get => field; - private set + internal set { if (value == field) return; @@ -84,26 +65,26 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver if (value is WindowMode.Windowed or WindowMode.BorderlessWindowed) { - SDL.SetWindowFullscreen(window, false); - SDL.SyncWindow(window); + SDL.SetWindowFullscreen(Window, false); + SDL.SyncWindow(Window); } - if (window != IntPtr.Zero) + if (Window != IntPtr.Zero) switch (value) { - case WindowMode.Windowed: SDL.SetWindowBordered(window, true); break; - case WindowMode.BorderlessWindowed: SDL.SetWindowBordered(window, false); break; - case WindowMode.BorderlessFullscreen: SDL.SetWindowFullscreen(window, true); break; - case WindowMode.Fullscreen: SDL.SetWindowFullscreen(window, true); break; + case WindowMode.Windowed: SDL.SetWindowBordered(Window, true); break; + case WindowMode.BorderlessWindowed: SDL.SetWindowBordered(Window, false); break; + case WindowMode.BorderlessFullscreen: SDL.SetWindowFullscreen(Window, true); break; + case WindowMode.Fullscreen: SDL.SetWindowFullscreen(Window, true); break; } - SDL.SyncWindow(window); + SDL.SyncWindow(Window); WindowMode previousMode = field; field = value; OnModeChanged.Invoke(this, new(previousMode)); } - } = WindowMode.Fullscreen; + } = WindowMode.Windowed; public WindowShowState ShowState { @@ -113,12 +94,12 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver if (value == field) return; - if (window != IntPtr.Zero) + if (Window != IntPtr.Zero) switch (value) { - case WindowShowState.Normal: SDL.RestoreWindow(window); break; - case WindowShowState.Minimized: SDL.MinimizeWindow(window); break; - case WindowShowState.Maximized: SDL.MaximizeWindow(window); break; + case WindowShowState.Normal: SDL.RestoreWindow(Window); break; + case WindowShowState.Minimized: SDL.MinimizeWindow(Window); break; + case WindowShowState.Maximized: SDL.MaximizeWindow(Window); break; } WindowShowState previousShowState = field; @@ -135,15 +116,15 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver if (value == field) return; - if (window != IntPtr.Zero) + if (Window != IntPtr.Zero) switch (value) { case WindowResizeMode.Fixed: - SDL.SetWindowResizable(window, false); + SDL.SetWindowResizable(Window, false); break; case WindowResizeMode.Resizable: case WindowResizeMode.AspectLocked: - SDL.SetWindowResizable(window, true); + SDL.SetWindowResizable(Window, true); break; } @@ -198,26 +179,6 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver } } = Vector2DInt.Zero; - public void Update() - { - while (SDL.PollEvent(out SDL.Event e)) - ProcessInternalEvents((SDL.EventType)e.Type); - - ulong currentTick = SDL.GetTicks(); - - if (currentTick >= nextRenderTick) - { - nextRenderTick = currentTick + renderInterval; - - SDL.SetRenderDrawColor(renderer, BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, 255); - SDL.RenderClear(renderer); - - // TODO Render - - SDL.RenderPresent(renderer); - } - } - public void ExitUniverse(IUniverse universe) => CloseWindow(); public void EnterUniverse(IUniverse universe) { @@ -242,43 +203,22 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver if (!SDL.Init(SDL.InitFlags.Video)) throw new Exception($"SDL_Init failed: {SDL.GetError()}"); - if (!SDL.CreateWindowAndRenderer(Title, Size.X, Size.Y, windowFlags, out window, out renderer)) + Window = SDL.CreateWindow(Title, Size.X, Size.Y, windowFlags | SDL.WindowFlags.OpenGL); + if (Window == IntPtr.Zero) throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}"); } private void CloseWindow() { - if (renderer != IntPtr.Zero) SDL.DestroyRenderer(renderer); - if (window != IntPtr.Zero) SDL.DestroyWindow(window); + if (Window != IntPtr.Zero) SDL.DestroyWindow(Window); - renderer = IntPtr.Zero; - window = IntPtr.Zero; - } - - private void ProcessInternalEvents(SDL.EventType eventType) - { - switch (eventType) - { - case SDL.EventType.WindowRestored: ShowState = WindowShowState.Normal; break; - case SDL.EventType.WindowMaximized: ShowState = WindowShowState.Maximized; break; - case SDL.EventType.WindowMinimized: ShowState = WindowShowState.Minimized; break; - - case SDL.EventType.WindowFocusGained: FocusState = WindowFocusState.Focused; break; - case SDL.EventType.WindowFocusLost: FocusState = WindowFocusState.Unfocused; break; - - case SDL.EventType.Quit: - State = WindowState.Closed; - BehaviourController.RemoveBehaviour(this); - break; - } + Window = IntPtr.Zero; } public void Dispose() { CloseWindow(); - // SDL.Quit(); // TODO This possibly prevents multi window - GC.SuppressFinalize(this); } } diff --git a/Platforms/Desktop/Engine.Integration.SDL3/Sdl3WindowManager.cs b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3WindowManager.cs new file mode 100644 index 0000000..d77b602 --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/Sdl3WindowManager.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using Engine.Core; + +using SDL3; + +namespace Engine.Integration.SDL3; + +public class Sdl3WindowManager : Behaviour, IUpdate, IEnterUniverse, IExitUniverse +{ + private readonly BehaviourCollector windows = new(); + private readonly BehaviourCollector eventProcessors = new(); + private readonly Dictionary windowsDictionary = []; + + public Sdl3WindowManager() + { + windows.OnCollected.AddListener(OnWindowCollected); + windows.OnRemoved.AddListener(OnWindowRemoved); + } + + private void OnWindowCollected(IBehaviourCollector sender, IBehaviourCollector.BehaviourCollectedArguments args) + => windowsDictionary.Add(args.BehaviourCollected.Window, args.BehaviourCollected); + + private void OnWindowRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) + { + windowsDictionary.Remove(args.BehaviourRemoved.Window); + if (windowsDictionary.Count == 0) + SDL.Quit(); + } + + public void EnterUniverse(IUniverse universe) + { + windows.Assign(universe); + eventProcessors.Assign(universe); + } + + public void ExitUniverse(IUniverse universe) + { + windows.Unassign(); + eventProcessors.Unassign(); + } + + public void Update() + { + while (SDL.PollEvent(out SDL.Event e)) + ProcessInternalEvents(e); + } + + private void ProcessInternalEvents(SDL.Event sdlEvent) + { + if (!windowsDictionary.TryGetValue(SDL.GetWindowFromID(sdlEvent.Window.WindowID), out Sdl3Window? window)) + return; + + for (int i = 0; i < eventProcessors.Count; i++) + eventProcessors[i].Process(sdlEvent); + + SDL.EventType eventType = (SDL.EventType)sdlEvent.Type; + + switch (eventType) + { + case SDL.EventType.WindowRestored: window.ShowState = WindowShowState.Normal; break; + case SDL.EventType.WindowMaximized: window.ShowState = WindowShowState.Maximized; break; + case SDL.EventType.WindowMinimized: window.ShowState = WindowShowState.Minimized; break; + + case SDL.EventType.WindowFocusGained: window.FocusState = WindowFocusState.Focused; break; + case SDL.EventType.WindowFocusLost: window.FocusState = WindowFocusState.Unfocused; break; + + case SDL.EventType.WindowCloseRequested: + case SDL.EventType.Quit: Universe.Remove(window.UniverseObject); break; + } + } +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/Windowing/IWindow.cs b/Platforms/Desktop/Engine.Integration.SDL3/Windowing/IWindow.cs index f6b54e5..be77cb8 100644 --- a/Platforms/Desktop/Engine.Integration.SDL3/Windowing/IWindow.cs +++ b/Platforms/Desktop/Engine.Integration.SDL3/Windowing/IWindow.cs @@ -22,7 +22,6 @@ public interface IWindow : IBehaviour WindowResizeMode ResizeMode { get; set; } string Title { get; set; } - uint TargetFps { get; set; } Vector2DInt Size { get; set; } ColorRGB BackgroundColor { get; set; } diff --git a/Platforms/Desktop/Program.cs b/Platforms/Desktop/Program.cs index fcad1d1..e92e3db 100644 --- a/Platforms/Desktop/Program.cs +++ b/Platforms/Desktop/Program.cs @@ -7,6 +7,7 @@ using Engine.Integration.SDL3; using Engine.Serializers.Yaml; using Engine.Systems.Network; using Engine.Systems.Time; +using MyUniverse.Shared.Behaviours; Universe universe = new(); @@ -43,22 +44,51 @@ desktopParent.AddChild(universe.InstantiateUniverseObject().SetUniverseObject("T Sdl3Window window = Engine.Core.Factory.BehaviourFactory.Instantiate(); window.Title = "MyUniverse"; window.Size = new(800, 600); -window.Mode = WindowMode.BorderlessWindowed; +window.Mode = WindowMode.Windowed; + +Sdl3Window window2 = Engine.Core.Factory.BehaviourFactory.Instantiate(); +window2.Title = "MyUniverse 2"; +window2.Size = new(400, 300); +window2.Mode = WindowMode.Windowed; + +universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Manager", desktopParent) + .BehaviourController.AddBehaviour() + .BehaviourController.AddBehaviour() + .BehaviourController.AddBehaviour(); IUniverseObject windowUO = universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Window", desktopParent); +windowUO.BehaviourController.AddBehaviour(); +windowUO.BehaviourController.AddBehaviour(Shape2D.Square, new ColorRGB(0, 0, 128)); windowUO.BehaviourController.AddBehaviour(window); +windowUO.BehaviourController.AddBehaviour().Zoom = 20f; +windowUO.BehaviourController.AddBehaviour(); +windowUO.BehaviourController.AddBehaviour(); + +IUniverseObject windowUO2 = universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Window 2", desktopParent); +windowUO2.BehaviourController.AddBehaviour().Rotation = 45f; +windowUO2.BehaviourController.AddBehaviour(Shape2D.Square, new ColorRGB(128, 0, 0)); +windowUO2.BehaviourController.AddBehaviour(window2); +windowUO2.BehaviourController.AddBehaviour().Zoom = 20f; +windowUO2.BehaviourController.AddBehaviour(); +windowUO2.BehaviourController.AddBehaviour(); // Testing Start -TickerStopwatch tickerStopwatch = windowUO.BehaviourController.AddBehaviour(); -tickerStopwatch.Period = 5f; -tickerStopwatch.OnTick.AddListener(sender => -{ - if (window.IsInitialized) - windowUO.BehaviourController.RemoveBehaviour(window); - else - windowUO.BehaviourController.AddBehaviour(window); -}); -tickerStopwatch.Start(); +// TickerStopwatch tickerStopwatch = windowUO.BehaviourController.AddBehaviour(); +// tickerStopwatch.Period = 5f; +// tickerStopwatch.OnTick.AddListener(sender => +// { +// if (window.IsInitialized) +// windowUO.BehaviourController.RemoveBehaviour(window); +// else +// windowUO.BehaviourController.AddBehaviour(window); +// }); +// tickerStopwatch.Start(); + +// universe.InstantiateUniverseObject() +// .SetUniverseObject("Square") +// .BehaviourController.AddBehaviour() +// .BehaviourController.AddBehaviour(Shape2D.Square, new ColorRGB(255, 255, 0)); + // Testing End universe.Initialize(); @@ -82,6 +112,7 @@ while (true) timeSinceStart += updateTimeSpan; universe.Update(new(timeSinceStart, updateTimeSpan)); + universe.Draw(); lastRun = now; System.Threading.Thread.Sleep(1);