Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring back SDL2 by default, lock SDL3 behind environment variable #6292

Merged
merged 22 commits into from
May 22, 2024

Conversation

smoogipoo
Copy link
Contributor

@smoogipoo smoogipoo commented May 21, 2024

Environment variable: OSU_SDL3=1.

The process of making this PR involved:

All of which culminates in a 99% 1:1 reimplementation of SDL2. Only supporting logic has been added. We should be able to delete the SDL2 pathway with little effort at any point.

As far as osu! is concerned, this is a non-breaking change.

Important changes/things to look out for:

  • Namespace osu.Framework.Platform.SDL -> osu.Framework.Platform.SDL3.
    • Platform.SDL2 added to hold SDL2 variant classes.
    • Can possibly combine both variants back into the SDL namespace if people believe that's better?
  • SDL3GameHost -> SDLGameHost.
    • It now creates SDL2/SDL3 window variants depending on env variable.
  • WindowMouseHandler contains both SDL2 and SDL3 implementations.
    • Required to not break deserialisation of input.json thus losing settings.
  • SDL3ReadableKeyCombinationProvider does not have an SDL2 variant.
    • Since the keycodes look identical, I think it's fine to use SDL3's implementation.
  • using static SDL2.SDL and using static SDL.SDL3 are used to avoid namespace conflicts.
  • Both iOS and Android use SDL3.

Testing:

  • Windows
  • macOS
  • Linux
  • iOS
  • Android

@smoogipoo
Copy link
Contributor Author

smoogipoo commented May 21, 2024

Not sure about the DesktopGameHost : SDLGameHost inheritance change. Only just realised DesktopGameHost is used in a few other places, like tests. It looks fine and passes the CI tests, but the prospect is a bit scary. Hard to do this otherwise...

Edit: Nevermind, I think I was thinking about a prior version of my changes. Nothing's actually changed in this PR.

Copy link
Member

@Susko3 Susko3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested to boot fine on Android, and Windows SDL2 & SDL3.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readable key combinations don't work on SDL2. Can be easily fixed in an ugly way as the SDL3 and SDL2 enums are compatible.

diff --git a/osu.Framework/Platform/SDL3/SDL3ReadableKeyCombinationProvider.cs b/osu.Framework/Platform/SDL3/SDL3ReadableKeyCombinationProvider.cs
index 126a73e56..9d8d81dab 100644
--- a/osu.Framework/Platform/SDL3/SDL3ReadableKeyCombinationProvider.cs
+++ b/osu.Framework/Platform/SDL3/SDL3ReadableKeyCombinationProvider.cs
@@ -11,9 +11,25 @@ namespace osu.Framework.Platform.SDL3
 {
     public class SDL3ReadableKeyCombinationProvider : ReadableKeyCombinationProvider
     {
+        private static SDL_Keycode getKeyFromScancode(SDL_Scancode scancode)
+        {
+            if (FrameworkEnvironment.UseSDL3)
+                return SDL_GetKeyFromScancode(scancode);
+
+            return (SDL_Keycode)global::SDL2.SDL.SDL_GetKeyFromScancode((global::SDL2.SDL.SDL_Scancode)scancode);
+        }
+
+        private static string? getKeyName(SDL_Keycode keycode)
+        {
+            if (FrameworkEnvironment.UseSDL3)
+                return SDL_GetKeyName(keycode);
+
+            return global::SDL2.SDL.SDL_GetKeyName((global::SDL2.SDL.SDL_Keycode)keycode);
+        }
+
         protected override string GetReadableKey(InputKey key)
         {
-            var keycode = SDL_GetKeyFromScancode(key.ToScancode());
+            var keycode = getKeyFromScancode(key.ToScancode());
 
             // early return if unknown. probably because key isn't a keyboard key, or doesn't map to an `SDL_Scancode`.
             if (keycode == SDL_Keycode.SDLK_UNKNOWN)
@@ -25,7 +41,7 @@ protected override string GetReadableKey(InputKey key)
             if (TryGetNameFromKeycode(keycode, out name))
                 return name;
 
-            name = SDL_GetKeyName(keycode);
+            name = getKeyName(keycode);
 
             // fall back if SDL couldn't find a name.
             if (string.IsNullOrEmpty(name))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Although it's a bit messy, I think this is fine for now.

Copy link
Member

@Susko3 Susko3 May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be split into multiple files containing the same partial class. Makes removal easier and better separates the SDL2 and generic implementation. (Plus the future SDL3 impl for #6291.)

The SDL2 impl should then use window is (not) SDL2WindowsWindow :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto for Native.Input.

Copy link
Contributor Author

@smoogipoo smoogipoo May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to do this in 6f0702a, I hope it's what you intended as I'm unsure whether the wndproc handler can truly be shared between the two so I just assumed that it can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native.Input isn't sdl2/sdl3 specific and it's internal. I'm trying to avoid making too many changes and just followed the two steps in the PR description that can be verified.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wndproc handler doesn't work for raw input on windows. I meant to split out the code so that the SDL2 part can just be deleted. Best illustrated by the below diff. With it, the changes of WindowsMouseHandler.cs relative to master is the smallest. There's no SDL3 code yet so that partial is removed.

Expected final diff to master:

diff --git a/osu.Framework/Platform/Windows/WindowsMouseHandler.cs b/osu.Framework/Platform/Windows/WindowsMouseHandler.cs
index b74535b78..4abe150a6 100644
--- a/osu.Framework/Platform/Windows/WindowsMouseHandler.cs
+++ b/osu.Framework/Platform/Windows/WindowsMouseHandler.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.

 using System.Runtime.Versioning;
@@ -12,16 +12,22 @@ namespace osu.Framework.Platform.Windows
     /// This is done to better handle quirks of some devices.
     /// </summary>
     [SupportedOSPlatform("windows")]
-    internal class WindowsMouseHandler : MouseHandler
+    internal partial class WindowsMouseHandler : MouseHandler
     {
-        private WindowsWindow window = null!;
+        private IWindowsWindow window = null!;
+
+        public override bool IsActive => Enabled.Value;

         public override bool Initialize(GameHost host)
         {
-            if (!(host.Window is WindowsWindow desktopWindow))
+            if (host.Window is not IWindowsWindow windowsWindow)
                 return false;

-            window = desktopWindow;
+            window = windowsWindow;
+
+            if (window is SDL2WindowsWindow)
+                initialiseSDL2(host);
+
             return base.Initialize(host);
         }
Diff

diff --git a/osu.Framework/Platform/Windows/WindowsMouseHandler.cs b/osu.Framework/Platform/Windows/WindowsMouseHandler.cs
index ef2cdcaad..4abe150a6 100644
--- a/osu.Framework/Platform/Windows/WindowsMouseHandler.cs
+++ b/osu.Framework/Platform/Windows/WindowsMouseHandler.cs
@@ -1,14 +1,8 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.
 
-using System;
-using System.Drawing;
 using System.Runtime.Versioning;
-using osu.Framework.Extensions.EnumExtensions;
 using osu.Framework.Input.Handlers.Mouse;
-using osu.Framework.Input.StateChanges;
-using osu.Framework.Platform.Windows.Native;
-using osu.Framework.Statistics;
 using osuTK;
 
 namespace osu.Framework.Platform.Windows
@@ -20,17 +14,7 @@ namespace osu.Framework.Platform.Windows
     [SupportedOSPlatform("windows")]
     internal partial class WindowsMouseHandler : MouseHandler
     {
-        private static readonly GlobalStatistic<ulong> statistic_relative_events = GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Relative events");
-        private static readonly GlobalStatistic<ulong> statistic_absolute_events = GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Absolute events");
-        private static readonly GlobalStatistic<ulong> statistic_dropped_touch_inputs = GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Dropped native touch inputs");
-
-        private static readonly GlobalStatistic<ulong> statistic_inputs_with_extra_information =
-            GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Native inputs with ExtraInformation");
-
-        private const int raw_input_coordinate_space = 65535;
-
         private IWindowsWindow window = null!;
-        private bool handlerBound;
 
         public override bool IsActive => Enabled.Value;
 
@@ -40,9 +24,9 @@ public override bool Initialize(GameHost host)
                 return false;
 
             window = windowsWindow;
-            handlerBound = window is SDL2WindowsWindow
-                ? bindHandlerSDL2(host)
-                : bindHandlerSDL3(host);
+
+            if (window is SDL2WindowsWindow)
+                initialiseSDL2(host);
 
             return base.Initialize(host);
         }
@@ -52,108 +36,5 @@ public override void FeedbackMousePositionChange(Vector2 position, bool isSelfFe
             window.LastMousePosition = position;
             base.FeedbackMousePositionChange(position, isSelfFeedback);
         }
-
-        protected override void HandleMouseMoveRelative(Vector2 delta)
-        {
-            if (handlerBound)
-            {
-                // When bound, relative movement is reported via the WndProc handler below.
-                return;
-            }
-
-            base.HandleMouseMoveRelative(delta);
-        }
-
-        private unsafe IntPtr onWndProcSDL2(IntPtr userData, IntPtr hWnd, uint message, ulong wParam, long lParam)
-        {
-            if (!Enabled.Value)
-                return IntPtr.Zero;
-
-            if (message != Native.Input.WM_INPUT)
-                return IntPtr.Zero;
-
-            if (Native.Input.IsTouchEvent(Native.Input.GetMessageExtraInfo()))
-            {
-                // sometimes GetMessageExtraInfo returns 0, so additionally, mouse.ExtraInformation is checked below.
-                // touch events are handled by TouchHandler
-                statistic_dropped_touch_inputs.Value++;
-                return IntPtr.Zero;
-            }
-
-            int payloadSize = sizeof(RawInputData);
-
-            Native.Input.GetRawInputData((IntPtr)lParam, RawInputCommand.Input, out var data, ref payloadSize, sizeof(RawInputHeader));
-
-            if (data.Header.Type != RawInputType.Mouse)
-                return IntPtr.Zero;
-
-            var mouse = data.Mouse;
-
-            // `ExtraInformation` doesn't have the MI_WP_SIGNATURE set, so we have to rely solely on the touch flag.
-            if (Native.Input.HasTouchFlag(mouse.ExtraInformation))
-            {
-                statistic_dropped_touch_inputs.Value++;
-                return IntPtr.Zero;
-            }
-
-            //TODO: this isn't correct.
-            if (mouse.ExtraInformation > 0)
-            {
-                statistic_inputs_with_extra_information.Value++;
-
-                // i'm not sure if there is a valid case where we need to handle packets with this present
-                // but the osu!tablet fires noise events with non-zero values, which we want to ignore.
-                // return IntPtr.Zero;
-            }
-
-            var position = new Vector2(mouse.LastX, mouse.LastY);
-            float sensitivity = (float)Sensitivity.Value;
-
-            if (mouse.Flags.HasFlagFast(RawMouseFlags.MoveAbsolute))
-            {
-                var screenRect = mouse.Flags.HasFlagFast(RawMouseFlags.VirtualDesktop) ? Native.Input.VirtualScreenRect : new Rectangle(window.Position, window.ClientSize);
-
-                Vector2 screenSize = new Vector2(screenRect.Width, screenRect.Height);
-
-                if (mouse.LastX == 0 && mouse.LastY == 0)
-                {
-                    // not sure if this is the case for all tablets, but on osu!tablet these can appear and are noise.
-                    return IntPtr.Zero;
-                }
-
-                // i am not sure what this 64 flag is, but it's set on the osu!tablet at very least.
-                // using it here as a method of determining where the coordinate space is incorrect.
-                if (((int)mouse.Flags & 64) == 0)
-                {
-                    position /= raw_input_coordinate_space;
-                    position *= screenSize;
-                }
-
-                if (Sensitivity.Value != 1)
-                {
-                    // apply absolute sensitivity adjustment from the centre of the screen area.
-                    Vector2 halfScreenSize = (screenSize / 2);
-
-                    position -= halfScreenSize;
-                    position *= (float)Sensitivity.Value;
-                    position += halfScreenSize;
-                }
-
-                // map from screen to client coordinate space.
-                // not using Window's PointToClient implementation to keep floating point precision here.
-                position -= new Vector2(window.Position.X, window.Position.Y);
-                position *= window.Scale;
-
-                PendingInputs.Enqueue(new MousePositionAbsoluteInput { Position = position });
-                statistic_absolute_events.Value++;
-            }
-            else
-            {
-                PendingInputs.Enqueue(new MousePositionRelativeInput { Delta = new Vector2(mouse.LastX, mouse.LastY) * sensitivity });
-                statistic_relative_events.Value++;
-            }
-
-            return IntPtr.Zero;
-        }
     }
 }
diff --git a/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs b/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs
index bd13d7b17..2d941ec17 100644
--- a/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs
+++ b/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs
@@ -2,15 +2,30 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using System;
+using System.Drawing;
+using osu.Framework.Extensions.EnumExtensions;
+using osu.Framework.Input.StateChanges;
+using osu.Framework.Platform.Windows.Native;
+using osu.Framework.Statistics;
+using osuTK;
 using static SDL2.SDL;
 
 namespace osu.Framework.Platform.Windows
 {
     internal partial class WindowsMouseHandler
     {
+        private static readonly GlobalStatistic<ulong> statistic_relative_events = GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Relative events");
+        private static readonly GlobalStatistic<ulong> statistic_absolute_events = GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Absolute events");
+        private static readonly GlobalStatistic<ulong> statistic_dropped_touch_inputs = GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Dropped native touch inputs");
+
+        private static readonly GlobalStatistic<ulong> statistic_inputs_with_extra_information =
+            GlobalStatistics.Get<ulong>(StatisticGroupFor<WindowsMouseHandler>(), "Native inputs with ExtraInformation");
+
+        private const int raw_input_coordinate_space = 65535;
+
         private SDL_WindowsMessageHook sdl2Callback = null!;
 
-        private bool bindHandlerSDL2(GameHost host)
+        private void initialiseSDL2(GameHost host)
         {
             // ReSharper disable once ConvertClosureToMethodGroup
             sdl2Callback = (ptr, wnd, u, param, l) => onWndProcSDL2(ptr, wnd, u, param, l);
@@ -19,8 +34,109 @@ private bool bindHandlerSDL2(GameHost host)
             {
                 host.InputThread.Scheduler.Add(() => SDL_SetWindowsMessageHook(enabled.NewValue ? sdl2Callback : null, IntPtr.Zero));
             }, true);
+        }
+
+        protected override void HandleMouseMoveRelative(Vector2 delta)
+        {
+            if (window is SDL2WindowsWindow)
+            {
+                // on SDL2, relative movement is reported via the WndProc handler below.
+                return;
+            }
+
+            base.HandleMouseMoveRelative(delta);
+        }
+
+        private unsafe IntPtr onWndProcSDL2(IntPtr userData, IntPtr hWnd, uint message, ulong wParam, long lParam)
+        {
+            if (!Enabled.Value)
+                return IntPtr.Zero;
+
+            if (message != Native.Input.WM_INPUT)
+                return IntPtr.Zero;
+
+            if (Native.Input.IsTouchEvent(Native.Input.GetMessageExtraInfo()))
+            {
+                // sometimes GetMessageExtraInfo returns 0, so additionally, mouse.ExtraInformation is checked below.
+                // touch events are handled by TouchHandler
+                statistic_dropped_touch_inputs.Value++;
+                return IntPtr.Zero;
+            }
+
+            int payloadSize = sizeof(RawInputData);
+
+            Native.Input.GetRawInputData((IntPtr)lParam, RawInputCommand.Input, out var data, ref payloadSize, sizeof(RawInputHeader));
+
+            if (data.Header.Type != RawInputType.Mouse)
+                return IntPtr.Zero;
+
+            var mouse = data.Mouse;
+
+            // `ExtraInformation` doesn't have the MI_WP_SIGNATURE set, so we have to rely solely on the touch flag.
+            if (Native.Input.HasTouchFlag(mouse.ExtraInformation))
+            {
+                statistic_dropped_touch_inputs.Value++;
+                return IntPtr.Zero;
+            }
+
+            //TODO: this isn't correct.
+            if (mouse.ExtraInformation > 0)
+            {
+                statistic_inputs_with_extra_information.Value++;
+
+                // i'm not sure if there is a valid case where we need to handle packets with this present
+                // but the osu!tablet fires noise events with non-zero values, which we want to ignore.
+                // return IntPtr.Zero;
+            }
+
+            var position = new Vector2(mouse.LastX, mouse.LastY);
+            float sensitivity = (float)Sensitivity.Value;
+
+            if (mouse.Flags.HasFlagFast(RawMouseFlags.MoveAbsolute))
+            {
+                var screenRect = mouse.Flags.HasFlagFast(RawMouseFlags.VirtualDesktop) ? Native.Input.VirtualScreenRect : new Rectangle(window.Position, window.ClientSize);
+
+                Vector2 screenSize = new Vector2(screenRect.Width, screenRect.Height);
+
+                if (mouse.LastX == 0 && mouse.LastY == 0)
+                {
+                    // not sure if this is the case for all tablets, but on osu!tablet these can appear and are noise.
+                    return IntPtr.Zero;
+                }
+
+                // i am not sure what this 64 flag is, but it's set on the osu!tablet at very least.
+                // using it here as a method of determining where the coordinate space is incorrect.
+                if (((int)mouse.Flags & 64) == 0)
+                {
+                    position /= raw_input_coordinate_space;
+                    position *= screenSize;
+                }
+
+                if (Sensitivity.Value != 1)
+                {
+                    // apply absolute sensitivity adjustment from the centre of the screen area.
+                    Vector2 halfScreenSize = (screenSize / 2);
+
+                    position -= halfScreenSize;
+                    position *= (float)Sensitivity.Value;
+                    position += halfScreenSize;
+                }
+
+                // map from screen to client coordinate space.
+                // not using Window's PointToClient implementation to keep floating point precision here.
+                position -= new Vector2(window.Position.X, window.Position.Y);
+                position *= window.Scale;
+
+                PendingInputs.Enqueue(new MousePositionAbsoluteInput { Position = position });
+                statistic_absolute_events.Value++;
+            }
+            else
+            {
+                PendingInputs.Enqueue(new MousePositionRelativeInput { Delta = new Vector2(mouse.LastX, mouse.LastY) * sensitivity });
+                statistic_relative_events.Value++;
+            }
 
-            return true;
+            return IntPtr.Zero;
         }
     }
 }
diff --git a/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL3.cs b/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL3.cs
deleted file mode 100644
index 0d67175e3..000000000
--- a/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL3.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Framework.Platform.Windows
-{
-    internal partial class WindowsMouseHandler
-    {
-        private bool bindHandlerSDL3(GameHost host)
-        {
-            return false;
-        }
-    }
-}

@peppy peppy self-requested a review May 21, 2024 14:16
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Platform.SDL3;
using static SDL.SDL3;
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dunno how i feel about this

@peppy
Copy link
Sponsor Member

peppy commented May 22, 2024

I think this looks OK.

@smoogipoo as discussed, can we add an SDL2 / SDL3 string here?

2024-05-22 14 18 19@2x

@smoogipoo
Copy link
Contributor Author

image

How does this look? Wanted to keep the line somewhat short...

@peppy
Copy link
Sponsor Member

peppy commented May 22, 2024

@bdach not sure if you want to give this a quick run on linux and make sure all is well?

as for iOS i don't really have the energy to test it but i'm sure we'll find out from @frenzibyte if it's failing at some point before next release, worst case.

@peppy peppy self-requested a review May 22, 2024 07:51
@smoogipoo
Copy link
Contributor Author

smoogipoo commented May 22, 2024

For linux I've tested that it runs and supports window modes with:

SDL_VIDEODRIVER=wayland
SDL_VIDEODRIVER=x11
OSU_SDL3=1;SDL_VIDEO_DRIVER=wayland
OSU_SDL3=1;SDL_VIDEO_DRIVER=x11

But note that this is via XWayland, not a true X11 impl.

@bdach
Copy link
Collaborator

bdach commented May 22, 2024

Linux boots here. Appears to not be more broken than already known on all of the configurations I tested on my box (x11/wayland, sdl2/3).

Not reading source because I presume enough eyes have looked at that already.

@peppy peppy merged commit 1f6b157 into ppy:master May 22, 2024
19 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants