diff --git a/Flax.flaxproj b/Flax.flaxproj
index ca1a1aff8..d7af144d0 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -13,6 +13,7 @@
"Configuration": {
"UseCSharp": true,
"UseLargeWorlds": false,
- "UseDotNet": true
+ "UseDotNet": true,
+ "UseSDL": true
}
}
\ No newline at end of file
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
index 511cda2e5..b4a0afa4d 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
@@ -1,7 +1,10 @@
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS || PLATFORM_SDL
#define USE_IS_FOREGROUND
#else
#endif
+#if PLATFORM_SDL
+#define USE_SDL_WORKAROUNDS
+#endif
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
@@ -111,7 +114,7 @@ namespace FlaxEditor.GUI.ContextMenu
}
///
- /// Shows the empty menu popup o na screen.
+ /// Shows the empty menu popup on a screen.
///
/// The target control.
/// The target control area to cover.
@@ -228,8 +231,6 @@ namespace FlaxEditor.GUI.ContextMenu
// Show
Visible = true;
- if (_window == null)
- return;
_window.Show();
PerformLayout();
_previouslyFocused = parentWin.FocusedControl;
@@ -378,6 +379,11 @@ namespace FlaxEditor.GUI.ContextMenu
}
}
+#if USE_SDL_WORKAROUNDS
+ private void OnWindowGotFocus()
+ {
+ }
+#else
private void OnWindowGotFocus()
{
var child = _childCM;
@@ -391,6 +397,7 @@ namespace FlaxEditor.GUI.ContextMenu
});
}
}
+#endif
private void OnWindowLostFocus()
{
@@ -489,7 +496,12 @@ namespace FlaxEditor.GUI.ContextMenu
// Let root context menu to check if none of the popup windows
if (_parentCM == null && !IsForeground)
{
+#if USE_SDL_WORKAROUNDS
+ if (!IsMouseOver)
+ Hide();
+#else
Hide();
+#endif
}
}
#endif
diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs
index 2b0902cf9..9eec806ea 100644
--- a/Source/Editor/GUI/Docking/DockHintWindow.cs
+++ b/Source/Editor/GUI/Docking/DockHintWindow.cs
@@ -67,9 +67,7 @@ namespace FlaxEditor.GUI.Docking
Proxy.Window.MouseUp += OnMouseUp;
Proxy.Window.MouseMove += OnMouseMove;
Proxy.Window.LostFocus += OnLostFocus;
-
- // Start tracking mouse
- Proxy.Window.StartTrackingMouse(false);
+ _toMove.Window.Window.MouseUp += OnMouseUp; // Intercept the drag release mouse event from source window
// Update window GUI
Proxy.Window.GUI.PerformLayout();
@@ -77,13 +75,16 @@ namespace FlaxEditor.GUI.Docking
// Update rectangles
UpdateRects();
- // Hide base window
- window.Hide();
-
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
Proxy.Window.Focus();
+
+ // Hide base window
+ window.Hide();
+
+ // Start tracking mouse
+ Proxy.Window.StartTrackingMouse(false);
}
///
@@ -101,6 +102,8 @@ namespace FlaxEditor.GUI.Docking
Proxy.Window.MouseUp -= OnMouseUp;
Proxy.Window.MouseMove -= OnMouseMove;
Proxy.Window.LostFocus -= OnLostFocus;
+ if (_toMove?.Window?.Window)
+ _toMove.Window.Window.MouseUp -= OnMouseUp;
// Hide the proxy
Proxy.Hide();
diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp
index 49257d281..4ec761658 100644
--- a/Source/Editor/Windows/SplashScreen.cpp
+++ b/Source/Editor/Windows/SplashScreen.cpp
@@ -28,7 +28,7 @@ const Char* SplashScreenQuotes[] =
#elif PLATFORM_LINUX
TEXT("Try it on a Raspberry"),
TEXT("Trying to exit vim"),
- TEXT("Sudo flax --loadproject"),
+ TEXT("sudo flax --project HelloWorld.flaxproj"),
#elif PLATFORM_MAC
TEXT("don't compare Macbooks to oranges."),
TEXT("Why does macbook heat up?\nBecause it doesn't have windows"),
@@ -104,6 +104,7 @@ const Char* SplashScreenQuotes[] =
TEXT("You have my bow.\nAnd my axe!"),
TEXT("To the bridge of Khazad-dum."),
TEXT("One ring to rule them all.\nOne ring to find them."),
+ TEXT("Where there's a whip, there's a way."),
TEXT("That's what she said"),
TEXT("We could be compiling shaders here"),
TEXT("Hello There"),
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index b26cbd25a..41e52f9c0 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -95,13 +95,14 @@ int32 Engine::Main(const Char* cmdLine)
CommandLine::Options.Std = true;
#endif
+ Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
+
if (Platform::Init())
{
Platform::Fatal(TEXT("Cannot init platform."));
return -1;
}
- Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
Time::StartupTime = DateTime::Now();
Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory();
#if USE_EDITOR
diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp
index ae644ad61..17aac7506 100644
--- a/Source/Engine/Engine/Screen.cpp
+++ b/Source/Engine/Engine/Screen.cpp
@@ -6,6 +6,8 @@
#include "Engine/Core/Types/Nullable.h"
#include "Engine/Platform/Window.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Mouse.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
@@ -13,10 +15,14 @@
#include "Engine/Engine/Engine.h"
#endif
-Nullable Fullscreen;
-Nullable Size;
-bool CursorVisible = true;
-CursorLockMode CursorLock = CursorLockMode::None;
+namespace
+{
+ Nullable Fullscreen;
+ Nullable Size;
+ bool CursorVisible = true;
+ CursorLockMode CursorLock = CursorLockMode::None;
+ bool LastGameViewportFocus = false;
+}
class ScreenService : public EngineService
{
@@ -104,6 +110,8 @@ void Screen::SetCursorVisible(const bool value)
{
win->SetCursor(value ? CursorType::Default : CursorType::Hidden);
}
+ else if (win)
+ win->SetCursor(CursorType::Default);
CursorVisible = value;
}
@@ -190,7 +198,11 @@ void ScreenService::Update()
{
#if USE_EDITOR
// Sync current cursor state in Editor (eg. when viewport focus can change)
- Screen::SetCursorVisible(CursorVisible);
+ const auto win = Editor::Managed->GetGameWindow(true);
+ bool gameViewportFocus = win && Engine::HasGameViewportFocus();
+ if (gameViewportFocus != LastGameViewportFocus)
+ Screen::SetCursorVisible(CursorVisible);
+ LastGameViewportFocus = gameViewportFocus;
#endif
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp
index 0fc80315f..55b68551d 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.cpp
@@ -4,6 +4,7 @@
#include "AndroidVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
+#include "Engine/Platform/Window.h"
void AndroidVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
{
@@ -17,8 +18,10 @@ void AndroidVulkanPlatform::GetDeviceExtensions(Array& extensions,
extensions.Add(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
}
-void AndroidVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
+void AndroidVulkanPlatform::CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface)
{
+ ASSERT(window);
+ void* windowHandle = window->GetNativePtr();
ASSERT(windowHandle);
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h
index ad2d68652..531c95e06 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h
@@ -17,7 +17,7 @@ class AndroidVulkanPlatform : public VulkanPlatformBase
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
static void GetDeviceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface);
+ static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface);
};
typedef AndroidVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
index 2379f9b33..1c12ec5f0 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
@@ -192,7 +192,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
ASSERT_LOW_LAYER(_backBuffers.Count() == 0);
// Create platform-dependent surface
- VulkanPlatform::CreateSurface(windowHandle, GPUDeviceVulkan::Instance, &_surface);
+ VulkanPlatform::CreateSurface(_window, GPUDeviceVulkan::Instance, &_surface);
if (_surface == VK_NULL_HANDLE)
{
LOG(Warning, "Failed to create Vulkan surface.");
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp
index 10de96369..c16847b14 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.cpp
@@ -4,62 +4,62 @@
#include "LinuxVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
+#include "Engine/Platform/Window.h"
-// Contents of vulkan\vulkan_xlib.h inlined here to prevent typename collisions with engine types due to X11 types
#include "Engine/Platform/Linux/IncludeX11.h"
-#ifdef __cplusplus
-extern "C" {
-#endif
-#define VK_KHR_xlib_surface 1
-#define VK_KHR_XLIB_SURFACE_SPEC_VERSION 6
-#define VK_KHR_XLIB_SURFACE_EXTENSION_NAME "VK_KHR_xlib_surface"
-typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
+#define Display X11::Display
+#define Window X11::Window
+#define VisualID X11::VisualID
+#include "vulkan/vulkan_xlib.h"
+#undef Display
+#undef Window
+#undef VisualID
-typedef struct VkXlibSurfaceCreateInfoKHR
-{
- VkStructureType sType;
- const void* pNext;
- VkXlibSurfaceCreateFlagsKHR flags;
- X11::Display* dpy;
- X11::Window window;
-} VkXlibSurfaceCreateInfoKHR;
+#include "vulkan/vulkan_wayland.h"
-typedef VkResult (VKAPI_PTR *PFN_vkCreateXlibSurfaceKHR)(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
-typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, X11::Display* dpy, X11::VisualID visualID);
-#ifndef VK_NO_PROTOTYPES
-VKAPI_ATTR VkResult VKAPI_CALL vkCreateXlibSurfaceKHR(
- VkInstance instance,
- const VkXlibSurfaceCreateInfoKHR* pCreateInfo,
- const VkAllocationCallbacks* pAllocator,
- VkSurfaceKHR* pSurface);
-VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceXlibPresentationSupportKHR(
- VkPhysicalDevice physicalDevice,
- uint32_t queueFamilyIndex,
- Display* dpy,
- VisualID visualID);
-#endif
-#ifdef __cplusplus
-}
-#endif
-//
-
-// Export X11 surface extension from volk
+// Export extension from volk
extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
+extern PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
+extern PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
void LinuxVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
{
extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME);
extensions.Add(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
+ extensions.Add(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
}
-void LinuxVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
+void LinuxVulkanPlatform::CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface)
{
+#if !PLATFORM_SDL
+ void* windowHandle = window->GetNativePtr();
VkXlibSurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR);
- surfaceCreateInfo.dpy = (X11::Display*)LinuxPlatform::GetXDisplay();
+ surfaceCreateInfo.dpy = (X11::Display*)Platform::GetXDisplay();
surfaceCreateInfo.window = (X11::Window)windowHandle;
VALIDATE_VULKAN_RESULT(vkCreateXlibSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
+#else
+ SDLWindow* sdlWindow = static_cast(window);
+ X11::Window x11Window = (X11::Window)sdlWindow->GetX11WindowHandle();
+ wl_surface* waylandSurface = (wl_surface*)sdlWindow->GetWaylandSurfacePtr();
+ if (waylandSurface != nullptr)
+ {
+ VkWaylandSurfaceCreateInfoKHR surfaceCreateInfo;
+ RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR);
+ surfaceCreateInfo.display = (wl_display*)sdlWindow->GetWaylandDisplay();
+ surfaceCreateInfo.surface = waylandSurface;
+ VALIDATE_VULKAN_RESULT(vkCreateWaylandSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
+ }
+ else if (x11Window != 0)
+ {
+ VkXlibSurfaceCreateInfoKHR surfaceCreateInfo;
+ RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR);
+ surfaceCreateInfo.dpy = (X11::Display*)sdlWindow->GetX11Display();
+ surfaceCreateInfo.window = x11Window;
+ VALIDATE_VULKAN_RESULT(vkCreateXlibSurfaceKHR(instance, &surfaceCreateInfo, nullptr, surface));
+ }
+#endif
}
#endif
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h
index a0e95cb2f..dce432644 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h
@@ -19,7 +19,7 @@ class LinuxVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
+ static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef LinuxVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp
index c327a77a4..c52a9636a 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp
@@ -4,6 +4,7 @@
#include "MacVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
+#include "Engine/Platform/Window.h"
#include
void MacVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
@@ -12,12 +13,13 @@ void MacVulkanPlatform::GetInstanceExtensions(Array& extensions, Ar
extensions.Add(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
}
-void MacVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
+void MacVulkanPlatform::CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface)
{
- NSWindow* window = (NSWindow*)windowHandle;
+ void* windowHandle = window->GetNativePtr();
+ NSWindow* nswindow = (NSWindow*)windowHandle;
VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK);
- surfaceCreateInfo.pView = (void*)window.contentView;
+ surfaceCreateInfo.pView = (void*)nswindow.contentView;
VALIDATE_VULKAN_RESULT(vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface));
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h
index 6044c4270..5bccb7a90 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.h
@@ -18,7 +18,7 @@ class MacVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
+ static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef MacVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp
index 27d690c91..9d50b1e4d 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.cpp
@@ -6,6 +6,7 @@
#include "../RenderToolsVulkan.h"
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Platform/Window.h"
void Win32VulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
{
@@ -13,8 +14,9 @@ void Win32VulkanPlatform::GetInstanceExtensions(Array& extensions,
extensions.Add(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
}
-void Win32VulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
+void Win32VulkanPlatform::CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface)
{
+ void* windowHandle = window->GetNativePtr();
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR);
surfaceCreateInfo.hinstance = GetModuleHandle(nullptr);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h
index 85658737f..9bfb52686 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h
@@ -17,7 +17,7 @@ class Win32VulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface);
+ static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface);
};
typedef Win32VulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp
index eceabef1d..c71d42013 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp
@@ -5,6 +5,7 @@
#include "iOSVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
#include "Engine/Core/Delegate.h"
+#include "Engine/Platform/Window.h"
#include
void iOSVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers)
@@ -13,8 +14,9 @@ void iOSVulkanPlatform::GetInstanceExtensions(Array& extensions, Ar
extensions.Add(VK_MVK_IOS_SURFACE_EXTENSION_NAME);
}
-void iOSVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
+void iOSVulkanPlatform::CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* surface)
{
+ void* windowHandle = window->GetNativePtr();
// Create surface on a main UI Thread
Function func = [&windowHandle, &instance, &surface]()
{
diff --git a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
index 789a61a53..07474dc88 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
@@ -18,7 +18,7 @@ class iOSVulkanPlatform : public VulkanPlatformBase
{
public:
static void GetInstanceExtensions(Array& extensions, Array& layers);
- static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
+ static void CreateSurface(Window* window, VkInstance instance, VkSurfaceKHR* outSurface);
};
typedef iOSVulkanPlatform VulkanPlatform;
diff --git a/Source/Engine/Platform/Clipboard.h b/Source/Engine/Platform/Clipboard.h
index 4cdf76666..a61552773 100644
--- a/Source/Engine/Platform/Clipboard.h
+++ b/Source/Engine/Platform/Clipboard.h
@@ -2,7 +2,9 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL && PLATFORM_LINUX
+#include "SDL/SDLClipboard.h"
+#elif PLATFORM_WINDOWS
#include "Windows/WindowsClipboard.h"
#elif PLATFORM_LINUX
#include "Linux/LinuxClipboard.h"
diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h
index b2f857937..e5af48209 100644
--- a/Source/Engine/Platform/Defines.h
+++ b/Source/Engine/Platform/Defines.h
@@ -140,6 +140,9 @@ API_ENUM() enum class ArchitectureType
#if !defined(PLATFORM_SWITCH)
#define PLATFORM_SWITCH 0
#endif
+#if !defined(PLATFORM_SDL)
+#define PLATFORM_SDL 0
+#endif
#if PLATFORM_WINDOWS
#include "Windows/WindowsDefines.h"
diff --git a/Source/Engine/Platform/Linux/IncludeX11.h b/Source/Engine/Platform/Linux/IncludeX11.h
index b8dc01cf0..d08c81a85 100644
--- a/Source/Engine/Platform/Linux/IncludeX11.h
+++ b/Source/Engine/Platform/Linux/IncludeX11.h
@@ -21,6 +21,7 @@ namespace X11
#include
#include
#include
+#include
}
// Helper macros
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
index 2c951effa..4dfe7beff 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
@@ -93,7 +93,9 @@ X11::Cursor Cursors[(int32)CursorType::MAX];
X11::XcursorImage* CursorsImg[(int32)CursorType::MAX];
Dictionary KeyNameMap;
Array KeyCodeMap;
-Delegate LinuxPlatform::xEventRecieved;
+#if !PLATFORM_SDL
+Delegate LinuxPlatform::xEventReceived;
+#endif
Window* MouseTrackingWindow = nullptr;
// Message boxes configuration
@@ -364,6 +366,8 @@ static int X11_MessageBoxInitPositions(MessageBoxData* data)
return 0;
}
+#if !PLATFORM_SDL
+
// Create and set up X11 dialog box window
static int X11_MessageBoxCreateWindow(MessageBoxData* data)
{
@@ -847,6 +851,7 @@ int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
LOG(Error, "X11 Error: {0}", String(buffer));
return 0;
}
+#endif
int32 CalculateDpi()
{
@@ -1203,17 +1208,18 @@ public:
}
};
-struct Property
+/*struct Property
{
unsigned char* data;
int format, nitems;
X11::Atom type;
-};
+};*/
namespace Impl
{
LinuxKeyboard* Keyboard;
LinuxMouse* Mouse;
+#if !PLATFORM_SDL
StringAnsi ClipboardText;
void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window)
@@ -1328,8 +1334,10 @@ namespace Impl
X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp);
return FindAppWindow(display, child);
}
+#endif
}
+#if !PLATFORM_SDL
class LinuxDropFilesData : public IGuiData
{
public:
@@ -1367,7 +1375,7 @@ public:
}
};
-DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
+DragDropEffect Window::DoDragDrop(const StringView& data)
{
if (CommandLine::Options.Headless)
return DragDropEffect::None;
@@ -1381,13 +1389,18 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
StringAnsi dataAnsi(data);
LinuxDropTextData dropData;
dropData.Text = data;
+#if !PLATFORM_SDL
+ unsigned long mainWindow = _window;
+#else
+ unsigned long mainWindow = (unsigned long)_handle;
+#endif
// Begin dragging
auto screen = X11::XDefaultScreen(xDisplay);
auto rootWindow = X11::XRootWindow(xDisplay, screen);
- if (X11::XGrabPointer(xDisplay, _window, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess)
+ if (X11::XGrabPointer(xDisplay, mainWindow, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess)
return DragDropEffect::None;
- X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, _window, CurrentTime);
+ X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, mainWindow, CurrentTime);
// Process events
X11::XEvent event;
@@ -1495,7 +1508,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow;
m.message_type = xAtomXdndLeave;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = 0;
m.data.l[3] = 0;
@@ -1524,7 +1537,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = window;
m.message_type = xAtomXdndEnter;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = Math::Min(5, version) << 24 | (formats.Count() > 3);
m.data.l[2] = formats.Count() > 0 ? formats[0] : 0;
m.data.l[3] = formats.Count() > 1 ? formats[1] : 0;
@@ -1559,7 +1572,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = window;
m.message_type = xAtomXdndPosition;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = (x << 16) | y;
m.data.l[3] = CurrentTime;
@@ -1602,7 +1615,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow;
m.message_type = xAtomXdndDrop;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = CurrentTime;
m.data.l[3] = 0;
@@ -1649,7 +1662,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data)
m.window = previousWindow;
m.message_type = xAtomXdndLeave;
m.format = 32;
- m.data.l[0] = _window;
+ m.data.l[0] = mainWindow;
m.data.l[1] = 0;
m.data.l[2] = 0;
m.data.l[3] = 0;
@@ -1675,7 +1688,7 @@ void LinuxClipboard::SetText(const StringView& text)
{
if (CommandLine::Options.Headless)
return;
- auto mainWindow = (LinuxWindow*)Engine::MainWindow;
+ auto mainWindow = Engine::MainWindow;
if (!mainWindow)
return;
X11::Window window = (X11::Window)mainWindow->GetNativePtr();
@@ -1698,7 +1711,7 @@ String LinuxClipboard::GetText()
if (CommandLine::Options.Headless)
return String::Empty;
String result;
- auto mainWindow = (LinuxWindow*)Engine::MainWindow;
+ auto mainWindow = Engine::MainWindow;
if (!mainWindow)
return result;
X11::Window window = (X11::Window)mainWindow->GetNativePtr();
@@ -1727,9 +1740,11 @@ Array LinuxClipboard::GetFiles()
return Array();
}
+#endif
+
void* LinuxPlatform::GetXDisplay()
{
- return xDisplay;
+ return xDisplay;
}
bool LinuxPlatform::CreateMutex(const Char* name)
@@ -2117,10 +2132,15 @@ bool LinuxPlatform::Init()
Platform::MemoryClear(Cursors, sizeof(Cursors));
Platform::MemoryClear(CursorsImg, sizeof(CursorsImg));
+
// Skip setup if running in headless mode (X11 might not be available on servers)
if (CommandLine::Options.Headless)
return false;
-
+#if PLATFORM_SDL
+ xDisplay = X11::XOpenDisplay(nullptr);
+#endif
+
+#if !PLATFORM_SDL
X11::XInitThreads();
xDisplay = X11::XOpenDisplay(nullptr);
@@ -2259,7 +2279,7 @@ bool LinuxPlatform::Init()
Input::Mouse = Impl::Mouse = New();
Input::Keyboard = Impl::Keyboard = New();
LinuxInput::Init();
-
+#endif
return false;
}
@@ -2269,6 +2289,7 @@ void LinuxPlatform::BeforeRun()
void LinuxPlatform::Tick()
{
+#if !PLATFORM_SDL
UnixPlatform::Tick();
LinuxInput::UpdateState();
@@ -2285,9 +2306,9 @@ void LinuxPlatform::Tick()
continue;
// External event handling
- xEventRecieved(&event);
+ xEventReceived(&event);
- LinuxWindow* window;
+ Window* window;
switch (event.type)
{
case ClientMessage:
@@ -2619,6 +2640,7 @@ void LinuxPlatform::Tick()
}
//X11::XFlush(xDisplay);
+#endif
}
void LinuxPlatform::BeforeExit()
@@ -2627,6 +2649,7 @@ void LinuxPlatform::BeforeExit()
void LinuxPlatform::Exit()
{
+#if !PLATFORM_SDL
for (int32 i = 0; i < (int32)CursorType::MAX; i++)
{
if (Cursors[i])
@@ -2652,6 +2675,7 @@ void LinuxPlatform::Exit()
X11::XCloseDisplay(xDisplay);
xDisplay = nullptr;
}
+#endif
}
int32 LinuxPlatform::GetDpi()
@@ -2872,7 +2896,11 @@ bool LinuxPlatform::SetWorkingDirectory(const String& path)
Window* LinuxPlatform::CreateWindow(const CreateWindowSettings& settings)
{
+#if PLATFORM_SDL
+ return New(settings);
+#else
return New(settings);
+#endif
}
extern char **environ;
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h
index 10c81f436..4154de737 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.h
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.h
@@ -36,7 +36,7 @@ public:
///
/// An event that is fired when an XEvent is received during platform tick.
///
- static Delegate xEventRecieved;
+ static Delegate xEventReceived;
public:
diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp
index 0272bc8c2..03a4c53ad 100644
--- a/Source/Engine/Platform/Linux/LinuxWindow.cpp
+++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp
@@ -1,6 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-#if PLATFORM_LINUX
+#if PLATFORM_LINUX && !PLATFORM_SDL
#include "../Window.h"
#include "Engine/Input/Input.h"
diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs
index ad7f49925..2251056bf 100644
--- a/Source/Engine/Platform/Platform.Build.cs
+++ b/Source/Engine/Platform/Platform.Build.cs
@@ -89,6 +89,19 @@ public class Platform : EngineModule
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
+
+ if (EngineConfiguration.WithSDL(options))
+ {
+ switch (options.Platform.Target)
+ {
+ case TargetPlatform.Windows:
+ case TargetPlatform.Linux:
+ case TargetPlatform.Mac:
+ options.PublicDependencies.Add("SDL");
+ options.SourcePaths.Add(Path.Combine(FolderPath, "SDL"));
+ break;
+ }
+ }
if (options.Target.IsEditor)
{
// Include platform settings headers
diff --git a/Source/Engine/Platform/Platform.h b/Source/Engine/Platform/Platform.h
index eb87db85c..e335f61ab 100644
--- a/Source/Engine/Platform/Platform.h
+++ b/Source/Engine/Platform/Platform.h
@@ -8,7 +8,9 @@
#include "Types.h"
#include "Defines.h"
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL
+#include "SDL/SDLPlatform.h"
+#elif PLATFORM_WINDOWS
#include "Windows/WindowsPlatform.h"
#elif PLATFORM_UWP
#include "UWP/UWPPlatform.h"
diff --git a/Source/Engine/Platform/SDL/SDLClipboard.h b/Source/Engine/Platform/SDL/SDLClipboard.h
new file mode 100644
index 000000000..54e9b0c59
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLClipboard.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_SDL && PLATFORM_LINUX
+
+#include "Engine/Platform/Base/ClipboardBase.h"
+
+///
+/// SDL implementation of the clipboard service for Linux platform.
+///
+class FLAXENGINE_API SDLClipboard : public ClipboardBase
+{
+public:
+
+ // [ClipboardBase]
+ static void Clear();
+ static void SetText(const StringView& text);
+ static void SetRawData(const Span& data);
+ static void SetFiles(const Array& files);
+ static String GetText();
+ static Array GetRawData();
+ static Array GetFiles();
+};
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLInput.cpp b/Source/Engine/Platform/SDL/SDLInput.cpp
new file mode 100644
index 000000000..720da2875
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLInput.cpp
@@ -0,0 +1,763 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL
+
+#include "SDLInput.h"
+#include "SDLWindow.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Mouse.h"
+#include "Engine/Input/Keyboard.h"
+#include "Engine/Input/Gamepad.h"
+#include "Engine/Core/Collections/Dictionary.h"
+
+#include
+
+class SDLMouse;
+class SDLKeyboard;
+class SDLGamepad;
+
+// TODO: Turn these into customizable values
+#define TRIGGER_THRESHOLD 30
+#define LEFT_STICK_THRESHOLD 7849
+#define RIGHT_STICK_THRESHOLD 8689
+
+namespace SDLInputImpl
+{
+ SDLMouse* Mouse = nullptr;
+ SDLKeyboard* Keyboard = nullptr;
+ Dictionary Gamepads;
+}
+
+static const KeyboardKeys SDL_TO_FLAX_KEYS_MAP[] =
+{
+ KeyboardKeys::None, // SDL_SCANCODE_UNKNOWN
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::A,
+ KeyboardKeys::B,
+ KeyboardKeys::C,
+ KeyboardKeys::D,
+ KeyboardKeys::E,
+ KeyboardKeys::F,
+ KeyboardKeys::G,
+ KeyboardKeys::H,
+ KeyboardKeys::I,
+ KeyboardKeys::J,
+ KeyboardKeys::K,
+ KeyboardKeys::L,
+ KeyboardKeys::M,
+ KeyboardKeys::N,
+ KeyboardKeys::O,
+ KeyboardKeys::P,
+ KeyboardKeys::Q,
+ KeyboardKeys::R,
+ KeyboardKeys::S,
+ KeyboardKeys::T,
+ KeyboardKeys::U,
+ KeyboardKeys::V,
+ KeyboardKeys::W,
+ KeyboardKeys::X,
+ KeyboardKeys::Y,
+ KeyboardKeys::Z, // 29
+ KeyboardKeys::Alpha1,
+ KeyboardKeys::Alpha2,
+ KeyboardKeys::Alpha3,
+ KeyboardKeys::Alpha4,
+ KeyboardKeys::Alpha5,
+ KeyboardKeys::Alpha6,
+ KeyboardKeys::Alpha7,
+ KeyboardKeys::Alpha8,
+ KeyboardKeys::Alpha9,
+ KeyboardKeys::Alpha0, // 39
+ KeyboardKeys::Return,
+ KeyboardKeys::Escape,
+ KeyboardKeys::Backspace,
+ KeyboardKeys::Tab,
+ KeyboardKeys::Spacebar,
+ KeyboardKeys::Minus,
+ KeyboardKeys::None, //KeyboardKeys::Equals, // ?
+ KeyboardKeys::LeftBracket,
+ KeyboardKeys::RightBracket,
+ KeyboardKeys::Backslash, // SDL_SCANCODE_BACKSLASH ?
+ KeyboardKeys::Oem102, // SDL_SCANCODE_NONUSHASH ?
+ KeyboardKeys::Colon, // SDL_SCANCODE_SEMICOLON ?
+ KeyboardKeys::Quote, // SDL_SCANCODE_APOSTROPHE
+ KeyboardKeys::BackQuote, // SDL_SCANCODE_GRAVE
+ KeyboardKeys::Comma,
+ KeyboardKeys::Period,
+ KeyboardKeys::Slash,
+ KeyboardKeys::Capital,
+ KeyboardKeys::F1,
+ KeyboardKeys::F2,
+ KeyboardKeys::F3,
+ KeyboardKeys::F4,
+ KeyboardKeys::F5,
+ KeyboardKeys::F6,
+ KeyboardKeys::F7,
+ KeyboardKeys::F8,
+ KeyboardKeys::F9,
+ KeyboardKeys::F10,
+ KeyboardKeys::F11,
+ KeyboardKeys::F12,
+ KeyboardKeys::PrintScreen,
+ KeyboardKeys::Scroll,
+ KeyboardKeys::Pause,
+ KeyboardKeys::Insert,
+ KeyboardKeys::Home,
+ KeyboardKeys::PageUp,
+ KeyboardKeys::Delete,
+ KeyboardKeys::End,
+ KeyboardKeys::PageDown,
+ KeyboardKeys::ArrowRight,
+ KeyboardKeys::ArrowLeft,
+ KeyboardKeys::ArrowDown,
+ KeyboardKeys::ArrowUp,
+ KeyboardKeys::Numlock,
+ KeyboardKeys::NumpadDivide,
+ KeyboardKeys::NumpadMultiply,
+ KeyboardKeys::NumpadSubtract,
+ KeyboardKeys::NumpadAdd,
+ KeyboardKeys::Return, // SDL_SCANCODE_KP_ENTER ?
+ KeyboardKeys::Numpad1,
+ KeyboardKeys::Numpad2,
+ KeyboardKeys::Numpad3,
+ KeyboardKeys::Numpad4,
+ KeyboardKeys::Numpad5,
+ KeyboardKeys::Numpad6,
+ KeyboardKeys::Numpad7,
+ KeyboardKeys::Numpad8,
+ KeyboardKeys::Numpad9,
+ KeyboardKeys::Numpad0, //98
+ KeyboardKeys::NumpadDecimal, // SDL_SCANCODE_KP_PERIOD
+ KeyboardKeys::Backslash, // SDL_SCANCODE_NONUSBACKSLASH ?
+ KeyboardKeys::Applications,
+ KeyboardKeys::Sleep, // SDL_SCANCODE_POWER ?
+ KeyboardKeys::None, // SDL_SCANCODE_KP_EQUALS ?
+ KeyboardKeys::F13,
+ KeyboardKeys::F14,
+ KeyboardKeys::F15,
+ KeyboardKeys::F16,
+ KeyboardKeys::F17,
+ KeyboardKeys::F18,
+ KeyboardKeys::F19,
+ KeyboardKeys::F20,
+ KeyboardKeys::F21,
+ KeyboardKeys::F22,
+ KeyboardKeys::F23,
+ KeyboardKeys::F24,
+ KeyboardKeys::Execute,
+ KeyboardKeys::Help,
+ KeyboardKeys::LeftMenu, // SDL_SCANCODE_MENU ?
+ KeyboardKeys::Select,
+ KeyboardKeys::None, // SDL_SCANCODE_STOP
+ KeyboardKeys::None, // SDL_SCANCODE_AGAIN
+ KeyboardKeys::None, // SDL_SCANCODE_UNDO
+ KeyboardKeys::None, // SDL_SCANCODE_CUT
+ KeyboardKeys::None, // SDL_SCANCODE_COPY
+ KeyboardKeys::None, // SDL_SCANCODE_PASTE
+ KeyboardKeys::None, // SDL_SCANCODE_FIND
+ KeyboardKeys::None, // SDL_SCANCODE_MUTE
+ KeyboardKeys::None, // SDL_SCANCODE_VOLUMEUP
+ KeyboardKeys::None, // SDL_SCANCODE_VOLUMEDOWN
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::NumpadSeparator, // SDL_SCANCODE_KP_COMMA ?
+ KeyboardKeys::None, // SDL_SCANCODE_KP_EQUALSAS400
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL1
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL2
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL3
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL4
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL5
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL6
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL7
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL8
+ KeyboardKeys::None, // SDL_SCANCODE_INTERNATIONAL9
+ KeyboardKeys::None, // SDL_SCANCODE_LANG1
+ KeyboardKeys::None, // SDL_SCANCODE_LANG2
+ KeyboardKeys::None, // SDL_SCANCODE_LANG3
+ KeyboardKeys::None, // SDL_SCANCODE_LANG4
+ KeyboardKeys::None, // SDL_SCANCODE_LANG5
+ KeyboardKeys::None, // SDL_SCANCODE_LANG6
+ KeyboardKeys::None, // SDL_SCANCODE_LANG7
+ KeyboardKeys::None, // SDL_SCANCODE_LANG8
+ KeyboardKeys::None, // SDL_SCANCODE_LANG9
+ KeyboardKeys::None, // SDL_SCANCODE_ALTERASE
+ KeyboardKeys::None, // SDL_SCANCODE_SYSREQ
+ KeyboardKeys::None, // SDL_SCANCODE_CANCEL
+ KeyboardKeys::Clear, // SDL_SCANCODE_CLEAR
+ KeyboardKeys::None, // SDL_SCANCODE_PRIOR
+ KeyboardKeys::None, // SDL_SCANCODE_RETURN2
+ KeyboardKeys::None, // SDL_SCANCODE_SEPARATOR
+ KeyboardKeys::None, // SDL_SCANCODE_OUT
+ KeyboardKeys::None, // SDL_SCANCODE_OPER
+ KeyboardKeys::None, // SDL_SCANCODE_CLEARAGAIN
+ KeyboardKeys::None, // SDL_SCANCODE_CRSEL
+ KeyboardKeys::None, // SDL_SCANCODE_EXSEL
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_00
+ KeyboardKeys::None, // SDL_SCANCODE_KP_000
+ KeyboardKeys::None, // SDL_SCANCODE_THOUSANDSSEPARATOR
+ KeyboardKeys::None, // SDL_SCANCODE_DECIMALSEPARATOR
+ KeyboardKeys::None, // SDL_SCANCODE_CURRENCYUNIT
+ KeyboardKeys::None, // SDL_SCANCODE_CURRENCYSUBUNIT
+ KeyboardKeys::None, // SDL_SCANCODE_KP_LEFTPAREN = 182,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_RIGHTPAREN = 183,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_LEFTBRACE = 184,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_RIGHTBRACE = 185,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_TAB = 186,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_BACKSPACE = 187,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_A = 188,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_B = 189,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_C = 190,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_D = 191,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_E = 192,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_F = 193,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_XOR = 194,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_POWER = 195,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_PERCENT = 196,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_LESS = 197,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_GREATER = 198,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_AMPERSAND = 199,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_DBLAMPERSAND = 200,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_VERTICALBAR = 201,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_DBLVERTICALBAR = 202,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_COLON = 203,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_HASH = 204,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_SPACE = 205,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_AT = 206,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_EXCLAM = 207,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_MEMSTORE = 208,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_MEMRECALL = 209,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_MEMCLEAR = 210,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_MEMADD = 211,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_MEMSUBTRACT = 212,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_MEMMULTIPLY = 213,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_MEMDIVIDE = 214,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_PLUSMINUS = 215,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_CLEAR = 216,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_CLEARENTRY = 217,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_BINARY = 218,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_OCTAL = 219,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_DECIMAL = 220,
+ KeyboardKeys::None, // SDL_SCANCODE_KP_HEXADECIMAL = 221,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::Control, // SDL_SCANCODE_LCTRL = 224,
+ KeyboardKeys::Shift, // SDL_SCANCODE_LSHIFT = 225,
+ KeyboardKeys::Alt, // SDL_SCANCODE_LALT = 226,
+ KeyboardKeys::LeftMenu, // SDL_SCANCODE_LGUI = 227,
+ KeyboardKeys::Control, // SDL_SCANCODE_RCTRL = 228,
+ KeyboardKeys::Shift, // SDL_SCANCODE_RSHIFT = 229,
+ KeyboardKeys::Alt, // SDL_SCANCODE_RALT = 230,
+ KeyboardKeys::RightMenu, // SDL_SCANCODE_RGUI = 231,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::None,
+ KeyboardKeys::Modechange, // SDL_SCANCODE_MODE
+ KeyboardKeys::Sleep, // SDL_SCANCODE_SLEEP
+ KeyboardKeys::None, // SDL_SCANCODE_WAKE
+ KeyboardKeys::None, // SDL_SCANCODE_CHANNEL_INCREMENT = 260,
+ KeyboardKeys::None, // SDL_SCANCODE_CHANNEL_DECREMENT = 261,
+ KeyboardKeys::None, // SDL_SCANCODE_MEDIA_PLAY = 262,
+ KeyboardKeys::None, // SDL_SCANCODE_MEDIA_PAUSE = 263,
+ KeyboardKeys::None, // SDL_SCANCODE_MEDIA_RECORD = 264,
+ KeyboardKeys::None, // SDL_SCANCODE_MEDIA_FAST_FORWARD = 265,
+ KeyboardKeys::None, // SDL_SCANCODE_MEDIA_REWIND = 266,
+ KeyboardKeys::MediaNextTrack, // SDL_SCANCODE_MEDIA_NEXT_TRACK = 267,
+ KeyboardKeys::MediaPrevTrack, // SDL_SCANCODE_MEDIA_PREVIOUS_TRACK = 268,
+ KeyboardKeys::MediaStop, // SDL_SCANCODE_MEDIA_STOP = 269,
+ KeyboardKeys::None, // SDL_SCANCODE_MEDIA_EJECT = 270,
+ KeyboardKeys::MediaPlayPause, // SDL_SCANCODE_MEDIA_PLAY_PAUSE = 271,
+ KeyboardKeys::None, // SDL_SCANCODE_MEDIA_SELECT = 272,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_NEW = 273,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_OPEN = 274,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_CLOSE = 275,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_EXIT = 276,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_SAVE = 277,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_PRINT = 278,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_PROPERTIES = 279,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_SEARCH = 280,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_HOME = 281,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_BACK = 282,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_FORWARD = 283,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_STOP = 284,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_REFRESH = 285,
+ KeyboardKeys::None, // SDL_SCANCODE_AC_BOOKMARKS = 286,
+ KeyboardKeys::None, // SDL_SCANCODE_SOFTLEFT = 287,
+ KeyboardKeys::None, // SDL_SCANCODE_SOFTRIGHT = 288,
+ KeyboardKeys::None, // SDL_SCANCODE_CALL = 289,
+ KeyboardKeys::None, // SDL_SCANCODE_ENDCALL = 290
+};
+
+///
+/// Implementation of the keyboard device for Windows platform.
+///
+///
+class SDLKeyboard : public Keyboard
+{
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ explicit SDLKeyboard()
+ : Keyboard()
+ {
+ }
+
+public:
+
+};
+
+///
+/// Implementation of the mouse device for SDL platform.
+///
+///
+class SDLMouse : public Mouse
+{
+private:
+ Float2 oldPosition;
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ explicit SDLMouse()
+ : Mouse()
+ {
+ }
+
+public:
+
+ Float2 GetMousePosition() const
+ {
+ return oldPosition;
+ }
+
+ // [Mouse]
+ void SetMousePosition(const Float2& newPosition) final override
+ {
+ SDL_WarpMouseGlobal(newPosition.X, newPosition.Y);
+
+ OnMouseMoved(newPosition);
+ }
+
+ void SetRelativeMode(bool relativeMode) final override
+ {
+ if (relativeMode == _relativeMode)
+ return;
+
+ if (relativeMode)
+ SDL_GetGlobalMouseState(&oldPosition.X, &oldPosition.Y);
+
+ Mouse::SetRelativeMode(relativeMode);
+ if (SDL_SetRelativeMouseMode(relativeMode ? SDL_TRUE : SDL_FALSE) != 0)
+ LOG(Error, "Failed to set mouse relative mode: {0}", String(SDL_GetError()));
+
+ if (!relativeMode)
+ {
+ SDL_WarpMouseGlobal(oldPosition.X, oldPosition.Y);
+ OnMouseMoved(oldPosition);
+ }
+ }
+};
+
+///
+/// Implementation of the gamepad device for SDL platform.
+///
+///
+class SDLGamepad : public Gamepad
+{
+private:
+
+ SDL_Gamepad* _gamepad;
+ SDL_JoystickID _instanceId;
+
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The joystick.
+ explicit SDLGamepad(SDL_JoystickID instanceId);
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~SDLGamepad();
+
+private:
+
+ SDLGamepad(SDL_Gamepad* gamepad, SDL_JoystickID instanceId);
+
+public:
+
+ static SDLGamepad* GetGamepadById(SDL_JoystickID id)
+ {
+ SDLGamepad* gamepad = nullptr;
+ SDLInputImpl::Gamepads.TryGet(id, gamepad);
+ return gamepad;
+ }
+
+ SDL_JoystickID GetJoystickInstanceId() const
+ {
+ return _instanceId;
+ }
+
+ void OnAxisMotion(SDL_GamepadAxis axis, int16 value);
+
+ void OnButtonState(SDL_GamepadButton axis, uint8 state);
+
+ // [Gamepad]
+ void SetVibration(const GamepadVibrationState& state) override;
+
+protected:
+
+ // [Gamepad]
+ bool UpdateState() override;
+};
+
+void SDLInput::Init()
+{
+ Input::Mouse = SDLInputImpl::Mouse = New();
+ Input::Keyboard = SDLInputImpl::Keyboard = New();
+}
+
+void SDLInput::Update()
+{
+}
+
+float NormalizeAxisValue(const int16 axisVal)
+{
+ // Normalize [-32768..32767] -> [-1..1]
+ const float norm = axisVal <= 0 ? 32768.0f : 32767.0f;
+ return float(axisVal) / norm;
+}
+
+bool SDLInput::HandleEvent(SDLWindow* window, SDL_Event& event)
+{
+ switch (event.type)
+ {
+ case SDL_EVENT_MOUSE_MOTION:
+ {
+ const Float2 mousePos = window->ClientToScreen({ event.motion.x, event.motion.y });
+ Input::Mouse->OnMouseMove(mousePos, window);
+ return true;
+ }
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ {
+ Input::Mouse->OnMouseLeave(window);
+ return true;
+ }
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ {
+ const Float2 mousePos = window->ClientToScreen({ event.button.x, event.button.y });
+ MouseButton button = MouseButton::None;
+ if (event.button.button == SDL_BUTTON_LEFT)
+ button = MouseButton::Left;
+ else if (event.button.button == SDL_BUTTON_RIGHT)
+ button = MouseButton::Right;
+ else if (event.button.button == SDL_BUTTON_MIDDLE)
+ button = MouseButton::Middle;
+ else if (event.button.button == SDL_BUTTON_X1)
+ button = MouseButton::Extended1;
+ else if (event.button.button == SDL_BUTTON_X2)
+ button = MouseButton::Extended2;
+
+ if (event.button.state == SDL_RELEASED)
+ Input::Mouse->OnMouseUp(mousePos, button, window);
+ // Prevent sending mouse down event when double-clicking
+ else if (event.button.clicks % 2 == 1)
+ Input::Mouse->OnMouseDown(mousePos, button, window);
+ else
+ Input::Mouse->OnMouseDoubleClick(mousePos, button, window);
+
+ return true;
+ }
+ case SDL_EVENT_MOUSE_WHEEL:
+ {
+ const Float2 mousePos = window->ClientToScreen({ event.wheel.mouse_x, event.wheel.mouse_y });
+ const float delta = event.wheel.y;
+
+ Input::Mouse->OnMouseWheel(mousePos, delta, window);
+ return true;
+ }
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_KEY_UP:
+ {
+ // TODO: scancode support
+ KeyboardKeys key = SDL_TO_FLAX_KEYS_MAP[event.key.scancode];
+ //event.key.mod
+ if (event.key.state == SDL_RELEASED)
+ Input::Keyboard->OnKeyUp(key, window);
+ else
+ Input::Keyboard->OnKeyDown(key, window);
+ return true;
+ }
+ case SDL_EVENT_TEXT_EDITING:
+ {
+ auto edit = event.edit;
+ return true;
+ }
+ case SDL_EVENT_TEXT_INPUT:
+ {
+ String text(event.text.text);
+ for (int i = 0; i < text.Length(); i++)
+ {
+ Input::Keyboard->OnCharInput(text[i], window);
+ }
+ return true;
+ }
+ case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+ {
+ SDLGamepad* gamepad = SDLGamepad::GetGamepadById(event.gaxis.which);
+ SDL_GamepadAxis axis = (SDL_GamepadAxis)event.gaxis.axis;
+ gamepad->OnAxisMotion(axis, event.gaxis.value);
+
+ LOG(Info, "SDL_EVENT_GAMEPAD_AXIS_MOTION");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+ case SDL_EVENT_GAMEPAD_BUTTON_UP:
+ {
+ SDLGamepad* gamepad = SDLGamepad::GetGamepadById(event.gbutton.which);
+ SDL_GamepadButton button = (SDL_GamepadButton)event.gbutton.button;
+ gamepad->OnButtonState(button, event.gbutton.state);
+
+ LOG(Info, "SDL_EVENT_GAMEPAD_BUTTON_");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_ADDED:
+ {
+ Input::Gamepads.Add(New(event.gdevice.which));
+ Input::OnGamepadsChanged();
+
+ LOG(Info, "SDL_EVENT_GAMEPAD_ADDED");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_REMOVED:
+ {
+ for (int i = 0; i < Input::Gamepads.Count(); i++)
+ {
+ SDLGamepad* gamepad = static_cast(Input::Gamepads[i]);
+ if (gamepad->GetJoystickInstanceId() == event.gdevice.which)
+ {
+ Input::Gamepads[i]->DeleteObject();
+ Input::Gamepads.RemoveAtKeepOrder(i);
+ Input::OnGamepadsChanged();
+ break;
+ }
+ }
+
+ LOG(Info, "SDL_EVENT_GAMEPAD_REMOVED");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_REMAPPED:
+ {
+ auto ev = event.gdevice;
+ LOG(Info, "SDL_EVENT_GAMEPAD_REMAPPED");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
+ {
+ LOG(Info, "SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
+ {
+ LOG(Info, "SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
+ {
+ LOG(Info, "SDL_EVENT_GAMEPAD_TOUCHPAD_UP");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
+ {
+ LOG(Info, "SDL_EVENT_GAMEPAD_SENSOR_UPDATE");
+ break;
+ }
+ case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
+ {
+ auto ev = event.gdevice;
+ LOG(Info, "SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED");
+ break;
+ }
+ }
+ return false;
+}
+
+Guid GetGamepadGuid(SDL_JoystickID instanceId)
+{
+ SDL_GUID joystickGuid = SDL_GetGamepadGUIDForID(instanceId);
+ Guid guid;
+ Platform::MemoryCopy(&guid.Raw, joystickGuid.data, sizeof(uint8) * 16);
+ return guid;
+}
+
+SDLGamepad::SDLGamepad(SDL_JoystickID instanceId)
+ : SDLGamepad(SDL_OpenGamepad(instanceId), instanceId)
+{
+}
+
+SDLGamepad::SDLGamepad(SDL_Gamepad* gamepad, SDL_JoystickID instanceId)
+ : Gamepad(GetGamepadGuid(instanceId), String(SDL_GetGamepadName(gamepad)))
+ , _gamepad(gamepad)
+ , _instanceId(instanceId)
+{
+ SDLInputImpl::Gamepads.Add(_instanceId, this);
+}
+
+SDLGamepad::~SDLGamepad()
+{
+ SDL_CloseGamepad(_gamepad);
+ SDLInputImpl::Gamepads.Remove(_instanceId);
+}
+
+void SDLGamepad::SetVibration(const GamepadVibrationState& state)
+{
+ Gamepad::SetVibration(state);
+}
+
+bool SDLGamepad::UpdateState()
+{
+ return false;
+}
+
+void SDLGamepad::OnAxisMotion(SDL_GamepadAxis sdlAxis, int16 value)
+{
+ GamepadAxis axis;
+ int16 deadzone = 1; // SDL reports -1 for centered axis?
+ float valueNormalized = NormalizeAxisValue(value);
+ switch (sdlAxis)
+ {
+ case SDL_GAMEPAD_AXIS_LEFTX:
+ axis = GamepadAxis::LeftStickX;
+ deadzone = LEFT_STICK_THRESHOLD;
+ _state.Buttons[(int32)GamepadButton::LeftStickLeft] = value > LEFT_STICK_THRESHOLD;
+ _state.Buttons[(int32)GamepadButton::LeftStickRight] = value < -LEFT_STICK_THRESHOLD;
+ break;
+ case SDL_GAMEPAD_AXIS_LEFTY:
+ axis = GamepadAxis::LeftStickY;
+ deadzone = LEFT_STICK_THRESHOLD;
+ _state.Buttons[(int32)GamepadButton::LeftStickUp] = value < -LEFT_STICK_THRESHOLD;
+ _state.Buttons[(int32)GamepadButton::LeftStickDown] = value > LEFT_STICK_THRESHOLD;
+ valueNormalized = -valueNormalized;
+ break;
+ case SDL_GAMEPAD_AXIS_RIGHTX:
+ deadzone = RIGHT_STICK_THRESHOLD;
+ axis = GamepadAxis::RightStickX;
+ _state.Buttons[(int32)GamepadButton::RightStickLeft] = value > RIGHT_STICK_THRESHOLD;
+ _state.Buttons[(int32)GamepadButton::RightStickRight] = value < -RIGHT_STICK_THRESHOLD;
+ break;
+ case SDL_GAMEPAD_AXIS_RIGHTY:
+ deadzone = RIGHT_STICK_THRESHOLD;
+ axis = GamepadAxis::RightStickY;
+ _state.Buttons[(int32)GamepadButton::RightStickUp] = value < -RIGHT_STICK_THRESHOLD;
+ _state.Buttons[(int32)GamepadButton::RightStickDown] = value > RIGHT_STICK_THRESHOLD;
+ valueNormalized = -valueNormalized;
+ break;
+ case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
+ deadzone = TRIGGER_THRESHOLD;
+ axis = GamepadAxis::LeftTrigger;
+ _state.Buttons[(int32)GamepadButton::LeftTrigger] = value > TRIGGER_THRESHOLD;
+ break;
+ case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
+ deadzone = TRIGGER_THRESHOLD;
+ axis = GamepadAxis::RightTrigger;
+ _state.Buttons[(int32)GamepadButton::RightTrigger] = value > TRIGGER_THRESHOLD;
+ break;
+ default:
+ return;
+ }
+ if (value <= deadzone && value >= -deadzone)
+ valueNormalized = 0.0f;
+ _state.Axis[(int32)axis] = valueNormalized;
+}
+
+void SDLGamepad::OnButtonState(SDL_GamepadButton sdlButton, uint8 state)
+{
+ switch (sdlButton)
+ {
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_SOUTH:
+ _state.Buttons[(int32)GamepadButton::A] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_EAST:
+ _state.Buttons[(int32)GamepadButton::B] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_WEST:
+ _state.Buttons[(int32)GamepadButton::X] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_NORTH:
+ _state.Buttons[(int32)GamepadButton::Y] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
+ _state.Buttons[(int32)GamepadButton::LeftShoulder] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
+ _state.Buttons[(int32)GamepadButton::RightShoulder] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_BACK:
+ _state.Buttons[(int32)GamepadButton::Back] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_START:
+ _state.Buttons[(int32)GamepadButton::Start] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_LEFT_STICK:
+ _state.Buttons[(int32)GamepadButton::LeftThumb] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_RIGHT_STICK:
+ _state.Buttons[(int32)GamepadButton::RightThumb] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_UP:
+ _state.Buttons[(int32)GamepadButton::DPadUp] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_DOWN:
+ _state.Buttons[(int32)GamepadButton::DPadDown] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_LEFT:
+ _state.Buttons[(int32)GamepadButton::DPadLeft] = state == SDL_PRESSED;
+ break;
+ case SDL_GamepadButton::SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
+ _state.Buttons[(int32)GamepadButton::DPadRight] = state == SDL_PRESSED;
+ break;
+ }
+}
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLInput.h b/Source/Engine/Platform/SDL/SDLInput.h
new file mode 100644
index 000000000..74a8737b5
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLInput.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_SDL
+
+class SDLWindow;
+union SDL_Event;
+
+///
+/// SDL specific implementation of the input system parts.
+///
+class SDLInput
+{
+public:
+
+ static void Init();
+ static void Update();
+ static bool HandleEvent(SDLWindow* window, SDL_Event& event);
+};
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp
new file mode 100644
index 000000000..9c37dcdba
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp
@@ -0,0 +1,930 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL && PLATFORM_LINUX
+
+#include "Engine/Platform/Platform.h"
+#include "SDLWindow.h"
+#include "Engine/Platform/IGuiData.h"
+#include "Engine/Engine/Engine.h"
+#include "Engine/Platform/SDL/SDLClipboard.h"
+#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Collections/Array.h"
+#include "Engine/Engine/CommandLine.h"
+#include "Engine/Platform/WindowsManager.h"
+#include "Engine/Platform/Linux/IncludeX11.h"
+
+#include
+#include
+#include
+#include
+
+Delegate LinuxPlatform::xEventReceived;
+
+// Missing Wayland features:
+// - Application icon (xdg-toplevel-icon-v1) https://github.com/libsdl-org/SDL/pull/9584
+// - Window positioning and position tracking
+// - Color picker (xdg-desktop-portal?) https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html
+// -
+
+namespace
+{
+ bool UseWayland = false;
+ X11::Display* xDisplay = nullptr;
+ X11::XIM IM = nullptr;
+ X11::XIC IC = nullptr;
+ X11::Atom xAtomDeleteWindow;
+ X11::Atom xAtomXdndEnter;
+ X11::Atom xAtomXdndPosition;
+ X11::Atom xAtomXdndLeave;
+ X11::Atom xAtomXdndDrop;
+ X11::Atom xAtomXdndActionCopy;
+ X11::Atom xAtomXdndStatus;
+ X11::Atom xAtomXdndSelection;
+ X11::Atom xAtomXdndFinished;
+ X11::Atom xAtomXdndAware;
+ X11::Atom xAtomWmState;
+ X11::Atom xAtomWmStateHidden;
+ X11::Atom xAtomWmStateMaxVert;
+ X11::Atom xAtomWmStateMaxHorz;
+ X11::Atom xAtomWmWindowOpacity;
+ X11::Atom xAtomWmName;
+ X11::Atom xAtomAtom;
+ X11::Atom xAtomClipboard;
+ X11::Atom xAtomPrimary;
+ X11::Atom xAtomTargets;
+ X11::Atom xAtomText;
+ X11::Atom xAtomString;
+ X11::Atom xAtomUTF8String;
+ X11::Atom xAtomXselData;
+
+ X11::Atom xDnDRequested = 0;
+ X11::Window xDndSourceWindow = 0;
+ DragDropEffect xDndResult;
+ Float2 xDndPos;
+ int32 xDnDVersion = 0;
+ int32 XFixesSelectionNotifyEvent = 0;
+}
+
+class LinuxDropFilesData : public IGuiData
+{
+public:
+ Array Files;
+
+ Type GetType() const override
+ {
+ return Type::Files;
+ }
+ String GetAsText() const override
+ {
+ return String::Empty;
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ files->Add(Files);
+ }
+};
+
+class LinuxDropTextData : public IGuiData
+{
+public:
+ StringView Text;
+
+ Type GetType() const override
+ {
+ return Type::Text;
+ }
+ String GetAsText() const override
+ {
+ return String(Text);
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ }
+};
+
+struct Property
+{
+ unsigned char* data;
+ int format, nitems;
+ X11::Atom type;
+};
+
+namespace Impl
+{
+ StringAnsi ClipboardText;
+
+ void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window)
+ {
+ X11::Window selectionOwner = X11::XGetSelectionOwner(xDisplay, source);
+ if (selectionOwner == 0)
+ {
+ // No copy owner
+ return;
+ }
+ if (selectionOwner == window)
+ {
+ // Copy/paste from self
+ result.Set(ClipboardText.Get(), ClipboardText.Length());
+ return;
+ }
+
+ // Send event to get data from the owner
+ int format;
+ unsigned long N, size;
+ char* data;
+ X11::Atom target;
+ X11::XEvent event;
+ X11::XConvertSelection(xDisplay, xAtomClipboard, atom, xAtomXselData, window, CurrentTime);
+ X11::XSync(xDisplay, 0);
+ if (X11::XCheckTypedEvent(xDisplay, SelectionNotify, &event))
+ {
+ if (event.xselection.selection != xAtomClipboard)
+ return;
+ if (event.xselection.property)
+ {
+ X11::XGetWindowProperty(event.xselection.display, event.xselection.requestor, event.xselection.property, 0L,(~0L), 0, AnyPropertyType, &target, &format, &size, &N,(unsigned char**)&data);
+ if (target == xAtomUTF8String || target == xAtomString)
+ {
+ // Got text to paste
+ result.Set(data , size);
+ X11::XFree(data);
+ }
+ X11::XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
+ }
+ }
+ }
+
+ Property ReadProperty(X11::Display* display, X11::Window window, X11::Atom property)
+ {
+ X11::Atom readType = 0;
+ int readFormat = 0;
+ unsigned long nitems = 0;
+ unsigned long readBytes = 0;
+ unsigned char* result = nullptr;
+ int bytesCount = 1024;
+ if (property != 0)
+ {
+ do
+ {
+ if (result != nullptr)
+ X11::XFree(result);
+ XGetWindowProperty(display, window, property, 0, bytesCount, 0, AnyPropertyType, &readType, &readFormat, &nitems, &readBytes, &result);
+ bytesCount *= 2;
+ } while (readBytes != 0);
+ }
+ Property p = { result, readFormat, (int)nitems, readType };
+ return p;
+ }
+
+ static X11::Atom SelectTargetFromList(X11::Display* display, const char* targetType, X11::Atom* list, int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ X11::Atom atom = list[i];
+ if (atom != 0 && StringAnsi(XGetAtomName(display, atom)) == targetType)
+ return atom;
+ }
+ return 0;
+ }
+
+ static X11::Atom SelectTargetFromAtoms(X11::Display* display, const char* targetType, X11::Atom t1, X11::Atom t2, X11::Atom t3)
+ {
+ if (t1 != 0 && StringAnsi(XGetAtomName(display, t1)) == targetType)
+ return t1;
+ if (t2 != 0 && StringAnsi(XGetAtomName(display, t2)) == targetType)
+ return t2;
+ if (t3 != 0 && StringAnsi(XGetAtomName(display, t3)) == targetType)
+ return t3;
+ return 0;
+ }
+
+ static X11::Window FindAppWindow(X11::Display* display, X11::Window w)
+ {
+ int nprops, i = 0;
+ X11::Atom* a;
+ if (w == 0)
+ return 0;
+ a = X11::XListProperties(display, w, &nprops);
+ for (i = 0; i < nprops; i++)
+ {
+ if (a[i] == xAtomXdndAware)
+ break;
+ }
+ if (nprops)
+ X11::XFree(a);
+ if (i != nprops)
+ return w;
+ X11::Window child, wtmp;
+ int tmp;
+ unsigned int utmp;
+ X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp);
+ return FindAppWindow(display, child);
+ }
+
+ static Float2 GetX11MousePosition()
+ {
+ if (!xDisplay)
+ return Float2::Zero;
+ int32 x = 0, y = 0;
+ uint32 screenCount = (uint32)X11::XScreenCount(xDisplay);
+ for (uint32 i = 0; i < screenCount; i++)
+ {
+ X11::Window outRoot, outChild;
+ int32 childX, childY;
+ uint32 mask;
+ if (X11::XQueryPointer(xDisplay, X11::XRootWindow(xDisplay, i), &outRoot, &outChild, &x, &y, &childX, &childY, &mask))
+ break;
+ }
+ return Float2((float)x, (float)y);
+ }
+}
+
+DragDropEffect Window::DoDragDrop(const StringView& data)
+{
+ if (CommandLine::Options.Headless)
+ return DragDropEffect::None;
+
+ if (UseWayland)
+ return DoDragDropWayland(data);
+ else
+ return DoDragDropX11(data);
+}
+
+DragDropEffect Window::DoDragDropWayland(const StringView& data)
+{
+ // TODO: Wayland
+ ASSERT(false);
+ return DragDropEffect::None;
+}
+
+DragDropEffect Window::DoDragDropX11(const StringView& data)
+{
+ auto cursorWrong = X11::XCreateFontCursor(xDisplay, 54);
+ auto cursorTransient = X11::XCreateFontCursor(xDisplay, 24);
+ auto cursorGood = X11::XCreateFontCursor(xDisplay, 4);
+ Array> formats;
+ formats.Add(X11::XInternAtom(xDisplay, "text/plain", 0));
+ formats.Add(xAtomText);
+ formats.Add(xAtomString);
+ StringAnsi dataAnsi(data);
+ LinuxDropTextData dropData;
+ dropData.Text = data;
+#if !PLATFORM_SDL
+ X11::Window mainWindow = _window;
+#else
+ X11::Window mainWindow = static_cast(GetX11WindowHandle());
+#endif
+
+ // Make sure SDL hasn't grabbed the pointer, and force ungrab it
+ XUngrabPointer(xDisplay, CurrentTime);
+ SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
+
+ // Begin dragging
+ auto screen = X11::XDefaultScreen(xDisplay);
+ auto rootWindow = X11::XRootWindow(xDisplay, screen);
+
+ if (X11::XGrabPointer(xDisplay, mainWindow, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess)
+ {
+ SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "1");
+ return DragDropEffect::None;
+ }
+ X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, mainWindow, CurrentTime);
+
+ // Process events
+ X11::XEvent event;
+ enum Status
+ {
+ Unaware,
+ Unreceptive,
+ CanDrop,
+ };
+ int status = Unaware, previousVersion = -1;
+ X11::Window previousWindow = 0;
+ DragDropEffect result = DragDropEffect::None;
+ float lastDraw = Platform::GetTimeSeconds();
+ float startTime = lastDraw;
+ while (true)
+ {
+ X11::XNextEvent(xDisplay, &event);
+
+ if (event.type == SelectionClear)
+ break;
+ if (event.type == SelectionRequest)
+ {
+ // Extract the relavent data
+ X11::Window owner = event.xselectionrequest.owner;
+ X11::Atom selection = event.xselectionrequest.selection;
+ X11::Atom target = event.xselectionrequest.target;
+ X11::Atom property = event.xselectionrequest.property;
+ X11::Window requestor = event.xselectionrequest.requestor;
+ X11::Time timestamp = event.xselectionrequest.time;
+ X11::Display* disp = event.xselection.display;
+ X11::XEvent s;
+ s.xselection.type = SelectionNotify;
+ s.xselection.requestor = requestor;
+ s.xselection.selection = selection;
+ s.xselection.target = target;
+ s.xselection.property = 0;
+ s.xselection.time = timestamp;
+ if (target == xAtomTargets)
+ {
+ Array targets;
+ targets.Add(target);
+ targets.Add(X11::XInternAtom(disp, "MULTIPLE", 0));
+ targets.Add(formats.Get(), formats.Count());
+ X11::XChangeProperty(disp, requestor, property, xAtomAtom, 32, PropModeReplace, (unsigned char*)targets.Get(), targets.Count());
+ s.xselection.property = property;
+ }
+ else if (formats.Contains(target))
+ {
+ s.xselection.property = property;
+ X11::XChangeProperty(disp, requestor, property, target, 8, PropModeReplace, reinterpret_cast(dataAnsi.Get()), dataAnsi.Length());
+ }
+ X11::XSendEvent(event.xselection.display, event.xselectionrequest.requestor, 1, 0, &s);
+ }
+ else if (event.type == MotionNotify)
+ {
+ // Find window under mouse
+ auto window = Impl::FindAppWindow(xDisplay, rootWindow);
+ int fmt, version = -1;
+ X11::Atom atmp;
+ unsigned long nitems, bytesLeft;
+ unsigned char* data = nullptr;
+ if (window == previousWindow)
+ version = previousVersion;
+ else if(window == 0)
+ ;
+ else if (X11::XGetWindowProperty(xDisplay, window, xAtomXdndAware, 0, 2, 0, AnyPropertyType, &atmp, &fmt, &nitems, &bytesLeft, &data) != Success)
+ continue;
+ else if (data == 0)
+ continue;
+ else if (fmt != 32)
+ continue;
+ else if (nitems != 1)
+ continue;
+ else
+ version = data[0];
+ if (status == Unaware && version != -1)
+ status = Unreceptive;
+ else if(version == -1)
+ status = Unaware;
+ xDndPos = Float2((float)event.xmotion.x_root, (float)event.xmotion.y_root);
+
+ // Update mouse grab
+ if (status == Unaware)
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorWrong, CurrentTime);
+ else if(status == Unreceptive)
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorTransient, CurrentTime);
+ else
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorGood, CurrentTime);
+
+ if (window != previousWindow && previousVersion != -1)
+ {
+ // Send drag left event
+ auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
+ if (ww)
+ {
+ ww->_dragOver = false;
+ ww->OnDragLeave();
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = previousWindow;
+ m.message_type = xAtomXdndLeave;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = 0;
+ m.data.l[3] = 0;
+ m.data.l[4] = 0;
+ X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ if (window != previousWindow && version != -1)
+ {
+ // Send drag enter event
+ auto ww = WindowsManager::GetByNativePtr((void*)window);
+ if (ww)
+ {
+ xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition());
+ xDndResult = DragDropEffect::None;
+ ww->OnDragEnter(&dropData, xDndPos, xDndResult);
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = window;
+ m.message_type = xAtomXdndEnter;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = Math::Min(5, version) << 24 | (formats.Count() > 3);
+ m.data.l[2] = formats.Count() > 0 ? formats[0] : 0;
+ m.data.l[3] = formats.Count() > 1 ? formats[1] : 0;
+ m.data.l[4] = formats.Count() > 2 ? formats[2] : 0;
+ X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ if (version != -1)
+ {
+ // Send position event
+ auto ww = WindowsManager::GetByNativePtr((void*)window);
+ if (ww)
+ {
+ xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition());
+ ww->_dragOver = true;
+ xDndResult = DragDropEffect::None;
+ ww->OnDragOver(&dropData, xDndPos, xDndResult);
+ status = CanDrop;
+ }
+ else
+ {
+ int x, y, tmp;
+ unsigned int utmp;
+ X11::Window wtmp;
+ X11::XQueryPointer(xDisplay, window, &wtmp, &wtmp, &tmp, &tmp, &x, &y, &utmp);
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = window;
+ m.message_type = xAtomXdndPosition;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = (x << 16) | y;
+ m.data.l[3] = CurrentTime;
+ m.data.l[4] = xAtomXdndActionCopy;
+ X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ previousWindow = window;
+ previousVersion = version;
+ }
+ else if (event.type == ClientMessage && event.xclient.message_type == xAtomXdndStatus)
+ {
+ if ((event.xclient.data.l[1]&1) && status != Unaware)
+ status = CanDrop;
+ if (!(event.xclient.data.l[1]&1) && status != Unaware)
+ status = Unreceptive;
+ }
+ else if (event.type == ButtonRelease && event.xbutton.button == Button1)
+ {
+ if (status == CanDrop)
+ {
+ // Send drop event
+ auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
+ if (ww)
+ {
+ xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition());
+ xDndResult = DragDropEffect::None;
+ ww->OnDragDrop(&dropData, xDndPos, xDndResult);
+ ww->Focus();
+ result = xDndResult;
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = previousWindow;
+ m.message_type = xAtomXdndDrop;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = CurrentTime;
+ m.data.l[3] = 0;
+ m.data.l[4] = 0;
+ X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ result = DragDropEffect::Copy;
+ }
+ }
+ break;
+ }
+
+ // Redraw
+ const float time = Platform::GetTimeSeconds();
+ if (time - lastDraw >= 1.0f / 20.0f)
+ {
+ lastDraw = time;
+
+ Engine::OnDraw();
+ }
+
+ // Prevent dead-loop
+ if (time - startTime >= 10.0f)
+ {
+ LOG(Warning, "DoDragDrop timed out after 10 seconds.");
+ break;
+ }
+ }
+
+ // Drag end
+ if (previousWindow != 0 && previousVersion != -1)
+ {
+ // Send drag left event
+ auto ww = WindowsManager::GetByNativePtr((void*)previousWindow);
+ if (ww)
+ {
+ ww->_dragOver = false;
+ ww->OnDragLeave();
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = previousWindow;
+ m.message_type = xAtomXdndLeave;
+ m.format = 32;
+ m.data.l[0] = mainWindow;
+ m.data.l[1] = 0;
+ m.data.l[2] = 0;
+ m.data.l[3] = 0;
+ m.data.l[4] = 0;
+ X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ }
+ }
+
+ // End grabbing
+ X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, 0, CurrentTime);
+ XUngrabPointer(xDisplay, CurrentTime);
+ X11::XFlush(xDisplay);
+
+ SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "1");
+
+ return result;
+}
+
+void SDLClipboard::Clear()
+{
+ SetText(StringView::Empty);
+}
+
+void SDLClipboard::SetText(const StringView& text)
+{
+ if (CommandLine::Options.Headless)
+ return;
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return;
+
+ if (xDisplay)
+ {
+ X11::Window window = (X11::Window)(mainWindow->GetX11WindowHandle());
+ Impl::ClipboardText.Set(text.Get(), text.Length());
+ X11::XSetSelectionOwner(xDisplay, xAtomClipboard, window, CurrentTime); // CLIPBOARD
+ //X11::XSetSelectionOwner(xDisplay, xAtomPrimary, window, CurrentTime); // XA_PRIMARY
+ X11::XFlush(xDisplay);
+ X11::XGetSelectionOwner(xDisplay, xAtomClipboard);
+ //X11::XGetSelectionOwner(xDisplay, xAtomPrimary);
+ }
+ else
+ {
+ ASSERT(false); // TODO: Wayland
+ }
+}
+
+void SDLClipboard::SetRawData(const Span& data)
+{
+}
+
+void SDLClipboard::SetFiles(const Array& files)
+{
+}
+
+String SDLClipboard::GetText()
+{
+ if (CommandLine::Options.Headless)
+ return String::Empty;
+ String result;
+ auto mainWindow = Engine::MainWindow;
+ if (!mainWindow)
+ return result;
+ if (xDisplay)
+ {
+ X11::Window window = (X11::Window)mainWindow->GetX11WindowHandle();
+
+ Impl::ClipboardGetText(result, xAtomClipboard, xAtomUTF8String, window);
+ if (result.HasChars())
+ return result;
+ Impl::ClipboardGetText(result, xAtomClipboard, xAtomString, window);
+ if (result.HasChars())
+ return result;
+ Impl::ClipboardGetText(result, xAtomPrimary, xAtomUTF8String, window);
+ if (result.HasChars())
+ return result;
+ Impl::ClipboardGetText(result, xAtomPrimary, xAtomString, window);
+ return result;
+ }
+ else
+ {
+ ASSERT(false); // TODO: Wayland
+ }
+}
+
+Array SDLClipboard::GetRawData()
+{
+ return Array();
+}
+
+Array SDLClipboard::GetFiles()
+{
+ return Array();
+}
+
+SDL_bool SDLCALL SDLPlatform::X11EventHook(void *userdata, _XEvent *xevent)
+{
+ const X11::XEvent& event = *(X11::XEvent*)xevent;
+ Window* window;
+
+ // External event handling
+ xEventReceived(xevent);
+
+ if (event.type == ClientMessage)
+ {
+ if ((uint32)event.xclient.message_type == (uint32)xAtomXdndEnter)
+ {
+ // Drag&drop enter
+ X11::Window source = event.xclient.data.l[0];
+ xDnDVersion = (int32)(event.xclient.data.l[1] >> 24);
+ const char* targetTypeFiles = "text/uri-list";
+ if (event.xclient.data.l[1] & 1)
+ {
+ Property p = Impl::ReadProperty(xDisplay, source, XInternAtom(xDisplay, "XdndTypeList", 0));
+ xDnDRequested = Impl::SelectTargetFromList(xDisplay, targetTypeFiles, (X11::Atom*)p.data, p.nitems);
+ X11::XFree(p.data);
+ }
+ else
+ {
+ xDnDRequested = Impl::SelectTargetFromAtoms(xDisplay, targetTypeFiles, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
+ }
+ return SDL_FALSE;
+ }
+ else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndPosition)
+ {
+ // Drag&drop move
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = event.xclient.data.l[0];
+ m.message_type = xAtomXdndStatus;
+ m.format = 32;
+ m.data.l[0] = event.xany.window;
+ m.data.l[1] = (xDnDRequested != 0);
+ m.data.l[2] = 0;
+ m.data.l[3] = 0;
+ m.data.l[4] = xAtomXdndActionCopy;
+ X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m);
+ X11::XFlush(xDisplay);
+ xDndPos = Float2((float)(event.xclient.data.l[2] >> 16), (float)(event.xclient.data.l[2] & 0xffff));
+ window = WindowsManager::GetByNativePtr((void*)event.xany.window);
+ if (window)
+ {
+ LinuxDropFilesData dropData;
+ xDndResult = DragDropEffect::None;
+ if (window->_dragOver)
+ {
+ window->OnDragOver(&dropData, xDndPos, xDndResult);
+ }
+ else
+ {
+ window->_dragOver = true;
+ window->OnDragEnter(&dropData, xDndPos, xDndResult);
+ }
+ }
+ return SDL_FALSE;
+ }
+ else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndLeave)
+ {
+ window = WindowsManager::GetByNativePtr((void*)event.xany.window);
+ if (window && window->_dragOver)
+ {
+ window->_dragOver = false;
+ window->OnDragLeave();
+ }
+ return SDL_FALSE;
+ }
+ else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndDrop)
+ {
+ auto w = event.xany.window;
+ if (xDnDRequested != 0)
+ {
+ xDndSourceWindow = event.xclient.data.l[0];
+ if (xDnDVersion >= 1)
+ XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, xAtomPrimary, w, event.xclient.data.l[2]);
+ else
+ XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, xAtomPrimary, w, CurrentTime);
+ }
+ else
+ {
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = event.xclient.display;
+ m.window = event.xclient.data.l[0];
+ m.message_type = xAtomXdndFinished;
+ m.format = 32;
+ m.data.l[0] = w;
+ m.data.l[1] = 0;
+ m.data.l[2] = 0;
+ X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m);
+ }
+ return SDL_FALSE;
+ }
+ }
+ else if (event.type == SelectionNotify)
+ {
+ if (event.xselection.target == xDnDRequested)
+ {
+ // Drag&drop
+ window = WindowsManager::GetByNativePtr((void*)event.xany.window);
+ if (window)
+ {
+ Property p = Impl::ReadProperty(xDisplay, event.xany.window, xAtomPrimary);
+ if (xDndResult != DragDropEffect::None)
+ {
+ LinuxDropFilesData dropData;
+ const String filesList((const char*)p.data);
+ filesList.Split('\n', dropData.Files);
+ for (auto& e : dropData.Files)
+ {
+ e.Replace(TEXT("file://"), TEXT(""));
+ e.Replace(TEXT("%20"), TEXT(" "));
+ e = e.TrimTrailing();
+ }
+ xDndResult = DragDropEffect::None;
+ window->OnDragDrop(&dropData, xDndPos, xDndResult);
+ }
+ }
+ X11::XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+ m.type = ClientMessage;
+ m.display = xDisplay;
+ m.window = xDndSourceWindow;
+ m.message_type = xAtomXdndFinished;
+ m.format = 32;
+ m.data.l[0] = event.xany.window;
+ m.data.l[1] = 1;
+ m.data.l[2] = xAtomXdndActionCopy;
+ XSendEvent(xDisplay, xDndSourceWindow, 0, NoEventMask, (X11::XEvent*)&m);
+ return SDL_FALSE;
+ }
+ return SDL_FALSE;
+ }
+ else if (event.type == SelectionRequest)
+ {
+ if (event.xselectionrequest.selection != xAtomClipboard)
+ return SDL_FALSE;
+
+ const X11::XSelectionRequestEvent* xsr = &event.xselectionrequest;
+ X11::XSelectionEvent ev = { 0 };
+ ev.type = SelectionNotify;
+ ev.display = xsr->display;
+ ev.requestor = xsr->requestor;
+ ev.selection = xsr->selection;
+ ev.time = xsr->time;
+ ev.target = xsr->target;
+ ev.property = xsr->property;
+
+ int result = 0;
+ if (ev.target == xAtomTargets)
+ {
+ Array> types(2);
+ types.Add(xAtomTargets);
+ types.Add(xAtomUTF8String);
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomAtom, 32, PropModeReplace, (unsigned char*)types.Get(), types.Count());
+ }
+ else if (ev.target == xAtomString || ev.target == xAtomText)
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomString, 8, PropModeReplace, (unsigned char*)Impl::ClipboardText.Get(), Impl::ClipboardText.Length());
+ else if (ev.target == xAtomUTF8String)
+ result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomUTF8String, 8, PropModeReplace, (unsigned char*)Impl::ClipboardText.Get(), Impl::ClipboardText.Length());
+ else
+ ev.property = 0;
+ if ((result & 2) == 0)
+ X11::XSendEvent(xDisplay, ev.requestor, 0, 0, (X11::XEvent*)&ev);
+ return SDL_FALSE;
+ }
+ else if (event.type == SelectionClear)
+ return SDL_FALSE;
+ else if (event.type == XFixesSelectionNotifyEvent)
+ return SDL_FALSE;
+ return SDL_TRUE;
+}
+
+int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
+{
+ if (event->error_code == 5)
+ return 0; // BadAtom (invalid Atom parameter)
+ char buffer[256];
+ XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
+ LOG(Error, "X11 Error: {0}", String(buffer));
+ return 0;
+}
+
+bool SDLPlatform::InitPlatform()
+{
+ if (LinuxPlatform::Init())
+ return true;
+
+ if (!CommandLine::Options.Headless)
+ UseWayland = strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0;
+
+ return false;
+}
+
+bool SDLPlatform::InitPlatformX11(void* display)
+{
+ if (xDisplay || UseWayland)
+ return false;
+
+ // The Display instance must be the same one SDL uses internally
+ xDisplay = (X11::Display*)display;
+ SDL_SetX11EventHook((SDL_X11EventHook)&X11EventHook, nullptr);
+ X11::XSetErrorHandler(X11ErrorHandler);
+
+ //xDisplay = X11::XOpenDisplay(nullptr);
+ xAtomDeleteWindow = X11::XInternAtom(xDisplay, "WM_DELETE_WINDOW", 0);
+ xAtomXdndEnter = X11::XInternAtom(xDisplay, "XdndEnter", 0);
+ xAtomXdndPosition = X11::XInternAtom(xDisplay, "XdndPosition", 0);
+ xAtomXdndLeave = X11::XInternAtom(xDisplay, "XdndLeave", 0);
+ xAtomXdndDrop = X11::XInternAtom(xDisplay, "XdndDrop", 0);
+ xAtomXdndActionCopy = X11::XInternAtom(xDisplay, "XdndActionCopy", 0);
+ xAtomXdndStatus = X11::XInternAtom(xDisplay, "XdndStatus", 0);
+ xAtomXdndSelection = X11::XInternAtom(xDisplay, "XdndSelection", 0);
+ xAtomXdndFinished = X11::XInternAtom(xDisplay, "XdndFinished", 0);
+ xAtomXdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0);
+ xAtomWmStateHidden = X11::XInternAtom(xDisplay, "_NET_WM_STATE_HIDDEN", 0);
+ xAtomWmStateMaxHorz = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", 0);
+ xAtomWmStateMaxVert = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", 0);
+ xAtomWmWindowOpacity = X11::XInternAtom(xDisplay, "_NET_WM_WINDOW_OPACITY", 0);
+ xAtomWmName = X11::XInternAtom(xDisplay, "_NET_WM_NAME", 0);
+ xAtomAtom = static_cast(4); // XA_ATOM
+ xAtomClipboard = X11::XInternAtom(xDisplay, "CLIPBOARD", 0);
+ xAtomPrimary = static_cast(1); // XA_PRIMARY
+ xAtomTargets = X11::XInternAtom(xDisplay, "TARGETS", 0);
+ xAtomText = X11::XInternAtom(xDisplay, "TEXT", 0);
+ xAtomString = static_cast(31); // XA_STRING
+ xAtomUTF8String = X11::XInternAtom(xDisplay, "UTF8_STRING", 1);
+ if (xAtomUTF8String == 0)
+ xAtomUTF8String = xAtomString;
+ xAtomXselData = X11::XInternAtom(xDisplay, "XSEL_DATA", 0);
+
+ // We need to override handling of the XFixes selection tracking events from SDL
+ auto screen = X11::XDefaultScreen(xDisplay);
+ auto rootWindow = X11::XRootWindow(xDisplay, screen);
+ int eventBase = 0, errorBase = 0;
+ if (X11::XFixesQueryExtension(xDisplay, &eventBase, &errorBase))
+ {
+ XFixesSelectionNotifyEvent = eventBase + XFixesSelectionNotify;
+ X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomClipboard, XFixesSetSelectionOwnerNotifyMask);
+ X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomPrimary, XFixesSetSelectionOwnerNotifyMask);
+ }
+
+ return false;
+}
+
+void* SDLPlatform::GetXDisplay()
+{
+ return xDisplay;
+}
+
+void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
+{
+ base::SetHighDpiAwarenessEnabled(enable);
+}
+
+bool SDLPlatform::UsesWayland()
+{
+ return UseWayland;
+}
+
+bool SDLPlatform::UsesXWayland()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesX11()
+{
+ return !UseWayland;
+}
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp
new file mode 100644
index 000000000..2ebf49b79
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp
@@ -0,0 +1,12 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL && PLATFORM_MAC
+
+static_assert(false, "TODO");
+
+void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
+{
+ // TODO: This is now called before Platform::Init, ensure the scaling is changed accordingly during Platform::Init (see ApplePlatform::SetHighDpiAwarenessEnabled)
+}
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Windows.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Windows.cpp
new file mode 100644
index 000000000..719999052
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.Windows.cpp
@@ -0,0 +1,64 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+
+#if PLATFORM_SDL && PLATFORM_WINDOWS
+
+#include "SDLPlatform.h"
+#include "Engine/Core/Collections/Array.h"
+#include "Engine/Platform/WindowsManager.h"
+#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
+
+#include
+#include
+#include
+
+// The events for releasing the mouse during window dragging are missing, handle the mouse release event here
+SDL_bool SDLCALL SDLPlatform::EventMessageHook(void* userdata, MSG* msg)
+{
+#define GET_WINDOW_WITH_HWND(window, hwnd) \
+ do { \
+ (window) = nullptr; \
+ WindowsManager::WindowsLocker.Lock(); \
+ for (int32 i = 0; i < WindowsManager::Windows.Count(); i++) \
+ { \
+ if (WindowsManager::Windows[i]->GetNativePtr() == (hwnd)) \
+ { \
+ (window) = WindowsManager::Windows[i]; \
+ break; \
+ } \
+ } \
+ WindowsManager::WindowsLocker.Unlock(); \
+ ASSERT((window) != nullptr); \
+ } while (false)
+
+ if (msg->message == WM_NCLBUTTONDOWN)
+ {
+ Window* window;
+ GET_WINDOW_WITH_HWND(window, msg->hwnd);
+
+ auto hit = static_cast(msg->wParam);
+ if (SDLPlatform::CheckWindowDragging(window, hit))
+ return SDL_FALSE;
+ }
+ return SDL_TRUE;
+#undef GET_WINDOW_WITH_HWND
+}
+
+bool SDLPlatform::InitPlatform()
+{
+ // Workaround required for handling window dragging events properly for DockHintWindow
+ SDL_SetWindowsMessageHook(&EventMessageHook, nullptr);
+
+ if (WindowsPlatform::Init())
+ return true;
+
+ return false;
+}
+
+void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
+{
+ // Other supported values: "permonitor", "permonitorv2"
+ SDL_SetHint("SDL_WINDOWS_DPI_AWARENESS", enable ? "system" : "unaware");
+}
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.cpp b/Source/Engine/Platform/SDL/SDLPlatform.cpp
new file mode 100644
index 000000000..89aa246a1
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.cpp
@@ -0,0 +1,485 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL
+
+#include "SDLPlatform.h"
+#include "SDLWindow.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Mouse.h"
+#include "Engine/Platform/BatteryInfo.h"
+#include "Engine/Platform/WindowsManager.h"
+#include "Engine/Platform/SDL/SDLInput.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if PLATFORM_LINUX
+#include "Engine/Engine/CommandLine.h"
+#include "Engine/Platform/MessageBox.h"
+#include
+#endif
+
+#define DefaultDPI 96
+
+uint32 SDLPlatform::DraggedWindowId = 0;
+
+namespace
+{
+ int32 SystemDpi = 96;
+}
+
+bool SDLPlatform::Init()
+{
+#if PLATFORM_LINUX
+ if (CommandLine::Options.X11)
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "x11", SDL_HINT_OVERRIDE);
+ else if (CommandLine::Options.Wayland)
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland", SDL_HINT_OVERRIDE);
+ else
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland", SDL_HINT_OVERRIDE);
+#endif
+
+#if PLATFORM_LINUX
+ // TODO: This should be read from the platform configuration (needed for desktop icon handling)
+#if USE_EDITOR
+ SDL_SetHint(SDL_HINT_APP_ID, StringAnsi("com.FlaxEngine.FlaxEditor").Get());
+#else
+ SDL_SetHint(SDL_HINT_APP_ID, StringAnsi("com.FlaxEngine.FlaxGame").Get());
+#endif
+#else
+ SDL_SetHint(SDL_HINT_APP_ID, StringAnsi(ApplicationClassName).Get());
+#endif
+
+ SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, "0");
+ SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "0");
+ SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); // Fixes context menu focus issues when clicking unfocused menus
+ SDL_SetHint(SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE, "0");
+ SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0"); // Already handled during platform initialization
+ SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); // Allow borderless windows to be resizable on Windows
+ SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_WARP_MOTION, "0");
+ SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, "1"); // Needed for tracking mode
+ SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "1");
+
+ //SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1"); // Disables raw mouse input
+ SDL_SetHint(SDL_HINT_WINDOWS_RAW_KEYBOARD, "1");
+
+ // Disable SDL clipboard support
+ SDL_SetEventEnabled(SDL_EVENT_CLIPBOARD_UPDATE, SDL_FALSE);
+
+ // Disable SDL drag and drop support
+ SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, SDL_FALSE);
+ SDL_SetEventEnabled(SDL_EVENT_DROP_TEXT, SDL_FALSE);
+ SDL_SetEventEnabled(SDL_EVENT_DROP_BEGIN, SDL_FALSE);
+ SDL_SetEventEnabled(SDL_EVENT_DROP_COMPLETE, SDL_FALSE);
+ SDL_SetEventEnabled(SDL_EVENT_DROP_POSITION, SDL_FALSE);
+
+ if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) < 0)
+ Platform::Fatal(String::Format(TEXT("Failed to initialize SDL: {0}."), String(SDL_GetError())));
+
+ if (InitPlatform())
+ return true;
+
+ SDLInput::Init();
+
+ SystemDpi = (int)(SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()) * DefaultDPI);
+
+ //SDL_StartTextInput(); // TODO: Call this only when text input is expected (shows virtual keyboard in some cases)
+
+ return base::Init();
+}
+
+void SDLPlatform::LogInfo()
+{
+ base::LogInfo();
+
+ const int32 runtimeVersion = SDL_GetVersion();
+ LOG(Info, "Using SDL version {}.{}.{} ({}), runtime: {}.{}.{} ({})",
+ SDL_VERSIONNUM_MAJOR(SDL_VERSION), SDL_VERSIONNUM_MINOR(SDL_VERSION), SDL_VERSIONNUM_MICRO(SDL_VERSION), String(SDL_REVISION),
+ SDL_VERSIONNUM_MAJOR(runtimeVersion), SDL_VERSIONNUM_MINOR(runtimeVersion), SDL_VERSIONNUM_MICRO(runtimeVersion), String(SDL_GetRevision()));
+}
+
+bool SDLPlatform::CheckWindowDragging(Window* window, WindowHitCodes hit)
+{
+ bool handled = false;
+ window->OnLeftButtonHit(hit, handled);
+ if (handled)
+ {
+ DraggedWindowId = window->_windowId;
+ LOG(Info, "Dragging: {}", window->_settings.Title);
+
+ String dockHintWindow("DockHint.Window");
+ Window* window = nullptr;
+ WindowsManager::WindowsLocker.Lock();
+ for (int32 i = 0; i < WindowsManager::Windows.Count(); i++)
+ {
+ if (WindowsManager::Windows[i]->_title.Compare(dockHintWindow) == 0)
+ //if (WindowsManager::Windows[i]->_windowId == DraggedWindowId)
+ {
+ window = WindowsManager::Windows[i];
+ break;
+ }
+ }
+ WindowsManager::WindowsLocker.Unlock();
+
+ Float2 mousePos;
+ auto buttons = SDL_GetGlobalMouseState(&mousePos.X, &mousePos.Y);
+ if (window != nullptr)
+ {
+ /*int top, left, bottom, right;
+ SDL_GetWindowBordersSize(window->_window, &top, &left, &bottom, &right);
+ mousePos += Float2(left, -top);
+ Input::Mouse->OnMouseDown(mousePos, MouseButton::Left, window);*/
+ }
+ }
+ return handled;
+}
+
+void SDLPlatform::Tick()
+{
+ SDLInput::Update();
+
+ if (DraggedWindowId != 0)
+ {
+ Float2 mousePos;
+ auto buttons = SDL_GetGlobalMouseState(&mousePos.X, &mousePos.Y);
+ if (!(buttons & SDL_BUTTON(SDL_BUTTON_LEFT)))
+ {
+ Window* window = nullptr;
+ WindowsManager::WindowsLocker.Lock();
+ for (int32 i = 0; i < WindowsManager::Windows.Count(); i++)
+ {
+ if (WindowsManager::Windows[i]->_windowId == DraggedWindowId)
+ {
+ window = WindowsManager::Windows[i];
+ break;
+ }
+ }
+ WindowsManager::WindowsLocker.Unlock();
+
+ if (window != nullptr)
+ {
+ int top, left, bottom, right;
+ SDL_GetWindowBordersSize(window->_window, &top, &left, &bottom, &right);
+ mousePos += Float2(static_cast(left), static_cast(-top));
+ Input::Mouse->OnMouseUp(mousePos, MouseButton::Left, window);
+ }
+ DraggedWindowId = 0;
+ }
+ else
+ {
+#if PLATFORM_LINUX
+ String dockHintWindow("DockHint.Window");
+ Window* window = nullptr;
+ WindowsManager::WindowsLocker.Lock();
+ for (int32 i = 0; i < WindowsManager::Windows.Count(); i++)
+ {
+ if (WindowsManager::Windows[i]->_title.Compare(dockHintWindow) == 0)
+ //if (WindowsManager::Windows[i]->_windowId == DraggedWindowId)
+ {
+ window = WindowsManager::Windows[i];
+ break;
+ }
+ }
+ WindowsManager::WindowsLocker.Unlock();
+
+ if (window != nullptr)
+ {
+ int top, left, bottom, right;
+ SDL_GetWindowBordersSize(window->_window, &top, &left, &bottom, &right);
+ mousePos += Float2(static_cast(left), static_cast(-top));
+ Input::Mouse->OnMouseMove(mousePos, window);
+ }
+#endif
+ }
+ }
+
+ SDL_PumpEvents();
+ SDL_Event events[32];
+ int count;
+ while ((count = SDL_PeepEvents(events, SDL_arraysize(events), SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST)))
+ {
+ for (int i = 0; i < count; ++i)
+ {
+ SDLWindow* window = SDLWindow::GetWindowFromEvent(events[i]);
+ if (window)
+ window->HandleEvent(events[i]);
+ else if (events[i].type >= SDL_EVENT_JOYSTICK_AXIS_MOTION && events[i].type <= SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED)
+ SDLInput::HandleEvent(nullptr, events[i]);
+ else
+ SDLPlatform::HandleEvent(events[i]);
+ }
+ SDL_PumpEvents();
+ }
+}
+
+bool SDLPlatform::HandleEvent(SDL_Event& event)
+{
+ return true;
+}
+
+BatteryInfo SDLPlatform::GetBatteryInfo()
+{
+ BatteryInfo info;
+ int percentage;
+ SDL_PowerState powerState = SDL_GetPowerInfo(nullptr, &percentage);
+
+ if (percentage < 0)
+ info.BatteryLifePercent = 1.0f;
+ else
+ info.BatteryLifePercent = (float)percentage / 100.0f;
+
+ switch (powerState)
+ {
+ case SDL_POWERSTATE_CHARGING:
+ info.State = BatteryInfo::States::BatteryCharging;
+ break;
+ case SDL_POWERSTATE_ON_BATTERY:
+ info.State = BatteryInfo::States::BatteryDischarging;
+ break;
+ case SDL_POWERSTATE_CHARGED:
+ info.State = BatteryInfo::States::Connected;
+ break;
+ default:
+ info.State = BatteryInfo::States::Unknown;
+ }
+ return info;
+}
+
+int32 SDLPlatform::GetDpi()
+{
+ return SystemDpi;
+}
+
+void SDLPlatform::OpenUrl(const StringView& url)
+{
+ StringAnsi urlStr(url);
+ SDL_OpenURL(urlStr.GetText());
+}
+
+Float2 SDLPlatform::GetMousePosition()
+{
+ Float2 pos;
+ SDL_GetGlobalMouseState(&pos.X, &pos.Y);
+ return pos;
+}
+
+void SDLPlatform::SetMousePosition(const Float2& pos)
+{
+ SDL_WarpMouseGlobal(pos.X, pos.Y);
+}
+
+Float2 SDLPlatform::GetDesktopSize()
+{
+ SDL_Rect rect;
+ SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &rect);
+ return Float2(static_cast(rect.w), static_cast(rect.h));
+}
+
+Rectangle SDLPlatform::GetMonitorBounds(const Float2& screenPos)
+{
+ SDL_Point point{ (int32)screenPos.X, (int32)screenPos.Y };
+ SDL_DisplayID display = SDL_GetDisplayForPoint(&point);
+ SDL_Rect rect;
+ SDL_GetDisplayBounds(display, &rect);
+ return Rectangle(static_cast(rect.x), static_cast(rect.y), static_cast(rect.w), static_cast(rect.h));
+}
+
+Rectangle SDLPlatform::GetVirtualDesktopBounds()
+{
+ int count;
+ const SDL_DisplayID* displays = SDL_GetDisplays(&count);
+ if (displays == nullptr)
+ return Rectangle::Empty;
+
+ Rectangle bounds = Rectangle::Empty;
+ for (int i = 0; i < count; i++)
+ {
+ SDL_DisplayID display = displays[i];
+ SDL_Rect rect;
+ SDL_GetDisplayBounds(display, &rect);
+ bounds = Rectangle::Union(bounds, Rectangle(static_cast(rect.x), static_cast(rect.y), static_cast(rect.w), static_cast(rect.h)));
+ }
+ return bounds;
+}
+
+Window* SDLPlatform::CreateWindow(const CreateWindowSettings& settings)
+{
+ return New(settings);
+}
+
+#if !PLATFORM_LINUX
+
+bool SDLPlatform::UsesWayland()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesXWayland()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesX11()
+{
+ return false;
+}
+
+#endif
+
+#if PLATFORM_LINUX
+DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
+{
+ StringAnsi textAnsi(text);
+ StringAnsi captionAnsi(caption);
+
+ SDL_MessageBoxData data;
+ SDL_MessageBoxButtonData dataButtons[3];
+ data.window = parent ? static_cast(parent)->_window : nullptr;
+ data.title = captionAnsi.GetText();
+ data.message = textAnsi.GetText();
+ data.colorScheme = nullptr;
+
+ switch (icon)
+ {
+ case MessageBoxIcon::Error:
+ case MessageBoxIcon::Hand:
+ case MessageBoxIcon::Stop:
+ data.flags |= SDL_MESSAGEBOX_ERROR;
+ break;
+ case MessageBoxIcon::Asterisk:
+ case MessageBoxIcon::Information:
+ case MessageBoxIcon::Question:
+ data.flags |= SDL_MESSAGEBOX_INFORMATION;
+ break;
+ case MessageBoxIcon::Exclamation:
+ case MessageBoxIcon::Warning:
+ data.flags |= SDL_MESSAGEBOX_WARNING;
+ break;
+ default:
+ break;
+ }
+
+ switch (buttons)
+ {
+ case MessageBoxButtons::AbortRetryIgnore:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Abort,
+ "Abort"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Retry,
+ "Retry"
+ };
+ dataButtons[2] =
+ {
+ 0,
+ (int)DialogResult::Ignore,
+ "Ignore"
+ };
+ data.numbuttons = 3;
+ break;
+ case MessageBoxButtons::OK:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT | SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::OK,
+ "OK"
+ };
+ data.numbuttons = 1;
+ break;
+ case MessageBoxButtons::OKCancel:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::OK,
+ "OK"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::RetryCancel:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Retry,
+ "Retry"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::YesNo:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Yes,
+ "Yes"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::No,
+ "No"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::YesNoCancel:
+ {
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Yes,
+ "Yes"
+ };
+ dataButtons[1] =
+ {
+ 0,
+ (int)DialogResult::No,
+ "No"
+ };
+ dataButtons[2] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 3;
+ break;
+ }
+ default:
+ break;
+ }
+ data.buttons = dataButtons;
+
+ int result = -1;
+ if (SDL_ShowMessageBox(&data, &result) != 0)
+ {
+ LOG(Error, "Failed to show message box: {0}", String(SDL_GetError()));
+ return DialogResult::Abort;
+ }
+ if (result < 0)
+ return DialogResult::None;
+ return (DialogResult)result;
+}
+#endif
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.h b/Source/Engine/Platform/SDL/SDLPlatform.h
new file mode 100644
index 000000000..47aabb1b0
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_SDL
+
+#include "Engine/Platform/Base/Enums.h"
+#if PLATFORM_WINDOWS
+#include "Engine/Platform/Windows/WindowsPlatform.h"
+#elif PLATFORM_LINUX
+#include "Engine/Platform/Linux/LinuxPlatform.h"
+#else
+#endif
+
+class SDLWindow;
+union SDL_Event;
+#if PLATFORM_WINDOWS
+typedef struct tagMSG MSG;
+#elif PLATFORM_LINUX
+union _XEvent;
+#endif
+
+///
+/// The Windows platform implementation and application management utilities.
+///
+class FLAXENGINE_API SDLPlatform
+#if PLATFORM_WINDOWS
+ : public WindowsPlatform
+{
+ using base = WindowsPlatform;
+#elif PLATFORM_LINUX
+ : public LinuxPlatform
+{
+ using base = LinuxPlatform;
+#else
+{
+#endif
+ friend SDLWindow;
+
+private:
+ static uint32 DraggedWindowId;
+
+private:
+ static bool InitPlatform();
+#if PLATFORM_LINUX
+ static bool InitPlatformX11(void* display);
+#endif
+ static bool HandleEvent(SDL_Event& event);
+#if PLATFORM_WINDOWS
+ static int __cdecl EventMessageHook(void* userdata, MSG* msg);
+#elif PLATFORM_LINUX
+ static int __cdecl X11EventHook(void *userdata, _XEvent *xevent);
+#endif
+
+public:
+ static bool CheckWindowDragging(Window* window, WindowHitCodes hit);
+#if PLATFORM_LINUX
+ static void* GetXDisplay();
+#endif
+ static bool UsesWayland();
+ static bool UsesXWayland();
+ static bool UsesX11();
+
+public:
+
+ // [PlatformBase]
+ static bool Init();
+ static void LogInfo();
+ static void Tick();
+ static void SetHighDpiAwarenessEnabled(bool enable);
+ static BatteryInfo GetBatteryInfo();
+ static int32 GetDpi();
+ static void OpenUrl(const StringView& url);
+ static Float2 GetMousePosition();
+ static void SetMousePosition(const Float2& pos);
+ static Float2 GetDesktopSize();
+ static Rectangle GetMonitorBounds(const Float2& screenPos);
+ static Rectangle GetVirtualDesktopBounds();
+ static Window* CreateWindow(const CreateWindowSettings& settings);
+};
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLWindow.cpp b/Source/Engine/Platform/SDL/SDLWindow.cpp
new file mode 100644
index 000000000..00294724b
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLWindow.cpp
@@ -0,0 +1,1067 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL
+
+#include "SDLWindow.h"
+#include "SDLInput.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Math/Math.h"
+#include "Engine/Core/Math/Rectangle.h"
+#include "Engine/Engine/Engine.h"
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/GPUSwapChain.h"
+#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Keyboard.h"
+#include "Engine/Input/Mouse.h"
+#include "Engine/Platform/IGuiData.h"
+#include "Engine/Platform/WindowsManager.h"
+
+#define NOGDI
+#include
+#include
+#include
+#include
+#undef CreateWindow
+
+#if PLATFORM_WINDOWS
+#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
+#if USE_EDITOR
+#include
+#endif
+#elif PLATFORM_LINUX
+#include "Engine/Platform/Linux/IncludeX11.h"
+#endif
+
+#define DefaultDPI 96
+SDLWindow* lastEventWindow = nullptr;
+
+void* GetNativeWindowPointer(SDL_Window* window);
+SDL_HitTestResult OnWindowHitTest(SDL_Window* win, const SDL_Point* area, void* data);
+
+class SDLDropFilesData : public IGuiData
+{
+public:
+ Array Files;
+
+ Type GetType() const override
+ {
+ return Type::Files;
+ }
+ String GetAsText() const override
+ {
+ return String::Empty;
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ files->Add(Files);
+ }
+};
+
+class SDLDropTextData : public IGuiData
+{
+public:
+ StringView Text;
+
+ Type GetType() const override
+ {
+ return Type::Text;
+ }
+ String GetAsText() const override
+ {
+ return String(Text);
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ }
+};
+
+SDLWindow::SDLWindow(const CreateWindowSettings& settings)
+ : WindowBase(settings)
+ , _handle(nullptr)
+ , _cachedClientRectangle(Rectangle())
+{
+ int32 x = Math::TruncToInt(settings.Position.X);
+ int32 y = Math::TruncToInt(settings.Position.Y);
+ int32 clientWidth = Math::TruncToInt(settings.Size.X);
+ int32 clientHeight = Math::TruncToInt(settings.Size.Y);
+ int32 windowWidth = clientWidth;
+ int32 windowHeight = clientHeight;
+ _clientSize = Float2((float)clientWidth, (float)clientHeight);
+
+ if (SDLPlatform::UsesWayland())
+ {
+ // The compositor seems to crash when something is rendered to the surface when window is hidden
+ _settings.ShowAfterFirstPaint = _showAfterFirstPaint = false;
+ }
+
+ uint32 flags = 0;
+ if (!_settings.HasBorder)
+ flags |= SDL_WINDOW_BORDERLESS;
+ if (_settings.IsRegularWindow)
+ flags |= SDL_WINDOW_INPUT_FOCUS;
+
+
+ if (!_settings.AllowInput)
+ flags |= SDL_WINDOW_NOT_FOCUSABLE;
+ if (!_settings.ShowInTaskbar && _settings.IsRegularWindow)
+ flags |= SDL_WINDOW_UTILITY;
+ if (_settings.ShowAfterFirstPaint)
+ flags |= SDL_WINDOW_HIDDEN;
+ if (_settings.HasSizingFrame)
+ flags |= SDL_WINDOW_RESIZABLE;
+ //flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
+
+ // The SDL window position is always relative to the parent window
+ if (_settings.Parent != nullptr)
+ {
+ x -= Math::TruncToInt(_settings.Parent->GetPosition().X);
+ y -= Math::TruncToInt(_settings.Parent->GetPosition().Y);
+ }
+
+ SDL_PropertiesID props = SDL_CreateProperties();
+ SDL_SetNumberProperty(props, "flags", flags);
+ SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, settings.Title.ToStringAnsi().Get());
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, windowWidth);
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, windowHeight);
+ if ((flags & SDL_WINDOW_TOOLTIP) != 0)
+ {
+ SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, SDL_TRUE);
+ SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, _settings.Parent->_window);
+ }
+ else if ((flags & SDL_WINDOW_POPUP_MENU) != 0)
+ {
+ SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, SDL_TRUE);
+ SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, _settings.Parent->_window);
+ }
+
+ _window = SDL_CreateWindowWithProperties(props);
+ if (_window == nullptr)
+ Platform::Fatal(String::Format(TEXT("Cannot create SDL window: {0}"), String(SDL_GetError())));
+
+ _windowId = SDL_GetWindowID(_window);
+ _handle = GetNativeWindowPointer(_window);
+ ASSERT(_handle != nullptr);
+
+ SDL_DisplayID display = SDL_GetDisplayForWindow(_window);
+ _dpiScale = SDL_GetDisplayContentScale(display);
+ _dpi = (int)(_dpiScale * DefaultDPI);
+
+ SDL_SetWindowMinimumSize(_window, (int)_settings.MinimumSize.X, (int)_settings.MinimumSize.Y);
+ SDL_SetWindowMaximumSize(_window, (int)_settings.MaximumSize.X, (int)_settings.MaximumSize.Y);
+
+ SDL_Rect rect;
+ SDL_GetWindowPosition(_window, &rect.x, &rect.y);
+ SDL_GetWindowSizeInPixels(_window, &rect.w, &rect.h);
+ _cachedClientRectangle = Rectangle((float)rect.x, (float)rect.y, (float)rect.w, (float)rect.h);
+
+ SDL_SetWindowHitTest(_window, &OnWindowHitTest, this);
+ InitSwapChain();
+
+#if USE_EDITOR
+ // Enable file drag & drop support
+ if (_settings.AllowDragAndDrop)
+ {
+#if PLATFORM_WINDOWS
+ const auto result = RegisterDragDrop((HWND)_handle, (LPDROPTARGET)(Windows::IDropTarget*)this);
+ if (result != S_OK)
+ {
+ LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 1);
+ }
+#elif PLATFORM_LINUX
+ auto xDisplay = (X11::Display*)GetX11Display();
+ if (xDisplay)
+ {
+ auto xdndVersion = 5;
+ auto xdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0);
+ if (xdndAware != 0)
+ X11::XChangeProperty(xDisplay, (X11::Window)_handle, xdndAware, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&xdndVersion, 1);
+ }
+ else
+ {
+ // TODO: Wayland
+ }
+#endif
+ }
+#endif
+
+ lastEventWindow = this;
+
+#if PLATFORM_LINUX
+ SDLPlatform::InitPlatformX11(GetX11Display());
+#endif
+}
+
+void* GetNativeWindowPointer(SDL_Window* window)
+{
+ void* windowPtr;
+ auto props = SDL_GetWindowProperties(window);
+#if PLATFORM_WINDOWS
+ windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
+#elif PLATFORM_LINUX
+ windowPtr = GetWaylandSurfacePtr();
+ if (windowPtr == nullptr)
+ windowPtr = (void*)GetX11WindowHandle();
+#elif PLATFORM_MAC
+ windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
+#elif PLATFORM_ANDROID
+ windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr);
+#elif PLATFORM_IOS
+ windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, nullptr);
+#else
+ static_assert(false, "unsupported platform");
+#endif
+ return windowPtr;
+}
+
+#if PLATFORM_LINUX
+
+void* SDLWindow::GetWaylandSurfacePtr() const
+{
+ return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
+}
+
+void* SDLWindow::GetWaylandDisplay() const
+{
+ return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr);
+}
+
+uintptr SDLWindow::GetX11WindowHandle() const
+{
+ return (uintptr)SDL_GetNumberProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
+}
+
+void* SDLWindow::GetX11Display() const
+{
+ return SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
+}
+
+#endif
+
+SDLWindow::~SDLWindow()
+{
+ if (lastEventWindow == this)
+ lastEventWindow = nullptr;
+
+ if (_window == nullptr)
+ return;
+
+ SDL_StopTextInput(_window);
+ SDL_DestroyWindow(_window);
+
+ _window = nullptr;
+ _handle = nullptr;
+ _windowId = 0;
+ _visible = false;
+}
+
+SDL_HitTestResult OnWindowHitTest(SDL_Window* win, const SDL_Point* area, void* data)
+{
+ SDLWindow* window = (SDLWindow*)data;
+ if (window->IsFullscreen())
+ return SDL_HITTEST_NORMAL;
+
+ Float2 clientPosition = Float2(static_cast(area->x), static_cast(area->y));
+ Float2 screenPosition = window->ClientToScreen(clientPosition);
+
+ //auto clientBounds = window->GetClientBounds();
+
+ WindowHitCodes hit = WindowHitCodes::Client;
+ bool handled = false;
+ window->OnHitTest(screenPosition, hit, handled);
+
+ if (!handled)
+ {
+ int margin = window->GetSettings().HasBorder ? 0 : 0;
+ auto size = window->GetClientSize();
+ //if (clientPosition.Y < 0)
+ // return SDL_HITTEST_DRAGGABLE;
+ if (clientPosition.Y < margin && clientPosition.X < margin)
+ return SDL_HITTEST_RESIZE_TOPLEFT;
+ else if (clientPosition.Y < margin && clientPosition.X > size.X - margin)
+ return SDL_HITTEST_RESIZE_TOPRIGHT;
+ else if (clientPosition.Y < margin)
+ return SDL_HITTEST_RESIZE_TOP;
+ else if (clientPosition.X < margin && clientPosition.Y > size.Y - margin)
+ return SDL_HITTEST_RESIZE_BOTTOMLEFT;
+ else if (clientPosition.X < margin)
+ return SDL_HITTEST_RESIZE_LEFT;
+ else if (clientPosition.X > size.X - margin && clientPosition.Y > size.Y - margin)
+ return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
+ else if (clientPosition.X > size.X - margin)
+ return SDL_HITTEST_RESIZE_RIGHT;
+ else if (clientPosition.Y > size.Y - margin)
+ return SDL_HITTEST_RESIZE_BOTTOM;
+ else
+ return SDL_HITTEST_NORMAL;
+ }
+
+ switch (hit)
+ {
+ case WindowHitCodes::Caption:
+ return SDL_HITTEST_DRAGGABLE;
+ case WindowHitCodes::TopLeft:
+ return SDL_HITTEST_RESIZE_TOPLEFT;
+ case WindowHitCodes::Top:
+ return SDL_HITTEST_RESIZE_TOP;
+ case WindowHitCodes::TopRight:
+ return SDL_HITTEST_RESIZE_TOPRIGHT;
+ case WindowHitCodes::Right:
+ return SDL_HITTEST_RESIZE_RIGHT;
+ case WindowHitCodes::BottomRight:
+ return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
+ case WindowHitCodes::Bottom:
+ return SDL_HITTEST_RESIZE_BOTTOM;
+ case WindowHitCodes::BottomLeft:
+ return SDL_HITTEST_RESIZE_BOTTOMLEFT;
+ case WindowHitCodes::Left:
+ return SDL_HITTEST_RESIZE_LEFT;
+ default:
+ return SDL_HITTEST_NORMAL;
+ }
+}
+
+SDLWindow* SDLWindow::GetWindowFromEvent(const SDL_Event& event)
+{
+ SDL_WindowID windowId;
+ if (event.type >= SDL_EVENT_WINDOW_FIRST && event.type <= SDL_EVENT_WINDOW_LAST)
+ windowId = event.window.windowID;
+ else if (event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP)
+ windowId = event.key.windowID;
+ else if (event.type == SDL_EVENT_TEXT_EDITING)
+ windowId = event.edit.windowID;
+ else if (event.type == SDL_EVENT_TEXT_INPUT)
+ windowId = event.text.windowID;
+ else if (event.type == SDL_EVENT_MOUSE_MOTION)
+ windowId = event.motion.windowID;
+ else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN || event.type == SDL_EVENT_MOUSE_BUTTON_UP)
+ windowId = event.button.windowID;
+ else if (event.type == SDL_EVENT_MOUSE_WHEEL)
+ windowId = event.wheel.windowID;
+ else if (event.type == SDL_EVENT_FINGER_MOTION || event.type == SDL_EVENT_FINGER_DOWN || event.type == SDL_EVENT_FINGER_UP)
+ windowId = event.tfinger.windowID;
+ else if (event.type == SDL_EVENT_PEN_DOWN || event.type == SDL_EVENT_PEN_UP)
+ windowId = event.ptip.windowID;
+ else if (event.type == SDL_EVENT_PEN_MOTION)
+ windowId = event.pmotion.windowID;
+ else if (event.type == SDL_EVENT_PEN_BUTTON_DOWN || event.type == SDL_EVENT_PEN_BUTTON_UP)
+ windowId = event.pbutton.windowID;
+ else if (event.type == SDL_EVENT_DROP_BEGIN || event.type == SDL_EVENT_DROP_FILE || event.type == SDL_EVENT_DROP_TEXT || event.type == SDL_EVENT_DROP_COMPLETE || event.type == SDL_EVENT_DROP_POSITION)
+ windowId = event.drop.windowID;
+ else if (event.type >= SDL_EVENT_USER && event.type <= SDL_EVENT_LAST)
+ windowId = event.user.windowID;
+ else
+ return nullptr;
+
+ if (lastEventWindow == nullptr || windowId != lastEventWindow->_windowId)
+ lastEventWindow = GetWindowWithId(windowId);
+ return lastEventWindow;
+}
+
+SDLWindow* SDLWindow::GetWindowWithId(uint32 windowId)
+{
+ WindowsManager::WindowsLocker.Lock();
+ for (auto win : WindowsManager::Windows)
+ {
+ if (win->_windowId == windowId)
+ return win;
+ }
+ WindowsManager::WindowsLocker.Unlock();
+ return nullptr;
+}
+
+void SDLWindow::HandleEvent(SDL_Event& event)
+{
+ if (_isClosing)
+ return;
+
+ // NOTE: Update window handling in SDLWindow::GetWindowFromEvent when any new events are added here
+ switch (event.type)
+ {
+ case SDL_EVENT_WINDOW_EXPOSED:
+ {
+ // Check if window is during resizing
+ if (_swapChain && !_isClosing)
+ {
+ // Redraw window backbuffer on DX11
+ switch (GPUDevice::Instance->GetRendererType())
+ {
+ case RendererType::DirectX10:
+ case RendererType::DirectX10_1:
+ case RendererType::DirectX11:
+ _swapChain->Present(false);
+ break;
+ }
+ }
+ return;
+ }
+ case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+ {
+ Close(ClosingReason::User);
+ return;
+ }
+ case SDL_EVENT_WINDOW_DESTROYED:
+ {
+#if USE_EDITOR && PLATFORM_WINDOWS
+ // Disable file dropping
+ if (_settings.AllowDragAndDrop)
+ {
+ const auto result = RevokeDragDrop((HWND)_handle);
+ if (result != S_OK)
+ LOG(Warning, "Window drag and drop service error: 0x{0:x}:{1}", result, 2);
+ }
+#endif
+
+ // Quit
+#if PLATFORM_WINDOWS
+ PostQuitMessage(0);
+#endif
+ return;
+ }
+ case SDL_EVENT_MOUSE_MOTION:
+ {
+ if (_isTrackingMouse && _isUsingMouseOffset)
+ {
+ Float2 delta(event.motion.xrel, event.motion.yrel);
+ _trackingMouseOffset += delta;
+ }
+ break;
+ }
+ case SDL_EVENT_KEY_DOWN:
+ {
+ if (event.key.scancode == SDL_SCANCODE_RETURN && Input::Keyboard->GetKey(KeyboardKeys::Alt))
+ {
+ LOG(Info, "Alt+Enter pressed");
+ SetIsFullscreen(!IsFullscreen());
+ return;
+ }
+ break;
+ }
+ case SDL_EVENT_WINDOW_MOVED:
+ {
+ _cachedClientRectangle.Location = Float2(static_cast(event.window.data1), static_cast(event.window.data2));
+#if PLATFORM_LINUX
+ if (SDLPlatform::UsesX11())
+ {
+ // X11 doesn't report any mouse events when mouse is over the caption area, send a simulated event instead...
+ Float2 mousePosition;
+ auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
+ if ((buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0)
+ SDLPlatform::CheckWindowDragging(this, WindowHitCodes::Caption);
+ }
+#endif
+ return;
+ }
+ case SDL_EVENT_WINDOW_HIT_TEST:
+ {
+ return;
+ }
+ case SDL_EVENT_WINDOW_MINIMIZED:
+ {
+ _minimized = true;
+ _maximized = false;
+ return;
+ }
+ case SDL_EVENT_WINDOW_MAXIMIZED:
+ {
+ _minimized = false;
+ _maximized = true;
+
+#if PLATFORM_WINDOWS
+ if (!_settings.HasBorder && _settings.HasSizingFrame)
+ {
+ // Borderless window doesn't maximize properly, manually force the window into correct location and size
+ SDL_Rect rect;
+ SDL_DisplayID display = SDL_GetDisplayForWindow(_window);
+ SDL_GetDisplayUsableBounds(display, &rect); // Excludes taskbar etc.
+
+ // Check if the window actually clips past the display work region
+ auto pos = GetPosition();
+ auto size = GetClientSize();
+ if (pos.X < rect.x || pos.Y < rect.y || size.X > rect.w || size.Y > rect.h)
+ {
+ // Disable resizable flag so SDL updates the client rectangle to expected values during WM_NCCALCSIZE
+ SDL_SetWindowResizable(_window, SDL_FALSE);
+ SDL_MaximizeWindow(_window);
+
+ // Set the internal floating window rectangle to expected position
+ SetClientBounds(Rectangle((float)rect.x, (float)rect.y, (float)rect.w, (float)rect.h));
+
+ // Flush and handle the events again
+ ::SetWindowPos((HWND)_handle, HWND_TOP, rect.x, rect.y, rect.w, rect.h, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+ SDL_PumpEvents();
+
+ // Restore previous values
+ SDL_SetWindowResizable(_window, SDL_TRUE);
+ SetClientBounds(_cachedClientRectangle);
+ }
+ }
+#endif
+ CheckForWindowResize();
+ return;
+ }
+ case SDL_EVENT_WINDOW_RESTORED:
+ {
+ if (_maximized)
+ {
+ _maximized = false;
+ // We assume SDL_EVENT_WINDOW_RESIZED is called right afterwards, no need to check for resize here
+ //CheckForWindowResize();
+ }
+ else if (_minimized)
+ {
+ _minimized = false;
+ CheckForWindowResize();
+ }
+ return;
+ }
+ case SDL_EVENT_WINDOW_RESIZED:
+ {
+ int32 width = event.window.data1;
+ int32 height = event.window.data2;
+
+ _clientSize = Float2(static_cast(width), static_cast(height));
+ _cachedClientRectangle.Size = _clientSize;
+
+ // Check if window size has been changed
+ if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight()))
+ OnResize(width, height);
+ return;
+ }
+ case SDL_EVENT_WINDOW_FOCUS_GAINED:
+ {
+ OnGotFocus();
+ SDL_StartTextInput(_window);
+ const SDL_Rect* currentClippingRect = SDL_GetWindowMouseRect(_window);
+ if (_isClippingCursor && currentClippingRect == nullptr)
+ {
+ SDL_Rect rect{ (int)_clipCursorRect.GetX(), (int)_clipCursorRect.GetY(), (int)_clipCursorRect.GetWidth(), (int)_clipCursorRect.GetHeight() };
+ SDL_SetWindowMouseRect(_window, &rect);
+ }
+ return;
+ }
+ case SDL_EVENT_WINDOW_FOCUS_LOST:
+ {
+ SDL_StopTextInput(_window);
+ const SDL_Rect* currentClippingRect = SDL_GetWindowMouseRect(_window);
+ if (currentClippingRect != nullptr)
+ SDL_SetWindowMouseRect(_window, nullptr);
+ OnLostFocus();
+ return;
+ }
+ case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
+ {
+ SDL_DisplayID display = SDL_GetDisplayForWindow(_window);
+ float scale = SDL_GetDisplayContentScale(display);
+ if (scale > 0.0f)
+ {
+ float oldScale = _dpiScale;
+ _dpiScale = scale;
+ _dpi = (int)(_dpiScale * DefaultDPI);
+
+ int w = (int)(_cachedClientRectangle.GetWidth() * (scale / oldScale));
+ int h = (int)(_cachedClientRectangle.GetHeight() * (scale / oldScale));
+ SDL_SetWindowSize(_window, w, h);
+ // TODO: Recalculate fonts
+ }
+ return;
+ }
+#if false
+ case SDL_EVENT_DROP_BEGIN:
+ {
+ Focus();
+ Float2 mousePosition;
+ SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
+ mousePosition = ScreenToClient(mousePosition);
+
+ DragDropEffect effect;
+ SDLDropTextData dropData;
+ OnDragEnter(&dropData, mousePosition, effect);
+ OnDragOver(&dropData, mousePosition, effect);
+ return;
+ }
+ case SDL_EVENT_DROP_POSITION:
+ {
+ DragDropEffect effect = DragDropEffect::None;
+
+ SDLDropTextData dropData;
+ OnDragOver(&dropData, Float2(static_cast(event.drop.x), static_cast(event.drop.y)), effect);
+ return;
+ }
+ case SDL_EVENT_DROP_FILE:
+ {
+ SDLDropFilesData dropData;
+ dropData.Files.Add(StringAnsi(event.drop.data).ToString()); // TODO: collect multiple files at once?
+
+ Focus();
+
+ Float2 mousePosition;
+ SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
+ mousePosition = ScreenToClient(mousePosition);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragDrop(&dropData, mousePosition, effect);
+ return;
+ }
+ case SDL_EVENT_DROP_TEXT:
+ {
+ SDLDropTextData dropData;
+ String str = StringAnsi(event.drop.data).ToString();
+ dropData.Text = StringView(str);
+
+ Focus();
+ Float2 mousePosition;
+ SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y);
+ mousePosition = ScreenToClient(mousePosition);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragDrop(&dropData, mousePosition, effect);
+ return;
+ }
+ case SDL_EVENT_DROP_COMPLETE:
+ {
+ return;
+ }
+#endif
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ {
+#if !PLATFORM_WINDOWS
+ OnDragLeave(); // Check for release of mouse button too?
+#endif
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (_settings.AllowInput)
+ {
+ if (SDLInput::HandleEvent(this, event))
+ return;
+ }
+
+ // ignored
+ if (event.type == SDL_EVENT_WINDOW_ICCPROF_CHANGED)
+ return;
+
+ //LOG(Info, "Unhandled SDL event: {0}", event.type);
+}
+
+void* SDLWindow::GetNativePtr() const
+{
+ return _handle;
+}
+
+void SDLWindow::Show()
+{
+ if (_visible)
+ return;
+
+ if (_showAfterFirstPaint)
+ {
+ if (RenderTask)
+ RenderTask->Enabled = true;
+ return;
+ }
+
+ SDL_ShowWindow(_window);
+ if (_settings.AllowInput && _settings.ActivateWhenFirstShown)
+ Focus();
+ else if (_settings.Parent == nullptr)
+ BringToFront();
+
+ // Reused top-most windows (DockHintWindow) doesn't stay on top for some reason
+ if (_settings.IsTopmost)
+ SDL_SetWindowAlwaysOnTop(_window, SDL_TRUE);
+
+ if (_isTrackingMouse)
+ {
+ if (SDL_CaptureMouse(SDL_TRUE) != 0)
+ LOG(Warning, "Show SDL_CaptureMouse: {0}", String(SDL_GetError()));
+ }
+
+ WindowBase::Show();
+}
+
+void SDLWindow::Hide()
+{
+ if (!_visible)
+ return;
+
+ SDL_HideWindow(_window);
+
+ WindowBase::Hide();
+}
+
+void SDLWindow::Minimize()
+{
+ if (!_settings.AllowMinimize)
+ return;
+
+ SDL_MinimizeWindow(_window);
+}
+
+void SDLWindow::Maximize()
+{
+ if (!_settings.AllowMaximize)
+ return;
+
+ SDL_MaximizeWindow(_window);
+}
+
+void SDLWindow::SetBorderless(bool isBorderless, bool maximized)
+{
+ if (IsFullscreen())
+ SetIsFullscreen(false);
+
+ // Fixes issue of borderless window not going full screen
+ if (IsMaximized())
+ Restore();
+
+ _settings.HasBorder = !isBorderless;
+
+ BringToFront();
+
+ if (isBorderless)
+ {
+ SDL_SetWindowBordered(_window, !isBorderless ? SDL_TRUE : SDL_FALSE);
+ if (maximized)
+ SDL_MaximizeWindow(_window);
+ else
+ Focus();
+ }
+ else
+ {
+ SDL_SetWindowBordered(_window, !isBorderless ? SDL_TRUE : SDL_FALSE);
+ if (maximized)
+ SDL_MaximizeWindow(_window);
+ else
+ Focus();
+ }
+
+ CheckForWindowResize();
+}
+
+void SDLWindow::Restore()
+{
+ SDL_RestoreWindow(_window);
+}
+
+bool SDLWindow::IsClosed() const
+{
+ return _handle == nullptr;
+}
+
+bool SDLWindow::IsForegroundWindow() const
+{
+ SDL_WindowFlags flags = SDL_GetWindowFlags(_window);
+ return (flags & SDL_WINDOW_MOUSE_FOCUS) != 0 || (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
+}
+
+void SDLWindow::BringToFront(bool force)
+{
+ auto activateWhenRaised = SDL_GetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED);
+ SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "0");
+ SDL_RaiseWindow(_window);
+ SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, activateWhenRaised);
+}
+
+void SDLWindow::SetClientBounds(const Rectangle& clientArea)
+{
+ SDL_SetWindowPosition(_window, (int)clientArea.GetLeft(), (int)clientArea.GetTop());
+ SDL_SetWindowSize(_window, (int)clientArea.GetWidth(), (int)clientArea.GetHeight());
+}
+
+void SDLWindow::SetPosition(const Float2& position)
+{
+ int top, left, bottom, right;
+ SDL_GetWindowBordersSize(_window, &top, &left, &bottom, &right);
+
+ // The position is relative to the parent window
+ Float2 newPosition = position;
+ SDLWindow* parent = (_settings.Type == WindowType::Tooltip || _settings.Type == WindowType::Popup) ? _settings.Parent : nullptr;
+ if (parent != nullptr)
+ newPosition -= parent->GetClientPosition();
+
+ SDL_SetWindowPosition(_window, static_cast(newPosition.X) + left, static_cast(newPosition.Y) + top);
+}
+
+void SDLWindow::SetClientPosition(const Float2& position)
+{
+ // The position is relative to the parent window
+ Float2 newPosition = position;
+ SDLWindow* parent = (_settings.Type == WindowType::Tooltip || _settings.Type == WindowType::Popup) ? _settings.Parent : nullptr;
+ if (parent != nullptr)
+ newPosition -= parent->GetClientPosition();
+
+ SDL_SetWindowPosition(_window, static_cast(newPosition.X), static_cast(newPosition.Y));
+}
+
+void SDLWindow::SetIsFullscreen(bool isFullscreen)
+{
+ SDL_SetWindowFullscreen(_window, isFullscreen ? SDL_TRUE : SDL_FALSE);
+ if (!isFullscreen)
+ {
+ // The window is set to always-on-top for some reason when leaving fullscreen
+ SDL_SetWindowAlwaysOnTop(_window, SDL_FALSE);
+ }
+
+ WindowBase::SetIsFullscreen(isFullscreen);
+}
+
+Float2 SDLWindow::GetPosition() const
+{
+ int top, left, bottom, right;
+ SDL_GetWindowBordersSize(_window, &top, &left, &bottom, &right);
+
+ // The position is relative to the parent window
+ Float2 newPosition = GetClientPosition();
+ SDLWindow* parent = (_settings.Type == WindowType::Tooltip || _settings.Type == WindowType::Popup) ? _settings.Parent : nullptr;
+ if (parent != nullptr)
+ newPosition += parent->GetClientPosition();
+
+ return newPosition - Float2(static_cast(left), static_cast(top));
+}
+
+Float2 SDLWindow::GetSize() const
+{
+ int top, left, bottom, right;
+ SDL_GetWindowBordersSize(_window, &top, &left, &bottom, &right);
+
+ int w, h;
+ SDL_GetWindowSizeInPixels(_window, &w, &h);
+ return Float2(static_cast(w + left + right), static_cast(h + top + bottom));
+}
+
+Float2 SDLWindow::GetClientSize() const
+{
+ int w, h;
+ SDL_GetWindowSizeInPixels(_window, &w, &h);
+
+ return Float2(static_cast(w), static_cast(h));;
+}
+
+Float2 SDLWindow::ScreenToClient(const Float2& screenPos) const
+{
+#if PLATFORM_LINUX
+ auto res1 = screenPos - GetPosition();
+ auto res1b = screenPos - GetClientPosition();
+
+ X11::Display* display = (X11::Display*)GetX11Display();
+ if (display)
+ {
+ X11::Window window = (X11::Window)GetX11WindowHandle();
+ if (!display)
+ return screenPos;
+ int32 x, y;
+ X11::Window child;
+ X11::XTranslateCoordinates(display, X11_DefaultRootWindow(display), window, (int32)screenPos.X, (int32)screenPos.Y, &x, &y, &child);
+
+ auto res2 = Float2((float)x, (float)y);
+ if (Float2::DistanceSquared(res1b, res2) > 1)
+ res1 = res1;
+ }
+#endif
+ return screenPos - GetClientPosition();
+}
+
+Float2 SDLWindow::ClientToScreen(const Float2& clientPos) const
+{
+ int x, y;
+ SDL_GetWindowPosition(_window, &x, &y);
+
+ return clientPos + Float2(static_cast(x), static_cast(y));
+}
+
+void SDLWindow::FlashWindow()
+{
+ SDL_FlashWindow(_window, SDL_FLASH_UNTIL_FOCUSED);
+}
+
+float SDLWindow::GetOpacity() const
+{
+ float opacity = SDL_GetWindowOpacity(_window);
+ if (opacity < 0.0f)
+ {
+ LOG(Warning, "SDL_GetWindowOpacity failed: {0}", String(SDL_GetError()));
+ opacity = 1.0f;
+ }
+ return opacity;
+}
+
+void SDLWindow::SetOpacity(const float opacity)
+{
+ SDL_SetWindowOpacity(_window, opacity);
+}
+
+void SDLWindow::Focus()
+{
+ auto activateWhenRaised = SDL_GetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED);
+ SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, "1");
+
+ // Forcing the window to focus causes issues with opening context menus while window is maximized
+ //auto forceRaiseWindow = SDL_GetHint(SDL_HINT_FORCE_RAISEWINDOW);
+ //SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, "1");
+
+ SDL_RaiseWindow(_window);
+
+ SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, activateWhenRaised);
+ //SDL_SetHint(SDL_HINT_FORCE_RAISEWINDOW, forceRaiseWindow);
+}
+
+String SDLWindow::GetTitle() const
+{
+ return String(SDL_GetWindowTitle(_window));
+}
+
+void SDLWindow::SetTitle(const StringView& title)
+{
+ SDL_SetWindowTitle(_window, title.ToStringAnsi().Get());
+}
+
+void SDLWindow::StartTrackingMouse(bool useMouseScreenOffset)
+{
+ if (_isTrackingMouse)
+ return;
+
+ _isTrackingMouse = true;
+ _trackingMouseOffset = Float2::Zero;
+ _isUsingMouseOffset = useMouseScreenOffset;
+
+ if (_visible)
+ {
+ if (SDL_CaptureMouse(SDL_TRUE) != 0)
+ LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
+ }
+}
+
+void SDLWindow::EndTrackingMouse()
+{
+ if (!_isTrackingMouse)
+ return;
+
+ _isTrackingMouse = false;
+ _isHorizontalFlippingMouse = false;
+ _isVerticalFlippingMouse = false;
+
+ if (SDL_CaptureMouse(SDL_FALSE) != 0)
+ LOG(Warning, "SDL_CaptureMouse: {0}", String(SDL_GetError()));
+
+ //SDL_SetWindowGrab(_window, SDL_FALSE);
+}
+
+void SDLWindow::StartClippingCursor(const Rectangle& bounds)
+{
+ if (!IsFocused())
+ return;
+
+ _isClippingCursor = true;
+ SDL_Rect rect{ (int)bounds.GetX(), (int)bounds.GetY(), (int)bounds.GetWidth(), (int)bounds.GetHeight() };
+ SDL_SetWindowMouseRect(_window, &rect);
+}
+
+void SDLWindow::EndClippingCursor()
+{
+ if (!_isClippingCursor)
+ return;
+
+ _isClippingCursor = false;
+ SDL_SetWindowMouseRect(_window, nullptr);
+}
+
+void SDLWindow::SetCursor(CursorType type)
+{
+ CursorType oldCursor = _cursor;
+ WindowBase::SetCursor(type);
+ if (oldCursor != type)
+ SDLWindow::UpdateCursor();
+}
+
+void SDLWindow::CheckForWindowResize()
+{
+ return;
+ // Cache client size
+ _clientSize = GetClientSize();
+ int32 width = (int32)(_clientSize.X);
+ int32 height = (int32)(_clientSize.Y);
+
+ // Check for windows maximized size and see if it needs to adjust position if needed
+ if (_maximized)
+ {
+ // Pick the current monitor data for sizing
+ SDL_Rect rect;
+ auto displayId = SDL_GetDisplayForWindow(_window);
+ SDL_GetDisplayUsableBounds(displayId, &rect);
+
+ if (width > rect.w && height > rect.h)
+ {
+ width = rect.w;
+ height = rect.h;
+ SDL_SetWindowSize(_window, width, height);
+ }
+ }
+
+ // Check if window size has been changed
+ if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight()))
+ OnResize(width, height);
+}
+
+void SDLWindow::UpdateCursor() const
+{
+ static SDL_Cursor* cursors[SDL_NUM_SYSTEM_CURSORS] = { nullptr };
+
+ if (_cursor == CursorType::Hidden)
+ {
+ SDL_HideCursor();
+ return;
+ }
+ SDL_ShowCursor();
+
+ int32 index = SDL_SYSTEM_CURSOR_DEFAULT;
+ switch (_cursor)
+ {
+ case CursorType::Cross:
+ index = SDL_SYSTEM_CURSOR_CROSSHAIR;
+ break;
+ case CursorType::Hand:
+ index = SDL_SYSTEM_CURSOR_POINTER;
+ break;
+ case CursorType::Help:
+ //index = SDL_SYSTEM_CURSOR_DEFAULT;
+ break;
+ case CursorType::IBeam:
+ index = SDL_SYSTEM_CURSOR_TEXT;
+ break;
+ case CursorType::No:
+ index = SDL_SYSTEM_CURSOR_NOT_ALLOWED;
+ break;
+ case CursorType::Wait:
+ index = SDL_SYSTEM_CURSOR_WAIT;
+ break;
+ case CursorType::SizeAll:
+ index = SDL_SYSTEM_CURSOR_MOVE;
+ break;
+ case CursorType::SizeNESW:
+ index = SDL_SYSTEM_CURSOR_NESW_RESIZE;
+ break;
+ case CursorType::SizeNS:
+ index = SDL_SYSTEM_CURSOR_NS_RESIZE;
+ break;
+ case CursorType::SizeNWSE:
+ index = SDL_SYSTEM_CURSOR_NWSE_RESIZE;
+ break;
+ case CursorType::SizeWE:
+ index = SDL_SYSTEM_CURSOR_EW_RESIZE;
+ break;
+ case CursorType::Default:
+ default:
+ break;
+ }
+
+ if (cursors[index] == nullptr)
+ cursors[index] = SDL_CreateSystemCursor(static_cast(index));
+ SDL_SetCursor(cursors[index]);
+}
+
+#endif
+
diff --git a/Source/Engine/Platform/SDL/SDLWindow.h b/Source/Engine/Platform/SDL/SDLWindow.h
new file mode 100644
index 000000000..6641091be
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLWindow.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_SDL
+
+#include "Engine/Platform/Base/WindowBase.h"
+
+struct SDL_Window;
+union SDL_Event;
+
+///
+/// Implementation of the window class for SDL platform
+///
+class FLAXENGINE_API SDLWindow : public WindowBase
+#if USE_EDITOR && PLATFORM_WINDOWS
+ , public Windows::IDropTarget
+#endif
+{
+ friend SDLPlatform;
+#if PLATFORM_LINUX
+ friend LinuxPlatform;
+ friend class LinuxWindow;
+#endif
+
+private:
+ void* _handle; // Opaque, platform specific window handle
+#if USE_EDITOR && PLATFORM_WINDOWS
+ Windows::ULONG _refCount;
+#endif
+#if PLATFORM_LINUX
+ bool _resizeDisabled, _focusOnMapped = false, _dragOver = false;
+#endif
+ SDL_Window* _window;
+ uint32 _windowId;
+ Rectangle _clipCursorRect;
+ Rectangle _cachedClientRectangle;
+
+public:
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The initial window settings.
+ SDLWindow(const CreateWindowSettings& settings);
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~SDLWindow();
+
+private:
+
+ static SDLWindow* GetWindowFromEvent(const SDL_Event& event);
+ static SDLWindow* GetWindowWithId(uint32 windowId);
+ void HandleEvent(SDL_Event& event);
+ void CheckForWindowResize();
+ void UpdateCursor() const;
+
+#if PLATFORM_LINUX
+ DragDropEffect DoDragDropWayland(const StringView& data);
+ DragDropEffect DoDragDropX11(const StringView& data);
+#endif
+
+public:
+
+#if PLATFORM_LINUX
+ void* GetWaylandSurfacePtr() const;
+ void* GetWaylandDisplay() const;
+ uintptr GetX11WindowHandle() const;
+ void* GetX11Display() const;
+#endif
+
+ // [WindowBase]
+ void* GetNativePtr() const override;
+ void Show() override;
+ void Hide() override;
+ void Minimize() override;
+ void Maximize() override;
+ void SetBorderless(bool isBorderless, bool maximized = false) override;
+ void Restore() override;
+ bool IsClosed() const override;
+ bool IsForegroundWindow() const override;
+ void BringToFront(bool force = false) override;
+ void SetClientBounds(const Rectangle& clientArea) override;
+ void SetPosition(const Float2& position) override;
+ void SetClientPosition(const Float2& position) override;
+ void SetIsFullscreen(bool isFullscreen) override;
+ Float2 GetPosition() const override;
+ Float2 GetSize() const override;
+ Float2 GetClientSize() const override;
+ Float2 ScreenToClient(const Float2& screenPos) const override;
+ Float2 ClientToScreen(const Float2& clientPos) const override;
+ void FlashWindow() override;
+ float GetOpacity() const override;
+ void SetOpacity(float opacity) override;
+ void Focus() override;
+ String GetTitle() const override;
+ void SetTitle(const StringView& title) override;
+ DragDropEffect DoDragDrop(const StringView& data) override;
+ void StartTrackingMouse(bool useMouseScreenOffset) override;
+ void EndTrackingMouse() override;
+ void StartClippingCursor(const Rectangle& bounds) override;
+ void EndClippingCursor() override;
+ void SetCursor(CursorType type) override;
+
+#if USE_EDITOR && PLATFORM_WINDOWS
+ // [IUnknown]
+ Windows::HRESULT __stdcall QueryInterface(const Windows::IID& id, void** ppvObject) override;
+ Windows::ULONG __stdcall AddRef() override;
+ Windows::ULONG __stdcall Release() override;
+
+ // [Windows::IDropTarget]
+ Windows::HRESULT __stdcall DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
+ Windows::HRESULT __stdcall DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
+ Windows::HRESULT __stdcall DragLeave() override;
+ Windows::HRESULT __stdcall Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
+#endif
+};
+
+#endif
diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h
index 3ef9e3edc..1222f651b 100644
--- a/Source/Engine/Platform/Types.h
+++ b/Source/Engine/Platform/Types.h
@@ -18,10 +18,12 @@ class Win32Thread;
typedef Win32Thread Thread;
class WindowsClipboard;
typedef WindowsClipboard Clipboard;
+#if !PLATFORM_SDL
class WindowsPlatform;
typedef WindowsPlatform Platform;
class WindowsWindow;
typedef WindowsWindow Window;
+#endif
class Win32Network;
typedef Win32Network Network;
class UserBase;
@@ -68,12 +70,14 @@ class UnixFile;
typedef UnixFile File;
class LinuxThread;
typedef LinuxThread Thread;
+#if !PLATFORM_SDL
class LinuxClipboard;
typedef LinuxClipboard Clipboard;
class LinuxPlatform;
typedef LinuxPlatform Platform;
class LinuxWindow;
typedef LinuxWindow Window;
+#endif
class UnixNetwork;
typedef UnixNetwork Network;
class UserBase;
@@ -288,3 +292,14 @@ typedef UserBase User;
#error Missing Types implementation!
#endif
+
+#if PLATFORM_SDL
+#if PLATFORM_LINUX
+class SDLClipboard;
+typedef SDLClipboard Clipboard;
+#endif
+class SDLPlatform;
+typedef SDLPlatform Platform;
+class SDLWindow;
+typedef SDLWindow Window;
+#endif
diff --git a/Source/Engine/Platform/Window.h b/Source/Engine/Platform/Window.h
index b54214ded..2548b3993 100644
--- a/Source/Engine/Platform/Window.h
+++ b/Source/Engine/Platform/Window.h
@@ -2,7 +2,9 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_SDL
+#include "SDL/SDLWindow.h"
+#elif PLATFORM_WINDOWS
#include "Windows/WindowsWindow.h"
#elif PLATFORM_UWP
#include "UWP/UWPWindow.h"
diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
index 90701a07b..203aaf8d9 100644
--- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
+++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
@@ -2,6 +2,7 @@
#if PLATFORM_WINDOWS
+#include "WindowsWindow.h"
#include "WindowsFileSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/Window.h"
@@ -317,7 +318,7 @@ bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const Strin
if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder))))
fd->SetFolder(defaultFolder);
- HWND hwndOwner = parentWindow ? parentWindow->GetHWND() : NULL;
+ HWND hwndOwner = parentWindow ? (HWND)parentWindow->GetNativePtr() : NULL;
if (SUCCEEDED(fd->Show(hwndOwner)))
{
ComPtr si;
diff --git a/Source/Engine/Platform/Windows/WindowsInput.cpp b/Source/Engine/Platform/Windows/WindowsInput.cpp
index 5d4b3e12f..7e5e55d77 100644
--- a/Source/Engine/Platform/Windows/WindowsInput.cpp
+++ b/Source/Engine/Platform/Windows/WindowsInput.cpp
@@ -1,6 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS && !PLATFORM_SDL
#include "WindowsInput.h"
#include "WindowsWindow.h"
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
index e45653fc1..e6e926893 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
@@ -4,6 +4,8 @@
#include "Engine/Platform/Platform.h"
#include "Engine/Platform/Window.h"
+#include "Engine/Platform/Windows/WindowsInput.h"
+#include "Engine/Platform/Windows/WindowsWindow.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CreateWindowSettings.h"
#include "Engine/Platform/CreateProcessSettings.h"
@@ -255,6 +257,8 @@ void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionM
RegCloseKey(hKey);
}
+#if !PLATFORM_SDL
+
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Find window to process that message
@@ -272,6 +276,8 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
return DefWindowProc(hwnd, msg, wParam, lParam);
}
+#endif
+
long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep)
{
if (ep->ExceptionRecord->ExceptionCode == CLR_EXCEPTION)
@@ -518,6 +524,7 @@ void WindowsPlatform::PreInit(void* hInstance)
// Disable the process from being showing "ghosted" while not responding messages during slow tasks
DisableProcessWindowsGhosting();
+#if !PLATFORM_SDL
// Register window class
WNDCLASS windowsClass;
Platform::MemoryClear(&windowsClass, sizeof(WNDCLASS));
@@ -532,6 +539,7 @@ void WindowsPlatform::PreInit(void* hInstance)
Error(TEXT("Window class registration failed!"));
exit(-1);
}
+#endif
// Init OLE
if (OleInitialize(nullptr) != S_OK)
@@ -680,7 +688,9 @@ bool WindowsPlatform::Init()
}
OnPlatformUserAdd(New(userName));
+#if !PLATFORM_SDL
WindowsInput::Init();
+#endif
return false;
}
@@ -708,7 +718,9 @@ void WindowsPlatform::LogInfo()
void WindowsPlatform::Tick()
{
+#if !PLATFORM_SDL
WindowsInput::Update();
+#endif
// Check to see if any messages are waiting in the queue
MSG msg;
@@ -739,8 +751,10 @@ void WindowsPlatform::Exit()
DbgHelpUnlock();
#endif
+#if !PLATFORM_SDL
// Unregister app class
UnregisterClassW(ApplicationClassName, nullptr);
+#endif
Win32Platform::Exit();
}
@@ -1185,11 +1199,15 @@ int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
return result;
}
+#if !PLATFORM_SDL
+
Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings)
{
return New(settings);
}
+#endif
+
void* WindowsPlatform::LoadLibrary(const Char* filename)
{
ASSERT(filename);
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp
index db19592ef..a7677fb93 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp
+++ b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp
@@ -2,6 +2,10 @@
#if PLATFORM_WINDOWS
+#if PLATFORM_SDL
+#include "Engine/Platform/SDL/SDLWindow.h"
+#endif
+
#include "Engine/Platform/Windows/WindowsWindow.h"
#if USE_EDITOR
@@ -596,7 +600,11 @@ DragDropEffect Window::DoDragDrop(const StringView& data)
{
::POINT point;
::GetCursorPos(&point);
+#if PLATFORM_SDL
+ Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, (Window*)this);
+#else
Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this);
+#endif
}
return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None;
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp
index b75082e58..29f42e9a2 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.cpp
+++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp
@@ -1,6 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS && !PLATFORM_SDL
#include "WindowsWindow.h"
#include "WindowsPlatform.h"
@@ -1308,7 +1308,7 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
if (_settings.AllowInput)
{
- if (WindowsInput::WndProc(this, msg, wParam, lParam))
+ if (WindowsInput::WndProc((Window*)this, msg, wParam, lParam))
return true;
}
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h
index 6c720ba07..18ac757c7 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.h
+++ b/Source/Engine/Platform/Windows/WindowsWindow.h
@@ -2,7 +2,7 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS && !PLATFORM_SDL
#include "Engine/Platform/Base/WindowBase.h"
#include "Engine/Platform/Platform.h"
@@ -136,7 +136,7 @@ public:
Windows::ULONG __stdcall AddRef() override;
Windows::ULONG __stdcall Release() override;
- // [IDropTarget]
+ // [Windows::IDropTarget]
Windows::HRESULT __stdcall DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
Windows::HRESULT __stdcall DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override;
Windows::HRESULT __stdcall DragLeave() override;
diff --git a/Source/ThirdParty/SDL/LICENSE.txt b/Source/ThirdParty/SDL/LICENSE.txt
new file mode 100644
index 000000000..74bb56cdd
--- /dev/null
+++ b/Source/ThirdParty/SDL/LICENSE.txt
@@ -0,0 +1,18 @@
+Copyright (C) 1997-2024 Sam Lantinga
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
diff --git a/Source/ThirdParty/SDL/SDL.Build.cs b/Source/ThirdParty/SDL/SDL.Build.cs
new file mode 100644
index 000000000..c5599a987
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL.Build.cs
@@ -0,0 +1,53 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System.IO;
+using Flax.Build;
+using Flax.Build.NativeCpp;
+
+///
+/// https://www.libsdl.org/
+///
+public class SDL : DepsModule
+{
+ ///
+ public override void Init()
+ {
+ base.Init();
+
+ LicenseType = LicenseTypes.Custom;
+ LicenseFilePath = "LICENSE.txt";
+
+ // Merge third-party modules into engine binary
+ BinaryModuleName = "FlaxEngine";
+ }
+
+ ///
+ public override void Setup(BuildOptions options)
+ {
+ base.Setup(options);
+
+ var depsRoot = options.DepsFolder;
+ switch (options.Platform.Target)
+ {
+ case TargetPlatform.Windows:
+ options.OutputFiles.Add(Path.Combine(depsRoot, "SDL3.lib"));
+ options.OptionalDependencyFiles.Add(Path.Combine(depsRoot, "SDL3.pdb"));
+ options.OptionalDependencyFiles.Add(Path.Combine(depsRoot, "SDL3.dll"));
+
+ // For static linkage
+ options.OutputFiles.Add("Setupapi.lib");
+ options.OutputFiles.Add("Version.lib");
+ options.OutputFiles.Add("Imm32.lib");
+ options.OutputFiles.Add("Gdi32.lib");
+
+ break;
+ case TargetPlatform.Linux:
+ case TargetPlatform.Mac:
+ options.OutputFiles.Add(Path.Combine(depsRoot, "libSDL3.a"));
+ break;
+ default: throw new InvalidPlatformException(options.Platform.Target);
+ }
+
+ options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, @"Source\ThirdParty\SDL"));
+ }
+}
diff --git a/Source/ThirdParty/SDL/SDL3/SDL.h b/Source/ThirdParty/SDL/SDL3/SDL.h
new file mode 100644
index 000000000..4e18f6382
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL.h
@@ -0,0 +1,83 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * \file SDL.h
+ *
+ * Main include header for the SDL library, version 3.1.2
+ */
+
+#ifndef SDL_h_
+#define SDL_h_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#endif /* SDL_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_assert.h b/Source/ThirdParty/SDL/SDL3/SDL_assert.h
new file mode 100644
index 000000000..346d1e3a4
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_assert.h
@@ -0,0 +1,551 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryAssert
+ *
+ * A helpful assertion macro!
+ *
+ * SDL assertions operate like your usual `assert` macro, but with some added
+ * features:
+ *
+ * - It uses a trick with the `sizeof` operator, so disabled assertions
+ * vaporize out of the compiled code, but variables only referenced in the
+ * assertion won't trigger compiler warnings about being unused.
+ * - It is safe to use with a dangling-else: `if (x) SDL_assert(y); else
+ * do_something();`
+ * - It works the same everywhere, instead of counting on various platforms'
+ * compiler and C runtime to behave.
+ * - It provides multiple levels of assertion (SDL_assert, SDL_assert_release,
+ * SDL_assert_paranoid) instead of a single all-or-nothing option.
+ * - It offers a variety of responses when an assertion fails (retry, trigger
+ * the debugger, abort the program, ignore the failure once, ignore it for
+ * the rest of the program's run).
+ * - It tries to show the user a dialog by default, if possible, but the app
+ * can provide a callback to handle assertion failures however they like.
+ * - It lets failed assertions be retried. Perhaps you had a network failure
+ * and just want to retry the test after plugging your network cable back
+ * in? You can.
+ * - It lets the user ignore an assertion failure, if there's a harmless
+ * problem that one can continue past.
+ * - It lets the user mark an assertion as ignored for the rest of the
+ * program's run; if there's a harmless problem that keeps popping up.
+ * - It provides statistics and data on all failed assertions to the app.
+ * - It allows the default assertion handler to be controlled with environment
+ * variables, in case an automated script needs to control it.
+ *
+ * To use it: do a debug build and just sprinkle around tests to check your
+ * code!
+ */
+
+#ifndef SDL_assert_h_
+#define SDL_assert_h_
+
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef SDL_WIKI_DOCUMENTATION_SECTION
+
+/**
+ * The level of assertion aggressiveness.
+ *
+ * This value changes depending on compiler options and other preprocessor
+ * defines.
+ *
+ * It is currently one of the following values, but future SDL releases might
+ * add more:
+ *
+ * - 0: All SDL assertion macros are disabled.
+ * - 1: Release settings: SDL_assert disabled, SDL_assert_release enabled.
+ * - 2: Debug settings: SDL_assert and SDL_assert_release enabled.
+ * - 3: Paranoid settings: All SDL assertion macros enabled, including
+ * SDL_assert_paranoid.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_ASSERT_LEVEL SomeNumberBasedOnVariousFactors
+
+#elif !defined(SDL_ASSERT_LEVEL)
+#ifdef SDL_DEFAULT_ASSERT_LEVEL
+#define SDL_ASSERT_LEVEL SDL_DEFAULT_ASSERT_LEVEL
+#elif defined(_DEBUG) || defined(DEBUG) || \
+ (defined(__GNUC__) && !defined(__OPTIMIZE__))
+#define SDL_ASSERT_LEVEL 2
+#else
+#define SDL_ASSERT_LEVEL 1
+#endif
+#endif
+
+#ifdef SDL_WIKI_DOCUMENTATION_SECTION
+
+/**
+ * Attempt to tell an attached debugger to pause.
+ *
+ * This allows an app to programmatically halt ("break") the debugger as if it
+ * had hit a breakpoint, allowing the developer to examine program state, etc.
+ *
+ * This is a macro--not a function--so that the debugger breaks on the source
+ * code line that used SDL_TriggerBreakpoint and not in some random guts of
+ * SDL. SDL_assert uses this macro for the same reason.
+ *
+ * If the program is not running under a debugger, SDL_TriggerBreakpoint will
+ * likely terminate the app, possibly without warning. If the current platform
+ * isn't supported (SDL doesn't know how to trigger a breakpoint), this macro
+ * does nothing.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_TriggerBreakpoint() TriggerABreakpointInAPlatformSpecificManner
+
+#elif defined(_MSC_VER)
+ /* Don't include intrin.h here because it contains C++ code */
+ extern void __cdecl __debugbreak(void);
+ #define SDL_TriggerBreakpoint() __debugbreak()
+#elif defined(ANDROID)
+ #include
+ #define SDL_TriggerBreakpoint() assert(0)
+#elif SDL_HAS_BUILTIN(__builtin_debugtrap)
+ #define SDL_TriggerBreakpoint() __builtin_debugtrap()
+#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
+ #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" )
+#elif (defined(__GNUC__) || defined(__clang__)) && defined(__riscv)
+ #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "ebreak\n\t" )
+#elif ( defined(SDL_PLATFORM_APPLE) && (defined(__arm64__) || defined(__aarch64__)) ) /* this might work on other ARM targets, but this is a known quantity... */
+ #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #22\n\t" )
+#elif defined(SDL_PLATFORM_APPLE) && defined(__arm__)
+ #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "bkpt #22\n\t" )
+#elif defined(_WIN32) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__arm64__) || defined(__aarch64__)) )
+ #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #0xF000\n\t" )
+#elif defined(__386__) && defined(__WATCOMC__)
+ #define SDL_TriggerBreakpoint() { _asm { int 0x03 } }
+#elif defined(HAVE_SIGNAL_H) && !defined(__WATCOMC__)
+ #include
+ #define SDL_TriggerBreakpoint() raise(SIGTRAP)
+#else
+ /* How do we trigger breakpoints on this platform? */
+ #define SDL_TriggerBreakpoint()
+#endif
+
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */
+# define SDL_FUNCTION __func__
+#elif ((defined(__GNUC__) && (__GNUC__ >= 2)) || defined(_MSC_VER) || defined (__WATCOMC__))
+# define SDL_FUNCTION __FUNCTION__
+#else
+# define SDL_FUNCTION "???"
+#endif
+#define SDL_FILE __FILE__
+#define SDL_LINE __LINE__
+
+/*
+sizeof (x) makes the compiler still parse the expression even without
+assertions enabled, so the code is always checked at compile time, but
+doesn't actually generate code for it, so there are no side effects or
+expensive checks at run time, just the constant size of what x WOULD be,
+which presumably gets optimized out as unused.
+This also solves the problem of...
+
+ int somevalue = blah();
+ SDL_assert(somevalue == 1);
+
+...which would cause compiles to complain that somevalue is unused if we
+disable assertions.
+*/
+
+/* "while (0,0)" fools Microsoft's compiler's /W4 warning level into thinking
+ this condition isn't constant. And looks like an owl's face! */
+#ifdef _MSC_VER /* stupid /W4 warnings. */
+#define SDL_NULL_WHILE_LOOP_CONDITION (0,0)
+#else
+#define SDL_NULL_WHILE_LOOP_CONDITION (0)
+#endif
+
+#define SDL_disabled_assert(condition) \
+ do { (void) sizeof ((condition)); } while (SDL_NULL_WHILE_LOOP_CONDITION)
+
+/**
+ * Possible outcomes from a triggered assertion.
+ *
+ * When an enabled assertion triggers, it may call the assertion handler
+ * (possibly one provided by the app via SDL_SetAssertionHandler), which will
+ * return one of these values, possibly after asking the user.
+ *
+ * Then SDL will respond based on this outcome (loop around to retry the
+ * condition, try to break in a debugger, kill the program, or ignore the
+ * problem).
+ *
+ * \since This enum is available since SDL 3.0.0.
+ */
+typedef enum SDL_AssertState
+{
+ SDL_ASSERTION_RETRY, /**< Retry the assert immediately. */
+ SDL_ASSERTION_BREAK, /**< Make the debugger trigger a breakpoint. */
+ SDL_ASSERTION_ABORT, /**< Terminate the program. */
+ SDL_ASSERTION_IGNORE, /**< Ignore the assert. */
+ SDL_ASSERTION_ALWAYS_IGNORE /**< Ignore the assert from now on. */
+} SDL_AssertState;
+
+/**
+ * Information about an assertion failure.
+ *
+ * This structure is filled in with information about a triggered assertion,
+ * used by the assertion handler, then added to the assertion report. This is
+ * returned as a linked list from SDL_GetAssertionReport().
+ *
+ * \since This struct is available since SDL 3.0.0.
+ */
+typedef struct SDL_AssertData
+{
+ SDL_bool always_ignore; /**< SDL_TRUE if app should always continue when assertion is triggered. */
+ unsigned int trigger_count; /**< Number of times this assertion has been triggered. */
+ const char *condition; /**< A string of this assert's test code. */
+ const char *filename; /**< The source file where this assert lives. */
+ int linenum; /**< The line in `filename` where this assert lives. */
+ const char *function; /**< The name of the function where this assert lives. */
+ const struct SDL_AssertData *next; /**< next item in the linked list. */
+} SDL_AssertData;
+
+/**
+ * Never call this directly.
+ *
+ * Use the SDL_assert* macros instead.
+ *
+ * \param data assert data structure.
+ * \param func function name.
+ * \param file file name.
+ * \param line line number.
+ * \returns assert state.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData *data,
+ const char *func,
+ const char *file, int line) SDL_ANALYZER_NORETURN;
+
+/* Define the trigger breakpoint call used in asserts */
+#ifndef SDL_AssertBreakpoint
+#if defined(ANDROID) && defined(assert)
+/* Define this as empty in case assert() is defined as SDL_assert */
+#define SDL_AssertBreakpoint()
+#else
+#define SDL_AssertBreakpoint() SDL_TriggerBreakpoint()
+#endif
+#endif /* !SDL_AssertBreakpoint */
+
+/* the do {} while(0) avoids dangling else problems:
+ if (x) SDL_assert(y); else blah();
+ ... without the do/while, the "else" could attach to this macro's "if".
+ We try to handle just the minimum we need here in a macro...the loop,
+ the static vars, and break points. The heavy lifting is handled in
+ SDL_ReportAssertion(), in SDL_assert.c.
+*/
+#define SDL_enabled_assert(condition) \
+ do { \
+ while ( !(condition) ) { \
+ static struct SDL_AssertData sdl_assert_data = { 0, 0, #condition, 0, 0, 0, 0 }; \
+ const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_FILE, SDL_LINE); \
+ if (sdl_assert_state == SDL_ASSERTION_RETRY) { \
+ continue; /* go again. */ \
+ } else if (sdl_assert_state == SDL_ASSERTION_BREAK) { \
+ SDL_AssertBreakpoint(); \
+ } \
+ break; /* not retrying. */ \
+ } \
+ } while (SDL_NULL_WHILE_LOOP_CONDITION)
+
+#ifdef SDL_WIKI_DOCUMENTATION_SECTION
+
+/**
+ * An assertion test that is normally performed only in debug builds.
+ *
+ * This macro is enabled when the SDL_ASSERT_LEVEL is >= 2, otherwise it is
+ * disabled. This is meant to only do these tests in debug builds, so they can
+ * tend to be more expensive, and they are meant to bring everything to a halt
+ * when they fail, with the programmer there to assess the problem.
+ *
+ * In short: you can sprinkle these around liberally and assume they will
+ * evaporate out of the build when building for end-users.
+ *
+ * When assertions are disabled, this wraps `condition` in a `sizeof`
+ * operator, which means any function calls and side effects will not run, but
+ * the compiler will not complain about any otherwise-unused variables that
+ * are only referenced in the assertion.
+ *
+ * One can set the environment variable "SDL_ASSERT" to one of several strings
+ * ("abort", "break", "retry", "ignore", "always_ignore") to force a default
+ * behavior, which may be desirable for automation purposes. If your platform
+ * requires GUI interfaces to happen on the main thread but you're debugging
+ * an assertion in a background thread, it might be desirable to set this to
+ * "break" so that your debugger takes control as soon as assert is triggered,
+ * instead of risking a bad UI interaction (deadlock, etc) in the application.
+ *
+ * Note that SDL_ASSERT is an _environment variable_ and not an SDL hint!
+ * Please refer to your platform's documentation for how to set it!
+ *
+ * \param condition boolean value to test.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_assert(condition) if (assertion_enabled && (condition)) { trigger_assertion; }
+
+/**
+ * An assertion test that is performed even in release builds.
+ *
+ * This macro is enabled when the SDL_ASSERT_LEVEL is >= 1, otherwise it is
+ * disabled. This is meant to be for tests that are cheap to make and
+ * extremely unlikely to fail; generally it is frowned upon to have an
+ * assertion failure in a release build, so these assertions generally need to
+ * be of more than life-and-death importance if there's a chance they might
+ * trigger. You should almost always consider handling these cases more
+ * gracefully than an assert allows.
+ *
+ * When assertions are disabled, this wraps `condition` in a `sizeof`
+ * operator, which means any function calls and side effects will not run, but
+ * the compiler will not complain about any otherwise-unused variables that
+ * are only referenced in the assertion.
+ *
+ * One can set the environment variable "SDL_ASSERT" to one of several strings
+ * ("abort", "break", "retry", "ignore", "always_ignore") to force a default
+ * behavior, which may be desirable for automation purposes. If your platform
+ * requires GUI interfaces to happen on the main thread but you're debugging
+ * an assertion in a background thread, it might be desirable to set this to
+ * "break" so that your debugger takes control as soon as assert is triggered,
+ * instead of risking a bad UI interaction (deadlock, etc) in the application.
+ *
+ * Note that SDL_ASSERT is an _environment variable_ and not an SDL hint!
+ * Please refer to your platform's documentation for how to set it!
+ *
+ * \param condition boolean value to test.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_assert_release(condition) SDL_disabled_assert(condition)
+
+/**
+ * An assertion test that is performed only when built with paranoid settings.
+ *
+ * This macro is enabled when the SDL_ASSERT_LEVEL is >= 3, otherwise it is
+ * disabled. This is a higher level than both release and debug, so these
+ * tests are meant to be expensive and only run when specifically looking for
+ * extremely unexpected failure cases in a special build.
+ *
+ * When assertions are disabled, this wraps `condition` in a `sizeof`
+ * operator, which means any function calls and side effects will not run, but
+ * the compiler will not complain about any otherwise-unused variables that
+ * are only referenced in the assertion.
+ *
+ * One can set the environment variable "SDL_ASSERT" to one of several strings
+ * ("abort", "break", "retry", "ignore", "always_ignore") to force a default
+ * behavior, which may be desirable for automation purposes. If your platform
+ * requires GUI interfaces to happen on the main thread but you're debugging
+ * an assertion in a background thread, it might be desirable to set this to
+ * "break" so that your debugger takes control as soon as assert is triggered,
+ * instead of risking a bad UI interaction (deadlock, etc) in the application.
+ *
+ * Note that SDL_ASSERT is an _environment variable_ and not an SDL hint!
+ * Please refer to your platform's documentation for how to set it!
+ *
+ * \param condition boolean value to test.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
+#endif
+
+/* Enable various levels of assertions. */
+#if SDL_ASSERT_LEVEL == 0 /* assertions disabled */
+# define SDL_assert(condition) SDL_disabled_assert(condition)
+# define SDL_assert_release(condition) SDL_disabled_assert(condition)
+# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
+#elif SDL_ASSERT_LEVEL == 1 /* release settings. */
+# define SDL_assert(condition) SDL_disabled_assert(condition)
+# define SDL_assert_release(condition) SDL_enabled_assert(condition)
+# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
+#elif SDL_ASSERT_LEVEL == 2 /* debug settings. */
+# define SDL_assert(condition) SDL_enabled_assert(condition)
+# define SDL_assert_release(condition) SDL_enabled_assert(condition)
+# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
+#elif SDL_ASSERT_LEVEL == 3 /* paranoid settings. */
+# define SDL_assert(condition) SDL_enabled_assert(condition)
+# define SDL_assert_release(condition) SDL_enabled_assert(condition)
+# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition)
+#else
+# error Unknown assertion level.
+#endif
+
+/**
+ * An assertion test that always performed.
+ *
+ * This macro is always enabled no matter what SDL_ASSERT_LEVEL is set to. You
+ * almost never want to use this, as it could trigger on an end-user's system,
+ * crashing your program.
+ *
+ * One can set the environment variable "SDL_ASSERT" to one of several strings
+ * ("abort", "break", "retry", "ignore", "always_ignore") to force a default
+ * behavior, which may be desirable for automation purposes. If your platform
+ * requires GUI interfaces to happen on the main thread but you're debugging
+ * an assertion in a background thread, it might be desirable to set this to
+ * "break" so that your debugger takes control as soon as assert is triggered,
+ * instead of risking a bad UI interaction (deadlock, etc) in the application.
+ *
+ * Note that SDL_ASSERT is an _environment variable_ and not an SDL hint!
+ * Please refer to your platform's documentation for how to set it!
+ *
+ * \param condition boolean value to test.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_assert_always(condition) SDL_enabled_assert(condition)
+
+
+/**
+ * A callback that fires when an SDL assertion fails.
+ *
+ * \param data a pointer to the SDL_AssertData structure corresponding to the
+ * current assertion.
+ * \param userdata what was passed as `userdata` to SDL_SetAssertionHandler().
+ * \returns an SDL_AssertState value indicating how to handle the failure.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ */
+typedef SDL_AssertState (SDLCALL *SDL_AssertionHandler)(
+ const SDL_AssertData *data, void *userdata);
+
+/**
+ * Set an application-defined assertion handler.
+ *
+ * This function allows an application to show its own assertion UI and/or
+ * force the response to an assertion failure. If the application doesn't
+ * provide this, SDL will try to do the right thing, popping up a
+ * system-specific GUI dialog, and probably minimizing any fullscreen windows.
+ *
+ * This callback may fire from any thread, but it runs wrapped in a mutex, so
+ * it will only fire from one thread at a time.
+ *
+ * This callback is NOT reset to SDL's internal handler upon SDL_Quit()!
+ *
+ * \param handler the SDL_AssertionHandler function to call when an assertion
+ * fails or NULL for the default handler.
+ * \param userdata a pointer that is passed to `handler`.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAssertionHandler
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_SetAssertionHandler(
+ SDL_AssertionHandler handler,
+ void *userdata);
+
+/**
+ * Get the default assertion handler.
+ *
+ * This returns the function pointer that is called by default when an
+ * assertion is triggered. This is an internal function provided by SDL, that
+ * is used for assertions when SDL_SetAssertionHandler() hasn't been used to
+ * provide a different function.
+ *
+ * \returns the default SDL_AssertionHandler that is called when an assert
+ * triggers.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAssertionHandler
+ */
+extern SDL_DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetDefaultAssertionHandler(void);
+
+/**
+ * Get the current assertion handler.
+ *
+ * This returns the function pointer that is called when an assertion is
+ * triggered. This is either the value last passed to
+ * SDL_SetAssertionHandler(), or if no application-specified function is set,
+ * is equivalent to calling SDL_GetDefaultAssertionHandler().
+ *
+ * The parameter `puserdata` is a pointer to a void*, which will store the
+ * "userdata" pointer that was passed to SDL_SetAssertionHandler(). This value
+ * will always be NULL for the default handler. If you don't care about this
+ * data, it is safe to pass a NULL pointer to this function to ignore it.
+ *
+ * \param puserdata pointer which is filled with the "userdata" pointer that
+ * was passed to SDL_SetAssertionHandler().
+ * \returns the SDL_AssertionHandler that is called when an assert triggers.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAssertionHandler
+ */
+extern SDL_DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetAssertionHandler(void **puserdata);
+
+/**
+ * Get a list of all assertion failures.
+ *
+ * This function gets all assertions triggered since the last call to
+ * SDL_ResetAssertionReport(), or the start of the program.
+ *
+ * The proper way to examine this data looks something like this:
+ *
+ * ```c
+ * const SDL_AssertData *item = SDL_GetAssertionReport();
+ * while (item) {
+ * printf("'%s', %s (%s:%d), triggered %u times, always ignore: %s.\\n",
+ * item->condition, item->function, item->filename,
+ * item->linenum, item->trigger_count,
+ * item->always_ignore ? "yes" : "no");
+ * item = item->next;
+ * }
+ * ```
+ *
+ * \returns a list of all failed assertions or NULL if the list is empty. This
+ * memory should not be modified or freed by the application.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ResetAssertionReport
+ */
+extern SDL_DECLSPEC const SDL_AssertData * SDLCALL SDL_GetAssertionReport(void);
+
+/**
+ * Clear the list of all assertion failures.
+ *
+ * This function will clear the list of all assertions triggered up to that
+ * point. Immediately following this call, SDL_GetAssertionReport will return
+ * no items. In addition, any previously-triggered assertions will be reset to
+ * a trigger_count of zero, and their always_ignore state will be false.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAssertionReport
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_ResetAssertionReport(void);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_assert_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_atomic.h b/Source/ThirdParty/SDL/SDL3/SDL_atomic.h
new file mode 100644
index 000000000..fbf01c4bc
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_atomic.h
@@ -0,0 +1,507 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryAtomic
+ *
+ * Atomic operations.
+ *
+ * IMPORTANT: If you are not an expert in concurrent lockless programming, you
+ * should not be using any functions in this file. You should be protecting
+ * your data structures with full mutexes instead.
+ *
+ * ***Seriously, here be dragons!***
+ *
+ * You can find out a little more about lockless programming and the subtle
+ * issues that can arise here:
+ * https://learn.microsoft.com/en-us/windows/win32/dxtecharts/lockless-programming
+ *
+ * There's also lots of good information here:
+ *
+ * - https://www.1024cores.net/home/lock-free-algorithms
+ * - https://preshing.com/
+ *
+ * These operations may or may not actually be implemented using processor
+ * specific atomic operations. When possible they are implemented as true
+ * processor specific atomic operations. When that is not possible the are
+ * implemented using locks that *do* use the available atomic operations.
+ *
+ * All of the atomic operations that modify memory are full memory barriers.
+ */
+
+#ifndef SDL_atomic_h_
+#define SDL_atomic_h_
+
+#include
+#include
+
+#include
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * An atomic spinlock.
+ *
+ * The atomic locks are efficient spinlocks using CPU instructions, but are
+ * vulnerable to starvation and can spin forever if a thread holding a lock
+ * has been terminated. For this reason you should minimize the code executed
+ * inside an atomic lock and never do expensive things like API or system
+ * calls while holding them.
+ *
+ * They are also vulnerable to starvation if the thread holding the lock is
+ * lower priority than other threads and doesn't get scheduled. In general you
+ * should use mutexes instead, since they have better performance and
+ * contention behavior.
+ *
+ * The atomic locks are not safe to lock recursively.
+ *
+ * Porting Note: The spin lock functions and type are required and can not be
+ * emulated because they are used in the atomic emulation code.
+ */
+typedef int SDL_SpinLock;
+
+/**
+ * Try to lock a spin lock by setting it to a non-zero value.
+ *
+ * ***Please note that spinlocks are dangerous if you don't know what you're
+ * doing. Please be careful using any sort of spinlock!***
+ *
+ * \param lock a pointer to a lock variable.
+ * \returns SDL_TRUE if the lock succeeded, SDL_FALSE if the lock is already
+ * held.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_LockSpinlock
+ * \sa SDL_UnlockSpinlock
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_TryLockSpinlock(SDL_SpinLock *lock);
+
+/**
+ * Lock a spin lock by setting it to a non-zero value.
+ *
+ * ***Please note that spinlocks are dangerous if you don't know what you're
+ * doing. Please be careful using any sort of spinlock!***
+ *
+ * \param lock a pointer to a lock variable.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_TryLockSpinlock
+ * \sa SDL_UnlockSpinlock
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_LockSpinlock(SDL_SpinLock *lock);
+
+/**
+ * Unlock a spin lock by setting it to 0.
+ *
+ * Always returns immediately.
+ *
+ * ***Please note that spinlocks are dangerous if you don't know what you're
+ * doing. Please be careful using any sort of spinlock!***
+ *
+ * \param lock a pointer to a lock variable.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_LockSpinlock
+ * \sa SDL_TryLockSpinlock
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_UnlockSpinlock(SDL_SpinLock *lock);
+
+
+#ifdef SDL_WIKI_DOCUMENTATION_SECTION
+
+/**
+ * Mark a compiler barrier.
+ *
+ * A compiler barrier prevents the compiler from reordering reads and writes
+ * to globally visible variables across the call.
+ *
+ * This macro only prevents the compiler from reordering reads and writes, it
+ * does not prevent the CPU from reordering reads and writes. However, all of
+ * the atomic operations that modify memory are full memory barriers.
+ *
+ * \threadsafety Obviously this macro is safe to use from any thread at any
+ * time, but if you find yourself needing this, you are probably
+ * dealing with some very sensitive code; be careful!
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_CompilerBarrier() DoCompilerSpecificReadWriteBarrier()
+#elif defined(_MSC_VER) && (_MSC_VER > 1200) && !defined(__clang__)
+void _ReadWriteBarrier(void);
+#pragma intrinsic(_ReadWriteBarrier)
+#define SDL_CompilerBarrier() _ReadWriteBarrier()
+#elif (defined(__GNUC__) && !defined(SDL_PLATFORM_EMSCRIPTEN)) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
+/* This is correct for all CPUs when using GCC or Solaris Studio 12.1+. */
+#define SDL_CompilerBarrier() __asm__ __volatile__ ("" : : : "memory")
+#elif defined(__WATCOMC__)
+extern __inline void SDL_CompilerBarrier(void);
+#pragma aux SDL_CompilerBarrier = "" parm [] modify exact [];
+#else
+#define SDL_CompilerBarrier() \
+{ SDL_SpinLock _tmp = 0; SDL_LockSpinlock(&_tmp); SDL_UnlockSpinlock(&_tmp); }
+#endif
+
+/**
+ * Insert a memory release barrier.
+ *
+ * Memory barriers are designed to prevent reads and writes from being
+ * reordered by the compiler and being seen out of order on multi-core CPUs.
+ *
+ * A typical pattern would be for thread A to write some data and a flag, and
+ * for thread B to read the flag and get the data. In this case you would
+ * insert a release barrier between writing the data and the flag,
+ * guaranteeing that the data write completes no later than the flag is
+ * written, and you would insert an acquire barrier between reading the flag
+ * and reading the data, to ensure that all the reads associated with the flag
+ * have completed.
+ *
+ * In this pattern you should always see a release barrier paired with an
+ * acquire barrier and you should gate the data reads/writes with a single
+ * flag variable.
+ *
+ * For more information on these semantics, take a look at the blog post:
+ * http://preshing.com/20120913/acquire-and-release-semantics
+ *
+ * \threadsafety Obviously this macro is safe to use from any thread at any
+ * time, but if you find yourself needing this, you are probably
+ * dealing with some very sensitive code; be careful!
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_MemoryBarrierReleaseFunction(void);
+
+/**
+ * Insert a memory acquire barrier.
+ *
+ * Please refer to SDL_MemoryBarrierReleaseFunction for the details!
+ *
+ * \threadsafety Obviously this function is safe to use from any thread at any
+ * time, but if you find yourself needing this, you are probably
+ * dealing with some very sensitive code; be careful!
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_MemoryBarrierReleaseFunction
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
+
+/* !!! FIXME: this should have documentation! */
+#if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
+#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("lwsync" : : : "memory")
+#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("lwsync" : : : "memory")
+#elif defined(__GNUC__) && defined(__aarch64__)
+#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
+#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
+#elif defined(__GNUC__) && defined(__arm__)
+#if 0 /* defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) */
+/* Information from:
+ https://chromium.googlesource.com/chromium/chromium/+/trunk/base/atomicops_internals_arm_gcc.h#19
+
+ The Linux kernel provides a helper function which provides the right code for a memory barrier,
+ hard-coded at address 0xffff0fa0
+*/
+typedef void (*SDL_KernelMemoryBarrierFunc)();
+#define SDL_MemoryBarrierRelease() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
+#define SDL_MemoryBarrierAcquire() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
+#else
+#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__)
+#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
+#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
+#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__)
+#ifdef __thumb__
+/* The mcr instruction isn't available in thumb mode, use real functions */
+#define SDL_MEMORY_BARRIER_USES_FUNCTION
+#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
+#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
+#else
+#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
+#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
+#endif /* __thumb__ */
+#else
+#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory")
+#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory")
+#endif /* SDL_PLATFORM_LINUX || SDL_PLATFORM_ANDROID */
+#endif /* __GNUC__ && __arm__ */
+#else
+#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
+/* This is correct for all CPUs on Solaris when using Solaris Studio 12.1+. */
+#include
+#define SDL_MemoryBarrierRelease() __machine_rel_barrier()
+#define SDL_MemoryBarrierAcquire() __machine_acq_barrier()
+#else
+/* This is correct for the x86 and x64 CPUs, and we'll expand this over time. */
+#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier()
+#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier()
+#endif
+#endif
+
+/* "REP NOP" is PAUSE, coded for tools that don't know it by that name. */
+#ifdef SDL_WIKI_DOCUMENTATION_SECTION
+
+/**
+ * A macro to insert a CPU-specific "pause" instruction into the program.
+ *
+ * This can be useful in busy-wait loops, as it serves as a hint to the CPU as
+ * to the program's intent; some CPUs can use this to do more efficient
+ * processing. On some platforms, this doesn't do anything, so using this
+ * macro might just be a harmless no-op.
+ *
+ * Note that if you are busy-waiting, there are often more-efficient
+ * approaches with other synchronization primitives: mutexes, semaphores,
+ * condition variables, etc.
+ *
+ * \threadsafety This macro is safe to use from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_CPUPauseInstruction() DoACPUPauseInACompilerAndArchitectureSpecificWay
+#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
+ #define SDL_CPUPauseInstruction() __asm__ __volatile__("pause\n") /* Some assemblers can't do REP NOP, so go with PAUSE. */
+#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(__aarch64__)
+ #define SDL_CPUPauseInstruction() __asm__ __volatile__("yield" ::: "memory")
+#elif (defined(__powerpc__) || defined(__powerpc64__))
+ #define SDL_CPUPauseInstruction() __asm__ __volatile__("or 27,27,27");
+#elif (defined(__riscv) && __riscv_xlen == 64)
+ #define SDL_CPUPauseInstruction() __asm__ __volatile__(".insn i 0x0F, 0, x0, x0, 0x010");
+#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+ #define SDL_CPUPauseInstruction() _mm_pause() /* this is actually "rep nop" and not a SIMD instruction. No inline asm in MSVC x86-64! */
+#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
+ #define SDL_CPUPauseInstruction() __yield()
+#elif defined(__WATCOMC__) && defined(__386__)
+ extern __inline void SDL_CPUPauseInstruction(void);
+ #pragma aux SDL_CPUPauseInstruction = ".686p" ".xmm2" "pause"
+#else
+ #define SDL_CPUPauseInstruction()
+#endif
+
+
+/**
+ * A type representing an atomic integer value.
+ *
+ * This can be used to manage a value that is synchronized across multiple
+ * CPUs without a race condition; when an app sets a value with SDL_AtomicSet
+ * all other threads, regardless of the CPU it is running on, will see that
+ * value when retrieved with SDL_AtomicGet, regardless of CPU caches, etc.
+ *
+ * This is also useful for atomic compare-and-swap operations: a thread can
+ * change the value as long as its current value matches expectations. When
+ * done in a loop, one can guarantee data consistency across threads without a
+ * lock (but the usual warnings apply: if you don't know what you're doing, or
+ * you don't do it carefully, you can confidently cause any number of
+ * disasters with this, so in most cases, you _should_ use a mutex instead of
+ * this!).
+ *
+ * This is a struct so people don't accidentally use numeric operations on it
+ * directly. You have to use SDL_Atomic* functions.
+ *
+ * \since This struct is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicCompareAndSwap
+ * \sa SDL_AtomicGet
+ * \sa SDL_AtomicSet
+ * \sa SDL_AtomicAdd
+ */
+typedef struct SDL_AtomicInt { int value; } SDL_AtomicInt;
+
+/**
+ * Set an atomic variable to a new value if it is currently an old value.
+ *
+ * ***Note: If you don't know what this function is for, you shouldn't use
+ * it!***
+ *
+ * \param a a pointer to an SDL_AtomicInt variable to be modified.
+ * \param oldval the old value.
+ * \param newval the new value.
+ * \returns SDL_TRUE if the atomic variable was set, SDL_FALSE otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicCompareAndSwapPointer
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_AtomicCompareAndSwap(SDL_AtomicInt *a, int oldval, int newval);
+
+/**
+ * Set an atomic variable to a value.
+ *
+ * This function also acts as a full memory barrier.
+ *
+ * ***Note: If you don't know what this function is for, you shouldn't use
+ * it!***
+ *
+ * \param a a pointer to an SDL_AtomicInt variable to be modified.
+ * \param v the desired value.
+ * \returns the previous value of the atomic variable.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicGet
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_AtomicSet(SDL_AtomicInt *a, int v);
+
+/**
+ * Get the value of an atomic variable.
+ *
+ * ***Note: If you don't know what this function is for, you shouldn't use
+ * it!***
+ *
+ * \param a a pointer to an SDL_AtomicInt variable.
+ * \returns the current value of an atomic variable.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicSet
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_AtomicGet(SDL_AtomicInt *a);
+
+/**
+ * Add to an atomic variable.
+ *
+ * This function also acts as a full memory barrier.
+ *
+ * ***Note: If you don't know what this function is for, you shouldn't use
+ * it!***
+ *
+ * \param a a pointer to an SDL_AtomicInt variable to be modified.
+ * \param v the desired value to add.
+ * \returns the previous value of the atomic variable.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicDecRef
+ * \sa SDL_AtomicIncRef
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_AtomicAdd(SDL_AtomicInt *a, int v);
+
+#ifndef SDL_AtomicIncRef
+
+/**
+ * Increment an atomic variable used as a reference count.
+ *
+ * ***Note: If you don't know what this macro is for, you shouldn't use it!***
+ *
+ * \param a a pointer to an SDL_AtomicInt to increment.
+ * \returns the previous value of the atomic variable.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicDecRef
+ */
+#define SDL_AtomicIncRef(a) SDL_AtomicAdd(a, 1)
+#endif
+
+#ifndef SDL_AtomicDecRef
+
+/**
+ * Decrement an atomic variable used as a reference count.
+ *
+ * ***Note: If you don't know what this macro is for, you shouldn't use it!***
+ *
+ * \param a a pointer to an SDL_AtomicInt to increment.
+ * \returns SDL_TRUE if the variable reached zero after decrementing,
+ * SDL_FALSE otherwise.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicIncRef
+ */
+#define SDL_AtomicDecRef(a) (SDL_AtomicAdd(a, -1) == 1)
+#endif
+
+/**
+ * Set a pointer to a new value if it is currently an old value.
+ *
+ * ***Note: If you don't know what this function is for, you shouldn't use
+ * it!***
+ *
+ * \param a a pointer to a pointer.
+ * \param oldval the old pointer value.
+ * \param newval the new pointer value.
+ * \returns SDL_TRUE if the pointer was set, SDL_FALSE otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicCompareAndSwap
+ * \sa SDL_AtomicGetPtr
+ * \sa SDL_AtomicSetPtr
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_AtomicCompareAndSwapPointer(void **a, void *oldval, void *newval);
+
+/**
+ * Set a pointer to a value atomically.
+ *
+ * ***Note: If you don't know what this function is for, you shouldn't use
+ * it!***
+ *
+ * \param a a pointer to a pointer.
+ * \param v the desired pointer value.
+ * \returns the previous value of the pointer.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicCompareAndSwapPointer
+ * \sa SDL_AtomicGetPtr
+ */
+extern SDL_DECLSPEC void * SDLCALL SDL_AtomicSetPtr(void **a, void *v);
+
+/**
+ * Get the value of a pointer atomically.
+ *
+ * ***Note: If you don't know what this function is for, you shouldn't use
+ * it!***
+ *
+ * \param a a pointer to a pointer.
+ * \returns the current value of a pointer.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AtomicCompareAndSwapPointer
+ * \sa SDL_AtomicSetPtr
+ */
+extern SDL_DECLSPEC void * SDLCALL SDL_AtomicGetPtr(void **a);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+
+#include
+
+#endif /* SDL_atomic_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_audio.h b/Source/ThirdParty/SDL/SDL3/SDL_audio.h
new file mode 100644
index 000000000..629cc3a95
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_audio.h
@@ -0,0 +1,2040 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryAudio
+ *
+ * Audio functionality for the SDL library.
+ *
+ * All audio in SDL3 revolves around SDL_AudioStream. Whether you want to play
+ * or record audio, convert it, stream it, buffer it, or mix it, you're going
+ * to be passing it through an audio stream.
+ *
+ * Audio streams are quite flexible; they can accept any amount of data at a
+ * time, in any supported format, and output it as needed in any other format,
+ * even if the data format changes on either side halfway through.
+ *
+ * An app opens an audio device and binds any number of audio streams to it,
+ * feeding more data to it as available. When the devices needs more data, it
+ * will pull it from all bound streams and mix them together for playback.
+ *
+ * Audio streams can also use an app-provided callback to supply data
+ * on-demand, which maps pretty closely to the SDL2 audio model.
+ *
+ * SDL also provides a simple .WAV loader in SDL_LoadWAV (and SDL_LoadWAV_IO
+ * if you aren't reading from a file) as a basic means to load sound data into
+ * your program.
+ *
+ * ## Channel layouts
+ *
+ * Audio data passing through SDL is uncompressed PCM data, interleaved. One
+ * can provide their own decompression through an MP3, etc, decoder, but SDL
+ * does not provide this directly. Each interleaved channel of data is meant
+ * to be in a specific order.
+ *
+ * Abbreviations:
+ *
+ * - FRONT = single mono speaker
+ * - FL = front left speaker
+ * - FR = front right speaker
+ * - FC = front center speaker
+ * - BL = back left speaker
+ * - BR = back right speaker
+ * - SR = surround right speaker
+ * - SL = surround left speaker
+ * - BC = back center speaker
+ * - LFE = low-frequency speaker
+ *
+ * These are listed in the order they are laid out in memory, so "FL, FR"
+ * means "the front left speaker is laid out in memory first, then the front
+ * right, then it repeats for the next audio frame".
+ *
+ * - 1 channel (mono) layout: FRONT
+ * - 2 channels (stereo) layout: FL, FR
+ * - 3 channels (2.1) layout: FL, FR, LFE
+ * - 4 channels (quad) layout: FL, FR, BL, BR
+ * - 5 channels (4.1) layout: FL, FR, LFE, BL, BR
+ * - 6 channels (5.1) layout: FL, FR, FC, LFE, BL, BR (last two can also be
+ * BL, BR)
+ * - 7 channels (6.1) layout: FL, FR, FC, LFE, BC, SL, SR
+ * - 8 channels (7.1) layout: FL, FR, FC, LFE, BL, BR, SL, SR
+ *
+ * This is the same order as DirectSound expects, but applied to all
+ * platforms; SDL will swizzle the channels as necessary if a platform expects
+ * something different.
+ *
+ * SDL_AudioStream can also be provided channel maps to change this ordering
+ * to whatever is necessary, in other audio processing scenarios.
+ */
+
+#ifndef SDL_audio_h_
+#define SDL_audio_h_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* masks for different parts of SDL_AudioFormat. */
+#define SDL_AUDIO_MASK_BITSIZE (0xFFu)
+#define SDL_AUDIO_MASK_FLOAT (1u<<8)
+#define SDL_AUDIO_MASK_BIG_ENDIAN (1u<<12)
+#define SDL_AUDIO_MASK_SIGNED (1u<<15)
+
+#define SDL_DEFINE_AUDIO_FORMAT(signed, bigendian, float, size) \
+ (((Uint16)(signed) << 15) | ((Uint16)(bigendian) << 12) | ((Uint16)(float) << 8) | ((size) & SDL_AUDIO_MASK_BITSIZE))
+
+/**
+ * Audio format.
+ *
+ * \since This enum is available since SDL 3.0.0.
+ *
+ * \sa SDL_AUDIO_BITSIZE
+ * \sa SDL_AUDIO_BYTESIZE
+ * \sa SDL_AUDIO_ISINT
+ * \sa SDL_AUDIO_ISFLOAT
+ * \sa SDL_AUDIO_ISBIGENDIAN
+ * \sa SDL_AUDIO_ISLITTLEENDIAN
+ * \sa SDL_AUDIO_ISSIGNED
+ * \sa SDL_AUDIO_ISUNSIGNED
+ */
+typedef enum SDL_AudioFormat
+{
+ SDL_AUDIO_U8 = 0x0008u, /**< Unsigned 8-bit samples */
+ /* SDL_DEFINE_AUDIO_FORMAT(0, 0, 0, 8), */
+ SDL_AUDIO_S8 = 0x8008u, /**< Signed 8-bit samples */
+ /* SDL_DEFINE_AUDIO_FORMAT(1, 0, 0, 8), */
+ SDL_AUDIO_S16LE = 0x8010u, /**< Signed 16-bit samples */
+ /* SDL_DEFINE_AUDIO_FORMAT(1, 0, 0, 16), */
+ SDL_AUDIO_S16BE = 0x9010u, /**< As above, but big-endian byte order */
+ /* SDL_DEFINE_AUDIO_FORMAT(1, 1, 0, 16), */
+ SDL_AUDIO_S32LE = 0x8020u, /**< 32-bit integer samples */
+ /* SDL_DEFINE_AUDIO_FORMAT(1, 0, 0, 32), */
+ SDL_AUDIO_S32BE = 0x9020u, /**< As above, but big-endian byte order */
+ /* SDL_DEFINE_AUDIO_FORMAT(1, 1, 0, 32), */
+ SDL_AUDIO_F32LE = 0x8120u, /**< 32-bit floating point samples */
+ /* SDL_DEFINE_AUDIO_FORMAT(1, 0, 1, 32), */
+ SDL_AUDIO_F32BE = 0x9120u, /**< As above, but big-endian byte order */
+ /* SDL_DEFINE_AUDIO_FORMAT(1, 1, 1, 32), */
+} SDL_AudioFormat;
+
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+#define SDL_AUDIO_S16 SDL_AUDIO_S16LE
+#define SDL_AUDIO_S32 SDL_AUDIO_S32LE
+#define SDL_AUDIO_F32 SDL_AUDIO_F32LE
+#else
+#define SDL_AUDIO_S16 SDL_AUDIO_S16BE
+#define SDL_AUDIO_S32 SDL_AUDIO_S32BE
+#define SDL_AUDIO_F32 SDL_AUDIO_F32BE
+#endif
+
+
+/**
+ * Retrieve the size, in bits, from an SDL_AudioFormat.
+ *
+ * For example, `SDL_AUDIO_BITSIZE(SDL_AUDIO_S16)` returns 16.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns data size in bits.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_BITSIZE(x) ((x) & SDL_AUDIO_MASK_BITSIZE)
+
+/**
+ * Retrieve the size, in bytes, from an SDL_AudioFormat.
+ *
+ * For example, `SDL_AUDIO_BYTESIZE(SDL_AUDIO_S16)` returns 2.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns data size in bytes.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_BYTESIZE(x) (SDL_AUDIO_BITSIZE(x) / 8)
+
+/**
+ * Determine if an SDL_AudioFormat represents floating point data.
+ *
+ * For example, `SDL_AUDIO_ISFLOAT(SDL_AUDIO_S16)` returns 0.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns non-zero if format is floating point, zero otherwise.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_ISFLOAT(x) ((x) & SDL_AUDIO_MASK_FLOAT)
+
+/**
+ * Determine if an SDL_AudioFormat represents bigendian data.
+ *
+ * For example, `SDL_AUDIO_ISBIGENDIAN(SDL_AUDIO_S16LE)` returns 0.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns non-zero if format is bigendian, zero otherwise.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_ISBIGENDIAN(x) ((x) & SDL_AUDIO_MASK_BIG_ENDIAN)
+
+/**
+ * Determine if an SDL_AudioFormat represents littleendian data.
+ *
+ * For example, `SDL_AUDIO_ISLITTLEENDIAN(SDL_AUDIO_S16BE)` returns 0.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns non-zero if format is littleendian, zero otherwise.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_ISLITTLEENDIAN(x) (!SDL_AUDIO_ISBIGENDIAN(x))
+
+/**
+ * Determine if an SDL_AudioFormat represents signed data.
+ *
+ * For example, `SDL_AUDIO_ISSIGNED(SDL_AUDIO_U8)` returns 0.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns non-zero if format is signed, zero otherwise.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_ISSIGNED(x) ((x) & SDL_AUDIO_MASK_SIGNED)
+
+/**
+ * Determine if an SDL_AudioFormat represents integer data.
+ *
+ * For example, `SDL_AUDIO_ISINT(SDL_AUDIO_F32)` returns 0.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns non-zero if format is integer, zero otherwise.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_ISINT(x) (!SDL_AUDIO_ISFLOAT(x))
+
+/**
+ * Determine if an SDL_AudioFormat represents unsigned data.
+ *
+ * For example, `SDL_AUDIO_ISUNSIGNED(SDL_AUDIO_S16)` returns 0.
+ *
+ * \param x an SDL_AudioFormat value.
+ * \returns non-zero if format is unsigned, zero otherwise.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_ISUNSIGNED(x) (!SDL_AUDIO_ISSIGNED(x))
+
+
+/**
+ * SDL Audio Device instance IDs.
+ *
+ * Zero is used to signify an invalid/null device.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ */
+typedef Uint32 SDL_AudioDeviceID;
+
+/**
+ * A value used to request a default playback audio device.
+ *
+ * Several functions that require an SDL_AudioDeviceID will accept this value
+ * to signify the app just wants the system to choose a default device instead
+ * of the app providing a specific one.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK ((SDL_AudioDeviceID) 0xFFFFFFFFu)
+
+/**
+ * A value used to request a default recording audio device.
+ *
+ * Several functions that require an SDL_AudioDeviceID will accept this value
+ * to signify the app just wants the system to choose a default device instead
+ * of the app providing a specific one.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_DEVICE_DEFAULT_RECORDING ((SDL_AudioDeviceID) 0xFFFFFFFEu)
+
+/**
+ * Format specifier for audio data.
+ *
+ * \since This struct is available since SDL 3.0.0.
+ *
+ * \sa SDL_AudioFormat
+ */
+typedef struct SDL_AudioSpec
+{
+ SDL_AudioFormat format; /**< Audio data format */
+ int channels; /**< Number of channels: 1 mono, 2 stereo, etc */
+ int freq; /**< sample rate: sample frames per second */
+} SDL_AudioSpec;
+
+/**
+ * Calculate the size of each audio frame (in bytes) from an SDL_AudioSpec.
+ *
+ * This reports on the size of an audio sample frame: stereo Sint16 data (2
+ * channels of 2 bytes each) would be 4 bytes per frame, for example.
+ *
+ * \param x an SDL_AudioSpec to query.
+ * \returns the number of bytes used per sample frame.
+ *
+ * \threadsafety It is safe to call this macro from any thread.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_AUDIO_FRAMESIZE(x) (SDL_AUDIO_BYTESIZE((x).format) * (x).channels)
+
+/**
+ * The opaque handle that represents an audio stream.
+ *
+ * SDL_AudioStream is an audio conversion interface.
+ *
+ * - It can handle resampling data in chunks without generating artifacts,
+ * when it doesn't have the complete buffer available.
+ * - It can handle incoming data in any variable size.
+ * - It can handle input/output format changes on the fly.
+ * - It can remap audio channels between inputs and outputs.
+ * - You push data as you have it, and pull it when you need it
+ * - It can also function as a basic audio data queue even if you just have
+ * sound that needs to pass from one place to another.
+ * - You can hook callbacks up to them when more data is added or requested,
+ * to manage data on-the-fly.
+ *
+ * Audio streams are the core of the SDL3 audio interface. You create one or
+ * more of them, bind them to an opened audio device, and feed data to them
+ * (or for recording, consume data from them).
+ *
+ * \since This struct is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateAudioStream
+ */
+typedef struct SDL_AudioStream SDL_AudioStream;
+
+
+/* Function prototypes */
+
+/**
+ * \name Driver discovery functions
+ *
+ * These functions return the list of built in audio drivers, in the
+ * order that they are normally initialized by default.
+ */
+/* @{ */
+
+/**
+ * Use this function to get the number of built-in audio drivers.
+ *
+ * This function returns a hardcoded number. This never returns a negative
+ * value; if there are no drivers compiled into this build of SDL, this
+ * function returns zero. The presence of a driver in this list does not mean
+ * it will function, it just means SDL is capable of interacting with that
+ * interface. For example, a build of SDL might have esound support, but if
+ * there's no esound server available, SDL's esound driver would fail if used.
+ *
+ * By default, SDL tries all drivers, in its preferred order, until one is
+ * found to be usable.
+ *
+ * \returns the number of built-in audio drivers.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioDriver
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void);
+
+/**
+ * Use this function to get the name of a built in audio driver.
+ *
+ * The list of audio drivers is given in the order that they are normally
+ * initialized by default; the drivers that seem more reasonable to choose
+ * first (as far as the SDL developers believe) are earlier in the list.
+ *
+ * The names of drivers are all simple, low-ASCII identifiers, like "alsa",
+ * "coreaudio" or "wasapi". These never have Unicode characters, and are not
+ * meant to be proper names.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param index the index of the audio driver; the value ranges from 0 to
+ * SDL_GetNumAudioDrivers() - 1.
+ * \returns the name of the audio driver at the requested index, or NULL if an
+ * invalid index was specified.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetNumAudioDrivers
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetAudioDriver(int index);
+/* @} */
+
+/**
+ * Get the name of the current audio driver.
+ *
+ * The names of drivers are all simple, low-ASCII identifiers, like "alsa",
+ * "coreaudio" or "wasapi". These never have Unicode characters, and are not
+ * meant to be proper names.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \returns the name of the current audio driver or NULL if no driver has been
+ * initialized.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetCurrentAudioDriver(void);
+
+/**
+ * Get a list of currently-connected audio playback devices.
+ *
+ * This returns of list of available devices that play sound, perhaps to
+ * speakers or headphones ("playback" devices). If you want devices that
+ * record audio, like a microphone ("recording" devices), use
+ * SDL_GetAudioRecordingDevices() instead.
+ *
+ * This only returns a list of physical devices; it will not have any device
+ * IDs returned by SDL_OpenAudioDevice().
+ *
+ * If this function returns NULL, to signify an error, `*count` will be set to
+ * zero.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param count a pointer filled in with the number of devices returned, may
+ * be NULL.
+ * \returns a 0 terminated array of device instance IDs or NULL on error; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenAudioDevice
+ * \sa SDL_GetAudioRecordingDevices
+ */
+extern SDL_DECLSPEC const SDL_AudioDeviceID * SDLCALL SDL_GetAudioPlaybackDevices(int *count);
+
+/**
+ * Get a list of currently-connected audio recording devices.
+ *
+ * This returns of list of available devices that record audio, like a
+ * microphone ("recording" devices). If you want devices that play sound,
+ * perhaps to speakers or headphones ("playback" devices), use
+ * SDL_GetAudioPlaybackDevices() instead.
+ *
+ * This only returns a list of physical devices; it will not have any device
+ * IDs returned by SDL_OpenAudioDevice().
+ *
+ * If this function returns NULL, to signify an error, `*count` will be set to
+ * zero.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param count a pointer filled in with the number of devices returned, may
+ * be NULL.
+ * \returns a 0 terminated array of device instance IDs, or NULL on failure;
+ * call SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenAudioDevice
+ * \sa SDL_GetAudioPlaybackDevices
+ */
+extern SDL_DECLSPEC const SDL_AudioDeviceID * SDLCALL SDL_GetAudioRecordingDevices(int *count);
+
+/**
+ * Get the human-readable name of a specific audio device.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param devid the instance ID of the device to query.
+ * \returns the name of the audio device, or NULL on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioPlaybackDevices
+ * \sa SDL_GetAudioRecordingDevices
+ * \sa SDL_GetDefaultAudioInfo
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid);
+
+/**
+ * Get the current audio format of a specific audio device.
+ *
+ * For an opened device, this will report the format the device is currently
+ * using. If the device isn't yet opened, this will report the device's
+ * preferred format (or a reasonable default if this can't be determined).
+ *
+ * You may also specify SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK or
+ * SDL_AUDIO_DEVICE_DEFAULT_RECORDING here, which is useful for getting a
+ * reasonable recommendation before opening the system-recommended default
+ * device.
+ *
+ * You can also use this to request the current device buffer size. This is
+ * specified in sample frames and represents the amount of data SDL will feed
+ * to the physical hardware in each chunk. This can be converted to
+ * milliseconds of audio with the following equation:
+ *
+ * `ms = (int) ((((Sint64) frames) * 1000) / spec.freq);`
+ *
+ * Buffer size is only important if you need low-level control over the audio
+ * playback timing. Most apps do not need this.
+ *
+ * \param devid the instance ID of the device to query.
+ * \param spec on return, will be filled with device details.
+ * \param sample_frames pointer to store device buffer size, in sample frames.
+ * Can be NULL.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames);
+
+/**
+ * Get the current channel map of an audio device.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * Audio devices usually have no remapping applied. This is represented by
+ * returning NULL, and does not signify an error.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param devid the instance ID of the device to query.
+ * \param count On output, set to number of channels in the map. Can be NULL.
+ * \returns an array of the current channel mapping, with as many elements as
+ * the current output spec's channels, or NULL if default.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC const int * SDLCALL SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count);
+
+/**
+ * Open a specific audio device.
+ *
+ * You can open both playback and recording devices through this function.
+ * Playback devices will take data from bound audio streams, mix it, and send
+ * it to the hardware. Recording devices will feed any bound audio streams
+ * with a copy of any incoming data.
+ *
+ * An opened audio device starts out with no audio streams bound. To start
+ * audio playing, bind a stream and supply audio data to it. Unlike SDL2,
+ * there is no audio callback; you only bind audio streams and make sure they
+ * have data flowing into them (however, you can simulate SDL2's semantics
+ * fairly closely by using SDL_OpenAudioDeviceStream instead of this
+ * function).
+ *
+ * If you don't care about opening a specific device, pass a `devid` of either
+ * `SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK` or
+ * `SDL_AUDIO_DEVICE_DEFAULT_RECORDING`. In this case, SDL will try to pick
+ * the most reasonable default, and may also switch between physical devices
+ * seamlessly later, if the most reasonable default changes during the
+ * lifetime of this opened device (user changed the default in the OS's system
+ * preferences, the default got unplugged so the system jumped to a new
+ * default, the user plugged in headphones on a mobile device, etc). Unless
+ * you have a good reason to choose a specific device, this is probably what
+ * you want.
+ *
+ * You may request a specific format for the audio device, but there is no
+ * promise the device will honor that request for several reasons. As such,
+ * it's only meant to be a hint as to what data your app will provide. Audio
+ * streams will accept data in whatever format you specify and manage
+ * conversion for you as appropriate. SDL_GetAudioDeviceFormat can tell you
+ * the preferred format for the device before opening and the actual format
+ * the device is using after opening.
+ *
+ * It's legal to open the same device ID more than once; each successful open
+ * will generate a new logical SDL_AudioDeviceID that is managed separately
+ * from others on the same physical device. This allows libraries to open a
+ * device separately from the main app and bind its own streams without
+ * conflicting.
+ *
+ * It is also legal to open a device ID returned by a previous call to this
+ * function; doing so just creates another logical device on the same physical
+ * device. This may be useful for making logical groupings of audio streams.
+ *
+ * This function returns the opened device ID on success. This is a new,
+ * unique SDL_AudioDeviceID that represents a logical device.
+ *
+ * Some backends might offer arbitrary devices (for example, a networked audio
+ * protocol that can connect to an arbitrary server). For these, as a change
+ * from SDL2, you should open a default device ID and use an SDL hint to
+ * specify the target if you care, or otherwise let the backend figure out a
+ * reasonable default. Most backends don't offer anything like this, and often
+ * this would be an end user setting an environment variable for their custom
+ * need, and not something an application should specifically manage.
+ *
+ * When done with an audio device, possibly at the end of the app's life, one
+ * should call SDL_CloseAudioDevice() on the returned device id.
+ *
+ * \param devid the device instance id to open, or
+ * SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK or
+ * SDL_AUDIO_DEVICE_DEFAULT_RECORDING for the most reasonable
+ * default device.
+ * \param spec the requested device configuration. Can be NULL to use
+ * reasonable defaults.
+ * \returns the device ID on success or 0 on failure; call SDL_GetError() for
+ * more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CloseAudioDevice
+ * \sa SDL_GetAudioDeviceFormat
+ */
+extern SDL_DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec);
+
+/**
+ * Use this function to pause audio playback on a specified device.
+ *
+ * This function pauses audio processing for a given device. Any bound audio
+ * streams will not progress, and no audio will be generated. Pausing one
+ * device does not prevent other unpaused devices from running.
+ *
+ * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app
+ * has to bind a stream before any audio will flow. Pausing a paused device is
+ * a legal no-op.
+ *
+ * Pausing a device can be useful to halt all audio without unbinding all the
+ * audio streams. This might be useful while a game is paused, or a level is
+ * loading, etc.
+ *
+ * Physical devices can not be paused or unpaused, only logical devices
+ * created through SDL_OpenAudioDevice() can be.
+ *
+ * \param dev a device opened by SDL_OpenAudioDevice().
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ResumeAudioDevice
+ * \sa SDL_AudioDevicePaused
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev);
+
+/**
+ * Use this function to unpause audio playback on a specified device.
+ *
+ * This function unpauses audio processing for a given device that has
+ * previously been paused with SDL_PauseAudioDevice(). Once unpaused, any
+ * bound audio streams will begin to progress again, and audio can be
+ * generated.
+ *
+ * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app
+ * has to bind a stream before any audio will flow. Unpausing an unpaused
+ * device is a legal no-op.
+ *
+ * Physical devices can not be paused or unpaused, only logical devices
+ * created through SDL_OpenAudioDevice() can be.
+ *
+ * \param dev a device opened by SDL_OpenAudioDevice().
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AudioDevicePaused
+ * \sa SDL_PauseAudioDevice
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_ResumeAudioDevice(SDL_AudioDeviceID dev);
+
+/**
+ * Use this function to query if an audio device is paused.
+ *
+ * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app
+ * has to bind a stream before any audio will flow.
+ *
+ * Physical devices can not be paused or unpaused, only logical devices
+ * created through SDL_OpenAudioDevice() can be. Physical and invalid device
+ * IDs will report themselves as unpaused here.
+ *
+ * \param dev a device opened by SDL_OpenAudioDevice().
+ * \returns SDL_TRUE if device is valid and paused, SDL_FALSE otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_PauseAudioDevice
+ * \sa SDL_ResumeAudioDevice
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_AudioDevicePaused(SDL_AudioDeviceID dev);
+
+/**
+ * Get the gain of an audio device.
+ *
+ * The gain of a device is its volume; a larger gain means a louder output,
+ * with a gain of zero being silence.
+ *
+ * Audio devices default to a gain of 1.0f (no change in output).
+ *
+ * Physical devices may not have their gain changed, only logical devices, and
+ * this function will always return -1.0f when used on physical devices.
+ *
+ * \param devid the audio device to query.
+ * \returns the gain of the device or -1.0f on failure; call SDL_GetError()
+ * for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioDeviceGain
+ */
+extern SDL_DECLSPEC float SDLCALL SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid);
+
+/**
+ * Change the gain of an audio device.
+ *
+ * The gain of a device is its volume; a larger gain means a louder output,
+ * with a gain of zero being silence.
+ *
+ * Audio devices default to a gain of 1.0f (no change in output).
+ *
+ * Physical devices may not have their gain changed, only logical devices, and
+ * this function will always return -1 when used on physical devices. While it
+ * might seem attractive to adjust several logical devices at once in this
+ * way, it would allow an app or library to interfere with another portion of
+ * the program's otherwise-isolated devices.
+ *
+ * This is applied, along with any per-audiostream gain, during playback to
+ * the hardware, and can be continuously changed to create various effects. On
+ * recording devices, this will adjust the gain before passing the data into
+ * an audiostream; that recording audiostream can then adjust its gain further
+ * when outputting the data elsewhere, if it likes, but that second gain is
+ * not applied until the data leaves the audiostream again.
+ *
+ * \param devid the audio device on which to change gain.
+ * \param gain the gain. 1.0f is no change, 0.0f is silence.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioDeviceGain
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain);
+
+/**
+ * Close a previously-opened audio device.
+ *
+ * The application should close open audio devices once they are no longer
+ * needed.
+ *
+ * This function may block briefly while pending audio data is played by the
+ * hardware, so that applications don't drop the last buffer of data they
+ * supplied if terminating immediately afterwards.
+ *
+ * \param devid an audio device id previously returned by
+ * SDL_OpenAudioDevice().
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenAudioDevice
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid);
+
+/**
+ * Bind a list of audio streams to an audio device.
+ *
+ * Audio data will flow through any bound streams. For a playback device, data
+ * for all bound streams will be mixed together and fed to the device. For a
+ * recording device, a copy of recorded data will be provided to each bound
+ * stream.
+ *
+ * Audio streams can only be bound to an open device. This operation is
+ * atomic--all streams bound in the same call will start processing at the
+ * same time, so they can stay in sync. Also: either all streams will be bound
+ * or none of them will be.
+ *
+ * It is an error to bind an already-bound stream; it must be explicitly
+ * unbound first.
+ *
+ * Binding a stream to a device will set its output format for playback
+ * devices, and its input format for recording devices, so they match the
+ * device's settings. The caller is welcome to change the other end of the
+ * stream's format at any time.
+ *
+ * \param devid an audio device to bind a stream to.
+ * \param streams an array of audio streams to unbind.
+ * \param num_streams number streams listed in the `streams` array.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_BindAudioStreams
+ * \sa SDL_UnbindAudioStream
+ * \sa SDL_GetAudioStreamDevice
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams);
+
+/**
+ * Bind a single audio stream to an audio device.
+ *
+ * This is a convenience function, equivalent to calling
+ * `SDL_BindAudioStreams(devid, &stream, 1)`.
+ *
+ * \param devid an audio device to bind a stream to.
+ * \param stream an audio stream to bind to a device.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_BindAudioStreams
+ * \sa SDL_UnbindAudioStream
+ * \sa SDL_GetAudioStreamDevice
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream);
+
+/**
+ * Unbind a list of audio streams from their audio devices.
+ *
+ * The streams being unbound do not all have to be on the same device. All
+ * streams on the same device will be unbound atomically (data will stop
+ * flowing through all unbound streams on the same device at the same time).
+ *
+ * Unbinding a stream that isn't bound to a device is a legal no-op.
+ *
+ * \param streams an array of audio streams to unbind.
+ * \param num_streams number streams listed in the `streams` array.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_BindAudioStreams
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams);
+
+/**
+ * Unbind a single audio stream from its audio device.
+ *
+ * This is a convenience function, equivalent to calling
+ * `SDL_UnbindAudioStreams(&stream, 1)`.
+ *
+ * \param stream an audio stream to unbind from a device.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_BindAudioStream
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream);
+
+/**
+ * Query an audio stream for its currently-bound device.
+ *
+ * This reports the audio device that an audio stream is currently bound to.
+ *
+ * If not bound, or invalid, this returns zero, which is not a valid device
+ * ID.
+ *
+ * \param stream the audio stream to query.
+ * \returns the bound audio device, or 0 if not bound or invalid.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_BindAudioStream
+ * \sa SDL_BindAudioStreams
+ */
+extern SDL_DECLSPEC SDL_AudioDeviceID SDLCALL SDL_GetAudioStreamDevice(SDL_AudioStream *stream);
+
+/**
+ * Create a new audio stream.
+ *
+ * \param src_spec the format details of the input audio.
+ * \param dst_spec the format details of the output audio.
+ * \returns a new audio stream on success or NULL on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_PutAudioStreamData
+ * \sa SDL_GetAudioStreamData
+ * \sa SDL_GetAudioStreamAvailable
+ * \sa SDL_FlushAudioStream
+ * \sa SDL_ClearAudioStream
+ * \sa SDL_SetAudioStreamFormat
+ * \sa SDL_DestroyAudioStream
+ */
+extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec);
+
+/**
+ * Get the properties associated with an audio stream.
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \returns a valid property ID on success or 0 on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetAudioStreamProperties(SDL_AudioStream *stream);
+
+/**
+ * Query the current format of an audio stream.
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \param src_spec where to store the input audio format; ignored if NULL.
+ * \param dst_spec where to store the output audio format; ignored if NULL.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamFormat
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream,
+ SDL_AudioSpec *src_spec,
+ SDL_AudioSpec *dst_spec);
+
+/**
+ * Change the input and output formats of an audio stream.
+ *
+ * Future calls to and SDL_GetAudioStreamAvailable and SDL_GetAudioStreamData
+ * will reflect the new format, and future calls to SDL_PutAudioStreamData
+ * must provide data in the new input formats.
+ *
+ * Data that was previously queued in the stream will still be operated on in
+ * the format that was current when it was added, which is to say you can put
+ * the end of a sound file in one format to a stream, change formats for the
+ * next sound file, and start putting that new data while the previous sound
+ * file is still queued, and everything will still play back correctly.
+ *
+ * \param stream the stream the format is being changed.
+ * \param src_spec the new format of the audio input; if NULL, it is not
+ * changed.
+ * \param dst_spec the new format of the audio output; if NULL, it is not
+ * changed.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioStreamFormat
+ * \sa SDL_SetAudioStreamFrequencyRatio
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamFormat(SDL_AudioStream *stream,
+ const SDL_AudioSpec *src_spec,
+ const SDL_AudioSpec *dst_spec);
+
+/**
+ * Get the frequency ratio of an audio stream.
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \returns the frequency ratio of the stream or 0.0 on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamFrequencyRatio
+ */
+extern SDL_DECLSPEC float SDLCALL SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream);
+
+/**
+ * Change the frequency ratio of an audio stream.
+ *
+ * The frequency ratio is used to adjust the rate at which input data is
+ * consumed. Changing this effectively modifies the speed and pitch of the
+ * audio. A value greater than 1.0 will play the audio faster, and at a higher
+ * pitch. A value less than 1.0 will play the audio slower, and at a lower
+ * pitch.
+ *
+ * This is applied during SDL_GetAudioStreamData, and can be continuously
+ * changed to create various effects.
+ *
+ * \param stream the stream the frequency ratio is being changed.
+ * \param ratio the frequency ratio. 1.0 is normal speed. Must be between 0.01
+ * and 100.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioStreamFrequencyRatio
+ * \sa SDL_SetAudioStreamFormat
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio);
+
+/**
+ * Get the gain of an audio stream.
+ *
+ * The gain of a stream is its volume; a larger gain means a louder output,
+ * with a gain of zero being silence.
+ *
+ * Audio streams default to a gain of 1.0f (no change in output).
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \returns the gain of the stream or -1.0f on failure; call SDL_GetError()
+ * for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamGain
+ */
+extern SDL_DECLSPEC float SDLCALL SDL_GetAudioStreamGain(SDL_AudioStream *stream);
+
+/**
+ * Change the gain of an audio stream.
+ *
+ * The gain of a stream is its volume; a larger gain means a louder output,
+ * with a gain of zero being silence.
+ *
+ * Audio streams default to a gain of 1.0f (no change in output).
+ *
+ * This is applied during SDL_GetAudioStreamData, and can be continuously
+ * changed to create various effects.
+ *
+ * \param stream the stream on which the gain is being changed.
+ * \param gain the gain. 1.0f is no change, 0.0f is silence.
+ * \returns 0 on successor a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioStreamGain
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain);
+
+/**
+ * Get the current input channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * Audio streams default to no remapping applied. This is represented by
+ * returning NULL, and does not signify an error.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \param count On output, set to number of channels in the map. Can be NULL.
+ * \returns an array of the current channel mapping, with as many elements as
+ * the current output spec's channels, or NULL if default.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC const int * SDLCALL SDL_GetAudioStreamInputChannelMap(SDL_AudioStream *stream, int *count);
+
+/**
+ * Get the current output channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * Audio streams default to no remapping applied. This is represented by
+ * returning NULL, and does not signify an error.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \param count On output, set to number of channels in the map. Can be NULL.
+ * \returns an array of the current channel mapping, with as many elements as
+ * the current output spec's channels, or NULL if default.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC const int * SDLCALL SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count);
+
+/**
+ * Set the current input channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * The input channel map reorders data that is added to a stream via
+ * SDL_PutAudioStreamData. Future calls to SDL_PutAudioStreamData must provide
+ * data in the new channel order.
+ *
+ * Each item in the array represents an input channel, and its value is the
+ * channel that it should be remapped to. To reverse a stereo signal's left
+ * and right values, you'd have an array of `{ 1, 0 }`. It is legal to remap
+ * multiple channels to the same thing, so `{ 1, 1 }` would duplicate the
+ * right channel to both channels of a stereo signal. You cannot change the
+ * number of channels through a channel map, just reorder them.
+ *
+ * Data that was previously queued in the stream will still be operated on in
+ * the order that was current when it was added, which is to say you can put
+ * the end of a sound file in one order to a stream, change orders for the
+ * next sound file, and start putting that new data while the previous sound
+ * file is still queued, and everything will still play back correctly.
+ *
+ * Audio streams default to no remapping applied. Passing a NULL channel map
+ * is legal, and turns off remapping.
+ *
+ * SDL will copy the channel map; the caller does not have to save this array
+ * after this call.
+ *
+ * If `count` is not equal to the current number of channels in the audio
+ * stream's format, this will fail. This is a safety measure to make sure a a
+ * race condition hasn't changed the format while you this call is setting the
+ * channel map.
+ *
+ * \param stream the SDL_AudioStream to change.
+ * \param chmap the new channel map, NULL to reset to default.
+ * \param count The number of channels in the map.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running. Don't change the
+ * stream's format to have a different number of channels from a
+ * a different thread at the same time, though!
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int count);
+
+/**
+ * Set the current output channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * The output channel map reorders data that leaving a stream via
+ * SDL_GetAudioStreamData.
+ *
+ * Each item in the array represents an output channel, and its value is the
+ * channel that it should be remapped to. To reverse a stereo signal's left
+ * and right values, you'd have an array of `{ 1, 0 }`. It is legal to remap
+ * multiple channels to the same thing, so `{ 1, 1 }` would duplicate the
+ * right channel to both channels of a stereo signal. You cannot change the
+ * number of channels through a channel map, just reorder them.
+ *
+ * The output channel map can be changed at any time, as output remapping is
+ * applied during SDL_GetAudioStreamData.
+ *
+ * Audio streams default to no remapping applied. Passing a NULL channel map
+ * is legal, and turns off remapping.
+ *
+ * SDL will copy the channel map; the caller does not have to save this array
+ * after this call.
+ *
+ * If `count` is not equal to the current number of channels in the audio
+ * stream's format, this will fail. This is a safety measure to make sure a a
+ * race condition hasn't changed the format while you this call is setting the
+ * channel map.
+ *
+ * \param stream the SDL_AudioStream to change.
+ * \param chmap the new channel map, NULL to reset to default.
+ * \param count The number of channels in the map.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ * a stream-specific mutex while running. Don't change the
+ * stream's format to have a different number of channels from a
+ * a different thread at the same time, though!
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamOutputChannelMap(SDL_AudioStream *stream, const int *chmap, int count);
+
+/**
+ * Add data to the stream.
+ *
+ * This data must match the format/channels/samplerate specified in the latest
+ * call to SDL_SetAudioStreamFormat, or the format specified when creating the
+ * stream if it hasn't been changed.
+ *
+ * Note that this call simply copies the unconverted data for later. This is
+ * different than SDL2, where data was converted during the Put call and the
+ * Get call would just dequeue the previously-converted data.
+ *
+ * \param stream the stream the audio data is being added to.
+ * \param buf a pointer to the audio data to add.
+ * \param len the number of bytes to write to the stream.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, but if the
+ * stream has a callback set, the caller might need to manage
+ * extra locking.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ClearAudioStream
+ * \sa SDL_FlushAudioStream
+ * \sa SDL_GetAudioStreamData
+ * \sa SDL_GetAudioStreamQueued
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len);
+
+/**
+ * Get converted/resampled data from the stream.
+ *
+ * The input/output data format/channels/samplerate is specified when creating
+ * the stream, and can be changed after creation by calling
+ * SDL_SetAudioStreamFormat.
+ *
+ * Note that any conversion and resampling necessary is done during this call,
+ * and SDL_PutAudioStreamData simply queues unconverted data for later. This
+ * is different than SDL2, where that work was done while inputting new data
+ * to the stream and requesting the output just copied the converted data.
+ *
+ * \param stream the stream the audio is being requested from.
+ * \param buf a buffer to fill with audio data.
+ * \param len the maximum number of bytes to fill.
+ * \returns the number of bytes read from the stream or a negative error code
+ * on failure; call SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread, but if the
+ * stream has a callback set, the caller might need to manage
+ * extra locking.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ClearAudioStream
+ * \sa SDL_GetAudioStreamAvailable
+ * \sa SDL_PutAudioStreamData
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetAudioStreamData(SDL_AudioStream *stream, void *buf, int len);
+
+/**
+ * Get the number of converted/resampled bytes available.
+ *
+ * The stream may be buffering data behind the scenes until it has enough to
+ * resample correctly, so this number might be lower than what you expect, or
+ * even be zero. Add more data or flush the stream if you need the data now.
+ *
+ * If the stream has so much data that it would overflow an int, the return
+ * value is clamped to a maximum value, but no queued data is lost; if there
+ * are gigabytes of data queued, the app might need to read some of it with
+ * SDL_GetAudioStreamData before this function's return value is no longer
+ * clamped.
+ *
+ * \param stream the audio stream to query.
+ * \returns the number of converted/resampled bytes available.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioStreamData
+ * \sa SDL_PutAudioStreamData
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetAudioStreamAvailable(SDL_AudioStream *stream);
+
+
+/**
+ * Get the number of bytes currently queued.
+ *
+ * Note that audio streams can change their input format at any time, even if
+ * there is still data queued in a different format, so the returned byte
+ * count will not necessarily match the number of _sample frames_ available.
+ * Users of this API should be aware of format changes they make when feeding
+ * a stream and plan accordingly.
+ *
+ * Queued data is not converted until it is consumed by
+ * SDL_GetAudioStreamData, so this value should be representative of the exact
+ * data that was put into the stream.
+ *
+ * If the stream has so much data that it would overflow an int, the return
+ * value is clamped to a maximum value, but no queued data is lost; if there
+ * are gigabytes of data queued, the app might need to read some of it with
+ * SDL_GetAudioStreamData before this function's return value is no longer
+ * clamped.
+ *
+ * \param stream the audio stream to query.
+ * \returns the number of bytes queued.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_PutAudioStreamData
+ * \sa SDL_ClearAudioStream
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetAudioStreamQueued(SDL_AudioStream *stream);
+
+
+/**
+ * Tell the stream that you're done sending data, and anything being buffered
+ * should be converted/resampled and made available immediately.
+ *
+ * It is legal to add more data to a stream after flushing, but there may be
+ * audio gaps in the output. Generally this is intended to signal the end of
+ * input, so the complete output becomes available.
+ *
+ * \param stream the audio stream to flush.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_PutAudioStreamData
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_FlushAudioStream(SDL_AudioStream *stream);
+
+/**
+ * Clear any pending data in the stream.
+ *
+ * This drops any queued data, so there will be nothing to read from the
+ * stream until more is added.
+ *
+ * \param stream the audio stream to clear.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioStreamAvailable
+ * \sa SDL_GetAudioStreamData
+ * \sa SDL_GetAudioStreamQueued
+ * \sa SDL_PutAudioStreamData
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_ClearAudioStream(SDL_AudioStream *stream);
+
+/**
+ * Use this function to pause audio playback on the audio device associated
+ * with an audio stream.
+ *
+ * This function pauses audio processing for a given device. Any bound audio
+ * streams will not progress, and no audio will be generated. Pausing one
+ * device does not prevent other unpaused devices from running.
+ *
+ * Pausing a device can be useful to halt all audio without unbinding all the
+ * audio streams. This might be useful while a game is paused, or a level is
+ * loading, etc.
+ *
+ * \param stream the audio stream associated with the audio device to pause.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ResumeAudioStreamDevice
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_PauseAudioStreamDevice(SDL_AudioStream *stream);
+
+/**
+ * Use this function to unpause audio playback on the audio device associated
+ * with an audio stream.
+ *
+ * This function unpauses audio processing for a given device that has
+ * previously been paused. Once unpaused, any bound audio streams will begin
+ * to progress again, and audio can be generated.
+ *
+ * \param stream the audio stream associated with the audio device to resume.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_PauseAudioStreamDevice
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream);
+
+/**
+ * Lock an audio stream for serialized access.
+ *
+ * Each SDL_AudioStream has an internal mutex it uses to protect its data
+ * structures from threading conflicts. This function allows an app to lock
+ * that mutex, which could be useful if registering callbacks on this stream.
+ *
+ * One does not need to lock a stream to use in it most cases, as the stream
+ * manages this lock internally. However, this lock is held during callbacks,
+ * which may run from arbitrary threads at any time, so if an app needs to
+ * protect shared data during those callbacks, locking the stream guarantees
+ * that the callback is not running while the lock is held.
+ *
+ * As this is just a wrapper over SDL_LockMutex for an internal lock; it has
+ * all the same attributes (recursive locks are allowed, etc).
+ *
+ * \param stream the audio stream to lock.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_UnlockAudioStream
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_LockAudioStream(SDL_AudioStream *stream);
+
+
+/**
+ * Unlock an audio stream for serialized access.
+ *
+ * This unlocks an audio stream after a call to SDL_LockAudioStream.
+ *
+ * \param stream the audio stream to unlock.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety You should only call this from the same thread that
+ * previously called SDL_LockAudioStream.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_LockAudioStream
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_UnlockAudioStream(SDL_AudioStream *stream);
+
+/**
+ * A callback that fires when data passes through an SDL_AudioStream.
+ *
+ * Apps can (optionally) register a callback with an audio stream that is
+ * called when data is added with SDL_PutAudioStreamData, or requested with
+ * SDL_GetAudioStreamData.
+ *
+ * Two values are offered here: one is the amount of additional data needed to
+ * satisfy the immediate request (which might be zero if the stream already
+ * has enough data queued) and the other is the total amount being requested.
+ * In a Get call triggering a Put callback, these values can be different. In
+ * a Put call triggering a Get callback, these values are always the same.
+ *
+ * Byte counts might be slightly overestimated due to buffering or resampling,
+ * and may change from call to call.
+ *
+ * This callback is not required to do anything. Generally this is useful for
+ * adding/reading data on demand, and the app will often put/get data as
+ * appropriate, but the system goes on with the data currently available to it
+ * if this callback does nothing.
+ *
+ * \param stream the SDL audio stream associated with this callback.
+ * \param additional_amount the amount of data, in bytes, that is needed right
+ * now.
+ * \param total_amount the total amount of data requested, in bytes, that is
+ * requested or available.
+ * \param userdata an opaque pointer provided by the app for their personal
+ * use.
+ *
+ * \threadsafety This callbacks may run from any thread, so if you need to
+ * protect shared data, you should use SDL_LockAudioStream to
+ * serialize access; this lock will be held before your callback
+ * is called, so your callback does not need to manage the lock
+ * explicitly.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamGetCallback
+ * \sa SDL_SetAudioStreamPutCallback
+ */
+typedef void (SDLCALL *SDL_AudioStreamCallback)(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount);
+
+/**
+ * Set a callback that runs when data is requested from an audio stream.
+ *
+ * This callback is called _before_ data is obtained from the stream, giving
+ * the callback the chance to add more on-demand.
+ *
+ * The callback can (optionally) call SDL_PutAudioStreamData() to add more
+ * audio to the stream during this call; if needed, the request that triggered
+ * this callback will obtain the new data immediately.
+ *
+ * The callback's `approx_request` argument is roughly how many bytes of
+ * _unconverted_ data (in the stream's input format) is needed by the caller,
+ * although this may overestimate a little for safety. This takes into account
+ * how much is already in the stream and only asks for any extra necessary to
+ * resolve the request, which means the callback may be asked for zero bytes,
+ * and a different amount on each call.
+ *
+ * The callback is not required to supply exact amounts; it is allowed to
+ * supply too much or too little or none at all. The caller will get what's
+ * available, up to the amount they requested, regardless of this callback's
+ * outcome.
+ *
+ * Clearing or flushing an audio stream does not call this callback.
+ *
+ * This function obtains the stream's lock, which means any existing callback
+ * (get or put) in progress will finish running before setting the new
+ * callback.
+ *
+ * Setting a NULL function turns off the callback.
+ *
+ * \param stream the audio stream to set the new callback on.
+ * \param callback the new callback function to call when data is added to the
+ * stream.
+ * \param userdata an opaque pointer provided to the callback for its own
+ * personal use.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information. This only fails if `stream`
+ * is NULL.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamPutCallback
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata);
+
+/**
+ * Set a callback that runs when data is added to an audio stream.
+ *
+ * This callback is called _after_ the data is added to the stream, giving the
+ * callback the chance to obtain it immediately.
+ *
+ * The callback can (optionally) call SDL_GetAudioStreamData() to obtain audio
+ * from the stream during this call.
+ *
+ * The callback's `approx_request` argument is how many bytes of _converted_
+ * data (in the stream's output format) was provided by the caller, although
+ * this may underestimate a little for safety. This value might be less than
+ * what is currently available in the stream, if data was already there, and
+ * might be less than the caller provided if the stream needs to keep a buffer
+ * to aid in resampling. Which means the callback may be provided with zero
+ * bytes, and a different amount on each call.
+ *
+ * The callback may call SDL_GetAudioStreamAvailable to see the total amount
+ * currently available to read from the stream, instead of the total provided
+ * by the current call.
+ *
+ * The callback is not required to obtain all data. It is allowed to read less
+ * or none at all. Anything not read now simply remains in the stream for
+ * later access.
+ *
+ * Clearing or flushing an audio stream does not call this callback.
+ *
+ * This function obtains the stream's lock, which means any existing callback
+ * (get or put) in progress will finish running before setting the new
+ * callback.
+ *
+ * Setting a NULL function turns off the callback.
+ *
+ * \param stream the audio stream to set the new callback on.
+ * \param callback the new callback function to call when data is added to the
+ * stream.
+ * \param userdata an opaque pointer provided to the callback for its own
+ * personal use.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information. This only fails if `stream`
+ * is NULL.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamGetCallback
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata);
+
+
+/**
+ * Free an audio stream.
+ *
+ * This will release all allocated data, including any audio that is still
+ * queued. You do not need to manually clear the stream first.
+ *
+ * If this stream was bound to an audio device, it is unbound during this
+ * call. If this stream was created with SDL_OpenAudioDeviceStream, the audio
+ * device that was opened alongside this stream's creation will be closed,
+ * too.
+ *
+ * \param stream the audio stream to destroy.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateAudioStream
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream);
+
+
+/**
+ * Convenience function for straightforward audio init for the common case.
+ *
+ * If all your app intends to do is provide a single source of PCM audio, this
+ * function allows you to do all your audio setup in a single call.
+ *
+ * This is also intended to be a clean means to migrate apps from SDL2.
+ *
+ * This function will open an audio device, create a stream and bind it.
+ * Unlike other methods of setup, the audio device will be closed when this
+ * stream is destroyed, so the app can treat the returned SDL_AudioStream as
+ * the only object needed to manage audio playback.
+ *
+ * Also unlike other functions, the audio device begins paused. This is to map
+ * more closely to SDL2-style behavior, since there is no extra step here to
+ * bind a stream to begin audio flowing. The audio device should be resumed
+ * with `SDL_ResumeAudioStreamDevice(stream);`
+ *
+ * This function works with both playback and recording devices.
+ *
+ * The `spec` parameter represents the app's side of the audio stream. That
+ * is, for recording audio, this will be the output format, and for playing
+ * audio, this will be the input format. If spec is NULL, the system will
+ * choose the format, and the app can use SDL_GetAudioStreamFormat() to obtain
+ * this information later.
+ *
+ * If you don't care about opening a specific audio device, you can (and
+ * probably _should_), use SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK for playback and
+ * SDL_AUDIO_DEVICE_DEFAULT_RECORDING for recording.
+ *
+ * One can optionally provide a callback function; if NULL, the app is
+ * expected to queue audio data for playback (or unqueue audio data if
+ * capturing). Otherwise, the callback will begin to fire once the device is
+ * unpaused.
+ *
+ * Destroying the returned stream with SDL_DestroyAudioStream will also close
+ * the audio device associated with this stream.
+ *
+ * \param devid an audio device to open, or SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK
+ * or SDL_AUDIO_DEVICE_DEFAULT_RECORDING.
+ * \param spec the audio stream's data format. Can be NULL.
+ * \param callback a callback where the app will provide new data for
+ * playback, or receive new data for recording. Can be NULL,
+ * in which case the app will need to call
+ * SDL_PutAudioStreamData or SDL_GetAudioStreamData as
+ * necessary.
+ * \param userdata app-controlled pointer passed to callback. Can be NULL.
+ * Ignored if callback is NULL.
+ * \returns an audio stream on success, ready to use, or NULL on failure; call
+ * SDL_GetError() for more information. When done with this stream,
+ * call SDL_DestroyAudioStream to free resources and close the
+ * device.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioStreamDevice
+ * \sa SDL_ResumeAudioStreamDevice
+ */
+extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata);
+
+/**
+ * A callback that fires when data is about to be fed to an audio device.
+ *
+ * This is useful for accessing the final mix, perhaps for writing a
+ * visualizer or applying a final effect to the audio data before playback.
+ *
+ * This callback should run as quickly as possible and not block for any
+ * significant time, as this callback delays submission of data to the audio
+ * device, which can cause audio playback problems.
+ *
+ * The postmix callback _must_ be able to handle any audio data format
+ * specified in `spec`, which can change between callbacks if the audio device
+ * changed. However, this only covers frequency and channel count; data is
+ * always provided here in SDL_AUDIO_F32 format.
+ *
+ * The postmix callback runs _after_ logical device gain and audiostream gain
+ * have been applied, which is to say you can make the output data louder at
+ * this point than the gain settings would suggest.
+ *
+ * \param userdata a pointer provided by the app through
+ * SDL_SetAudioPostmixCallback, for its own use.
+ * \param spec the current format of audio that is to be submitted to the
+ * audio device.
+ * \param buffer the buffer of audio samples to be submitted. The callback can
+ * inspect and/or modify this data.
+ * \param buflen the size of `buffer` in bytes.
+ *
+ * \threadsafety This will run from a background thread owned by SDL. The
+ * application is responsible for locking resources the callback
+ * touches that need to be protected.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioPostmixCallback
+ */
+typedef void (SDLCALL *SDL_AudioPostmixCallback)(void *userdata, const SDL_AudioSpec *spec, float *buffer, int buflen);
+
+/**
+ * Set a callback that fires when data is about to be fed to an audio device.
+ *
+ * This is useful for accessing the final mix, perhaps for writing a
+ * visualizer or applying a final effect to the audio data before playback.
+ *
+ * The buffer is the final mix of all bound audio streams on an opened device;
+ * this callback will fire regularly for any device that is both opened and
+ * unpaused. If there is no new data to mix, either because no streams are
+ * bound to the device or all the streams are empty, this callback will still
+ * fire with the entire buffer set to silence.
+ *
+ * This callback is allowed to make changes to the data; the contents of the
+ * buffer after this call is what is ultimately passed along to the hardware.
+ *
+ * The callback is always provided the data in float format (values from -1.0f
+ * to 1.0f), but the number of channels or sample rate may be different than
+ * the format the app requested when opening the device; SDL might have had to
+ * manage a conversion behind the scenes, or the playback might have jumped to
+ * new physical hardware when a system default changed, etc. These details may
+ * change between calls. Accordingly, the size of the buffer might change
+ * between calls as well.
+ *
+ * This callback can run at any time, and from any thread; if you need to
+ * serialize access to your app's data, you should provide and use a mutex or
+ * other synchronization device.
+ *
+ * All of this to say: there are specific needs this callback can fulfill, but
+ * it is not the simplest interface. Apps should generally provide audio in
+ * their preferred format through an SDL_AudioStream and let SDL handle the
+ * difference.
+ *
+ * This function is extremely time-sensitive; the callback should do the least
+ * amount of work possible and return as quickly as it can. The longer the
+ * callback runs, the higher the risk of audio dropouts or other problems.
+ *
+ * This function will block until the audio device is in between iterations,
+ * so any existing callback that might be running will finish before this
+ * function sets the new callback and returns.
+ *
+ * Setting a NULL callback function disables any previously-set callback.
+ *
+ * \param devid the ID of an opened audio device.
+ * \param callback a callback function to be called. Can be NULL.
+ * \param userdata app-controlled pointer passed to callback. Can be NULL.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata);
+
+
+/**
+ * Load the audio data of a WAVE file into memory.
+ *
+ * Loading a WAVE file requires `src`, `spec`, `audio_buf` and `audio_len` to
+ * be valid pointers. The entire data portion of the file is then loaded into
+ * memory and decoded if necessary.
+ *
+ * Supported formats are RIFF WAVE files with the formats PCM (8, 16, 24, and
+ * 32 bits), IEEE Float (32 bits), Microsoft ADPCM and IMA ADPCM (4 bits), and
+ * A-law and mu-law (8 bits). Other formats are currently unsupported and
+ * cause an error.
+ *
+ * If this function succeeds, the return value is zero and the pointer to the
+ * audio data allocated by the function is written to `audio_buf` and its
+ * length in bytes to `audio_len`. The SDL_AudioSpec members `freq`,
+ * `channels`, and `format` are set to the values of the audio data in the
+ * buffer.
+ *
+ * It's necessary to use SDL_free() to free the audio data returned in
+ * `audio_buf` when it is no longer used.
+ *
+ * Because of the underspecification of the .WAV format, there are many
+ * problematic files in the wild that cause issues with strict decoders. To
+ * provide compatibility with these files, this decoder is lenient in regards
+ * to the truncation of the file, the fact chunk, and the size of the RIFF
+ * chunk. The hints `SDL_HINT_WAVE_RIFF_CHUNK_SIZE`,
+ * `SDL_HINT_WAVE_TRUNCATION`, and `SDL_HINT_WAVE_FACT_CHUNK` can be used to
+ * tune the behavior of the loading process.
+ *
+ * Any file that is invalid (due to truncation, corruption, or wrong values in
+ * the headers), too big, or unsupported causes an error. Additionally, any
+ * critical I/O error from the data source will terminate the loading process
+ * with an error. The function returns NULL on error and in all cases (with
+ * the exception of `src` being NULL), an appropriate error message will be
+ * set.
+ *
+ * It is required that the data source supports seeking.
+ *
+ * Example:
+ *
+ * ```c
+ * SDL_LoadWAV_IO(SDL_IOFromFile("sample.wav", "rb"), 1, &spec, &buf, &len);
+ * ```
+ *
+ * Note that the SDL_LoadWAV function does this same thing for you, but in a
+ * less messy way:
+ *
+ * ```c
+ * SDL_LoadWAV("sample.wav", &spec, &buf, &len);
+ * ```
+ *
+ * \param src the data source for the WAVE data.
+ * \param closeio if SDL_TRUE, calls SDL_CloseIO() on `src` before returning,
+ * even in the case of an error.
+ * \param spec a pointer to an SDL_AudioSpec that will be set to the WAVE
+ * data's format details on successful return.
+ * \param audio_buf a pointer filled with the audio data, allocated by the
+ * function.
+ * \param audio_len a pointer filled with the length of the audio data buffer
+ * in bytes.
+ * \returns 0 on success. `audio_buf` will be filled with a pointer to an
+ * allocated buffer containing the audio data, and `audio_len` is
+ * filled with the length of that audio buffer in bytes.
+ *
+ * This function returns -1 if the .WAV file cannot be opened, uses
+ * an unknown data format, or is corrupt; call SDL_GetError() for
+ * more information.
+ *
+ * When the application is done with the data returned in
+ * `audio_buf`, it should call SDL_free() to dispose of it.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_free
+ * \sa SDL_LoadWAV
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_LoadWAV_IO(SDL_IOStream * src, SDL_bool closeio,
+ SDL_AudioSpec * spec, Uint8 ** audio_buf,
+ Uint32 * audio_len);
+
+/**
+ * Loads a WAV from a file path.
+ *
+ * This is a convenience function that is effectively the same as:
+ *
+ * ```c
+ * SDL_LoadWAV_IO(SDL_IOFromFile(path, "rb"), 1, spec, audio_buf, audio_len);
+ * ```
+ *
+ * \param path the file path of the WAV file to open.
+ * \param spec a pointer to an SDL_AudioSpec that will be set to the WAVE
+ * data's format details on successful return.
+ * \param audio_buf a pointer filled with the audio data, allocated by the
+ * function.
+ * \param audio_len a pointer filled with the length of the audio data buffer
+ * in bytes.
+ * \returns 0 on success. `audio_buf` will be filled with a pointer to an
+ * allocated buffer containing the audio data, and `audio_len` is
+ * filled with the length of that audio buffer in bytes.
+ *
+ * This function returns -1 if the .WAV file cannot be opened, uses
+ * an unknown data format, or is corrupt; call SDL_GetError() for
+ * more information.
+ *
+ * When the application is done with the data returned in
+ * `audio_buf`, it should call SDL_free() to dispose of it.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_free
+ * \sa SDL_LoadWAV_IO
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_LoadWAV(const char *path, SDL_AudioSpec * spec,
+ Uint8 ** audio_buf, Uint32 * audio_len);
+
+/**
+ * Mix audio data in a specified format.
+ *
+ * This takes an audio buffer `src` of `len` bytes of `format` data and mixes
+ * it into `dst`, performing addition, volume adjustment, and overflow
+ * clipping. The buffer pointed to by `dst` must also be `len` bytes of
+ * `format` data.
+ *
+ * This is provided for convenience -- you can mix your own audio data.
+ *
+ * Do not use this function for mixing together more than two streams of
+ * sample data. The output from repeated application of this function may be
+ * distorted by clipping, because there is no accumulator with greater range
+ * than the input (not to mention this being an inefficient way of doing it).
+ *
+ * It is a common misconception that this function is required to write audio
+ * data to an output stream in an audio callback. While you can do that,
+ * SDL_MixAudio() is really only needed when you're mixing a single audio
+ * stream with a volume adjustment.
+ *
+ * \param dst the destination for the mixed audio.
+ * \param src the source audio buffer to be mixed.
+ * \param format the SDL_AudioFormat structure representing the desired audio
+ * format.
+ * \param len the length of the audio buffer in bytes.
+ * \param volume ranges from 0.0 - 1.0, and should be set to 1.0 for full
+ * audio volume.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_MixAudio(Uint8 * dst,
+ const Uint8 * src,
+ SDL_AudioFormat format,
+ Uint32 len, float volume);
+
+/**
+ * Convert some audio data of one format to another format.
+ *
+ * Please note that this function is for convenience, but should not be used
+ * to resample audio in blocks, as it will introduce audio artifacts on the
+ * boundaries. You should only use this function if you are converting audio
+ * data in its entirety in one call. If you want to convert audio in smaller
+ * chunks, use an SDL_AudioStream, which is designed for this situation.
+ *
+ * Internally, this function creates and destroys an SDL_AudioStream on each
+ * use, so it's also less efficient than using one directly, if you need to
+ * convert multiple times.
+ *
+ * \param src_spec the format details of the input audio.
+ * \param src_data the audio data to be converted.
+ * \param src_len the len of src_data.
+ * \param dst_spec the format details of the output audio.
+ * \param dst_data will be filled with a pointer to converted audio data,
+ * which should be freed with SDL_free(). On error, it will be
+ * NULL.
+ * \param dst_len will be filled with the len of dst_data.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec,
+ const Uint8 *src_data,
+ int src_len,
+ const SDL_AudioSpec *dst_spec,
+ Uint8 **dst_data,
+ int *dst_len);
+
+
+/**
+ * Get the appropriate memset value for silencing an audio format.
+ *
+ * The value returned by this function can be used as the second argument to
+ * memset (or SDL_memset) to set an audio buffer in a specific format to
+ * silence.
+ *
+ * \param format the audio data format to query.
+ * \returns a byte value that can be passed to memset.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetSilenceValueForFormat(SDL_AudioFormat format);
+
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_audio_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_begin_code.h b/Source/ThirdParty/SDL/SDL3/SDL_begin_code.h
new file mode 100644
index 000000000..f418c6109
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_begin_code.h
@@ -0,0 +1,227 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/* WIKI CATEGORY: BeginCode */
+
+/**
+ * SDL_begin_code.h sets things up for C dynamic library function definitions,
+ * static inlined functions, and structures aligned at 4-byte alignment.
+ * If you don't like ugly C preprocessor code, don't look at this file. :)
+ */
+
+/* This shouldn't be nested -- included it around code only. */
+#ifdef SDL_begin_code_h
+#error Nested inclusion of SDL_begin_code.h
+#endif
+#define SDL_begin_code_h
+
+#ifndef SDL_DEPRECATED
+# if defined(__GNUC__) && (__GNUC__ >= 4) /* technically, this arrived in gcc 3.1, but oh well. */
+# define SDL_DEPRECATED __attribute__((deprecated))
+# elif defined(_MSC_VER)
+# define SDL_DEPRECATED __declspec(deprecated)
+# else
+# define SDL_DEPRECATED
+# endif
+#endif
+
+#ifndef SDL_UNUSED
+# ifdef __GNUC__
+# define SDL_UNUSED __attribute__((unused))
+# else
+# define SDL_UNUSED
+# endif
+#endif
+
+/* Some compilers use a special export keyword */
+#ifndef SDL_DECLSPEC
+# if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_CYGWIN) || defined(SDL_PLATFORM_GDK)
+# ifdef DLL_EXPORT
+# define SDL_DECLSPEC __declspec(dllexport)
+# else
+# define SDL_DECLSPEC
+# endif
+# else
+# if defined(__GNUC__) && __GNUC__ >= 4
+# define SDL_DECLSPEC __attribute__ ((visibility("default")))
+# else
+# define SDL_DECLSPEC
+# endif
+# endif
+#endif
+
+/* By default SDL uses the C calling convention */
+#ifndef SDLCALL
+#if (defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK)) && !defined(__GNUC__)
+#define SDLCALL __cdecl
+#else
+#define SDLCALL
+#endif
+#endif /* SDLCALL */
+
+/* Force structure packing at 4 byte alignment.
+ This is necessary if the header is included in code which has structure
+ packing set to an alternate value, say for loading structures from disk.
+ The packing is reset to the previous value in SDL_close_code.h
+ */
+#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__)
+#ifdef _MSC_VER
+#pragma warning(disable: 4103)
+#endif
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wpragma-pack"
+#endif
+#ifdef __BORLANDC__
+#pragma nopackwarning
+#endif
+#ifdef _WIN64
+/* Use 8-byte alignment on 64-bit architectures, so pointers are aligned */
+#pragma pack(push,8)
+#else
+#pragma pack(push,4)
+#endif
+#endif /* Compiler needs structure packing set */
+
+#ifndef SDL_INLINE
+#ifdef __GNUC__
+#define SDL_INLINE __inline__
+#elif defined(_MSC_VER) || defined(__BORLANDC__) || \
+ defined(__DMC__) || defined(__SC__) || \
+ defined(__WATCOMC__) || defined(__LCC__) || \
+ defined(__DECC) || defined(__CC_ARM)
+#define SDL_INLINE __inline
+#ifndef __inline__
+#define __inline__ __inline
+#endif
+#else
+#define SDL_INLINE inline
+#ifndef __inline__
+#define __inline__ inline
+#endif
+#endif
+#endif /* SDL_INLINE not defined */
+
+#ifndef SDL_FORCE_INLINE
+#ifdef _MSC_VER
+#define SDL_FORCE_INLINE __forceinline
+#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) )
+#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__
+#else
+#define SDL_FORCE_INLINE static SDL_INLINE
+#endif
+#endif /* SDL_FORCE_INLINE not defined */
+
+#ifndef SDL_NORETURN
+#ifdef __GNUC__
+#define SDL_NORETURN __attribute__((noreturn))
+#elif defined(_MSC_VER)
+#define SDL_NORETURN __declspec(noreturn)
+#else
+#define SDL_NORETURN
+#endif
+#endif /* SDL_NORETURN not defined */
+
+#ifdef __clang__
+#if __has_feature(attribute_analyzer_noreturn)
+#define SDL_ANALYZER_NORETURN __attribute__((analyzer_noreturn))
+#endif
+#endif
+
+#ifndef SDL_ANALYZER_NORETURN
+#define SDL_ANALYZER_NORETURN
+#endif
+
+/* Apparently this is needed by several Windows compilers */
+#ifndef __MACH__
+#ifndef NULL
+#ifdef __cplusplus
+#define NULL 0
+#else
+#define NULL ((void *)0)
+#endif
+#endif /* NULL */
+#endif /* ! macOS - breaks precompiled headers */
+
+#ifndef SDL_FALLTHROUGH
+#if (defined(__cplusplus) && __cplusplus >= 201703L) || \
+ (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L)
+#define SDL_FALLTHROUGH [[fallthrough]]
+#else
+#if defined(__has_attribute) && !defined(__SUNPRO_C) && !defined(__SUNPRO_CC)
+#define SDL_HAS_FALLTHROUGH __has_attribute(__fallthrough__)
+#else
+#define SDL_HAS_FALLTHROUGH 0
+#endif /* __has_attribute */
+#if SDL_HAS_FALLTHROUGH && \
+ ((defined(__GNUC__) && __GNUC__ >= 7) || \
+ (defined(__clang_major__) && __clang_major__ >= 10))
+#define SDL_FALLTHROUGH __attribute__((__fallthrough__))
+#else
+#define SDL_FALLTHROUGH do {} while (0) /* fallthrough */
+#endif /* SDL_HAS_FALLTHROUGH */
+#undef SDL_HAS_FALLTHROUGH
+#endif /* C++17 or C2x */
+#endif /* SDL_FALLTHROUGH not defined */
+
+#ifndef SDL_NODISCARD
+#if (defined(__cplusplus) && __cplusplus >= 201703L) || \
+ (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L)
+#define SDL_NODISCARD [[nodiscard]]
+#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) )
+#define SDL_NODISCARD __attribute__((warn_unused_result))
+#elif defined(_MSC_VER) && (_MSC_VER >= 1700)
+#define SDL_NODISCARD _Check_return_
+#else
+#define SDL_NODISCARD
+#endif /* C++17 or C23 */
+#endif /* SDL_NODISCARD not defined */
+
+#ifndef SDL_MALLOC
+#if defined(__GNUC__) && (__GNUC__ >= 3)
+#define SDL_MALLOC __attribute__((malloc))
+/** FIXME
+#elif defined(_MSC_VER)
+#define SDL_MALLOC __declspec(allocator) __desclspec(restrict)
+**/
+#else
+#define SDL_MALLOC
+#endif
+#endif /* SDL_MALLOC not defined */
+
+#ifndef SDL_ALLOC_SIZE
+#if (defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)))
+#define SDL_ALLOC_SIZE(p) __attribute__((alloc_size(p)))
+#elif defined(_MSC_VER)
+#define SDL_ALLOC_SIZE(p)
+#else
+#define SDL_ALLOC_SIZE(p)
+#endif
+#endif /* SDL_ALLOC_SIZE not defined */
+
+#ifndef SDL_ALLOC_SIZE2
+#if (defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)))
+#define SDL_ALLOC_SIZE2(p1, p2) __attribute__((alloc_size(p1, p2)))
+#elif defined(_MSC_VER)
+#define SDL_ALLOC_SIZE2(p1, p2)
+#else
+#define SDL_ALLOC_SIZE2(p1, p2)
+#endif
+#endif /* SDL_ALLOC_SIZE2 not defined */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_bits.h b/Source/ThirdParty/SDL/SDL3/SDL_bits.h
new file mode 100644
index 000000000..72be08272
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_bits.h
@@ -0,0 +1,152 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryBits
+ *
+ * Functions for fiddling with bits and bitmasks.
+ */
+
+#ifndef SDL_bits_h_
+#define SDL_bits_h_
+
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file SDL_bits.h
+ */
+
+#if defined(__WATCOMC__) && defined(__386__)
+extern __inline int _SDL_bsr_watcom(Uint32);
+#pragma aux _SDL_bsr_watcom = \
+ "bsr eax, eax" \
+ parm [eax] nomemory \
+ value [eax] \
+ modify exact [eax] nomemory;
+#endif
+
+/**
+ * Get the index of the most significant (set) bit in a 32-bit number.
+ *
+ * Result is undefined when called with 0. This operation can also be stated
+ * as "count leading zeroes" and "log base 2".
+ *
+ * Note that this is a forced-inline function in a header, and not a public
+ * API function available in the SDL library (which is to say, the code is
+ * embedded in the calling program and the linker and dynamic loader will not
+ * be able to find this function inside SDL itself).
+ *
+ * \param x the 32-bit value to examine.
+ * \returns the index of the most significant bit, or -1 if the value is 0.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+SDL_FORCE_INLINE int SDL_MostSignificantBitIndex32(Uint32 x)
+{
+#if defined(__GNUC__) && (__GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
+ /* Count Leading Zeroes builtin in GCC.
+ * http://gcc.gnu.org/onlinedocs/gcc-4.3.4/gcc/Other-Builtins.html
+ */
+ if (x == 0) {
+ return -1;
+ }
+ return 31 - __builtin_clz(x);
+#elif defined(__WATCOMC__) && defined(__386__)
+ if (x == 0) {
+ return -1;
+ }
+ return _SDL_bsr_watcom(x);
+#elif defined(_MSC_VER)
+ unsigned long index;
+ if (_BitScanReverse(&index, x)) {
+ return index;
+ }
+ return -1;
+#else
+ /* Based off of Bit Twiddling Hacks by Sean Eron Anderson
+ * , released in the public domain.
+ * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
+ */
+ const Uint32 b[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000};
+ const int S[] = {1, 2, 4, 8, 16};
+
+ int msbIndex = 0;
+ int i;
+
+ if (x == 0) {
+ return -1;
+ }
+
+ for (i = 4; i >= 0; i--)
+ {
+ if (x & b[i])
+ {
+ x >>= S[i];
+ msbIndex |= S[i];
+ }
+ }
+
+ return msbIndex;
+#endif
+}
+
+/**
+ * Determine if a unsigned 32-bit value has exactly one bit set.
+ *
+ * If there are no bits set (`x` is zero), or more than one bit set, this
+ * returns SDL_FALSE. If any one bit is exclusively set, this returns
+ * SDL_TRUE.
+ *
+ * Note that this is a forced-inline function in a header, and not a public
+ * API function available in the SDL library (which is to say, the code is
+ * embedded in the calling program and the linker and dynamic loader will not
+ * be able to find this function inside SDL itself).
+ *
+ * \param x the 32-bit value to examine.
+ * \returns SDL_TRUE if exactly one bit is set in `x`, SDL_FALSE otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+SDL_FORCE_INLINE SDL_bool SDL_HasExactlyOneBitSet32(Uint32 x)
+{
+ if (x && !(x & (x - 1))) {
+ return SDL_TRUE;
+ }
+ return SDL_FALSE;
+}
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_bits_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_blendmode.h b/Source/ThirdParty/SDL/SDL3/SDL_blendmode.h
new file mode 100644
index 000000000..76aa19732
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_blendmode.h
@@ -0,0 +1,200 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryBlendmode
+ *
+ * Blend modes decide how two colors will mix together. There are both
+ * standard modes for basic needs and a means to create custom modes,
+ * dictating what sort of math to do what on what color components.
+ */
+
+#ifndef SDL_blendmode_h_
+#define SDL_blendmode_h_
+
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A set of blend modes used in drawing operations.
+ *
+ * These predefined blend modes are supported everywhere.
+ *
+ * Additional values may be obtained from SDL_ComposeCustomBlendMode.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_ComposeCustomBlendMode
+ */
+typedef Uint32 SDL_BlendMode;
+
+#define SDL_BLENDMODE_NONE 0x00000000u /**< no blending: dstRGBA = srcRGBA */
+#define SDL_BLENDMODE_BLEND 0x00000001u /**< alpha blending: dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA)), dstA = srcA + (dstA * (1-srcA)) */
+#define SDL_BLENDMODE_BLEND_PREMULTIPLIED 0x00000010u /**< pre-multiplied alpha blending: dstRGBA = srcRGBA + (dstRGBA * (1-srcA)) */
+#define SDL_BLENDMODE_ADD 0x00000002u /**< additive blending: dstRGB = (srcRGB * srcA) + dstRGB, dstA = dstA */
+#define SDL_BLENDMODE_ADD_PREMULTIPLIED 0x00000020u /**< pre-multiplied additive blending: dstRGB = srcRGB + dstRGB, dstA = dstA */
+#define SDL_BLENDMODE_MOD 0x00000004u /**< color modulate: dstRGB = srcRGB * dstRGB, dstA = dstA */
+#define SDL_BLENDMODE_MUL 0x00000008u /**< color multiply: dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA)), dstA = dstA */
+#define SDL_BLENDMODE_INVALID 0x7FFFFFFFu
+
+/**
+ * The blend operation used when combining source and destination pixel
+ * components.
+ *
+ * \since This enum is available since SDL 3.0.0.
+ */
+typedef enum SDL_BlendOperation
+{
+ SDL_BLENDOPERATION_ADD = 0x1, /**< dst + src: supported by all renderers */
+ SDL_BLENDOPERATION_SUBTRACT = 0x2, /**< src - dst : supported by D3D, OpenGL, OpenGLES, and Vulkan */
+ SDL_BLENDOPERATION_REV_SUBTRACT = 0x3, /**< dst - src : supported by D3D, OpenGL, OpenGLES, and Vulkan */
+ SDL_BLENDOPERATION_MINIMUM = 0x4, /**< min(dst, src) : supported by D3D, OpenGL, OpenGLES, and Vulkan */
+ SDL_BLENDOPERATION_MAXIMUM = 0x5 /**< max(dst, src) : supported by D3D, OpenGL, OpenGLES, and Vulkan */
+} SDL_BlendOperation;
+
+/**
+ * The normalized factor used to multiply pixel components.
+ *
+ * The blend factors are multiplied with the pixels from a drawing operation
+ * (src) and the pixels from the render target (dst) before the blend
+ * operation. The comma-separated factors listed above are always applied in
+ * the component order red, green, blue, and alpha.
+ *
+ * \since This enum is available since SDL 3.0.0.
+ */
+typedef enum SDL_BlendFactor
+{
+ SDL_BLENDFACTOR_ZERO = 0x1, /**< 0, 0, 0, 0 */
+ SDL_BLENDFACTOR_ONE = 0x2, /**< 1, 1, 1, 1 */
+ SDL_BLENDFACTOR_SRC_COLOR = 0x3, /**< srcR, srcG, srcB, srcA */
+ SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR = 0x4, /**< 1-srcR, 1-srcG, 1-srcB, 1-srcA */
+ SDL_BLENDFACTOR_SRC_ALPHA = 0x5, /**< srcA, srcA, srcA, srcA */
+ SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA = 0x6, /**< 1-srcA, 1-srcA, 1-srcA, 1-srcA */
+ SDL_BLENDFACTOR_DST_COLOR = 0x7, /**< dstR, dstG, dstB, dstA */
+ SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR = 0x8, /**< 1-dstR, 1-dstG, 1-dstB, 1-dstA */
+ SDL_BLENDFACTOR_DST_ALPHA = 0x9, /**< dstA, dstA, dstA, dstA */
+ SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA = 0xA /**< 1-dstA, 1-dstA, 1-dstA, 1-dstA */
+} SDL_BlendFactor;
+
+/**
+ * Compose a custom blend mode for renderers.
+ *
+ * The functions SDL_SetRenderDrawBlendMode and SDL_SetTextureBlendMode accept
+ * the SDL_BlendMode returned by this function if the renderer supports it.
+ *
+ * A blend mode controls how the pixels from a drawing operation (source) get
+ * combined with the pixels from the render target (destination). First, the
+ * components of the source and destination pixels get multiplied with their
+ * blend factors. Then, the blend operation takes the two products and
+ * calculates the result that will get stored in the render target.
+ *
+ * Expressed in pseudocode, it would look like this:
+ *
+ * ```c
+ * dstRGB = colorOperation(srcRGB * srcColorFactor, dstRGB * dstColorFactor);
+ * dstA = alphaOperation(srcA * srcAlphaFactor, dstA * dstAlphaFactor);
+ * ```
+ *
+ * Where the functions `colorOperation(src, dst)` and `alphaOperation(src,
+ * dst)` can return one of the following:
+ *
+ * - `src + dst`
+ * - `src - dst`
+ * - `dst - src`
+ * - `min(src, dst)`
+ * - `max(src, dst)`
+ *
+ * The red, green, and blue components are always multiplied with the first,
+ * second, and third components of the SDL_BlendFactor, respectively. The
+ * fourth component is not used.
+ *
+ * The alpha component is always multiplied with the fourth component of the
+ * SDL_BlendFactor. The other components are not used in the alpha
+ * calculation.
+ *
+ * Support for these blend modes varies for each renderer. To check if a
+ * specific SDL_BlendMode is supported, create a renderer and pass it to
+ * either SDL_SetRenderDrawBlendMode or SDL_SetTextureBlendMode. They will
+ * return with an error if the blend mode is not supported.
+ *
+ * This list describes the support of custom blend modes for each renderer.
+ * All renderers support the four blend modes listed in the SDL_BlendMode
+ * enumeration.
+ *
+ * - **direct3d**: Supports all operations with all factors. However, some
+ * factors produce unexpected results with `SDL_BLENDOPERATION_MINIMUM` and
+ * `SDL_BLENDOPERATION_MAXIMUM`.
+ * - **direct3d11**: Same as Direct3D 9.
+ * - **opengl**: Supports the `SDL_BLENDOPERATION_ADD` operation with all
+ * factors. OpenGL versions 1.1, 1.2, and 1.3 do not work correctly here.
+ * - **opengles2**: Supports the `SDL_BLENDOPERATION_ADD`,
+ * `SDL_BLENDOPERATION_SUBTRACT`, `SDL_BLENDOPERATION_REV_SUBTRACT`
+ * operations with all factors.
+ * - **psp**: No custom blend mode support.
+ * - **software**: No custom blend mode support.
+ *
+ * Some renderers do not provide an alpha component for the default render
+ * target. The `SDL_BLENDFACTOR_DST_ALPHA` and
+ * `SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA` factors do not have an effect in this
+ * case.
+ *
+ * \param srcColorFactor the SDL_BlendFactor applied to the red, green, and
+ * blue components of the source pixels.
+ * \param dstColorFactor the SDL_BlendFactor applied to the red, green, and
+ * blue components of the destination pixels.
+ * \param colorOperation the SDL_BlendOperation used to combine the red,
+ * green, and blue components of the source and
+ * destination pixels.
+ * \param srcAlphaFactor the SDL_BlendFactor applied to the alpha component of
+ * the source pixels.
+ * \param dstAlphaFactor the SDL_BlendFactor applied to the alpha component of
+ * the destination pixels.
+ * \param alphaOperation the SDL_BlendOperation used to combine the alpha
+ * component of the source and destination pixels.
+ * \returns an SDL_BlendMode that represents the chosen factors and
+ * operations.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetRenderDrawBlendMode
+ * \sa SDL_GetRenderDrawBlendMode
+ * \sa SDL_SetTextureBlendMode
+ * \sa SDL_GetTextureBlendMode
+ */
+extern SDL_DECLSPEC SDL_BlendMode SDLCALL SDL_ComposeCustomBlendMode(SDL_BlendFactor srcColorFactor,
+ SDL_BlendFactor dstColorFactor,
+ SDL_BlendOperation colorOperation,
+ SDL_BlendFactor srcAlphaFactor,
+ SDL_BlendFactor dstAlphaFactor,
+ SDL_BlendOperation alphaOperation);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_blendmode_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_camera.h b/Source/ThirdParty/SDL/SDL3/SDL_camera.h
new file mode 100644
index 000000000..590214cec
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_camera.h
@@ -0,0 +1,495 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryCamera
+ *
+ * Video capture for the SDL library.
+ *
+ * This API lets apps read input from video sources, like webcams. Camera
+ * devices can be enumerated, queried, and opened. Once opened, it will
+ * provide SDL_Surface objects as new frames of video come in. These surfaces
+ * can be uploaded to an SDL_Texture or processed as pixels in memory.
+ */
+
+#ifndef SDL_camera_h_
+#define SDL_camera_h_
+
+#include
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This is a unique ID for a camera device for the time it is connected to the
+ * system, and is never reused for the lifetime of the application.
+ *
+ * If the device is disconnected and reconnected, it will get a new ID.
+ *
+ * The ID value starts at 1 and increments from there. The value 0 is an
+ * invalid ID.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameras
+ */
+typedef Uint32 SDL_CameraID;
+
+/**
+ * The opaque structure used to identify an opened SDL camera.
+ *
+ * \since This struct is available since SDL 3.0.0.
+ */
+typedef struct SDL_Camera SDL_Camera;
+
+/**
+ * The details of an output format for a camera device.
+ *
+ * Cameras often support multiple formats; each one will be encapsulated in
+ * this struct.
+ *
+ * \since This struct is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameraSupportedFormats
+ * \sa SDL_GetCameraFormat
+ */
+typedef struct SDL_CameraSpec
+{
+ SDL_PixelFormat format; /**< Frame format */
+ SDL_Colorspace colorspace; /**< Frame colorspace */
+ int width; /**< Frame width */
+ int height; /**< Frame height */
+ int framerate_numerator; /**< Frame rate numerator ((num / denom) == FPS, (denom / num) == duration in seconds) */
+ int framerate_denominator; /**< Frame rate demoninator ((num / denom) == FPS, (denom / num) == duration in seconds) */
+} SDL_CameraSpec;
+
+/**
+ * The position of camera in relation to system device.
+ *
+ * \since This enum is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameraPosition
+ */
+typedef enum SDL_CameraPosition
+{
+ SDL_CAMERA_POSITION_UNKNOWN,
+ SDL_CAMERA_POSITION_FRONT_FACING,
+ SDL_CAMERA_POSITION_BACK_FACING
+} SDL_CameraPosition;
+
+
+/**
+ * Use this function to get the number of built-in camera drivers.
+ *
+ * This function returns a hardcoded number. This never returns a negative
+ * value; if there are no drivers compiled into this build of SDL, this
+ * function returns zero. The presence of a driver in this list does not mean
+ * it will function, it just means SDL is capable of interacting with that
+ * interface. For example, a build of SDL might have v4l2 support, but if
+ * there's no kernel support available, SDL's v4l2 driver would fail if used.
+ *
+ * By default, SDL tries all drivers, in its preferred order, until one is
+ * found to be usable.
+ *
+ * \returns the number of built-in camera drivers.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameraDriver
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetNumCameraDrivers(void);
+
+/**
+ * Use this function to get the name of a built in camera driver.
+ *
+ * The list of camera drivers is given in the order that they are normally
+ * initialized by default; the drivers that seem more reasonable to choose
+ * first (as far as the SDL developers believe) are earlier in the list.
+ *
+ * The names of drivers are all simple, low-ASCII identifiers, like "v4l2",
+ * "coremedia" or "android". These never have Unicode characters, and are not
+ * meant to be proper names.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param index the index of the camera driver; the value ranges from 0 to
+ * SDL_GetNumCameraDrivers() - 1.
+ * \returns the name of the camera driver at the requested index, or NULL if
+ * an invalid index was specified.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetNumCameraDrivers
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetCameraDriver(int index);
+
+/**
+ * Get the name of the current camera driver.
+ *
+ * The names of drivers are all simple, low-ASCII identifiers, like "v4l2",
+ * "coremedia" or "android". These never have Unicode characters, and are not
+ * meant to be proper names.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \returns the name of the current camera driver or NULL if no driver has
+ * been initialized.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetCurrentCameraDriver(void);
+
+/**
+ * Get a list of currently connected camera devices.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param count a pointer filled in with the number of cameras returned, may
+ * be NULL.
+ * \returns a 0 terminated array of camera instance IDs or NULL on failure;
+ * call SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenCamera
+ */
+extern SDL_DECLSPEC const SDL_CameraID * SDLCALL SDL_GetCameras(int *count);
+
+/**
+ * Get the list of native formats/sizes a camera supports.
+ *
+ * This returns a list of all formats and frame sizes that a specific camera
+ * can offer. This is useful if your app can accept a variety of image formats
+ * and sizes and so want to find the optimal spec that doesn't require
+ * conversion.
+ *
+ * This function isn't strictly required; if you call SDL_OpenCamera with a
+ * NULL spec, SDL will choose a native format for you, and if you instead
+ * specify a desired format, it will transparently convert to the requested
+ * format on your behalf.
+ *
+ * If `count` is not NULL, it will be filled with the number of elements in
+ * the returned array.
+ *
+ * Note that it's legal for a camera to supply an empty list. This is what
+ * will happen on Emscripten builds, since that platform won't tell _anything_
+ * about available cameras until you've opened one, and won't even tell if
+ * there _is_ a camera until the user has given you permission to check
+ * through a scary warning popup.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param devid the camera device instance ID to query.
+ * \param count a pointer filled in with the number of elements in the list,
+ * may be NULL.
+ * \returns a NULL terminated array of pointers to SDL_CameraSpec or NULL on
+ * failure; call SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameras
+ * \sa SDL_OpenCamera
+ */
+extern SDL_DECLSPEC const SDL_CameraSpec * const * SDLCALL SDL_GetCameraSupportedFormats(SDL_CameraID devid, int *count);
+
+/**
+ * Get the human-readable device name for a camera.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \param instance_id the camera device instance ID.
+ * \returns a human-readable device name or NULL on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameras
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetCameraName(SDL_CameraID instance_id);
+
+/**
+ * Get the position of the camera in relation to the system.
+ *
+ * Most platforms will report UNKNOWN, but mobile devices, like phones, can
+ * often make a distinction between cameras on the front of the device (that
+ * points towards the user, for taking "selfies") and cameras on the back (for
+ * filming in the direction the user is facing).
+ *
+ * \param instance_id the camera device instance ID.
+ * \returns the position of the camera on the system hardware.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameras
+ */
+extern SDL_DECLSPEC SDL_CameraPosition SDLCALL SDL_GetCameraPosition(SDL_CameraID instance_id);
+
+/**
+ * Open a video recording device (a "camera").
+ *
+ * You can open the device with any reasonable spec, and if the hardware can't
+ * directly support it, it will convert data seamlessly to the requested
+ * format. This might incur overhead, including scaling of image data.
+ *
+ * If you would rather accept whatever format the device offers, you can pass
+ * a NULL spec here and it will choose one for you (and you can use
+ * SDL_Surface's conversion/scaling functions directly if necessary).
+ *
+ * You can call SDL_GetCameraFormat() to get the actual data format if passing
+ * a NULL spec here. You can see the exact specs a device can support without
+ * conversion with SDL_GetCameraSupportedSpecs().
+ *
+ * SDL will not attempt to emulate framerate; it will try to set the hardware
+ * to the rate closest to the requested speed, but it won't attempt to limit
+ * or duplicate frames artificially; call SDL_GetCameraFormat() to see the
+ * actual framerate of the opened the device, and check your timestamps if
+ * this is crucial to your app!
+ *
+ * Note that the camera is not usable until the user approves its use! On some
+ * platforms, the operating system will prompt the user to permit access to
+ * the camera, and they can choose Yes or No at that point. Until they do, the
+ * camera will not be usable. The app should either wait for an
+ * SDL_EVENT_CAMERA_DEVICE_APPROVED (or SDL_EVENT_CAMERA_DEVICE_DENIED) event,
+ * or poll SDL_IsCameraApproved() occasionally until it returns non-zero. On
+ * platforms that don't require explicit user approval (and perhaps in places
+ * where the user previously permitted access), the approval event might come
+ * immediately, but it might come seconds, minutes, or hours later!
+ *
+ * \param instance_id the camera device instance ID.
+ * \param spec the desired format for data the device will provide. Can be
+ * NULL.
+ * \returns an SDL_Camera object or NULL on failure; call SDL_GetError() for
+ * more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetCameras
+ * \sa SDL_GetCameraFormat
+ */
+extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec);
+
+/**
+ * Query if camera access has been approved by the user.
+ *
+ * Cameras will not function between when the device is opened by the app and
+ * when the user permits access to the hardware. On some platforms, this
+ * presents as a popup dialog where the user has to explicitly approve access;
+ * on others the approval might be implicit and not alert the user at all.
+ *
+ * This function can be used to check the status of that approval. It will
+ * return 0 if still waiting for user response, 1 if the camera is approved
+ * for use, and -1 if the user denied access.
+ *
+ * Instead of polling with this function, you can wait for a
+ * SDL_EVENT_CAMERA_DEVICE_APPROVED (or SDL_EVENT_CAMERA_DEVICE_DENIED) event
+ * in the standard SDL event loop, which is guaranteed to be sent once when
+ * permission to use the camera is decided.
+ *
+ * If a camera is declined, there's nothing to be done but call
+ * SDL_CloseCamera() to dispose of it.
+ *
+ * \param camera the opened camera device to query.
+ * \returns -1 if user denied access to the camera, 1 if user approved access,
+ * 0 if no decision has been made yet.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenCamera
+ * \sa SDL_CloseCamera
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera);
+
+/**
+ * Get the instance ID of an opened camera.
+ *
+ * \param camera an SDL_Camera to query.
+ * \returns the instance ID of the specified camera on success or 0 on
+ * failure; call SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenCamera
+ */
+extern SDL_DECLSPEC SDL_CameraID SDLCALL SDL_GetCameraID(SDL_Camera *camera);
+
+/**
+ * Get the properties associated with an opened camera.
+ *
+ * \param camera the SDL_Camera obtained from SDL_OpenCamera().
+ * \returns a valid property ID on success or 0 on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetCameraProperties(SDL_Camera *camera);
+
+/**
+ * Get the spec that a camera is using when generating images.
+ *
+ * Note that this might not be the native format of the hardware, as SDL might
+ * be converting to this format behind the scenes.
+ *
+ * If the system is waiting for the user to approve access to the camera, as
+ * some platforms require, this will return -1, but this isn't necessarily a
+ * fatal error; you should either wait for an SDL_EVENT_CAMERA_DEVICE_APPROVED
+ * (or SDL_EVENT_CAMERA_DEVICE_DENIED) event, or poll SDL_IsCameraApproved()
+ * occasionally until it returns non-zero.
+ *
+ * \param camera opened camera device.
+ * \param spec the SDL_CameraSpec to be initialized by this function.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenCamera
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec);
+
+/**
+ * Acquire a frame.
+ *
+ * The frame is a memory pointer to the image data, whose size and format are
+ * given by the spec requested when opening the device.
+ *
+ * This is a non blocking API. If there is a frame available, a non-NULL
+ * surface is returned, and timestampNS will be filled with a non-zero value.
+ *
+ * Note that an error case can also return NULL, but a NULL by itself is
+ * normal and just signifies that a new frame is not yet available. Note that
+ * even if a camera device fails outright (a USB camera is unplugged while in
+ * use, etc), SDL will send an event separately to notify the app, but
+ * continue to provide blank frames at ongoing intervals until
+ * SDL_CloseCamera() is called, so real failure here is almost always an out
+ * of memory condition.
+ *
+ * After use, the frame should be released with SDL_ReleaseCameraFrame(). If
+ * you don't do this, the system may stop providing more video!
+ *
+ * Do not call SDL_FreeSurface() on the returned surface! It must be given
+ * back to the camera subsystem with SDL_ReleaseCameraFrame!
+ *
+ * If the system is waiting for the user to approve access to the camera, as
+ * some platforms require, this will return NULL (no frames available); you
+ * should either wait for an SDL_EVENT_CAMERA_DEVICE_APPROVED (or
+ * SDL_EVENT_CAMERA_DEVICE_DENIED) event, or poll SDL_IsCameraApproved()
+ * occasionally until it returns non-zero.
+ *
+ * \param camera opened camera device.
+ * \param timestampNS a pointer filled in with the frame's timestamp, or 0 on
+ * error. Can be NULL.
+ * \returns a new frame of video on success, NULL if none is currently
+ * available.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ReleaseCameraFrame
+ */
+extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS);
+
+/**
+ * Release a frame of video acquired from a camera.
+ *
+ * Let the back-end re-use the internal buffer for camera.
+ *
+ * This function _must_ be called only on surface objects returned by
+ * SDL_AcquireCameraFrame(). This function should be called as quickly as
+ * possible after acquisition, as SDL keeps a small FIFO queue of surfaces for
+ * video frames; if surfaces aren't released in a timely manner, SDL may drop
+ * upcoming video frames from the camera.
+ *
+ * If the app needs to keep the surface for a significant time, they should
+ * make a copy of it and release the original.
+ *
+ * The app should not use the surface again after calling this function;
+ * assume the surface is freed and the pointer is invalid.
+ *
+ * \param camera opened camera device.
+ * \param frame the video frame surface to release.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_AcquireCameraFrame
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame);
+
+/**
+ * Use this function to shut down camera processing and close the camera
+ * device.
+ *
+ * \param camera opened camera device.
+ *
+ * \threadsafety It is safe to call this function from any thread, but no
+ * thread may reference `device` once this function is called.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenCameraWithSpec
+ * \sa SDL_OpenCamera
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_CloseCamera(SDL_Camera *camera);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_camera_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_clipboard.h b/Source/ThirdParty/SDL/SDL3/SDL_clipboard.h
new file mode 100644
index 000000000..4318b6132
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_clipboard.h
@@ -0,0 +1,256 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryClipboard
+ *
+ * SDL provides access to the system clipboard, both for reading information
+ * from other processes and publishing information of its own.
+ *
+ * This is not just text! SDL apps can access and publish data by mimetype.
+ */
+
+#ifndef SDL_clipboard_h_
+#define SDL_clipboard_h_
+
+#include
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Function prototypes */
+
+/**
+ * Put UTF-8 text into the clipboard.
+ *
+ * \param text the text to store in the clipboard.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetClipboardText
+ * \sa SDL_HasClipboardText
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetClipboardText(const char *text);
+
+/**
+ * Get UTF-8 text from the clipboard.
+ *
+ * This functions returns empty string if there was not enough memory left for
+ * a copy of the clipboard's content.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \returns the clipboard text on success or an empty string on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasClipboardText
+ * \sa SDL_SetClipboardText
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetClipboardText(void);
+
+/**
+ * Query whether the clipboard exists and contains a non-empty text string.
+ *
+ * \returns SDL_TRUE if the clipboard has text, or SDL_FALSE if it does not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetClipboardText
+ * \sa SDL_SetClipboardText
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasClipboardText(void);
+
+/**
+ * Put UTF-8 text into the primary selection.
+ *
+ * \param text the text to store in the primary selection.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetPrimarySelectionText
+ * \sa SDL_HasPrimarySelectionText
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetPrimarySelectionText(const char *text);
+
+/**
+ * Get UTF-8 text from the primary selection.
+ *
+ * This functions returns empty string if there was not enough memory left for
+ * a copy of the primary selection's content.
+ *
+ * This returns temporary memory which will be automatically freed later, and
+ * can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \returns the primary selection text on success or an empty string on
+ * failure; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasPrimarySelectionText
+ * \sa SDL_SetPrimarySelectionText
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetPrimarySelectionText(void);
+
+/**
+ * Query whether the primary selection exists and contains a non-empty text
+ * string.
+ *
+ * \returns SDL_TRUE if the primary selection has text, or SDL_FALSE if it
+ * does not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetPrimarySelectionText
+ * \sa SDL_SetPrimarySelectionText
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasPrimarySelectionText(void);
+
+/**
+ * Callback function that will be called when data for the specified mime-type
+ * is requested by the OS.
+ *
+ * The callback function is called with NULL as the mime_type when the
+ * clipboard is cleared or new data is set. The clipboard is automatically
+ * cleared in SDL_Quit().
+ *
+ * \param userdata a pointer to provided user data.
+ * \param mime_type the requested mime-type.
+ * \param size a pointer filled in with the length of the returned data.
+ * \returns a pointer to the data for the provided mime-type. Returning NULL
+ * or setting length to 0 will cause no data to be sent to the
+ * "receiver". It is up to the receiver to handle this. Essentially
+ * returning no data is more or less undefined behavior and may cause
+ * breakage in receiving applications. The returned data will not be
+ * freed so it needs to be retained and dealt with internally.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetClipboardData
+ */
+typedef const void *(SDLCALL *SDL_ClipboardDataCallback)(void *userdata, const char *mime_type, size_t *size);
+
+/**
+ * Callback function that will be called when the clipboard is cleared, or new
+ * data is set.
+ *
+ * \param userdata a pointer to provided user data.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetClipboardData
+ */
+typedef void (SDLCALL *SDL_ClipboardCleanupCallback)(void *userdata);
+
+/**
+ * Offer clipboard data to the OS.
+ *
+ * Tell the operating system that the application is offering clipboard data
+ * for each of the proivded mime-types. Once another application requests the
+ * data the callback function will be called allowing it to generate and
+ * respond with the data for the requested mime-type.
+ *
+ * The size of text data does not include any terminator, and the text does
+ * not need to be null terminated (e.g. you can directly copy a portion of a
+ * document)
+ *
+ * \param callback a function pointer to the function that provides the
+ * clipboard data.
+ * \param cleanup a function pointer to the function that cleans up the
+ * clipboard data.
+ * \param userdata an opaque pointer that will be forwarded to the callbacks.
+ * \param mime_types a list of mime-types that are being offered.
+ * \param num_mime_types the number of mime-types in the mime_types list.
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ClearClipboardData
+ * \sa SDL_GetClipboardData
+ * \sa SDL_HasClipboardData
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanupCallback cleanup, void *userdata, const char **mime_types, size_t num_mime_types);
+
+/**
+ * Clear the clipboard data.
+ *
+ * \returns 0 on success or a negative error code on failure; call
+ * SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetClipboardData
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_ClearClipboardData(void);
+
+/**
+ * Get the data from clipboard for a given mime type.
+ *
+ * The size of text data does not include the terminator, but the text is
+ * guaranteed to be null terminated.
+ *
+ * \param mime_type the mime type to read from the clipboard.
+ * \param size a pointer filled in with the length of the returned data.
+ * \returns the retrieved data buffer or NULL on failure; call SDL_GetError()
+ * for more information.
+ *
+ * This returns temporary memory which will be automatically freed
+ * later, and can be claimed with SDL_ClaimTemporaryMemory().
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasClipboardData
+ * \sa SDL_SetClipboardData
+ */
+extern SDL_DECLSPEC const void * SDLCALL SDL_GetClipboardData(const char *mime_type, size_t *size);
+
+/**
+ * Query whether there is data in the clipboard for the provided mime type.
+ *
+ * \param mime_type the mime type to check for data for.
+ * \returns SDL_TRUE if there exists data in clipboard for the provided mime
+ * type, SDL_FALSE if it does not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetClipboardData
+ * \sa SDL_GetClipboardData
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasClipboardData(const char *mime_type);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_clipboard_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_close_code.h b/Source/ThirdParty/SDL/SDL3/SDL_close_code.h
new file mode 100644
index 000000000..fa4d3d17b
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_close_code.h
@@ -0,0 +1,38 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/*
+ * This file reverses the effects of SDL_begin_code.h and should be included
+ * after you finish any function and structure declarations in your headers
+ */
+
+#ifndef SDL_begin_code_h
+#error SDL_close_code.h included without matching SDL_begin_code.h
+#endif
+#undef SDL_begin_code_h
+
+/* Reset structure packing at previous byte alignment */
+#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__)
+#ifdef __BORLANDC__
+#pragma nopackwarning
+#endif
+#pragma pack(pop)
+#endif /* Compiler needs structure packing set */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_copying.h b/Source/ThirdParty/SDL/SDL3/SDL_copying.h
new file mode 100644
index 000000000..dcbdcea82
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_copying.h
@@ -0,0 +1,22 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/* Header file containing SDL's license. */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_cpuinfo.h b/Source/ThirdParty/SDL/SDL3/SDL_cpuinfo.h
new file mode 100644
index 000000000..e7ced26d8
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_cpuinfo.h
@@ -0,0 +1,313 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/* WIKI CATEGORY: CPUInfo */
+
+/**
+ * # CategoryCPUInfo
+ *
+ * CPU feature detection for SDL.
+ *
+ * These functions are largely concerned with reporting if the system has
+ * access to various SIMD instruction sets, but also has other important info
+ * to share, such as system RAM size and number of logical CPU cores.
+ */
+
+#ifndef SDL_cpuinfo_h_
+#define SDL_cpuinfo_h_
+
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A guess for the cacheline size used for padding.
+ *
+ * Most x86 processors have a 64 byte cache line. The 64-bit PowerPC
+ * processors have a 128 byte cache line. We use the larger value to be
+ * generally safe.
+ *
+ * \since This macro is available since SDL 3.0.0.
+ */
+#define SDL_CACHELINE_SIZE 128
+
+/**
+ * Get the number of CPU cores available.
+ *
+ * \returns the total number of logical CPU cores. On CPUs that include
+ * technologies such as hyperthreading, the number of logical cores
+ * may be more than the number of physical cores.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetCPUCount(void);
+
+/**
+ * Determine the L1 cache line size of the CPU.
+ *
+ * This is useful for determining multi-threaded structure padding or SIMD
+ * prefetch sizes.
+ *
+ * \returns the L1 cache line size of the CPU, in bytes.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetCPUCacheLineSize(void);
+
+/**
+ * Determine whether the CPU has AltiVec features.
+ *
+ * This always returns false on CPUs that aren't using PowerPC instruction
+ * sets.
+ *
+ * \returns SDL_TRUE if the CPU has AltiVec features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasAltiVec(void);
+
+/**
+ * Determine whether the CPU has MMX features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has MMX features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasMMX(void);
+
+/**
+ * Determine whether the CPU has SSE features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has SSE features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasSSE2
+ * \sa SDL_HasSSE3
+ * \sa SDL_HasSSE41
+ * \sa SDL_HasSSE42
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasSSE(void);
+
+/**
+ * Determine whether the CPU has SSE2 features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has SSE2 features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasSSE
+ * \sa SDL_HasSSE3
+ * \sa SDL_HasSSE41
+ * \sa SDL_HasSSE42
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasSSE2(void);
+
+/**
+ * Determine whether the CPU has SSE3 features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has SSE3 features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasSSE
+ * \sa SDL_HasSSE2
+ * \sa SDL_HasSSE41
+ * \sa SDL_HasSSE42
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasSSE3(void);
+
+/**
+ * Determine whether the CPU has SSE4.1 features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has SSE4.1 features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasSSE
+ * \sa SDL_HasSSE2
+ * \sa SDL_HasSSE3
+ * \sa SDL_HasSSE42
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasSSE41(void);
+
+/**
+ * Determine whether the CPU has SSE4.2 features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has SSE4.2 features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasSSE
+ * \sa SDL_HasSSE2
+ * \sa SDL_HasSSE3
+ * \sa SDL_HasSSE41
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasSSE42(void);
+
+/**
+ * Determine whether the CPU has AVX features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has AVX features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasAVX2
+ * \sa SDL_HasAVX512F
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void);
+
+/**
+ * Determine whether the CPU has AVX2 features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has AVX2 features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasAVX
+ * \sa SDL_HasAVX512F
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasAVX2(void);
+
+/**
+ * Determine whether the CPU has AVX-512F (foundation) features.
+ *
+ * This always returns false on CPUs that aren't using Intel instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has AVX-512F features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasAVX
+ * \sa SDL_HasAVX2
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasAVX512F(void);
+
+/**
+ * Determine whether the CPU has ARM SIMD (ARMv6) features.
+ *
+ * This is different from ARM NEON, which is a different instruction set.
+ *
+ * This always returns false on CPUs that aren't using ARM instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has ARM SIMD features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasNEON
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasARMSIMD(void);
+
+/**
+ * Determine whether the CPU has NEON (ARM SIMD) features.
+ *
+ * This always returns false on CPUs that aren't using ARM instruction sets.
+ *
+ * \returns SDL_TRUE if the CPU has ARM NEON features or SDL_FALSE if not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
+
+/**
+ * Determine whether the CPU has LSX (LOONGARCH SIMD) features.
+ *
+ * This always returns false on CPUs that aren't using LOONGARCH instruction
+ * sets.
+ *
+ * \returns SDL_TRUE if the CPU has LOONGARCH LSX features or SDL_FALSE if
+ * not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasLSX(void);
+
+/**
+ * Determine whether the CPU has LASX (LOONGARCH SIMD) features.
+ *
+ * This always returns false on CPUs that aren't using LOONGARCH instruction
+ * sets.
+ *
+ * \returns SDL_TRUE if the CPU has LOONGARCH LASX features or SDL_FALSE if
+ * not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_HasLASX(void);
+
+/**
+ * Get the amount of RAM configured in the system.
+ *
+ * \returns the amount of RAM configured in the system in MiB.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
+
+/**
+ * Report the alignment this system needs for SIMD allocations.
+ *
+ * This will return the minimum number of bytes to which a pointer must be
+ * aligned to be compatible with SIMD instructions on the current machine. For
+ * example, if the machine supports SSE only, it will return 16, but if it
+ * supports AVX-512F, it'll return 64 (etc). This only reports values for
+ * instruction sets SDL knows about, so if your SDL build doesn't have
+ * SDL_HasAVX512F(), then it might return 16 for the SSE support it sees and
+ * not 64 for the AVX-512 instructions that exist but SDL doesn't know about.
+ * Plan accordingly.
+ *
+ * \returns the alignment in bytes needed for available, known SIMD
+ * instructions.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_aligned_alloc
+ * \sa SDL_aligned_free
+ */
+extern SDL_DECLSPEC size_t SDLCALL SDL_GetSIMDAlignment(void);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_cpuinfo_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_dialog.h b/Source/ThirdParty/SDL/SDL3/SDL_dialog.h
new file mode 100644
index 000000000..8a198a844
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_dialog.h
@@ -0,0 +1,264 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryDialog
+ *
+ * File dialog support.
+ */
+
+#ifndef SDL_dialog_h_
+#define SDL_dialog_h_
+
+#include
+#include
+
+#include
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * An entry for filters for file dialogs.
+ *
+ * `name` is a user-readable label for the filter (for example, "Office
+ * document").
+ *
+ * `pattern` is a semicolon-separated list of file extensions (for example,
+ * "doc;docx"). File extensions may only contain alphanumeric characters,
+ * hyphens, underscores and periods. Alternatively, the whole string can be a
+ * single asterisk ("*"), which serves as an "All files" filter.
+ *
+ * \since This struct is available since SDL 3.0.0.
+ *
+ * \sa SDL_DialogFileCallback
+ * \sa SDL_ShowOpenFileDialog
+ * \sa SDL_ShowSaveFileDialog
+ * \sa SDL_ShowOpenFolderDialog
+ */
+typedef struct SDL_DialogFileFilter
+{
+ const char *name;
+ const char *pattern;
+} SDL_DialogFileFilter;
+
+/**
+ * Callback used by file dialog functions.
+ *
+ * The specific usage is described in each function.
+ *
+ * If `filelist` is:
+ *
+ * - NULL, an error occurred. Details can be obtained with SDL_GetError().
+ * - A pointer to NULL, the user either didn't choose any file or canceled the
+ * dialog.
+ * - A pointer to non-`NULL`, the user chose one or more files. The argument
+ * is a null-terminated list of pointers to C strings, each containing a
+ * path.
+ *
+ * The filelist argument does not need to be freed; it will automatically be
+ * freed when the callback returns.
+ *
+ * The filter argument is the index of the filter that was selected, or -1 if
+ * no filter was selected or if the platform or method doesn't support
+ * fetching the selected filter.
+ *
+ * \param userdata an app-provided pointer, for the callback's use.
+ * \param filelist the file(s) chosen by the user.
+ * \param filter index of the selected filter.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_DialogFileFilter
+ * \sa SDL_ShowOpenFileDialog
+ * \sa SDL_ShowSaveFileDialog
+ * \sa SDL_ShowOpenFolderDialog
+ */
+typedef void (SDLCALL *SDL_DialogFileCallback)(void *userdata, const char * const *filelist, int filter);
+
+/**
+ * Displays a dialog that lets the user select a file on their filesystem.
+ *
+ * This function should only be invoked from the main thread.
+ *
+ * This is an asynchronous function; it will return immediately, and the
+ * result will be passed to the callback.
+ *
+ * The callback will be invoked with a null-terminated list of files the user
+ * chose. The list will be empty if the user canceled the dialog, and it will
+ * be NULL if an error occurred.
+ *
+ * Note that the callback may be called from a different thread than the one
+ * the function was invoked on.
+ *
+ * Depending on the platform, the user may be allowed to input paths that
+ * don't yet exist.
+ *
+ * On Linux, dialogs may require XDG Portals, which requires DBus, which
+ * requires an event-handling loop. Apps that do not use SDL to handle events
+ * should add a call to SDL_PumpEvents in their main loop.
+ *
+ * \param callback an SDL_DialogFileCallback to be invoked when the user
+ * selects a file and accepts, or cancels the dialog, or an
+ * error occurs. The first argument is a null-terminated list
+ * of C strings, representing the paths chosen by the user.
+ * The list will be empty if the user canceled the dialog, and
+ * it will be NULL if an error occurred. If an error occurred,
+ * it can be fetched with SDL_GetError(). The second argument
+ * is the userdata pointer passed to the function. The third
+ * argument is the index of the filter selected by the user,
+ * or one past the index of the last filter (therefore the
+ * index of the terminating NULL filter) if no filter was
+ * chosen, or -1 if the platform does not support detecting
+ * the selected filter.
+ * \param userdata an optional pointer to pass extra data to the callback when
+ * it will be invoked.
+ * \param window the window that the dialog should be modal for. May be NULL.
+ * Not all platforms support this option.
+ * \param filters a list of SDL_DialogFileFilter's. May be NULL. Not all
+ * platforms support this option, and platforms that do support
+ * it may allow the user to ignore the filters.
+ * \param nfilters the number of filters. Ignored if filters is NULL.
+ * \param default_location the default folder or file to start the dialog at.
+ * May be NULL. Not all platforms support this option.
+ * \param allow_many if non-zero, the user will be allowed to select multiple
+ * entries. Not all platforms support this option.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_DialogFileCallback
+ * \sa SDL_DialogFileFilter
+ * \sa SDL_ShowSaveFileDialog
+ * \sa SDL_ShowOpenFolderDialog
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, SDL_bool allow_many);
+
+/**
+ * Displays a dialog that lets the user choose a new or existing file on their
+ * filesystem.
+ *
+ * This function should only be invoked from the main thread.
+ *
+ * This is an asynchronous function; it will return immediately, and the
+ * result will be passed to the callback.
+ *
+ * The callback will be invoked with a null-terminated list of files the user
+ * chose. The list will be empty if the user canceled the dialog, and it will
+ * be NULL if an error occurred.
+ *
+ * Note that the callback may be called from a different thread than the one
+ * the function was invoked on.
+ *
+ * The chosen file may or may not already exist.
+ *
+ * On Linux, dialogs may require XDG Portals, which requires DBus, which
+ * requires an event-handling loop. Apps that do not use SDL to handle events
+ * should add a call to SDL_PumpEvents in their main loop.
+ *
+ * \param callback an SDL_DialogFileCallback to be invoked when the user
+ * selects a file and accepts, or cancels the dialog, or an
+ * error occurs. The first argument is a null-terminated list
+ * of C strings, representing the paths chosen by the user.
+ * The list will be empty if the user canceled the dialog, and
+ * it will be NULL if an error occurred. If an error occurred,
+ * it can be fetched with SDL_GetError(). The second argument
+ * is the userdata pointer passed to the function. The third
+ * argument is the index of the filter selected by the user,
+ * or one past the index of the last filter (therefore the
+ * index of the terminating NULL filter) if no filter was
+ * chosen, or -1 if the platform does not support detecting
+ * the selected filter.
+ * \param userdata an optional pointer to pass extra data to the callback when
+ * it will be invoked.
+ * \param window the window that the dialog should be modal for. May be NULL.
+ * Not all platforms support this option.
+ * \param filters a list of SDL_DialogFileFilter's. May be NULL. Not all
+ * platforms support this option, and platforms that do support
+ * it may allow the user to ignore the filters.
+ * \param nfilters the number of filters. Ignored if filters is NULL.
+ * \param default_location the default folder or file to start the dialog at.
+ * May be NULL. Not all platforms support this option.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_DialogFileCallback
+ * \sa SDL_DialogFileFilter
+ * \sa SDL_ShowOpenFileDialog
+ * \sa SDL_ShowOpenFolderDialog
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location);
+
+/**
+ * Displays a dialog that lets the user select a folder on their filesystem.
+ *
+ * This function should only be invoked from the main thread.
+ *
+ * This is an asynchronous function; it will return immediately, and the
+ * result will be passed to the callback.
+ *
+ * The callback will be invoked with a null-terminated list of files the user
+ * chose. The list will be empty if the user canceled the dialog, and it will
+ * be NULL if an error occurred.
+ *
+ * Note that the callback may be called from a different thread than the one
+ * the function was invoked on.
+ *
+ * Depending on the platform, the user may be allowed to input paths that
+ * don't yet exist.
+ *
+ * On Linux, dialogs may require XDG Portals, which requires DBus, which
+ * requires an event-handling loop. Apps that do not use SDL to handle events
+ * should add a call to SDL_PumpEvents in their main loop.
+ *
+ * \param callback an SDL_DialogFileCallback to be invoked when the user
+ * selects a file and accepts, or cancels the dialog, or an
+ * error occurs. The first argument is a null-terminated list
+ * of C strings, representing the paths chosen by the user.
+ * The list will be empty if the user canceled the dialog, and
+ * it will be NULL if an error occurred. If an error occurred,
+ * it can be fetched with SDL_GetError(). The second argument
+ * is the userdata pointer passed to the function. The third
+ * argument is always -1 for SDL_ShowOpenFolderDialog.
+ * \param userdata an optional pointer to pass extra data to the callback when
+ * it will be invoked.
+ * \param window the window that the dialog should be modal for. May be NULL.
+ * Not all platforms support this option.
+ * \param default_location the default folder or file to start the dialog at.
+ * May be NULL. Not all platforms support this option.
+ * \param allow_many if non-zero, the user will be allowed to select multiple
+ * entries. Not all platforms support this option.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_DialogFileCallback
+ * \sa SDL_ShowOpenFileDialog
+ * \sa SDL_ShowSaveFileDialog
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, SDL_bool allow_many);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include
+
+#endif /* SDL_joystick_h_ */
diff --git a/Source/ThirdParty/SDL/SDL3/SDL_egl.h b/Source/ThirdParty/SDL/SDL3/SDL_egl.h
new file mode 100644
index 000000000..cc557d60e
--- /dev/null
+++ b/Source/ThirdParty/SDL/SDL3/SDL_egl.h
@@ -0,0 +1,2355 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/*
+ * This is a simple file to encapsulate the EGL API headers.
+ */
+
+#include
+
+#if !defined(_MSC_VER) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_USE_BUILTIN_OPENGL_DEFINITIONS)
+
+#if defined(SDL_PLATFORM_VITA)
+#include
+#include
+#include
+#endif
+
+#include