[Scummvm-git-logs] scummvm master -> 8afb2c1f62f518fa5872eefbd5a951b29e6ceb32

sev- noreply at scummvm.org
Tue Feb 18 21:14:00 UTC 2025


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
8afb2c1f62 BACKENDS: Add SDL3 backend + update imgui


Commit: 8afb2c1f62f518fa5872eefbd5a951b29e6ceb32
    https://github.com/scummvm/scummvm/commit/8afb2c1f62f518fa5872eefbd5a951b29e6ceb32
Author: scemino (scemino74 at gmail.com)
Date: 2025-02-18T22:13:56+01:00

Commit Message:
BACKENDS: Add SDL3 backend + update imgui

Changed paths:
  A backends/events/sdl/sdl-common-events.cpp
  A backends/events/sdl/sdl1-events.cpp
  A backends/events/sdl/sdl2-events.cpp
  A backends/events/sdl/sdl3-events.cpp
  A backends/imgui/backends/imgui_impl_sdl3.cpp
  A backends/imgui/backends/imgui_impl_sdl3.h
  A backends/imgui/backends/imgui_impl_sdlrenderer3.cpp
  A backends/imgui/backends/imgui_impl_sdlrenderer3.h
  R backends/events/sdl/sdl-events.cpp
    backends/events/sdl/sdl-events.h
    backends/graphics/openglsdl/openglsdl-graphics.cpp
    backends/graphics/sdl/sdl-graphics.cpp
    backends/graphics/sdl/sdl-graphics.h
    backends/graphics/surfacesdl/surfacesdl-graphics.cpp
    backends/graphics/surfacesdl/surfacesdl-graphics.h
    backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
    backends/imgui/scummvm.patch
    backends/mixer/sdl/sdl-mixer.cpp
    backends/mixer/sdl/sdl-mixer.h
    backends/module.mk
    backends/mutex/sdl/sdl-mutex.cpp
    backends/platform/sdl/amigaos/amigaos.cpp
    backends/platform/sdl/macosx/macosx-window.mm
    backends/platform/sdl/sdl-sys.h
    backends/platform/sdl/sdl-window.cpp
    backends/platform/sdl/sdl-window.h
    backends/platform/sdl/sdl.cpp
    backends/timer/sdl/sdl-timer.cpp
    base/commandLine.cpp
    configure


diff --git a/backends/events/sdl/sdl-common-events.cpp b/backends/events/sdl/sdl-common-events.cpp
new file mode 100644
index 00000000000..2821bf1ec8d
--- /dev/null
+++ b/backends/events/sdl/sdl-common-events.cpp
@@ -0,0 +1,254 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#if defined(SDL_BACKEND)
+
+#include "backends/events/sdl/sdl-events.h"
+#include "backends/platform/sdl/sdl.h"
+#include "backends/graphics/graphics.h"
+#include "common/config-manager.h"
+#include "common/textconsole.h"
+#include "common/fs.h"
+#include "engines/engine.h"
+#include "gui/gui-manager.h"
+
+SdlEventSource::~SdlEventSource() {
+	closeJoystick();
+}
+
+bool SdlEventSource::processMouseEvent(Common::Event &event, int x, int y, int relx, int rely) {
+	_mouseX = x;
+	_mouseY = y;
+
+	event.mouse.x = x;
+	event.mouse.y = y;
+	event.relMouse.x = relx;
+	event.relMouse.y = rely;
+
+	if (_graphicsManager) {
+		return _graphicsManager->notifyMousePosition(event.mouse);
+	}
+
+	return true;
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+Common::Point SdlEventSource::getTouchscreenSize() {
+	int windowWidth, windowHeight;
+	SDL_GetWindowSize((dynamic_cast<SdlGraphicsManager*>(_graphicsManager))->getWindow()->getSDLWindow(), &windowWidth, &windowHeight);
+	return Common::Point(windowWidth, windowHeight);
+}
+
+bool SdlEventSource::isTouchPortTouchpadMode(SDL_TouchID port) {
+       return g_system->getFeatureState(OSystem::kFeatureTouchpadMode);
+}
+
+bool SdlEventSource::isTouchPortActive(SDL_TouchID port) {
+	return true;
+}
+
+void SdlEventSource::convertTouchXYToGameXY(float touchX, float touchY, int *gameX, int *gameY) {
+	int windowWidth, windowHeight;
+	SDL_GetWindowSize((dynamic_cast<SdlGraphicsManager*>(_graphicsManager))->getWindow()->getSDLWindow(), &windowWidth, &windowHeight);
+
+	*gameX = windowWidth * touchX;
+	*gameY = windowHeight * touchY;
+}
+#endif
+
+bool SdlEventSource::handleMouseMotion(SDL_Event &ev, Common::Event &event) {
+	event.type = Common::EVENT_MOUSEMOVE;
+
+	return processMouseEvent(event, ev.motion.x, ev.motion.y, ev.motion.xrel, ev.motion.yrel);
+}
+
+bool SdlEventSource::handleMouseButtonDown(SDL_Event &ev, Common::Event &event) {
+	if (ev.button.button == SDL_BUTTON_LEFT)
+		event.type = Common::EVENT_LBUTTONDOWN;
+	else if (ev.button.button == SDL_BUTTON_RIGHT)
+		event.type = Common::EVENT_RBUTTONDOWN;
+#if defined(SDL_BUTTON_WHEELUP) && defined(SDL_BUTTON_WHEELDOWN)
+	else if (ev.button.button == SDL_BUTTON_WHEELUP)
+		event.type = Common::EVENT_WHEELUP;
+	else if (ev.button.button == SDL_BUTTON_WHEELDOWN)
+		event.type = Common::EVENT_WHEELDOWN;
+#endif
+#if defined(SDL_BUTTON_MIDDLE)
+	else if (ev.button.button == SDL_BUTTON_MIDDLE)
+		event.type = Common::EVENT_MBUTTONDOWN;
+#endif
+#if defined(SDL_BUTTON_X1)
+	else if (ev.button.button == SDL_BUTTON_X1)
+		event.type = Common::EVENT_X1BUTTONDOWN;
+#endif
+#if defined(SDL_BUTTON_X2)
+	else if (ev.button.button == SDL_BUTTON_X2)
+		event.type = Common::EVENT_X2BUTTONDOWN;
+#endif
+	else
+		return false;
+
+	return processMouseEvent(event, ev.button.x, ev.button.y);
+}
+
+bool SdlEventSource::handleMouseButtonUp(SDL_Event &ev, Common::Event &event) {
+	if (ev.button.button == SDL_BUTTON_LEFT)
+		event.type = Common::EVENT_LBUTTONUP;
+	else if (ev.button.button == SDL_BUTTON_RIGHT)
+		event.type = Common::EVENT_RBUTTONUP;
+#if defined(SDL_BUTTON_MIDDLE)
+	else if (ev.button.button == SDL_BUTTON_MIDDLE)
+		event.type = Common::EVENT_MBUTTONUP;
+#endif
+#if defined(SDL_BUTTON_X1)
+	else if (ev.button.button == SDL_BUTTON_X1)
+		event.type = Common::EVENT_X1BUTTONUP;
+#endif
+#if defined(SDL_BUTTON_X2)
+	else if (ev.button.button == SDL_BUTTON_X2)
+		event.type = Common::EVENT_X2BUTTONUP;
+#endif
+	else
+		return false;
+
+	return processMouseEvent(event, ev.button.x, ev.button.y);
+}
+
+bool SdlEventSource::handleSysWMEvent(SDL_Event &ev, Common::Event &event) {
+	return false;
+}
+
+int SdlEventSource::mapSDLJoystickButtonToOSystem(Uint8 sdlButton) {
+	const Common::JoystickButton osystemButtons[] = {
+	    Common::JOYSTICK_BUTTON_A,
+	    Common::JOYSTICK_BUTTON_B,
+	    Common::JOYSTICK_BUTTON_X,
+	    Common::JOYSTICK_BUTTON_Y,
+	    Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_BACK,
+	    Common::JOYSTICK_BUTTON_START,
+	    Common::JOYSTICK_BUTTON_LEFT_STICK,
+	    Common::JOYSTICK_BUTTON_RIGHT_STICK
+	};
+
+	if (sdlButton >= ARRAYSIZE(osystemButtons)) {
+		return -1;
+	}
+
+	return osystemButtons[sdlButton];
+}
+
+bool SdlEventSource::handleJoyButtonDown(SDL_Event &ev, Common::Event &event) {
+	int button = mapSDLJoystickButtonToOSystem(ev.jbutton.button);
+	if (button < 0) {
+		return false;
+	}
+
+	event.type = Common::EVENT_JOYBUTTON_DOWN;
+	event.joystick.button = button;
+
+	return true;
+}
+
+bool SdlEventSource::handleJoyButtonUp(SDL_Event &ev, Common::Event &event) {
+	int button = mapSDLJoystickButtonToOSystem(ev.jbutton.button);
+	if (button < 0) {
+		return false;
+	}
+
+	event.type = Common::EVENT_JOYBUTTON_UP;
+	event.joystick.button = button;
+
+	return true;
+}
+
+bool SdlEventSource::handleJoyAxisMotion(SDL_Event &ev, Common::Event &event) {
+	event.type = Common::EVENT_JOYAXIS_MOTION;
+	event.joystick.axis = ev.jaxis.axis;
+	event.joystick.position = ev.jaxis.value;
+
+	return true;
+}
+
+#define HANDLE_HAT_UP(new, old, mask, joybutton) \
+	if ((old & mask) && !(new & mask)) { \
+		event.joystick.button = joybutton; \
+		g_system->getEventManager()->pushEvent(event); \
+	}
+
+#define HANDLE_HAT_DOWN(new, old, mask, joybutton) \
+	if ((new & mask) && !(old & mask)) { \
+		event.joystick.button = joybutton; \
+		g_system->getEventManager()->pushEvent(event); \
+	}
+
+bool SdlEventSource::handleJoyHatMotion(SDL_Event &ev, Common::Event &event) {
+	event.type = Common::EVENT_JOYBUTTON_UP;
+	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_UP, Common::JOYSTICK_BUTTON_DPAD_UP)
+	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_DOWN, Common::JOYSTICK_BUTTON_DPAD_DOWN)
+	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_LEFT, Common::JOYSTICK_BUTTON_DPAD_LEFT)
+	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_RIGHT, Common::JOYSTICK_BUTTON_DPAD_RIGHT)
+
+	event.type = Common::EVENT_JOYBUTTON_DOWN;
+	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_UP, Common::JOYSTICK_BUTTON_DPAD_UP)
+	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_DOWN, Common::JOYSTICK_BUTTON_DPAD_DOWN)
+	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_LEFT, Common::JOYSTICK_BUTTON_DPAD_LEFT)
+	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_RIGHT, Common::JOYSTICK_BUTTON_DPAD_RIGHT)
+
+	_lastHatPosition = ev.jhat.value;
+
+	return false;
+}
+
+bool SdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) {
+	return false;
+}
+
+void SdlEventSource::fakeWarpMouse(const int x, const int y) {
+	_queuedFakeMouseMove = true;
+	_fakeMouseMove.type = Common::EVENT_MOUSEMOVE;
+	_fakeMouseMove.mouse = Common::Point(x, y);
+}
+
+void SdlEventSource::setEngineRunning(const bool value) {
+	_engineRunning = value;
+}
+
+bool SdlEventSource::handleResizeEvent(Common::Event &event, int w, int h) {
+	if (_graphicsManager) {
+		_graphicsManager->notifyResize(w, h);
+
+		// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
+		int screenID = g_system->getScreenChangeID();
+		if (screenID != _lastScreenID) {
+			_lastScreenID = screenID;
+			event.type = Common::EVENT_SCREEN_CHANGED;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+#endif
diff --git a/backends/events/sdl/sdl-events.h b/backends/events/sdl/sdl-events.h
index 2fcf289e7bc..02b548ee9de 100644
--- a/backends/events/sdl/sdl-events.h
+++ b/backends/events/sdl/sdl-events.h
@@ -73,7 +73,10 @@ protected:
 	/** Joystick */
 	SDL_Joystick *_joystick;
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	/** Game controller */
+	SDL_Gamepad *_controller;
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	/** Game controller */
 	SDL_GameController *_controller;
 #endif
@@ -186,15 +189,17 @@ protected:
 	bool handleResizeEvent(Common::Event &event, int w, int h);
 
 	/**
-	 * Extracts unicode information for the specific key sym.
+	 * Extracts unicode information for the specific key.
 	 * May only be used for key down events.
 	 */
-	uint32 obtainUnicode(const SDL_Keysym keySym);
+	uint32 obtainUnicode(const SDL_KeyboardEvent &key);
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	/**
 	 * Extracts the keycode for the specified key sym.
 	 */
 	SDL_Keycode obtainKeycode(const SDL_Keysym keySym);
+#endif
 
 	/**
 	 * Whether _fakeMouseMove contains an event we need to send.
diff --git a/backends/events/sdl/sdl1-events.cpp b/backends/events/sdl/sdl1-events.cpp
new file mode 100644
index 00000000000..24931213611
--- /dev/null
+++ b/backends/events/sdl/sdl1-events.cpp
@@ -0,0 +1,478 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#if defined(SDL_BACKEND)
+
+#include "backends/events/sdl/sdl-events.h"
+#include "backends/platform/sdl/sdl.h"
+#include "backends/graphics/graphics.h"
+#include "common/config-manager.h"
+#include "common/textconsole.h"
+#include "common/fs.h"
+#include "engines/engine.h"
+#include "gui/gui-manager.h"
+
+SdlEventSource::SdlEventSource()
+	: EventSource(), _scrollLock(false), _joystick(nullptr), _lastScreenID(0), _graphicsManager(nullptr), _queuedFakeMouseMove(false),
+	  _lastHatPosition(SDL_HAT_CENTERED), _mouseX(0), _mouseY(0), _engineRunning(false)
+	  {
+	int joystick_num = ConfMan.getInt("joystick_num");
+	if (joystick_num >= 0) {
+		// Initialize SDL joystick subsystem
+		if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) {
+			warning("Could not initialize SDL joystick: %s", SDL_GetError());
+			return;
+		}
+
+		openJoystick(joystick_num);
+	}
+}
+
+int SdlEventSource::mapKey(SDL_Keycode sdlKey, SDL_Keymod mod, Uint16 unicode) {
+	Common::KeyCode key = SDLToOSystemKeycode(sdlKey);
+
+	// Keep unicode in case it's regular ASCII text, Hebrew or in case we didn't get a valid keycode
+	//
+	// We need to use unicode in those cases, simply because SDL1.x passes us non-layout-adjusted keycodes.
+	// So unicode is the only way to get layout-adjusted keys.
+	if (unicode < 0x20) {
+		// don't use unicode, in case it's control characters
+		unicode = 0;
+	} else {
+		// Use unicode, in case keycode is invalid.
+		// Umlauts and others will set KEYCODE_INVALID on SDL2, so in such a case always keep unicode.
+		if (key != Common::KEYCODE_INVALID) {
+			// keycode is valid, check further also depending on modifiers
+			if (mod & (KMOD_CTRL | KMOD_ALT)) {
+				// Ctrl and/or Alt is active
+				//
+				// We need to restrict unicode to only up to 0x7E, because on macOS the option/alt key will switch to
+				// an alternate keyboard, which will cause us to receive Unicode characters for some keys, which are outside
+				// of the ASCII range (e.g. alt-x will get us U+2248). We need to return 'x' for alt-x, so using unicode
+				// in that case would break alt-shortcuts.
+				if (unicode > 0x7E)
+					unicode = 0; // do not allow any characters above 0x7E
+			} else {
+				// We allow Hebrew characters
+				if (unicode >= 0x05D0 && unicode <= 0x05EA)
+					return unicode;
+
+				// Cyrillic
+				if (unicode >= 0x0400 && unicode <= 0x045F)
+					return unicode;
+
+				// We must not restrict as much as when Ctrl/Alt-modifiers are active, otherwise
+				// we wouldn't let umlauts through for SDL1. For SDL1 umlauts may set for example KEYCODE_QUOTE, KEYCODE_MINUS, etc.
+				if (unicode > 0xFF)
+					unicode = 0; // do not allow any characters above 0xFF
+			}
+		}
+	}
+
+	// Attention:
+	// When using SDL1.x, we will get scancodes via sdlKey, that are raw scancodes, so NOT adjusted to keyboard layout/
+	// mapping. So for example for certain locales, we will get KEYCODE_y, when 'z' is pressed and so on.
+	// When using SDL2.x however, we will get scancodes based on the keyboard layout.
+
+	if (key >= Common::KEYCODE_F1 && key <= Common::KEYCODE_F9) {
+		return key - Common::KEYCODE_F1 + Common::ASCII_F1;
+	} else if (key >= Common::KEYCODE_KP0 && key <= Common::KEYCODE_KP9) {
+		if ((mod & KMOD_NUM) == 0)
+			return 0; // In case Num-Lock is NOT enabled, return 0 for ascii, so that directional keys on numpad work
+		return key - Common::KEYCODE_KP0 + '0';
+	} else if (key >= Common::KEYCODE_UP && key <= Common::KEYCODE_PAGEDOWN) {
+		return key;
+	} else if (unicode) {
+		// Return unicode in case it's still set and wasn't filtered.
+		return unicode;
+	} else if (key >= 'a' && key <= 'z' && (mod & KMOD_SHIFT)) {
+		return key & ~0x20;
+	} else if (key >= Common::KEYCODE_NUMLOCK && key < Common::KEYCODE_LAST) {
+		return 0;
+	} else {
+		return key;
+	}
+}
+
+void SdlEventSource::SDLModToOSystemKeyFlags(SDL_Keymod mod, Common::Event &event) {
+
+	event.kbd.flags = 0;
+
+	if (mod & KMOD_SHIFT)
+		event.kbd.flags |= Common::KBD_SHIFT;
+	if (mod & KMOD_ALT)
+		event.kbd.flags |= Common::KBD_ALT;
+	if (mod & KMOD_CTRL)
+		event.kbd.flags |= Common::KBD_CTRL;
+	if (mod & KMOD_META)
+		event.kbd.flags |= Common::KBD_META;
+
+	// Sticky flags
+	if (mod & KMOD_NUM)
+		event.kbd.flags |= Common::KBD_NUM;
+	if (mod & KMOD_CAPS)
+		event.kbd.flags |= Common::KBD_CAPS;
+}
+
+Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDL_Keycode key) {
+	switch (key) {
+	case SDLK_BACKSPACE: return Common::KEYCODE_BACKSPACE;
+	case SDLK_TAB: return Common::KEYCODE_TAB;
+	case SDLK_CLEAR: return Common::KEYCODE_CLEAR;
+	case SDLK_RETURN: return Common::KEYCODE_RETURN;
+	case SDLK_PAUSE: return Common::KEYCODE_PAUSE;
+	case SDLK_ESCAPE: return Common::KEYCODE_ESCAPE;
+	case SDLK_SPACE: return Common::KEYCODE_SPACE;
+	case SDLK_EXCLAIM: return Common::KEYCODE_EXCLAIM;
+	case SDLK_QUOTEDBL: return Common::KEYCODE_QUOTEDBL;
+	case SDLK_HASH: return Common::KEYCODE_HASH;
+	case SDLK_DOLLAR: return Common::KEYCODE_DOLLAR;
+	case SDLK_AMPERSAND: return Common::KEYCODE_AMPERSAND;
+	case SDLK_QUOTE: return Common::KEYCODE_QUOTE;
+	case SDLK_LEFTPAREN: return Common::KEYCODE_LEFTPAREN;
+	case SDLK_RIGHTPAREN: return Common::KEYCODE_RIGHTPAREN;
+	case SDLK_ASTERISK: return Common::KEYCODE_ASTERISK;
+	case SDLK_PLUS: return Common::KEYCODE_PLUS;
+	case SDLK_COMMA: return Common::KEYCODE_COMMA;
+	case SDLK_MINUS: return Common::KEYCODE_MINUS;
+	case SDLK_PERIOD: return Common::KEYCODE_PERIOD;
+	case SDLK_SLASH: return Common::KEYCODE_SLASH;
+	case SDLK_0: return Common::KEYCODE_0;
+	case SDLK_1: return Common::KEYCODE_1;
+	case SDLK_2: return Common::KEYCODE_2;
+	case SDLK_3: return Common::KEYCODE_3;
+	case SDLK_4: return Common::KEYCODE_4;
+	case SDLK_5: return Common::KEYCODE_5;
+	case SDLK_6: return Common::KEYCODE_6;
+	case SDLK_7: return Common::KEYCODE_7;
+	case SDLK_8: return Common::KEYCODE_8;
+	case SDLK_9: return Common::KEYCODE_9;
+	case SDLK_COLON: return Common::KEYCODE_COLON;
+	case SDLK_SEMICOLON: return Common::KEYCODE_SEMICOLON;
+	case SDLK_LESS: return Common::KEYCODE_LESS;
+	case SDLK_EQUALS: return Common::KEYCODE_EQUALS;
+	case SDLK_GREATER: return Common::KEYCODE_GREATER;
+	case SDLK_QUESTION: return Common::KEYCODE_QUESTION;
+	case SDLK_AT: return Common::KEYCODE_AT;
+	case SDLK_LEFTBRACKET: return Common::KEYCODE_LEFTBRACKET;
+	case SDLK_BACKSLASH: return Common::KEYCODE_BACKSLASH;
+	case SDLK_RIGHTBRACKET: return Common::KEYCODE_RIGHTBRACKET;
+	case SDLK_CARET: return Common::KEYCODE_CARET;
+	case SDLK_UNDERSCORE: return Common::KEYCODE_UNDERSCORE;
+	case SDLK_BACKQUOTE: return Common::KEYCODE_BACKQUOTE;
+	case SDLK_a: return Common::KEYCODE_a;
+	case SDLK_b: return Common::KEYCODE_b;
+	case SDLK_c: return Common::KEYCODE_c;
+	case SDLK_d: return Common::KEYCODE_d;
+	case SDLK_e: return Common::KEYCODE_e;
+	case SDLK_f: return Common::KEYCODE_f;
+	case SDLK_g: return Common::KEYCODE_g;
+	case SDLK_h: return Common::KEYCODE_h;
+	case SDLK_i: return Common::KEYCODE_i;
+	case SDLK_j: return Common::KEYCODE_j;
+	case SDLK_k: return Common::KEYCODE_k;
+	case SDLK_l: return Common::KEYCODE_l;
+	case SDLK_m: return Common::KEYCODE_m;
+	case SDLK_n: return Common::KEYCODE_n;
+	case SDLK_o: return Common::KEYCODE_o;
+	case SDLK_p: return Common::KEYCODE_p;
+	case SDLK_q: return Common::KEYCODE_q;
+	case SDLK_r: return Common::KEYCODE_r;
+	case SDLK_s: return Common::KEYCODE_s;
+	case SDLK_t: return Common::KEYCODE_t;
+	case SDLK_u: return Common::KEYCODE_u;
+	case SDLK_v: return Common::KEYCODE_v;
+	case SDLK_w: return Common::KEYCODE_w;
+	case SDLK_x: return Common::KEYCODE_x;
+	case SDLK_y: return Common::KEYCODE_y;
+	case SDLK_z: return Common::KEYCODE_z;
+	case SDLK_DELETE: return Common::KEYCODE_DELETE;
+	case SDLK_KP_PERIOD: return Common::KEYCODE_KP_PERIOD;
+	case SDLK_KP_DIVIDE: return Common::KEYCODE_KP_DIVIDE;
+	case SDLK_KP_MULTIPLY: return Common::KEYCODE_KP_MULTIPLY;
+	case SDLK_KP_MINUS: return Common::KEYCODE_KP_MINUS;
+	case SDLK_KP_PLUS: return Common::KEYCODE_KP_PLUS;
+	case SDLK_KP_ENTER: return Common::KEYCODE_KP_ENTER;
+	case SDLK_KP_EQUALS: return Common::KEYCODE_KP_EQUALS;
+	case SDLK_UP: return Common::KEYCODE_UP;
+	case SDLK_DOWN: return Common::KEYCODE_DOWN;
+	case SDLK_RIGHT: return Common::KEYCODE_RIGHT;
+	case SDLK_LEFT: return Common::KEYCODE_LEFT;
+	case SDLK_INSERT: return Common::KEYCODE_INSERT;
+	case SDLK_HOME: return Common::KEYCODE_HOME;
+	case SDLK_END: return Common::KEYCODE_END;
+	case SDLK_PAGEUP: return Common::KEYCODE_PAGEUP;
+	case SDLK_PAGEDOWN: return Common::KEYCODE_PAGEDOWN;
+	case SDLK_F1: return Common::KEYCODE_F1;
+	case SDLK_F2: return Common::KEYCODE_F2;
+	case SDLK_F3: return Common::KEYCODE_F3;
+	case SDLK_F4: return Common::KEYCODE_F4;
+	case SDLK_F5: return Common::KEYCODE_F5;
+	case SDLK_F6: return Common::KEYCODE_F6;
+	case SDLK_F7: return Common::KEYCODE_F7;
+	case SDLK_F8: return Common::KEYCODE_F8;
+	case SDLK_F9: return Common::KEYCODE_F9;
+	case SDLK_F10: return Common::KEYCODE_F10;
+	case SDLK_F11: return Common::KEYCODE_F11;
+	case SDLK_F12: return Common::KEYCODE_F12;
+	case SDLK_F13: return Common::KEYCODE_F13;
+	case SDLK_F14: return Common::KEYCODE_F14;
+	case SDLK_F15: return Common::KEYCODE_F15;
+	case SDLK_CAPSLOCK: return Common::KEYCODE_CAPSLOCK;
+	case SDLK_RSHIFT: return Common::KEYCODE_RSHIFT;
+	case SDLK_LSHIFT: return Common::KEYCODE_LSHIFT;
+	case SDLK_RCTRL: return Common::KEYCODE_RCTRL;
+	case SDLK_LCTRL: return Common::KEYCODE_LCTRL;
+	case SDLK_RALT: return Common::KEYCODE_RALT;
+	case SDLK_LALT: return Common::KEYCODE_LALT;
+	case SDLK_MODE: return Common::KEYCODE_MODE;
+	case SDLK_HELP: return Common::KEYCODE_HELP;
+	case SDLK_SYSREQ: return Common::KEYCODE_SYSREQ;
+	case SDLK_MENU: return Common::KEYCODE_MENU;
+	case SDLK_POWER: return Common::KEYCODE_POWER;
+#if SDL_VERSION_ATLEAST(1, 2, 3)
+	case SDLK_UNDO: return Common::KEYCODE_UNDO;
+#endif
+	case SDLK_SCROLLOCK: return Common::KEYCODE_SCROLLOCK;
+	case SDLK_NUMLOCK: return Common::KEYCODE_NUMLOCK;
+	case SDLK_LSUPER: return Common::KEYCODE_LSUPER;
+	case SDLK_RSUPER: return Common::KEYCODE_RSUPER;
+	case SDLK_PRINT: return Common::KEYCODE_PRINT;
+	case SDLK_COMPOSE: return Common::KEYCODE_COMPOSE;
+	case SDLK_KP0: return Common::KEYCODE_KP0;
+	case SDLK_KP1: return Common::KEYCODE_KP1;
+	case SDLK_KP2: return Common::KEYCODE_KP2;
+	case SDLK_KP3: return Common::KEYCODE_KP3;
+	case SDLK_KP4: return Common::KEYCODE_KP4;
+	case SDLK_KP5: return Common::KEYCODE_KP5;
+	case SDLK_KP6: return Common::KEYCODE_KP6;
+	case SDLK_KP7: return Common::KEYCODE_KP7;
+	case SDLK_KP8: return Common::KEYCODE_KP8;
+	case SDLK_KP9: return Common::KEYCODE_KP9;
+	case SDLK_WORLD_16: return Common::KEYCODE_TILDE;
+	case SDLK_BREAK: return Common::KEYCODE_BREAK;
+	case SDLK_LMETA: return Common::KEYCODE_LMETA;
+	case SDLK_RMETA: return Common::KEYCODE_RMETA;
+	case SDLK_EURO: return Common::KEYCODE_EURO;
+	default: return Common::KEYCODE_INVALID;
+	}
+}
+
+bool SdlEventSource::pollEvent(Common::Event &event) {
+	// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
+	int screenID = g_system->getScreenChangeID();
+	if (screenID != _lastScreenID) {
+		_lastScreenID = screenID;
+		event.type = Common::EVENT_SCREEN_CHANGED;
+		return true;
+	}
+
+	if (_queuedFakeMouseMove) {
+		event = _fakeMouseMove;
+		_queuedFakeMouseMove = false;
+		return true;
+	}
+
+	SDL_Event ev;
+	while (SDL_PollEvent(&ev)) {
+		preprocessEvents(&ev);
+		if (dispatchSDLEvent(ev, event))
+			return true;
+	}
+
+	return false;
+}
+
+bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
+	switch (ev.type) {
+	case SDL_KEYDOWN:
+		return handleKeyDown(ev, event);
+	case SDL_KEYUP:
+		return handleKeyUp(ev, event);
+	case SDL_MOUSEMOTION:
+		return handleMouseMotion(ev, event);
+	case SDL_MOUSEBUTTONDOWN:
+		return handleMouseButtonDown(ev, event);
+	case SDL_MOUSEBUTTONUP:
+		return handleMouseButtonUp(ev, event);
+	case SDL_SYSWMEVENT:
+		return handleSysWMEvent(ev, event);
+	case SDL_VIDEOEXPOSE:
+		if (_graphicsManager) {
+			_graphicsManager->notifyVideoExpose();
+		}
+		return false;
+
+	case SDL_VIDEORESIZE:
+		return handleResizeEvent(event, ev.resize.w, ev.resize.h);
+	case SDL_QUIT:
+		event.type = Common::EVENT_QUIT;
+		return true;
+
+	default:
+		break;
+	}
+
+	if (_joystick) {
+		switch (ev.type) {
+		case SDL_JOYBUTTONDOWN:
+			return handleJoyButtonDown(ev, event);
+		case SDL_JOYBUTTONUP:
+			return handleJoyButtonUp(ev, event);
+		case SDL_JOYAXISMOTION:
+			return handleJoyAxisMotion(ev, event);
+		case SDL_JOYHATMOTION:
+			return handleJoyHatMotion(ev, event);
+		default:
+			break;
+		}
+	}
+
+	return false;
+}
+
+
+bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) {
+
+	SDLModToOSystemKeyFlags(SDL_GetModState(), event);
+
+	SDL_Keycode sdlKeycode = obtainKeycode(ev.key.keysym);
+	Common::KeyCode key = SDLToOSystemKeycode(sdlKeycode);
+
+	// Handle scroll lock as a key modifier
+	if (key == Common::KEYCODE_SCROLLOCK)
+		_scrollLock = !_scrollLock;
+
+	if (_scrollLock)
+		event.kbd.flags |= Common::KBD_SCRL;
+
+	if (remapKey(ev, event))
+		return true;
+
+	event.type = Common::EVENT_KEYDOWN;
+	event.kbd.keycode = key;
+
+	SDL_Keymod mod = (SDL_Keymod)ev.key.keysym.mod;
+#if defined(__amigaos4__)
+	// On AmigaOS, SDL always reports numlock as off. However, we get KEYCODE_KP# only when
+	// it is on, and get different keycodes (for example KEYCODE_PAGEDOWN) when it is off.
+	if (event.kbd.keycode >= Common::KEYCODE_KP0 && event.kbd.keycode <= Common::KEYCODE_KP9) {
+		event.kbd.flags |= Common::KBD_NUM;
+		mod = SDL_Keymod(mod | KMOD_NUM);
+	}
+#endif
+	event.kbd.ascii = mapKey(sdlKeycode, mod, ev.key.keysym.unicode);
+
+	return true;
+}
+
+bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) {
+	if (remapKey(ev, event))
+		return true;
+
+	SDLModToOSystemKeyFlags(SDL_GetModState(), event);
+
+	SDL_Keycode sdlKeycode = obtainKeycode(ev.key.keysym);
+
+	// Set the scroll lock sticky flag
+	if (_scrollLock)
+		event.kbd.flags |= Common::KBD_SCRL;
+
+	event.type = Common::EVENT_KEYUP;
+	event.kbd.keycode = SDLToOSystemKeycode(sdlKeycode);
+
+	SDL_Keymod mod = (SDL_Keymod)ev.key.keysym.mod;
+#if defined(__amigaos4__)
+	// On AmigaOS, SDL always reports numlock as off. However, we get KEYCODE_KP# only when
+	// it is on, and get different keycodes (for example KEYCODE_PAGEDOWN) when it is off.
+	if (event.kbd.keycode >= Common::KEYCODE_KP0 && event.kbd.keycode <= Common::KEYCODE_KP9) {
+		event.kbd.flags |= Common::KBD_NUM;
+		mod = SDL_Keymod(mod | KMOD_NUM);
+	}
+#endif
+	event.kbd.ascii = mapKey(sdlKeycode, mod, 0);
+
+	return true;
+}
+
+void SdlEventSource::openJoystick(int joystickIndex) {
+	if (SDL_NumJoysticks() > joystickIndex) {
+			_joystick = SDL_JoystickOpen(joystickIndex);
+			debug("Using joystick: %s",
+				  SDL_JoystickName(joystickIndex)
+			);
+	} else {
+		debug(5, "Invalid joystick: %d", joystickIndex);
+	}
+}
+
+void SdlEventSource::closeJoystick() {
+	if (_joystick) {
+		SDL_JoystickClose(_joystick);
+		_joystick = nullptr;
+	}
+}
+
+bool SdlEventSource::isJoystickConnected() const {
+	return _joystick;
+}
+
+SDL_Keycode SdlEventSource::obtainKeycode(const SDL_Keysym keySym) {
+#ifdef WIN32
+	// WORKAROUND: SDL 1.2 on Windows does not use the user configured keyboard layout,
+	// resulting in "keySym.sym" values to always be those expected for an US keyboard.
+	// For example, SDL returns SDLK_Q when pressing the 'A' key on an AZERTY keyboard.
+	// This defeats the purpose of keycodes which is to be able to refer to a key without
+	// knowing where it is physically located.
+	// We work around this issue by querying the currently active Windows keyboard layout
+	// using the scancode provided by SDL.
+
+	if (keySym.sym >= SDLK_0 && keySym.sym <= SDLK_9) {
+		// The keycode returned by SDL is kept for the number keys.
+		// Querying the keyboard layout for those would return the base key values
+		// for AZERTY keyboards, which are not numbers. For example, SDLK_1 would
+		// map to SDLK_AMPERSAND. This is theoretically correct but practically unhelpful,
+		// because it makes it impossible to handle key combinations such as "ctrl-1".
+		return keySym.sym;
+	}
+
+	int vk = MapVirtualKey(keySym.scancode, MAPVK_VSC_TO_VK);
+	if (vk) {
+		int ch = (MapVirtualKey(vk, MAPVK_VK_TO_CHAR) & 0x7FFF);
+		// The top bit of the result of MapVirtualKey with MAPVK_VSC_TO_VK signals
+		// a dead key was pressed. In that case we keep the value of the accent alone.
+		if (ch) {
+			if (ch >= 'A' && ch <= 'Z') {
+				// Windows returns uppercase ASCII whereas SDL expects lowercase
+				return (SDL_Keycode)(SDLK_a + (ch - 'A'));
+			} else {
+				return (SDL_Keycode)ch;
+			}
+		}
+	}
+#endif
+
+	return keySym.sym;
+}
+
+#endif
diff --git a/backends/events/sdl/sdl-events.cpp b/backends/events/sdl/sdl2-events.cpp
similarity index 78%
rename from backends/events/sdl/sdl-events.cpp
rename to backends/events/sdl/sdl2-events.cpp
index 3cd124e6dfa..af2a0acb60d 100644
--- a/backends/events/sdl/sdl-events.cpp
+++ b/backends/events/sdl/sdl2-events.cpp
@@ -32,11 +32,10 @@
 #include "engines/engine.h"
 #include "gui/gui-manager.h"
 
-#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+#if defined(USE_IMGUI)
 #include "backends/imgui/backends/imgui_impl_sdl2.h"
 #endif
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 #define GAMECONTROLLERDB_FILE "gamecontrollerdb.txt"
 
 static uint32 convUTF8ToUTF32(const char *src) {
@@ -72,30 +71,25 @@ void SdlEventSource::loadGameControllerMappingFile() {
 		}
 	}
 }
-#endif
 
 SdlEventSource::SdlEventSource()
 	: EventSource(), _scrollLock(false), _joystick(nullptr), _lastScreenID(0), _graphicsManager(nullptr), _queuedFakeMouseMove(false),
 	  _lastHatPosition(SDL_HAT_CENTERED), _mouseX(0), _mouseY(0), _engineRunning(false)
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 	  , _queuedFakeKeyUp(false), _fakeKeyUp(), _controller(nullptr)
-#endif
 	  {
 	int joystick_num = ConfMan.getInt("joystick_num");
 	if (joystick_num >= 0) {
 		// Initialize SDL joystick subsystem
-		if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) {
+		if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) {
 			warning("Could not initialize SDL joystick: %s", SDL_GetError());
 			return;
 		}
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
-		if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) == -1) {
+		if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
 			warning("Could not initialize SDL game controller: %s", SDL_GetError());
 			return;
 		}
 		loadGameControllerMappingFile();
-#endif
 
 		openJoystick(joystick_num);
 	}
@@ -107,10 +101,6 @@ SdlEventSource::SdlEventSource()
 
 }
 
-SdlEventSource::~SdlEventSource() {
-	closeJoystick();
-}
-
 int SdlEventSource::mapKey(SDL_Keycode sdlKey, SDL_Keymod mod, Uint16 unicode) {
 	Common::KeyCode key = SDLToOSystemKeycode(sdlKey);
 
@@ -177,39 +167,18 @@ int SdlEventSource::mapKey(SDL_Keycode sdlKey, SDL_Keymod mod, Uint16 unicode) {
 	}
 }
 
-bool SdlEventSource::processMouseEvent(Common::Event &event, int x, int y, int relx, int rely) {
-	_mouseX = x;
-	_mouseY = y;
-
-	event.mouse.x = x;
-	event.mouse.y = y;
-	event.relMouse.x = relx;
-	event.relMouse.y = rely;
-
-	if (_graphicsManager) {
-		return _graphicsManager->notifyMousePosition(event.mouse);
-	}
-
-	return true;
-}
-
 void SdlEventSource::SDLModToOSystemKeyFlags(SDL_Keymod mod, Common::Event &event) {
 
 	event.kbd.flags = 0;
 
+	if (mod & KMOD_GUI)
+		event.kbd.flags |= Common::KBD_META;
 	if (mod & KMOD_SHIFT)
 		event.kbd.flags |= Common::KBD_SHIFT;
 	if (mod & KMOD_ALT)
 		event.kbd.flags |= Common::KBD_ALT;
 	if (mod & KMOD_CTRL)
 		event.kbd.flags |= Common::KBD_CTRL;
-#if SDL_VERSION_ATLEAST(2, 0, 0)
-	if (mod & KMOD_GUI)
-		event.kbd.flags |= Common::KBD_META;
-#else
-	if (mod & KMOD_META)
-		event.kbd.flags |= Common::KBD_META;
-#endif
 
 	// Sticky flags
 	if (mod & KMOD_NUM)
@@ -337,7 +306,6 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDL_Keycode key) {
 #if SDL_VERSION_ATLEAST(1, 2, 3)
 	case SDLK_UNDO: return Common::KEYCODE_UNDO;
 #endif
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 	case SDLK_SCROLLLOCK: return Common::KEYCODE_SCROLLOCK;
 	case SDLK_NUMLOCKCLEAR: return Common::KEYCODE_NUMLOCK;
 	case SDLK_LGUI: return Common::KEYCODE_LSUPER;
@@ -360,13 +328,17 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDL_Keycode key) {
 	case SDLK_F17: return Common::KEYCODE_F17;
 	case SDLK_F18: return Common::KEYCODE_F18;
 	case SDLK_SLEEP: return Common::KEYCODE_SLEEP;
-	case SDLK_MUTE: return Common::KEYCODE_MUTE;
 	case SDLK_VOLUMEUP: return Common::KEYCODE_VOLUMEUP;
 	case SDLK_VOLUMEDOWN: return Common::KEYCODE_VOLUMEDOWN;
 	case SDLK_EJECT: return Common::KEYCODE_EJECT;
-	case SDLK_WWW: return Common::KEYCODE_WWW;
 	case SDLK_MAIL: return Common::KEYCODE_MAIL;
+	case SDLK_WWW: return Common::KEYCODE_WWW;
 	case SDLK_CALCULATOR: return Common::KEYCODE_CALCULATOR;
+	case SDLK_AUDIONEXT: return Common::KEYCODE_AUDIONEXT;
+	case SDLK_AUDIOPREV: return Common::KEYCODE_AUDIOPREV;
+	case SDLK_AUDIOSTOP: return Common::KEYCODE_AUDIOSTOP;
+	case SDLK_AUDIOPLAY: return Common::KEYCODE_AUDIOPLAYPAUSE;
+	case SDLK_AUDIOMUTE: return Common::KEYCODE_AUDIOMUTE;
 	case SDLK_CUT: return Common::KEYCODE_CUT;
 	case SDLK_COPY: return Common::KEYCODE_COPY;
 	case SDLK_PASTE: return Common::KEYCODE_PASTE;
@@ -379,43 +351,14 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDL_Keycode key) {
 	case SDLK_AC_STOP: return Common::KEYCODE_AC_STOP;
 	case SDLK_AC_REFRESH: return Common::KEYCODE_AC_REFRESH;
 	case SDLK_AC_BOOKMARKS: return Common::KEYCODE_AC_BOOKMARKS;
-	case SDLK_AUDIONEXT: return Common::KEYCODE_AUDIONEXT;
-	case SDLK_AUDIOPREV: return Common::KEYCODE_AUDIOPREV;
-	case SDLK_AUDIOSTOP: return Common::KEYCODE_AUDIOSTOP;
-	case SDLK_AUDIOPLAY: return Common::KEYCODE_AUDIOPLAYPAUSE;
-	case SDLK_AUDIOMUTE: return Common::KEYCODE_AUDIOMUTE;
 #if SDL_VERSION_ATLEAST(2, 0, 6)
 	case SDLK_AUDIOREWIND: return Common::KEYCODE_AUDIOREWIND;
 	case SDLK_AUDIOFASTFORWARD: return Common::KEYCODE_AUDIOFASTFORWARD;
-#endif
-#else
-	case SDLK_SCROLLOCK: return Common::KEYCODE_SCROLLOCK;
-	case SDLK_NUMLOCK: return Common::KEYCODE_NUMLOCK;
-	case SDLK_LSUPER: return Common::KEYCODE_LSUPER;
-	case SDLK_RSUPER: return Common::KEYCODE_RSUPER;
-	case SDLK_PRINT: return Common::KEYCODE_PRINT;
-	case SDLK_COMPOSE: return Common::KEYCODE_COMPOSE;
-	case SDLK_KP0: return Common::KEYCODE_KP0;
-	case SDLK_KP1: return Common::KEYCODE_KP1;
-	case SDLK_KP2: return Common::KEYCODE_KP2;
-	case SDLK_KP3: return Common::KEYCODE_KP3;
-	case SDLK_KP4: return Common::KEYCODE_KP4;
-	case SDLK_KP5: return Common::KEYCODE_KP5;
-	case SDLK_KP6: return Common::KEYCODE_KP6;
-	case SDLK_KP7: return Common::KEYCODE_KP7;
-	case SDLK_KP8: return Common::KEYCODE_KP8;
-	case SDLK_KP9: return Common::KEYCODE_KP9;
-	case SDLK_WORLD_16: return Common::KEYCODE_TILDE;
-	case SDLK_BREAK: return Common::KEYCODE_BREAK;
-	case SDLK_LMETA: return Common::KEYCODE_LMETA;
-	case SDLK_RMETA: return Common::KEYCODE_RMETA;
-	case SDLK_EURO: return Common::KEYCODE_EURO;
 #endif
 	default: return Common::KEYCODE_INVALID;
 	}
 }
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 void SdlEventSource::preprocessFingerDown(SDL_Event *event) {
 	// front (1) or back (2) panel
 	SDL_TouchID port = event->tfinger.touchId;
@@ -452,28 +395,6 @@ void SdlEventSource::preprocessFingerDown(SDL_Event *event) {
 	}
 }
 
-Common::Point SdlEventSource::getTouchscreenSize() {
-	int windowWidth, windowHeight;
-	SDL_GetWindowSize((dynamic_cast<SdlGraphicsManager*>(_graphicsManager))->getWindow()->getSDLWindow(), &windowWidth, &windowHeight);
-	return Common::Point(windowWidth, windowHeight);
-}
-
-bool SdlEventSource::isTouchPortTouchpadMode(SDL_TouchID port) {
-       return g_system->getFeatureState(OSystem::kFeatureTouchpadMode);
-}
-
-bool SdlEventSource::isTouchPortActive(SDL_TouchID port) {
-	return true;
-}
-
-void SdlEventSource::convertTouchXYToGameXY(float touchX, float touchY, int *gameX, int *gameY) {
-	int windowWidth, windowHeight;
-	SDL_GetWindowSize((dynamic_cast<SdlGraphicsManager*>(_graphicsManager))->getWindow()->getSDLWindow(), &windowWidth, &windowHeight);
-
-	*gameX = windowWidth * touchX;
-	*gameY = windowHeight * touchY;
-}
-
 void SdlEventSource::finishSimulatedMouseClicks() {
 	for (auto &panel : _touchPanels) {
 		for (int i = 0; i < 2; i++) {
@@ -707,10 +628,8 @@ void SdlEventSource::preprocessFingerMotion(SDL_Event *event) {
 		}
 	}
 }
-#endif
 
 bool SdlEventSource::pollEvent(Common::Event &event) {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 	finishSimulatedMouseClicks();
 
 	// In case we still need to send a key up event for a key down from a
@@ -720,7 +639,6 @@ bool SdlEventSource::pollEvent(Common::Event &event) {
 		_queuedFakeKeyUp = false;
 		return true;
 	}
-#endif
 
 	// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
 	int screenID = g_system->getScreenChangeID();
@@ -740,7 +658,6 @@ bool SdlEventSource::pollEvent(Common::Event &event) {
 	while (SDL_PollEvent(&ev)) {
 		preprocessEvents(&ev);
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 		// Supported touch gestures:
 		// left mouse click: single finger short tap
 		// right mouse click: second finger short tap while first finger is still down
@@ -766,9 +683,8 @@ bool SdlEventSource::pollEvent(Common::Event &event) {
 				}
 			}
 		}
-#endif
 
-#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+#if defined(USE_IMGUI)
 		if (ImGui_ImplSDL2_Ready()) {
 			ImGui_ImplSDL2_ProcessEvent(&ev);
 			ImGuiIO &io = ImGui::GetIO();
@@ -798,7 +714,6 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 	case SDL_SYSWMEVENT:
 		return handleSysWMEvent(ev, event);
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 	case SDL_MOUSEWHEEL: {
 		Sint32 yDir = ev.wheel.y;
 		// We want the mouse coordinates supplied with a mouse wheel event.
@@ -849,6 +764,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		}
 
 		switch (ev.window.event) {
+
 		case SDL_WINDOWEVENT_EXPOSED:
 			if (_graphicsManager) {
 				_graphicsManager->notifyVideoExpose();
@@ -907,16 +823,6 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 	case SDL_CLIPBOARDUPDATE:
 		event.type = Common::EVENT_CLIPBOARD_UPDATE;
 		return true;
-#else
-	case SDL_VIDEOEXPOSE:
-		if (_graphicsManager) {
-			_graphicsManager->notifyVideoExpose();
-		}
-		return false;
-
-	case SDL_VIDEORESIZE:
-		return handleResizeEvent(event, ev.resize.w, ev.resize.h);
-#endif
 
 	case SDL_QUIT:
 		event.type = Common::EVENT_QUIT;
@@ -941,7 +847,6 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		}
 	}
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 	if (_controller) {
 		switch (ev.type) {
 		case SDL_CONTROLLERBUTTONDOWN:
@@ -954,8 +859,6 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 			break;
 		}
 	}
-#endif
-
 	return false;
 }
 
@@ -989,11 +892,9 @@ bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) {
 		mod = SDL_Keymod(mod | KMOD_NUM);
 	}
 #endif
-	event.kbd.ascii = mapKey(sdlKeycode, mod, obtainUnicode(ev.key.keysym));
+	event.kbd.ascii = mapKey(sdlKeycode, mod, obtainUnicode(ev.key));
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 	event.kbdRepeat = ev.key.repeat;
-#endif
 
 	return true;
 }
@@ -1014,6 +915,7 @@ bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) {
 	event.kbd.keycode = SDLToOSystemKeycode(sdlKeycode);
 
 	SDL_Keymod mod = (SDL_Keymod)ev.key.keysym.mod;
+
 #if defined(__amigaos4__)
 	// On AmigaOS, SDL always reports numlock as off. However, we get KEYCODE_KP# only when
 	// it is on, and get different keycodes (for example KEYCODE_PAGEDOWN) when it is off.
@@ -1027,85 +929,14 @@ bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) {
 	return true;
 }
 
-bool SdlEventSource::handleMouseMotion(SDL_Event &ev, Common::Event &event) {
-	event.type = Common::EVENT_MOUSEMOVE;
-
-	return processMouseEvent(event, ev.motion.x, ev.motion.y, ev.motion.xrel, ev.motion.yrel);
-}
-
-bool SdlEventSource::handleMouseButtonDown(SDL_Event &ev, Common::Event &event) {
-	if (ev.button.button == SDL_BUTTON_LEFT)
-		event.type = Common::EVENT_LBUTTONDOWN;
-	else if (ev.button.button == SDL_BUTTON_RIGHT)
-		event.type = Common::EVENT_RBUTTONDOWN;
-#if defined(SDL_BUTTON_WHEELUP) && defined(SDL_BUTTON_WHEELDOWN)
-	else if (ev.button.button == SDL_BUTTON_WHEELUP)
-		event.type = Common::EVENT_WHEELUP;
-	else if (ev.button.button == SDL_BUTTON_WHEELDOWN)
-		event.type = Common::EVENT_WHEELDOWN;
-#endif
-#if defined(SDL_BUTTON_MIDDLE)
-	else if (ev.button.button == SDL_BUTTON_MIDDLE)
-		event.type = Common::EVENT_MBUTTONDOWN;
-#endif
-#if defined(SDL_BUTTON_X1)
-	else if (ev.button.button == SDL_BUTTON_X1)
-		event.type = Common::EVENT_X1BUTTONDOWN;
-#endif
-#if defined(SDL_BUTTON_X2)
-	else if (ev.button.button == SDL_BUTTON_X2)
-		event.type = Common::EVENT_X2BUTTONDOWN;
-#endif
-	else
-		return false;
-
-	return processMouseEvent(event, ev.button.x, ev.button.y);
-}
-
-bool SdlEventSource::handleMouseButtonUp(SDL_Event &ev, Common::Event &event) {
-	if (ev.button.button == SDL_BUTTON_LEFT)
-		event.type = Common::EVENT_LBUTTONUP;
-	else if (ev.button.button == SDL_BUTTON_RIGHT)
-		event.type = Common::EVENT_RBUTTONUP;
-#if defined(SDL_BUTTON_MIDDLE)
-	else if (ev.button.button == SDL_BUTTON_MIDDLE)
-		event.type = Common::EVENT_MBUTTONUP;
-#endif
-#if defined(SDL_BUTTON_X1)
-	else if (ev.button.button == SDL_BUTTON_X1)
-		event.type = Common::EVENT_X1BUTTONUP;
-#endif
-#if defined(SDL_BUTTON_X2)
-	else if (ev.button.button == SDL_BUTTON_X2)
-		event.type = Common::EVENT_X2BUTTONUP;
-#endif
-	else
-		return false;
-
-	return processMouseEvent(event, ev.button.x, ev.button.y);
-}
-
-bool SdlEventSource::handleSysWMEvent(SDL_Event &ev, Common::Event &event) {
-	return false;
-}
-
 void SdlEventSource::openJoystick(int joystickIndex) {
 	if (SDL_NumJoysticks() > joystickIndex) {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 		if (SDL_IsGameController(joystickIndex)) {
 			_controller = SDL_GameControllerOpen(joystickIndex);
 			debug("Using game controller: %s", SDL_GameControllerName(_controller));
-		} else
-#endif
-		{
+		} else {
 			_joystick = SDL_JoystickOpen(joystickIndex);
-			debug("Using joystick: %s",
-#if SDL_VERSION_ATLEAST(2, 0, 0)
-				  SDL_JoystickName(_joystick)
-#else
-				  SDL_JoystickName(joystickIndex)
-#endif
-			);
+			debug("Using joystick: %s", SDL_JoystickName(_joystick));
 		}
 	} else {
 		debug(5, "Invalid joystick: %d", joystickIndex);
@@ -1113,102 +944,16 @@ void SdlEventSource::openJoystick(int joystickIndex) {
 }
 
 void SdlEventSource::closeJoystick() {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 	if (_controller) {
 		SDL_GameControllerClose(_controller);
 		_controller = nullptr;
 	}
-#endif
 	if (_joystick) {
 		SDL_JoystickClose(_joystick);
 		_joystick = nullptr;
 	}
 }
 
-int SdlEventSource::mapSDLJoystickButtonToOSystem(Uint8 sdlButton) {
-	Common::JoystickButton osystemButtons[] = {
-	    Common::JOYSTICK_BUTTON_A,
-	    Common::JOYSTICK_BUTTON_B,
-	    Common::JOYSTICK_BUTTON_X,
-	    Common::JOYSTICK_BUTTON_Y,
-	    Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
-	    Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
-	    Common::JOYSTICK_BUTTON_BACK,
-	    Common::JOYSTICK_BUTTON_START,
-	    Common::JOYSTICK_BUTTON_LEFT_STICK,
-	    Common::JOYSTICK_BUTTON_RIGHT_STICK
-	};
-
-	if (sdlButton >= ARRAYSIZE(osystemButtons)) {
-		return -1;
-	}
-
-	return osystemButtons[sdlButton];
-}
-
-bool SdlEventSource::handleJoyButtonDown(SDL_Event &ev, Common::Event &event) {
-	int button = mapSDLJoystickButtonToOSystem(ev.jbutton.button);
-	if (button < 0) {
-		return false;
-	}
-
-	event.type = Common::EVENT_JOYBUTTON_DOWN;
-	event.joystick.button = button;
-
-	return true;
-}
-
-bool SdlEventSource::handleJoyButtonUp(SDL_Event &ev, Common::Event &event) {
-	int button = mapSDLJoystickButtonToOSystem(ev.jbutton.button);
-	if (button < 0) {
-		return false;
-	}
-
-	event.type = Common::EVENT_JOYBUTTON_UP;
-	event.joystick.button = button;
-
-	return true;
-}
-
-bool SdlEventSource::handleJoyAxisMotion(SDL_Event &ev, Common::Event &event) {
-	event.type = Common::EVENT_JOYAXIS_MOTION;
-	event.joystick.axis = ev.jaxis.axis;
-	event.joystick.position = ev.jaxis.value;
-
-	return true;
-}
-
-#define HANDLE_HAT_UP(new, old, mask, joybutton) \
-	if ((old & mask) && !(new & mask)) { \
-		event.joystick.button = joybutton; \
-		g_system->getEventManager()->pushEvent(event); \
-	}
-
-#define HANDLE_HAT_DOWN(new, old, mask, joybutton) \
-	if ((new & mask) && !(old & mask)) { \
-		event.joystick.button = joybutton; \
-		g_system->getEventManager()->pushEvent(event); \
-	}
-
-bool SdlEventSource::handleJoyHatMotion(SDL_Event &ev, Common::Event &event) {
-	event.type = Common::EVENT_JOYBUTTON_UP;
-	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_UP, Common::JOYSTICK_BUTTON_DPAD_UP)
-	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_DOWN, Common::JOYSTICK_BUTTON_DPAD_DOWN)
-	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_LEFT, Common::JOYSTICK_BUTTON_DPAD_LEFT)
-	HANDLE_HAT_UP(ev.jhat.value, _lastHatPosition, SDL_HAT_RIGHT, Common::JOYSTICK_BUTTON_DPAD_RIGHT)
-
-	event.type = Common::EVENT_JOYBUTTON_DOWN;
-	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_UP, Common::JOYSTICK_BUTTON_DPAD_UP)
-	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_DOWN, Common::JOYSTICK_BUTTON_DPAD_DOWN)
-	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_LEFT, Common::JOYSTICK_BUTTON_DPAD_LEFT)
-	HANDLE_HAT_DOWN(ev.jhat.value, _lastHatPosition, SDL_HAT_RIGHT, Common::JOYSTICK_BUTTON_DPAD_RIGHT)
-
-	_lastHatPosition = ev.jhat.value;
-
-	return false;
-}
-
-#if SDL_VERSION_ATLEAST(2, 0, 0)
 bool SdlEventSource::handleJoystickAdded(const SDL_JoyDeviceEvent &device, Common::Event &event) {
 	debug(5, "SdlEventSource: Received joystick added event for index '%d'", device.which);
 
@@ -1280,7 +1025,6 @@ int SdlEventSource::mapSDLControllerButtonToOSystem(Uint8 sdlButton) {
 
 bool SdlEventSource::handleControllerButton(const SDL_Event &ev, Common::Event &event, bool buttonUp) {
 	int button = mapSDLControllerButtonToOSystem(ev.cbutton.button);
-
 	if (button < 0)
 		return false;
 
@@ -1291,92 +1035,22 @@ bool SdlEventSource::handleControllerButton(const SDL_Event &ev, Common::Event &
 }
 
 bool SdlEventSource::handleControllerAxisMotion(const SDL_Event &ev, Common::Event &event) {
-	event.type = Common::EVENT_JOYAXIS_MOTION;
 	event.joystick.axis = ev.caxis.axis;
 	event.joystick.position = ev.caxis.value;
+	event.type = Common::EVENT_JOYAXIS_MOTION;
 
 	return true;
 }
-#endif
-
-bool SdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) {
-	return false;
-}
-
-void SdlEventSource::fakeWarpMouse(const int x, const int y) {
-	_queuedFakeMouseMove = true;
-	_fakeMouseMove.type = Common::EVENT_MOUSEMOVE;
-	_fakeMouseMove.mouse = Common::Point(x, y);
-}
 
 bool SdlEventSource::isJoystickConnected() const {
-	return _joystick
-#if SDL_VERSION_ATLEAST(2, 0, 0)
-	        || _controller
-#endif
-	        ;
-}
-
-void SdlEventSource::setEngineRunning(const bool value) {
-	_engineRunning = value;
-}
-
-bool SdlEventSource::handleResizeEvent(Common::Event &event, int w, int h) {
-	if (_graphicsManager) {
-		_graphicsManager->notifyResize(w, h);
-
-		// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
-		int screenID = g_system->getScreenChangeID();
-		if (screenID != _lastScreenID) {
-			_lastScreenID = screenID;
-			event.type = Common::EVENT_SCREEN_CHANGED;
-			return true;
-		}
-	}
-
-	return false;
+	return _joystick || _controller;
 }
 
 SDL_Keycode SdlEventSource::obtainKeycode(const SDL_Keysym keySym) {
-#if !SDL_VERSION_ATLEAST(2, 0, 0) && defined(WIN32)
-	// WORKAROUND: SDL 1.2 on Windows does not use the user configured keyboard layout,
-	// resulting in "keySym.sym" values to always be those expected for an US keyboard.
-	// For example, SDL returns SDLK_Q when pressing the 'A' key on an AZERTY keyboard.
-	// This defeats the purpose of keycodes which is to be able to refer to a key without
-	// knowing where it is physically located.
-	// We work around this issue by querying the currently active Windows keyboard layout
-	// using the scancode provided by SDL.
-
-	if (keySym.sym >= SDLK_0 && keySym.sym <= SDLK_9) {
-		// The keycode returned by SDL is kept for the number keys.
-		// Querying the keyboard layout for those would return the base key values
-		// for AZERTY keyboards, which are not numbers. For example, SDLK_1 would
-		// map to SDLK_AMPERSAND. This is theoretically correct but practically unhelpful,
-		// because it makes it impossible to handle key combinations such as "ctrl-1".
-		return keySym.sym;
-	}
-
-	int vk = MapVirtualKey(keySym.scancode, MAPVK_VSC_TO_VK);
-	if (vk) {
-		int ch = (MapVirtualKey(vk, MAPVK_VK_TO_CHAR) & 0x7FFF);
-		// The top bit of the result of MapVirtualKey with MAPVK_VSC_TO_VK signals
-		// a dead key was pressed. In that case we keep the value of the accent alone.
-		if (ch) {
-			if (ch >= 'A' && ch <= 'Z') {
-				// Windows returns uppercase ASCII whereas SDL expects lowercase
-				return (SDL_Keycode)(SDLK_a + (ch - 'A'));
-			} else {
-				return (SDL_Keycode)ch;
-			}
-		}
-	}
-#endif
-
 	return keySym.sym;
 }
 
-uint32 SdlEventSource::obtainUnicode(const SDL_Keysym keySym) {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+uint32 SdlEventSource::obtainUnicode(const SDL_KeyboardEvent &key) {
 	SDL_Event events[2];
 
 	// Update the event queue here to give SDL a chance to insert TEXTINPUT
@@ -1417,9 +1091,6 @@ uint32 SdlEventSource::obtainUnicode(const SDL_Keysym keySym) {
 	} else {
 		return 0;
 	}
-#else
-	return keySym.unicode;
-#endif
 }
 
 #endif
diff --git a/backends/events/sdl/sdl3-events.cpp b/backends/events/sdl/sdl3-events.cpp
new file mode 100644
index 00000000000..c2f7080a49e
--- /dev/null
+++ b/backends/events/sdl/sdl3-events.cpp
@@ -0,0 +1,1052 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#if defined(SDL_BACKEND)
+
+#include "backends/events/sdl/sdl-events.h"
+#include "backends/platform/sdl/sdl.h"
+#include "backends/graphics/graphics.h"
+#include "common/config-manager.h"
+#include "common/textconsole.h"
+#include "common/fs.h"
+#include "engines/engine.h"
+#include "gui/gui-manager.h"
+
+#if defined(USE_IMGUI)
+#include "backends/imgui/backends/imgui_impl_sdl3.h"
+#endif
+
+#define GAMECONTROLLERDB_FILE "gamecontrollerdb.txt"
+
+static uint32 convUTF8ToUTF32(const char *src) {
+	if (!src || src[0] == 0)
+		return 0;
+
+	Common::U32String u32(src);
+	return u32[0];
+}
+
+void SdlEventSource::loadGameControllerMappingFile() {
+	bool loaded = false;
+	if (ConfMan.hasKey("controller_map_db")) {
+		Common::FSNode file = Common::FSNode(ConfMan.getPath("controller_map_db"));
+		if (file.exists()) {
+			if (!SDL_AddGamepadMappingsFromFile(file.getPath().toString(Common::Path::kNativeSeparator).c_str()))
+				error("File %s not valid: %s", file.getPath().toString(Common::Path::kNativeSeparator).c_str(), SDL_GetError());
+			else {
+				loaded = true;
+				debug("Game controller DB file loaded: %s", file.getPath().toString(Common::Path::kNativeSeparator).c_str());
+			}
+		} else
+			warning("Game controller DB file not found: %s", file.getPath().toString(Common::Path::kNativeSeparator).c_str());
+	}
+	if (!loaded && ConfMan.hasKey("extrapath")) {
+		Common::FSNode dir = Common::FSNode(ConfMan.getPath("extrapath"));
+		Common::FSNode file = dir.getChild(GAMECONTROLLERDB_FILE);
+		if (file.exists()) {
+			if (!SDL_AddGamepadMappingsFromFile(file.getPath().toString(Common::Path::kNativeSeparator).c_str()))
+				error("File %s not valid: %s", file.getPath().toString(Common::Path::kNativeSeparator).c_str(), SDL_GetError());
+			else
+				debug("Game controller DB file loaded: %s", file.getPath().toString(Common::Path::kNativeSeparator).c_str());
+		}
+	}
+}
+
+SdlEventSource::SdlEventSource()
+	: EventSource(), _scrollLock(false), _joystick(nullptr), _lastScreenID(0), _graphicsManager(nullptr), _queuedFakeMouseMove(false),
+	  _lastHatPosition(SDL_HAT_CENTERED), _mouseX(0), _mouseY(0), _engineRunning(false)
+	  , _queuedFakeKeyUp(false), _fakeKeyUp(), _controller(nullptr) {
+	int joystick_num = ConfMan.getInt("joystick_num");
+	if (joystick_num >= 0) {
+		// Initialize SDL joystick subsystem
+		if (!SDL_InitSubSystem(SDL_INIT_JOYSTICK)) {
+			warning("Could not initialize SDL joystick: %s", SDL_GetError());
+			return;
+		}
+
+		if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
+			warning("Could not initialize SDL game controller: %s", SDL_GetError());
+			return;
+		}
+		loadGameControllerMappingFile();
+
+		openJoystick(joystick_num);
+	}
+
+	// ensure that touch doesn't create double-events
+	SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
+}
+
+int SdlEventSource::mapKey(SDL_Keycode sdlKey, SDL_Keymod mod, Uint16 unicode) {
+	Common::KeyCode key = SDLToOSystemKeycode(sdlKey);
+
+	// Keep unicode in case it's regular ASCII text, Hebrew or in case we didn't get a valid keycode
+	//
+	// We need to use unicode in those cases, simply because SDL1.x passes us non-layout-adjusted keycodes.
+	// So unicode is the only way to get layout-adjusted keys.
+	if (unicode < 0x20) {
+		// don't use unicode, in case it's control characters
+		unicode = 0;
+	} else {
+		// Use unicode, in case keycode is invalid.
+		// Umlauts and others will set KEYCODE_INVALID on SDL2, so in such a case always keep unicode.
+		if (key != Common::KEYCODE_INVALID) {
+			// keycode is valid, check further also depending on modifiers
+			if (mod & (SDL_KMOD_CTRL | SDL_KMOD_ALT)) {
+				// Ctrl and/or Alt is active
+				//
+				// We need to restrict unicode to only up to 0x7E, because on macOS the option/alt key will switch to
+				// an alternate keyboard, which will cause us to receive Unicode characters for some keys, which are outside
+				// of the ASCII range (e.g. alt-x will get us U+2248). We need to return 'x' for alt-x, so using unicode
+				// in that case would break alt-shortcuts.
+				if (unicode > 0x7E)
+					unicode = 0; // do not allow any characters above 0x7E
+			} else {
+				// We allow Hebrew characters
+				if (unicode >= 0x05D0 && unicode <= 0x05EA)
+					return unicode;
+
+				// Cyrillic
+				if (unicode >= 0x0400 && unicode <= 0x045F)
+					return unicode;
+
+				// We must not restrict as much as when Ctrl/Alt-modifiers are active, otherwise
+				// we wouldn't let umlauts through for SDL1. For SDL1 umlauts may set for example KEYCODE_QUOTE, KEYCODE_MINUS, etc.
+				if (unicode > 0xFF)
+					unicode = 0; // do not allow any characters above 0xFF
+			}
+		}
+	}
+
+	if (key >= Common::KEYCODE_F1 && key <= Common::KEYCODE_F9) {
+		return key - Common::KEYCODE_F1 + Common::ASCII_F1;
+	} else if (key >= Common::KEYCODE_KP0 && key <= Common::KEYCODE_KP9) {
+		if ((mod & SDL_KMOD_NUM) == 0)
+			return 0; // In case Num-Lock is NOT enabled, return 0 for ascii, so that directional keys on numpad work
+		return key - Common::KEYCODE_KP0 + '0';
+	} else if (key >= Common::KEYCODE_UP && key <= Common::KEYCODE_PAGEDOWN) {
+		return key;
+	} else if (unicode) {
+		// Return unicode in case it's still set and wasn't filtered.
+		return unicode;
+	} else if (key >= 'a' && key <= 'z' && (mod & SDL_KMOD_SHIFT)) {
+		return key & ~0x20;
+	} else if (key >= Common::KEYCODE_NUMLOCK && key < Common::KEYCODE_LAST) {
+		return 0;
+	} else {
+		return key;
+	}
+}
+
+void SdlEventSource::SDLModToOSystemKeyFlags(SDL_Keymod mod, Common::Event &event) {
+
+	event.kbd.flags = 0;
+
+	if (mod & SDL_KMOD_GUI)
+		event.kbd.flags |= Common::KBD_META;
+	if (mod & SDL_KMOD_SHIFT)
+		event.kbd.flags |= Common::KBD_SHIFT;
+	if (mod & SDL_KMOD_ALT)
+		event.kbd.flags |= Common::KBD_ALT;
+	if (mod & SDL_KMOD_CTRL)
+		event.kbd.flags |= Common::KBD_CTRL;
+
+	// Sticky flags
+	if (mod & SDL_KMOD_NUM)
+		event.kbd.flags |= Common::KBD_NUM;
+	if (mod & SDL_KMOD_CAPS)
+		event.kbd.flags |= Common::KBD_CAPS;
+}
+
+Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDL_Keycode key) {
+	switch (key) {
+	case SDLK_BACKSPACE: return Common::KEYCODE_BACKSPACE;
+	case SDLK_TAB: return Common::KEYCODE_TAB;
+	case SDLK_CLEAR: return Common::KEYCODE_CLEAR;
+	case SDLK_RETURN: return Common::KEYCODE_RETURN;
+	case SDLK_PAUSE: return Common::KEYCODE_PAUSE;
+	case SDLK_ESCAPE: return Common::KEYCODE_ESCAPE;
+	case SDLK_SPACE: return Common::KEYCODE_SPACE;
+	case SDLK_EXCLAIM: return Common::KEYCODE_EXCLAIM;
+	case SDLK_DBLAPOSTROPHE: return Common::KEYCODE_QUOTEDBL;
+	case SDLK_HASH: return Common::KEYCODE_HASH;
+	case SDLK_DOLLAR: return Common::KEYCODE_DOLLAR;
+	case SDLK_AMPERSAND: return Common::KEYCODE_AMPERSAND;
+	case SDLK_APOSTROPHE: return Common::KEYCODE_QUOTE;
+	case SDLK_LEFTPAREN: return Common::KEYCODE_LEFTPAREN;
+	case SDLK_RIGHTPAREN: return Common::KEYCODE_RIGHTPAREN;
+	case SDLK_ASTERISK: return Common::KEYCODE_ASTERISK;
+	case SDLK_PLUS: return Common::KEYCODE_PLUS;
+	case SDLK_COMMA: return Common::KEYCODE_COMMA;
+	case SDLK_MINUS: return Common::KEYCODE_MINUS;
+	case SDLK_PERIOD: return Common::KEYCODE_PERIOD;
+	case SDLK_SLASH: return Common::KEYCODE_SLASH;
+	case SDLK_0: return Common::KEYCODE_0;
+	case SDLK_1: return Common::KEYCODE_1;
+	case SDLK_2: return Common::KEYCODE_2;
+	case SDLK_3: return Common::KEYCODE_3;
+	case SDLK_4: return Common::KEYCODE_4;
+	case SDLK_5: return Common::KEYCODE_5;
+	case SDLK_6: return Common::KEYCODE_6;
+	case SDLK_7: return Common::KEYCODE_7;
+	case SDLK_8: return Common::KEYCODE_8;
+	case SDLK_9: return Common::KEYCODE_9;
+	case SDLK_COLON: return Common::KEYCODE_COLON;
+	case SDLK_SEMICOLON: return Common::KEYCODE_SEMICOLON;
+	case SDLK_LESS: return Common::KEYCODE_LESS;
+	case SDLK_EQUALS: return Common::KEYCODE_EQUALS;
+	case SDLK_GREATER: return Common::KEYCODE_GREATER;
+	case SDLK_QUESTION: return Common::KEYCODE_QUESTION;
+	case SDLK_AT: return Common::KEYCODE_AT;
+	case SDLK_LEFTBRACKET: return Common::KEYCODE_LEFTBRACKET;
+	case SDLK_BACKSLASH: return Common::KEYCODE_BACKSLASH;
+	case SDLK_RIGHTBRACKET: return Common::KEYCODE_RIGHTBRACKET;
+	case SDLK_CARET: return Common::KEYCODE_CARET;
+	case SDLK_UNDERSCORE: return Common::KEYCODE_UNDERSCORE;
+	case SDLK_GRAVE: return Common::KEYCODE_BACKQUOTE;
+	case SDLK_A: return Common::KEYCODE_a;
+	case SDLK_B: return Common::KEYCODE_b;
+	case SDLK_C: return Common::KEYCODE_c;
+	case SDLK_D: return Common::KEYCODE_d;
+	case SDLK_E: return Common::KEYCODE_e;
+	case SDLK_F: return Common::KEYCODE_f;
+	case SDLK_G: return Common::KEYCODE_g;
+	case SDLK_H: return Common::KEYCODE_h;
+	case SDLK_I: return Common::KEYCODE_i;
+	case SDLK_J: return Common::KEYCODE_j;
+	case SDLK_K: return Common::KEYCODE_k;
+	case SDLK_L: return Common::KEYCODE_l;
+	case SDLK_M: return Common::KEYCODE_m;
+	case SDLK_N: return Common::KEYCODE_n;
+	case SDLK_O: return Common::KEYCODE_o;
+	case SDLK_P: return Common::KEYCODE_p;
+	case SDLK_Q: return Common::KEYCODE_q;
+	case SDLK_R: return Common::KEYCODE_r;
+	case SDLK_S: return Common::KEYCODE_s;
+	case SDLK_T: return Common::KEYCODE_t;
+	case SDLK_U: return Common::KEYCODE_u;
+	case SDLK_V: return Common::KEYCODE_v;
+	case SDLK_W: return Common::KEYCODE_w;
+	case SDLK_X: return Common::KEYCODE_x;
+	case SDLK_Y: return Common::KEYCODE_y;
+	case SDLK_Z: return Common::KEYCODE_z;
+	case SDLK_DELETE: return Common::KEYCODE_DELETE;
+	case SDLK_KP_PERIOD: return Common::KEYCODE_KP_PERIOD;
+	case SDLK_KP_DIVIDE: return Common::KEYCODE_KP_DIVIDE;
+	case SDLK_KP_MULTIPLY: return Common::KEYCODE_KP_MULTIPLY;
+	case SDLK_KP_MINUS: return Common::KEYCODE_KP_MINUS;
+	case SDLK_KP_PLUS: return Common::KEYCODE_KP_PLUS;
+	case SDLK_KP_ENTER: return Common::KEYCODE_KP_ENTER;
+	case SDLK_KP_EQUALS: return Common::KEYCODE_KP_EQUALS;
+	case SDLK_UP: return Common::KEYCODE_UP;
+	case SDLK_DOWN: return Common::KEYCODE_DOWN;
+	case SDLK_RIGHT: return Common::KEYCODE_RIGHT;
+	case SDLK_LEFT: return Common::KEYCODE_LEFT;
+	case SDLK_INSERT: return Common::KEYCODE_INSERT;
+	case SDLK_HOME: return Common::KEYCODE_HOME;
+	case SDLK_END: return Common::KEYCODE_END;
+	case SDLK_PAGEUP: return Common::KEYCODE_PAGEUP;
+	case SDLK_PAGEDOWN: return Common::KEYCODE_PAGEDOWN;
+	case SDLK_F1: return Common::KEYCODE_F1;
+	case SDLK_F2: return Common::KEYCODE_F2;
+	case SDLK_F3: return Common::KEYCODE_F3;
+	case SDLK_F4: return Common::KEYCODE_F4;
+	case SDLK_F5: return Common::KEYCODE_F5;
+	case SDLK_F6: return Common::KEYCODE_F6;
+	case SDLK_F7: return Common::KEYCODE_F7;
+	case SDLK_F8: return Common::KEYCODE_F8;
+	case SDLK_F9: return Common::KEYCODE_F9;
+	case SDLK_F10: return Common::KEYCODE_F10;
+	case SDLK_F11: return Common::KEYCODE_F11;
+	case SDLK_F12: return Common::KEYCODE_F12;
+	case SDLK_F13: return Common::KEYCODE_F13;
+	case SDLK_F14: return Common::KEYCODE_F14;
+	case SDLK_F15: return Common::KEYCODE_F15;
+	case SDLK_CAPSLOCK: return Common::KEYCODE_CAPSLOCK;
+	case SDLK_RSHIFT: return Common::KEYCODE_RSHIFT;
+	case SDLK_LSHIFT: return Common::KEYCODE_LSHIFT;
+	case SDLK_RCTRL: return Common::KEYCODE_RCTRL;
+	case SDLK_LCTRL: return Common::KEYCODE_LCTRL;
+	case SDLK_RALT: return Common::KEYCODE_RALT;
+	case SDLK_LALT: return Common::KEYCODE_LALT;
+	case SDLK_MODE: return Common::KEYCODE_MODE;
+	case SDLK_HELP: return Common::KEYCODE_HELP;
+	case SDLK_SYSREQ: return Common::KEYCODE_SYSREQ;
+	case SDLK_MENU: return Common::KEYCODE_MENU;
+	case SDLK_POWER: return Common::KEYCODE_POWER;
+	case SDLK_UNDO: return Common::KEYCODE_UNDO;
+	case SDLK_SCROLLLOCK: return Common::KEYCODE_SCROLLOCK;
+	case SDLK_NUMLOCKCLEAR: return Common::KEYCODE_NUMLOCK;
+	case SDLK_LGUI: return Common::KEYCODE_LSUPER;
+	case SDLK_RGUI: return Common::KEYCODE_RSUPER;
+	case SDLK_PRINTSCREEN: return Common::KEYCODE_PRINT;
+	case SDLK_APPLICATION: return Common::KEYCODE_COMPOSE;
+	case SDLK_KP_0: return Common::KEYCODE_KP0;
+	case SDLK_KP_1: return Common::KEYCODE_KP1;
+	case SDLK_KP_2: return Common::KEYCODE_KP2;
+	case SDLK_KP_3: return Common::KEYCODE_KP3;
+	case SDLK_KP_4: return Common::KEYCODE_KP4;
+	case SDLK_KP_5: return Common::KEYCODE_KP5;
+	case SDLK_KP_6: return Common::KEYCODE_KP6;
+	case SDLK_KP_7: return Common::KEYCODE_KP7;
+	case SDLK_KP_8: return Common::KEYCODE_KP8;
+	case SDLK_KP_9: return Common::KEYCODE_KP9;
+	case SDLK_PERCENT: return Common::KEYCODE_PERCENT;
+	case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_GRAVE): return Common::KEYCODE_TILDE;
+	case SDLK_F16: return Common::KEYCODE_F16;
+	case SDLK_F17: return Common::KEYCODE_F17;
+	case SDLK_F18: return Common::KEYCODE_F18;
+	case SDLK_SLEEP: return Common::KEYCODE_SLEEP;
+	case SDLK_VOLUMEUP: return Common::KEYCODE_VOLUMEUP;
+	case SDLK_VOLUMEDOWN: return Common::KEYCODE_VOLUMEDOWN;
+	case SDLK_MEDIA_EJECT: return Common::KEYCODE_EJECT;
+	case SDLK_MEDIA_NEXT_TRACK: return Common::KEYCODE_AUDIONEXT;
+	case SDLK_MEDIA_PREVIOUS_TRACK: return Common::KEYCODE_AUDIOPREV;
+	case SDLK_MEDIA_STOP: return Common::KEYCODE_AUDIOSTOP;
+	case SDLK_MEDIA_PLAY: return Common::KEYCODE_AUDIOPLAYPAUSE;
+	case SDLK_MUTE: return Common::KEYCODE_AUDIOMUTE;
+	case SDLK_CUT: return Common::KEYCODE_CUT;
+	case SDLK_COPY: return Common::KEYCODE_COPY;
+	case SDLK_PASTE: return Common::KEYCODE_PASTE;
+	case SDLK_SELECT: return Common::KEYCODE_SELECT;
+	case SDLK_CANCEL: return Common::KEYCODE_CANCEL;
+	case SDLK_AC_SEARCH: return Common::KEYCODE_AC_SEARCH;
+	case SDLK_AC_HOME: return Common::KEYCODE_AC_HOME;
+	case SDLK_AC_BACK: return Common::KEYCODE_AC_BACK;
+	case SDLK_AC_FORWARD: return Common::KEYCODE_AC_FORWARD;
+	case SDLK_AC_STOP: return Common::KEYCODE_AC_STOP;
+	case SDLK_AC_REFRESH: return Common::KEYCODE_AC_REFRESH;
+	case SDLK_AC_BOOKMARKS: return Common::KEYCODE_AC_BOOKMARKS;
+	case SDLK_MEDIA_REWIND: return Common::KEYCODE_AUDIOREWIND;
+	case SDLK_MEDIA_FAST_FORWARD: return Common::KEYCODE_AUDIOFASTFORWARD;
+	default: return Common::KEYCODE_INVALID;
+	}
+}
+
+void SdlEventSource::preprocessFingerDown(SDL_Event *event) {
+	// front (1) or back (2) panel
+	SDL_TouchID port = event->tfinger.touchID;
+	// id (for multitouch)
+	SDL_FingerID id = event->tfinger.fingerID;
+
+	int x = _mouseX;
+	int y = _mouseY;
+
+	if (!isTouchPortTouchpadMode(port)) {
+		convertTouchXYToGameXY(event->tfinger.x, event->tfinger.y, &x, &y);
+	}
+
+	// make sure each finger is not reported down multiple times
+	for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+		if (_touchPanels[port]._finger[i].id == id) {
+			_touchPanels[port]._finger[i].id = -1;
+		}
+	}
+
+	// we need the timestamps to decide later if the user performed a short tap (click)
+	// or a long tap (drag)
+	// we also need the last coordinates for each finger to keep track of dragging
+	for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+		if (_touchPanels[port]._finger[i].id == -1) {
+			_touchPanels[port]._finger[i].id = id;
+			_touchPanels[port]._finger[i].timeLastDown = event->tfinger.timestamp;
+			_touchPanels[port]._finger[i].lastDownX = event->tfinger.x;
+			_touchPanels[port]._finger[i].lastDownY = event->tfinger.y;
+			_touchPanels[port]._finger[i].lastX = x;
+			_touchPanels[port]._finger[i].lastY = y;
+			break;
+		}
+	}
+}
+
+void SdlEventSource::finishSimulatedMouseClicks() {
+	for (auto &panel : _touchPanels) {
+		for (int i = 0; i < 2; i++) {
+			if (panel._value._simulatedClickStartTime[i] != 0) {
+				Uint32 currentTime = SDL_GetTicks();
+				if (currentTime - panel._value._simulatedClickStartTime[i] >= SIMULATED_CLICK_DURATION) {
+					int simulatedButton;
+					if (i == 0) {
+						simulatedButton = SDL_BUTTON_LEFT;
+					} else {
+						simulatedButton = SDL_BUTTON_RIGHT;
+					}
+					SDL_Event ev;
+					ev.type = SDL_EVENT_MOUSE_BUTTON_UP;
+					ev.button.button = simulatedButton;
+					ev.button.x = _mouseX;
+					ev.button.y = _mouseY;
+					SDL_PushEvent(&ev);
+
+					panel._value._simulatedClickStartTime[i] = 0;
+				}
+			}
+		}
+	}
+}
+
+bool SdlEventSource::preprocessFingerUp(SDL_Event *event, Common::Event *ev) {
+	// front (1) or back (2) panel
+	SDL_TouchID port = event->tfinger.touchID;
+	// id (for multitouch)
+	SDL_FingerID id = event->tfinger.fingerID;
+
+	// find out how many fingers were down before this event
+	int numFingersDown = 0;
+	for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+		if (_touchPanels[port]._finger[i].id >= 0) {
+			numFingersDown++;
+		}
+	}
+
+	int x = _mouseX;
+	int y = _mouseY;
+
+	for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+		if (_touchPanels[port]._finger[i].id == id) {
+			_touchPanels[port]._finger[i].id = -1;
+			if (!_touchPanels[port]._multiFingerDragging) {
+				if ((event->tfinger.timestamp - _touchPanels[port]._finger[i].timeLastDown) <= MAX_TAP_TIME && !_touchPanels[port]._tapMade) {
+					// short (<MAX_TAP_TIME ms) tap is interpreted as right/left mouse click depending on # fingers already down
+					// but only if the finger hasn't moved since it was pressed down by more than MAX_TAP_MOTION_DISTANCE pixels
+					Common::Point touchscreenSize = getTouchscreenSize();
+					float xrel = ((event->tfinger.x * (float) touchscreenSize.x) - (_touchPanels[port]._finger[i].lastDownX * (float) touchscreenSize.x));
+					float yrel = ((event->tfinger.y * (float) touchscreenSize.y) - (_touchPanels[port]._finger[i].lastDownY * (float) touchscreenSize.y));
+					float maxRSquared = (float) (MAX_TAP_MOTION_DISTANCE * MAX_TAP_MOTION_DISTANCE);
+					if ((xrel * xrel + yrel * yrel) < maxRSquared) {
+						if (numFingersDown == 3) {
+							_touchPanels[port]._tapMade = true;
+							ev->type = Common::EVENT_VIRTUAL_KEYBOARD;
+							return true;
+						} else if (numFingersDown == 2 || numFingersDown == 1) {
+							Uint8 simulatedButton = 0;
+							if (numFingersDown == 2) {
+								simulatedButton = SDL_BUTTON_RIGHT;
+								// need to raise the button later
+								_touchPanels[port]._simulatedClickStartTime[1] = event->tfinger.timestamp;
+								_touchPanels[port]._tapMade = true;
+							} else if (numFingersDown == 1) {
+								simulatedButton = SDL_BUTTON_LEFT;
+								// need to raise the button later
+								_touchPanels[port]._simulatedClickStartTime[0] = event->tfinger.timestamp;
+								if (!isTouchPortTouchpadMode(port)) {
+									convertTouchXYToGameXY(event->tfinger.x, event->tfinger.y, &x, &y);
+								}
+							}
+
+							event->type = SDL_EVENT_MOUSE_BUTTON_DOWN;
+							event->button.button = simulatedButton;
+							event->button.x = x;
+							event->button.y = y;
+						}
+					}
+				}
+			} else if (numFingersDown == 1) {
+				// when dragging, and the last finger is lifted, the drag is over
+				if (!isTouchPortTouchpadMode(port)) {
+					convertTouchXYToGameXY(event->tfinger.x, event->tfinger.y, &x, &y);
+				}
+				Uint8 simulatedButton = 0;
+				if (_touchPanels[port]._multiFingerDragging == DRAG_THREE_FINGER)
+					simulatedButton = SDL_BUTTON_RIGHT;
+				else {
+					simulatedButton = SDL_BUTTON_LEFT;
+				}
+				event->type = SDL_EVENT_MOUSE_BUTTON_UP;
+				event->button.button = simulatedButton;
+				event->button.x = x;
+				event->button.y = y;
+				_touchPanels[port]._multiFingerDragging = DRAG_NONE;
+			}
+		}
+	}
+
+	if (numFingersDown == 1) {
+		_touchPanels[port]._tapMade = false;
+	}
+
+	return false;
+}
+
+void SdlEventSource::preprocessFingerMotion(SDL_Event *event) {
+	// front (1) or back (2) panel
+	SDL_TouchID port = event->tfinger.touchID;
+	// id (for multitouch)
+	SDL_FingerID id = event->tfinger.fingerID;
+
+	// find out how many fingers were down before this event
+	int numFingersDown = 0;
+	for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+		if (_touchPanels[port]._finger[i].id >= 0) {
+			numFingersDown++;
+		}
+	}
+
+	if (numFingersDown >= 1) {
+		int x = _mouseX;
+		int y = _mouseY;
+		int xMax = _graphicsManager->getWindowWidth() - 1;
+		int yMax = _graphicsManager->getWindowHeight() - 1;
+
+		if (!isTouchPortTouchpadMode(port)) {
+			convertTouchXYToGameXY(event->tfinger.x, event->tfinger.y, &x, &y);
+		} else {
+			// for relative mode, use the pointer speed setting
+			const int kbdMouseSpeed = CLIP<int>(ConfMan.getInt("kbdmouse_speed"), 0, 7);
+			float speedFactor = (kbdMouseSpeed + 1) * 0.25;
+
+			// convert touch events to relative mouse pointer events
+			// track sub-pixel relative finger motion using the FINGER_SUBPIXEL_MULTIPLIER
+			_touchPanels[port]._hiresDX += (event->tfinger.dx * 1.25 * speedFactor * xMax * FINGER_SUBPIXEL_MULTIPLIER);
+			_touchPanels[port]._hiresDY += (event->tfinger.dy * 1.25 * speedFactor * yMax * FINGER_SUBPIXEL_MULTIPLIER);
+			int xRel = _touchPanels[port]._hiresDX / FINGER_SUBPIXEL_MULTIPLIER;
+			int yRel = _touchPanels[port]._hiresDY / FINGER_SUBPIXEL_MULTIPLIER;
+			x = _mouseX + xRel;
+			y = _mouseY + yRel;
+			_touchPanels[port]._hiresDX %= FINGER_SUBPIXEL_MULTIPLIER;
+			_touchPanels[port]._hiresDY %= FINGER_SUBPIXEL_MULTIPLIER;
+		}
+
+		x = CLIP(x, 0, xMax);
+		y = CLIP(y, 0, yMax);
+
+		// update the current finger's coordinates so we can track it later
+		for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+			if (_touchPanels[port]._finger[i].id == id) {
+				_touchPanels[port]._finger[i].lastX = x;
+				_touchPanels[port]._finger[i].lastY = y;
+			}
+		}
+
+		// If we are starting a multi-finger drag, start holding down the mouse button
+		if (numFingersDown >= 2) {
+			if (!_touchPanels[port]._multiFingerDragging) {
+				// only start a multi-finger drag if at least two fingers have been down long enough
+				int numFingersDownLong = 0;
+				for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+					if (_touchPanels[port]._finger[i].id >= 0) {
+						if (event->tfinger.timestamp - _touchPanels[port]._finger[i].timeLastDown > MAX_TAP_TIME) {
+							numFingersDownLong++;
+						}
+					}
+				}
+				if (numFingersDownLong >= 2) {
+					// starting drag, so push mouse down at current location (back)
+					// or location of "oldest" finger (front)
+					int mouseDownX = _mouseX;
+					int mouseDownY = _mouseY;
+					if (!isTouchPortTouchpadMode(port)) {
+						for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+							if (_touchPanels[port]._finger[i].id == id) {
+								Uint32 earliestTime = _touchPanels[port]._finger[i].timeLastDown;
+								for (int j = 0; j < MAX_NUM_FINGERS; j++) {
+									if (_touchPanels[port]._finger[j].id >= 0 && (i != j) ) {
+										if (_touchPanels[port]._finger[j].timeLastDown < earliestTime) {
+											mouseDownX = _touchPanels[port]._finger[j].lastX;
+											mouseDownY = _touchPanels[port]._finger[j].lastY;
+											earliestTime = _touchPanels[port]._finger[j].timeLastDown;
+										}
+									}
+								}
+								break;
+							}
+						}
+					}
+					Uint8 simulatedButton = 0;
+					if (numFingersDownLong == 2) {
+						simulatedButton = SDL_BUTTON_LEFT;
+						_touchPanels[port]._multiFingerDragging = DRAG_TWO_FINGER;
+					} else {
+						simulatedButton = SDL_BUTTON_RIGHT;
+						_touchPanels[port]._multiFingerDragging = DRAG_THREE_FINGER;
+					}
+					SDL_Event ev;
+					ev.type = SDL_EVENT_MOUSE_BUTTON_DOWN;
+					ev.button.button = simulatedButton;
+					ev.button.x = mouseDownX;
+					ev.button.y = mouseDownY;
+					SDL_PushEvent(&ev);
+				}
+			}
+		}
+
+		//check if this is the "oldest" finger down (or the only finger down), otherwise it will not affect mouse motion
+		bool updatePointer = true;
+		if (numFingersDown > 1) {
+			for (int i = 0; i < MAX_NUM_FINGERS; i++) {
+				if (_touchPanels[port]._finger[i].id == id) {
+					for (int j = 0; j < MAX_NUM_FINGERS; j++) {
+						if (_touchPanels[port]._finger[j].id >= 0 && (i != j) ) {
+							if (_touchPanels[port]._finger[j].timeLastDown < _touchPanels[port]._finger[i].timeLastDown) {
+								updatePointer = false;
+							}
+						}
+					}
+				}
+			}
+		}
+		if (updatePointer) {
+			event->type = SDL_EVENT_MOUSE_MOTION;
+			event->motion.x = x;
+			event->motion.y = y;
+		}
+	}
+}
+
+bool SdlEventSource::pollEvent(Common::Event &event) {
+	finishSimulatedMouseClicks();
+
+	// In case we still need to send a key up event for a key down from a
+	// TEXTINPUT event we do this immediately.
+	if (_queuedFakeKeyUp) {
+		event = _fakeKeyUp;
+		_queuedFakeKeyUp = false;
+		return true;
+	}
+
+	// If the screen changed, send an Common::EVENT_SCREEN_CHANGED
+	int screenID = g_system->getScreenChangeID();
+	if (screenID != _lastScreenID) {
+		_lastScreenID = screenID;
+		event.type = Common::EVENT_SCREEN_CHANGED;
+		return true;
+	}
+
+	if (_queuedFakeMouseMove) {
+		event = _fakeMouseMove;
+		_queuedFakeMouseMove = false;
+		return true;
+	}
+
+	SDL_Event ev;
+	while (SDL_PollEvent(&ev)) {
+		preprocessEvents(&ev);
+
+		// Supported touch gestures:
+		// left mouse click: single finger short tap
+		// right mouse click: second finger short tap while first finger is still down
+		// pointer motion: single finger drag
+		if (ev.type == SDL_EVENT_FINGER_DOWN || ev.type == SDL_EVENT_FINGER_UP || ev.type == SDL_EVENT_FINGER_MOTION) {
+			// front (0) or back (1) panel
+			SDL_TouchID port = ev.tfinger.touchID;
+			// touchpad_mouse_mode off: use only front panel for direct touch control of pointer
+			// touchpad_mouse_mode on: also enable rear touch with indirect touch control
+			// where the finger can be somewhere else than the pointer and still move it
+			if (isTouchPortActive(port)) {
+				switch (ev.type) {
+				case SDL_EVENT_FINGER_DOWN:
+					preprocessFingerDown(&ev);
+					break;
+				case SDL_EVENT_FINGER_UP:
+					if (preprocessFingerUp(&ev, &event))
+						return true;
+					break;
+				case SDL_EVENT_FINGER_MOTION:
+					preprocessFingerMotion(&ev);
+					break;
+				}
+			}
+		}
+
+#if defined(USE_IMGUI)
+		ImGui_ImplSDL3_ProcessEvent(&ev);
+		ImGuiIO &io = ImGui::GetIO();
+		if (io.WantTextInput || io.WantCaptureMouse)
+			continue;
+#endif
+		if (dispatchSDLEvent(ev, event))
+			return true;
+	}
+
+	return false;
+}
+
+bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
+	switch (ev.type) {
+	case SDL_EVENT_KEY_DOWN:
+		return handleKeyDown(ev, event);
+	case SDL_EVENT_KEY_UP:
+		return handleKeyUp(ev, event);
+	case SDL_EVENT_MOUSE_MOTION:
+		return handleMouseMotion(ev, event);
+	case SDL_EVENT_MOUSE_BUTTON_DOWN:
+		return handleMouseButtonDown(ev, event);
+	case SDL_EVENT_MOUSE_BUTTON_UP:
+		return handleMouseButtonUp(ev, event);
+
+	case SDL_EVENT_MOUSE_WHEEL: {
+		Sint32 yDir = ev.wheel.y;
+		// We want the mouse coordinates supplied with a mouse wheel event.
+		// However, SDL2 does not supply these, thus we use whatever we got
+		// last time.
+		if (!processMouseEvent(event, _mouseX, _mouseY)) {
+			return false;
+		}
+		if (yDir < 0) {
+			event.type = Common::EVENT_WHEELDOWN;
+			return true;
+		} else if (yDir > 0) {
+			event.type = Common::EVENT_WHEELUP;
+			return true;
+		} else {
+			return false;
+		}
+		}
+
+	case SDL_EVENT_TEXT_INPUT: {
+		// When we get a TEXTINPUT event it means we got some user input for
+		// which no KEYDOWN exists. SDL 1.2 introduces a "fake" key down+up
+		// in such cases. We will do the same to mimic it's behavior.
+		event.type = Common::EVENT_KEYDOWN;
+
+		event.kbd = Common::KeyState(Common::KEYCODE_INVALID, convUTF8ToUTF32(ev.text.text), 0);
+
+		SDLModToOSystemKeyFlags(SDL_GetModState(), event);
+		// Set the scroll lock sticky flag
+		if (_scrollLock)
+			event.kbd.flags |= Common::KBD_SCRL;
+
+		// Fake a key up when we have a proper ascii value.
+		_queuedFakeKeyUp = (event.kbd.ascii != 0);
+		_fakeKeyUp = event;
+		_fakeKeyUp.type = Common::EVENT_KEYUP;
+
+		return _queuedFakeKeyUp;
+		}
+
+		case SDL_EVENT_WINDOW_EXPOSED:
+			if (_graphicsManager) {
+				_graphicsManager->notifyVideoExpose();
+			}
+			return false;
+
+		case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
+			return handleResizeEvent(event, ev.window.data1, ev.window.data2);
+
+		case SDL_EVENT_WINDOW_FOCUS_GAINED: {
+			// When we gain focus, we to update whether the display can turn off
+			// dependingif a game isn't running or not
+			event.type = Common::EVENT_FOCUS_GAINED;
+			if (_engineRunning) {
+				SDL_DisableScreenSaver();
+			} else {
+				SDL_EnableScreenSaver();
+			}
+			return true;
+		}
+
+		case SDL_EVENT_WINDOW_FOCUS_LOST: {
+			// Always allow the display to turn off if ScummVM is out of focus
+			event.type = Common::EVENT_FOCUS_LOST;
+			SDL_EnableScreenSaver();
+			return true;
+		}
+
+	case SDL_EVENT_JOYSTICK_ADDED:
+		return handleJoystickAdded(ev.jdevice, event);
+
+	case SDL_EVENT_JOYSTICK_REMOVED:
+		return handleJoystickRemoved(ev.jdevice, event);
+
+	case SDL_EVENT_DROP_FILE:
+		event.type = Common::EVENT_DROP_FILE;
+		event.path = Common::Path(ev.drop.data, Common::Path::kNativeSeparator);
+		return true;
+
+	case SDL_EVENT_CLIPBOARD_UPDATE:
+		event.type = Common::EVENT_CLIPBOARD_UPDATE;
+		return true;
+
+	case SDL_EVENT_QUIT:
+		event.type = Common::EVENT_QUIT;
+		return true;
+
+	default:
+		break;
+	}
+
+	if (_joystick) {
+		switch (ev.type) {
+		case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
+			return handleJoyButtonDown(ev, event);
+		case SDL_EVENT_JOYSTICK_BUTTON_UP:
+			return handleJoyButtonUp(ev, event);
+		case SDL_EVENT_JOYSTICK_AXIS_MOTION:
+			return handleJoyAxisMotion(ev, event);
+		case SDL_EVENT_JOYSTICK_HAT_MOTION:
+			return handleJoyHatMotion(ev, event);
+		default:
+			break;
+		}
+	}
+
+	if (_controller) {
+		switch (ev.type) {
+		case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+			return handleControllerButton(ev, event, false);
+		case SDL_EVENT_GAMEPAD_BUTTON_UP:
+			return handleControllerButton(ev, event, true);
+		case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+			return handleControllerAxisMotion(ev, event);
+		default:
+			break;
+		}
+	}
+	return false;
+}
+
+
+bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) {
+
+	SDLModToOSystemKeyFlags(SDL_GetModState(), event);
+
+	SDL_Keycode sdlKeycode = ev.key.key;
+	Common::KeyCode key = SDLToOSystemKeycode(sdlKeycode);
+
+	// Handle scroll lock as a key modifier
+	if (key == Common::KEYCODE_SCROLLOCK)
+		_scrollLock = !_scrollLock;
+
+	if (_scrollLock)
+		event.kbd.flags |= Common::KBD_SCRL;
+
+	if (remapKey(ev, event))
+		return true;
+
+	event.type = Common::EVENT_KEYDOWN;
+	event.kbd.keycode = key;
+
+	SDL_Keymod mod = ev.key.mod;
+#if defined(__amigaos4__)
+	// On AmigaOS, SDL always reports numlock as off. However, we get KEYCODE_KP# only when
+	// it is on, and get different keycodes (for example KEYCODE_PAGEDOWN) when it is off.
+	if (event.kbd.keycode >= Common::KEYCODE_KP0 && event.kbd.keycode <= Common::KEYCODE_KP9) {
+		event.kbd.flags |= Common::KBD_NUM;
+		mod = SDL_Keymod(mod | KMOD_NUM);
+	}
+#endif
+	event.kbd.ascii = mapKey(sdlKeycode, mod, obtainUnicode(ev.key));
+
+	event.kbdRepeat = ev.key.repeat;
+
+	return true;
+}
+
+bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) {
+	if (remapKey(ev, event))
+		return true;
+
+	SDLModToOSystemKeyFlags(SDL_GetModState(), event);
+
+	SDL_Keycode sdlKeycode = ev.key.key;
+
+	// Set the scroll lock sticky flag
+	if (_scrollLock)
+		event.kbd.flags |= Common::KBD_SCRL;
+
+	event.type = Common::EVENT_KEYUP;
+	event.kbd.keycode = SDLToOSystemKeycode(sdlKeycode);
+
+	SDL_Keymod mod = ev.key.mod;
+
+#if defined(__amigaos4__)
+	// On AmigaOS, SDL always reports numlock as off. However, we get KEYCODE_KP# only when
+	// it is on, and get different keycodes (for example KEYCODE_PAGEDOWN) when it is off.
+	if (event.kbd.keycode >= Common::KEYCODE_KP0 && event.kbd.keycode <= Common::KEYCODE_KP9) {
+		event.kbd.flags |= Common::KBD_NUM;
+		mod = SDL_Keymod(mod | KMOD_NUM);
+	}
+#endif
+	event.kbd.ascii = mapKey(sdlKeycode, mod, 0);
+
+	return true;
+}
+
+void SdlEventSource::openJoystick(int joystickIndex) {
+	int numJoysticks = 0;
+	SDL_GetJoysticks(&numJoysticks);
+	if (numJoysticks > joystickIndex) {
+		if (SDL_IsGamepad(joystickIndex)) {
+			_controller = SDL_OpenGamepad(joystickIndex);
+			debug("Using game controller: %s", SDL_GetGamepadName(_controller));
+		} else {
+			_joystick = SDL_OpenJoystick(joystickIndex);
+			debug("Using joystick: %s", SDL_GetJoystickName(_joystick));
+		}
+	} else {
+		debug(5, "Invalid joystick: %d", joystickIndex);
+	}
+}
+
+void SdlEventSource::closeJoystick() {
+	if (_controller) {
+		SDL_CloseGamepad(_controller);
+		_controller = nullptr;
+	}
+	if (_joystick) {
+		SDL_CloseJoystick(_joystick);
+		_joystick = nullptr;
+	}
+}
+
+bool SdlEventSource::handleJoystickAdded(const SDL_JoyDeviceEvent &device, Common::Event &event) {
+	debug(5, "SdlEventSource: Received joystick added event for index '%d'", device.which);
+
+	int joystick_num = ConfMan.getInt("joystick_num");
+	if (joystick_num != device.which) {
+		return false;
+	}
+
+	debug(5, "SdlEventSource: Newly added joystick with index '%d' matches 'joysticky_num', trying to use it", device.which);
+
+	closeJoystick();
+	openJoystick(joystick_num);
+
+	event.type = Common::EVENT_INPUT_CHANGED;
+	return true;
+}
+
+bool SdlEventSource::handleJoystickRemoved(const SDL_JoyDeviceEvent &device, Common::Event &event) {
+	debug(5, "SdlEventSource: Received joystick removed event for instance id '%d'", device.which);
+
+	SDL_Joystick *joystick;
+	if (_controller) {
+		joystick = SDL_GetGamepadJoystick(_controller);
+	} else {
+		joystick = _joystick;
+	}
+
+	if (!joystick) {
+		return false;
+	}
+
+	if (SDL_GetJoystickID(joystick) != device.which) {
+		return false;
+	}
+
+	debug(5, "SdlEventSource: Newly removed joystick with instance id '%d' matches currently used joystick, closing current joystick", device.which);
+
+	closeJoystick();
+
+	event.type = Common::EVENT_INPUT_CHANGED;
+	return true;
+}
+
+int SdlEventSource::mapSDLControllerButtonToOSystem(Uint8 sdlButton) {
+	Common::JoystickButton osystemButtons[] = {
+	    Common::JOYSTICK_BUTTON_A,
+	    Common::JOYSTICK_BUTTON_B,
+	    Common::JOYSTICK_BUTTON_X,
+	    Common::JOYSTICK_BUTTON_Y,
+	    Common::JOYSTICK_BUTTON_BACK,
+	    Common::JOYSTICK_BUTTON_GUIDE,
+	    Common::JOYSTICK_BUTTON_START,
+	    Common::JOYSTICK_BUTTON_LEFT_STICK,
+	    Common::JOYSTICK_BUTTON_RIGHT_STICK,
+	    Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_DPAD_UP,
+	    Common::JOYSTICK_BUTTON_DPAD_DOWN,
+	    Common::JOYSTICK_BUTTON_DPAD_LEFT,
+	    Common::JOYSTICK_BUTTON_DPAD_RIGHT
+	};
+
+	if (sdlButton >= ARRAYSIZE(osystemButtons)) {
+		return -1;
+	}
+
+	return osystemButtons[sdlButton];
+}
+
+bool SdlEventSource::handleControllerButton(const SDL_Event &ev, Common::Event &event, bool buttonUp) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int button = mapSDLControllerButtonToOSystem(ev.gbutton.button);
+#else
+	int button = mapSDLControllerButtonToOSystem(ev.cbutton.button);
+#endif
+
+	if (button < 0)
+		return false;
+
+	event.type = buttonUp ? Common::EVENT_JOYBUTTON_UP : Common::EVENT_JOYBUTTON_DOWN;
+	event.joystick.button = button;
+
+	return true;
+}
+
+bool SdlEventSource::handleControllerAxisMotion(const SDL_Event &ev, Common::Event &event) {
+	event.joystick.axis = ev.gaxis.axis;
+	event.joystick.position = ev.gaxis.value;
+	event.type = Common::EVENT_JOYAXIS_MOTION;
+
+	return true;
+}
+
+bool SdlEventSource::isJoystickConnected() const {
+	return _joystick || _controller;
+}
+
+uint32 SdlEventSource::obtainUnicode(const SDL_KeyboardEvent &key) {
+	SDL_Event events[2];
+
+	// Update the event queue here to give SDL a chance to insert TEXTINPUT
+	// events for KEYDOWN events. Otherwise we have a high chance that on
+	// Windows the TEXTINPUT event is not in the event queue at this point.
+	// In this case we will get two events with ascii values due to mapKey
+	// and dispatchSDLEvent. This results in nasty double input of characters
+	// in the GUI.
+	//
+	// FIXME: This is all a bit fragile because in mapKey we derive the ascii
+	// value from the key code if no unicode value is given. This is legacy
+	// behavior and should be removed anyway. If that is removed, we might not
+	// even need to do this peeking here but instead can rely on the
+	// SDL_TEXTINPUT case in dispatchSDLEvent to introduce keydown/keyup with
+	// proper ASCII values (but with KEYCODE_INVALID as keycode).
+	SDL_PumpEvents();
+
+	// In SDL2, the unicode field has been removed from the keysym struct.
+	// Instead a SDL_TEXTINPUT event is generated on key combinations that
+	// generates unicode.
+	// Here we peek into the event queue for the event to see if it exists.
+	int n = SDL_PeepEvents(events, 2, SDL_PEEKEVENT, SDL_EVENT_KEY_DOWN, SDL_EVENT_TEXT_INPUT);
+	// Make sure that the TEXTINPUT event belongs to this KEYDOWN
+	// event and not another pending one.
+	if ((n > 0 && events[0].type == SDL_EVENT_TEXT_INPUT)
+	    || (n > 1 && events[0].type != SDL_EVENT_KEY_DOWN && events[1].type == SDL_EVENT_TEXT_INPUT)) {
+		// Remove the text input event we associate with the key press. This
+		// makes sure we never get any SDL_TEXTINPUT events which do "belong"
+		// to SDL_KEYDOWN events.
+		n = SDL_PeepEvents(events, 1, SDL_GETEVENT, SDL_EVENT_TEXT_INPUT, SDL_EVENT_TEXT_INPUT);
+		// This is basically a paranoia safety check because we know there
+		// must be a text input event in the queue.
+		if (n > 0) {
+			return convUTF8ToUTF32(events[0].text.text);
+		} else {
+			return 0;
+		}
+	} else {
+		return 0;
+	}
+}
+
+#endif // SDL_BACKEND
diff --git a/backends/graphics/openglsdl/openglsdl-graphics.cpp b/backends/graphics/openglsdl/openglsdl-graphics.cpp
index 0a97b912efd..1e84e21be32 100644
--- a/backends/graphics/openglsdl/openglsdl-graphics.cpp
+++ b/backends/graphics/openglsdl/openglsdl-graphics.cpp
@@ -34,6 +34,36 @@
 #include "common/translation.h"
 #endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static void sdlGLDestroyContext(SDL_GLContext context) {
+	SDL_GL_DestroyContext(context);
+}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
+static void sdlGLDestroyContext(SDL_GLContext context) {
+	SDL_GL_DeleteContext(context);
+}
+#endif
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static bool sdlSetSwapInterval(int interval) {
+	return SDL_GL_SetSwapInterval(interval);
+}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
+static bool sdlSetSwapInterval(int interval) {
+	return SDL_GL_SetSwapInterval(interval) == 0;
+}
+#endif
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static bool sdlGetAttribute(SDL_GLAttr attr, int *value) {
+	return SDL_GL_GetAttribute(attr, value);
+}
+#else
+static bool sdlGetAttribute(SDL_GLattr attr, int *value) {
+	return SDL_GL_GetAttribute(attr, value) != 0;
+}
+#endif
+
 OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(SdlEventSource *eventSource, SdlWindow *window)
 	: SdlGraphicsManager(eventSource, window), _lastRequestedHeight(0),
 #if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -91,16 +121,16 @@ OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(SdlEventSource *eventSource,
 	// because then we already set up what we want to use.
 	//
 	// In case no defaults are given we prefer OpenGL over OpenGL ES.
-	if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &_glContextProfileMask) != 0) {
+	if (!sdlGetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &_glContextProfileMask)) {
 		_glContextProfileMask = 0;
 		noDefaults = true;
 	}
 
-	if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &_glContextMajor) != 0) {
+	if (!sdlGetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &_glContextMajor)) {
 		noDefaults = true;
 	}
 
-	if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &_glContextMinor) != 0) {
+	if (!sdlGetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &_glContextMinor)) {
 		noDefaults = true;
 	}
 
@@ -201,7 +231,7 @@ OpenGLSdlGraphicsManager::~OpenGLSdlGraphicsManager() {
 #endif
 
 	notifyContextDestroy();
-	SDL_GL_DeleteContext(_glContext);
+	sdlGLDestroyContext(_glContext);
 #else
 	if (_hwScreen) {
 		notifyContextDestroy();
@@ -255,7 +285,14 @@ void OpenGLSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable)
 bool OpenGLSdlGraphicsManager::getFeatureState(OSystem::Feature f) const {
 	switch (f) {
 	case OSystem::kFeatureFullscreenMode:
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (_window && _window->getSDLWindow()) {
+			// SDL_GetWindowFullscreenMode returns a pointer to the exclusive fullscreen mode to use or NULL for borderless
+			return ((SDL_GetWindowFlags(_window->getSDLWindow()) & SDL_WINDOW_FULLSCREEN) != 0) && (SDL_GetWindowFullscreenMode(_window->getSDLWindow()) == NULL);
+		} else {
+			return _wantsFullScreen;
+		}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 		if (_window && _window->getSDLWindow()) {
 			return (SDL_GetWindowFlags(_window->getSDLWindow()) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
 		} else {
@@ -574,11 +611,15 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
 		destroyImGui();
 #endif
 
-		SDL_GL_DeleteContext(_glContext);
+		sdlGLDestroyContext(_glContext);
 		_glContext = nullptr;
 	}
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
+#else
 	uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
+#endif
 
 	if (_wantsFullScreen) {
 		// On Linux/X11, when toggling to fullscreen, the window manager saves
@@ -596,7 +637,13 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
 		width  = _desiredFullscreenWidth;
 		height = _desiredFullscreenHeight;
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		flags |= SDL_WINDOW_FULLSCREEN;
+		SDL_SetWindowFullscreenMode(_window->getSDLWindow(), NULL);
+		SDL_SyncWindow(_window->getSDLWindow());
+#else
 		flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+#endif
 	}
 
 	if (!_wantsFullScreen && ConfMan.getBool("window_maximized", Common::ConfigManager::kApplicationDomain)) {
@@ -608,7 +655,7 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, _glContextMajor);
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, _glContextMinor);
 
-#ifdef NINTENDO_SWITCH
+#if defined(NINTENDO_SWITCH) && !SDL_VERSION_ATLEAST(3, 0, 0)
 	// Switch quirk: Switch seems to need this flag, otherwise the screen
 	// is zoomed when switching from Normal graphics mode to OpenGL
 	flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
@@ -622,7 +669,7 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
 		return false;
 	}
 
-	if (SDL_GL_SetSwapInterval(_vsync ? 1 : 0)) {
+	if (!sdlSetSwapInterval(_vsync ? 1 : 0)) {
 		warning("Unable to %s VSync: %s", _vsync ? "enable" : "disable", SDL_GetError());
 	}
 
diff --git a/backends/graphics/sdl/sdl-graphics.cpp b/backends/graphics/sdl/sdl-graphics.cpp
index d57fd73fe26..acf84aaf91b 100644
--- a/backends/graphics/sdl/sdl-graphics.cpp
+++ b/backends/graphics/sdl/sdl-graphics.cpp
@@ -38,7 +38,15 @@
 #include "backends/platform/sdl/emscripten/emscripten.h"
 #endif
 
-#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(3, 0, 0)
+#include "backends/imgui/backends/imgui_impl_sdl3.h"
+#ifdef USE_OPENGL
+#include "backends/imgui/backends/imgui_impl_opengl3.h"
+#endif
+#ifdef USE_IMGUI_SDLRENDERER3
+#include "backends/imgui/backends/imgui_impl_sdlrenderer3.h"
+#endif
+#elif defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
 #include "backends/imgui/backends/imgui_impl_sdl2.h"
 #ifdef USE_OPENGL
 #include "backends/imgui/backends/imgui_impl_opengl3.h"
@@ -48,6 +56,18 @@
 #endif
 #endif
 
+
+static void getMouseState(int *x, int *y) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	float fx, fy;
+	SDL_GetMouseState(&fx, &fy);
+	*x = static_cast<int>(fx);
+	*y = static_cast<int>(fy);
+#else
+	SDL_GetMouseState(x, y);
+#endif
+}
+
 SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source, SdlWindow *window)
 	: _eventSource(source), _window(window), _hwScreen(nullptr)
 #if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -56,7 +76,7 @@ SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source, SdlWindow *window
 {
 	ConfMan.registerDefault("fullscreen_res", "desktop");
 
-	SDL_GetMouseState(&_cursorX, &_cursorY);
+	getMouseState(&_cursorX, &_cursorY);
 }
 
 void SdlGraphicsManager::activateManager() {
@@ -220,7 +240,7 @@ bool SdlGraphicsManager::showMouse(bool visible) {
 		// area, so we need to ask SDL where the system's mouse cursor is
 		// instead
 		int x, y;
-		SDL_GetMouseState(&x, &y);
+		getMouseState(&x, &y);
 		if (!_activeArea.drawRect.contains(Common::Point(x, y))) {
 			showCursor = true;
 		}
@@ -312,7 +332,15 @@ bool SdlGraphicsManager::notifyMousePosition(Common::Point &mouse) {
 }
 
 void SdlGraphicsManager::showSystemMouseCursor(bool visible) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (visible) {
+		SDL_ShowCursor();
+	} else {
+		SDL_HideCursor();
+	}
+#else
 	SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
+#endif
 }
 
 void SdlGraphicsManager::setSystemMousePosition(const int x, const int y) {
@@ -354,7 +382,11 @@ bool SdlGraphicsManager::createOrUpdateWindow(int width, int height, const Uint3
 	// resized the game window), or when the launcher is visible (since a user
 	// may change the scaler, which should reset the window size)
 	if (!_window->getSDLWindow() || _lastFlags != flags || _overlayVisible || _allowWindowSizeReset) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		const bool fullscreen = (flags & (SDL_WINDOW_FULLSCREEN)) != 0;
+#else
 		const bool fullscreen = (flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
+#endif
 		const bool maximized = (flags & SDL_WINDOW_MAXIMIZED);
 		if (!fullscreen && !maximized) {
 			if (_hintedWidth > width) {
@@ -368,6 +400,14 @@ bool SdlGraphicsManager::createOrUpdateWindow(int width, int height, const Uint3
 		if (!_window->createOrUpdateWindow(width, height, flags)) {
 			return false;
 		}
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (fullscreen) {
+			if (!SDL_SetWindowFullscreenMode(_window->getSDLWindow(), NULL))
+				return false;
+			if (!SDL_SyncWindow(_window->getSDLWindow()))
+				return false;
+		}
+#endif
 
 		_lastFlags = flags;
 		_allowWindowSizeReset = false;
@@ -601,7 +641,11 @@ void SdlGraphicsManager::initImGui(SDL_Renderer *renderer, void *glContext) {
 
 		io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;       // Enable Multi-Viewport / Platform Windows
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (!ImGui_ImplSDL3_InitForOpenGL(_window->getSDLWindow(), glContext)) {
+#else
 		if (!ImGui_ImplSDL2_InitForOpenGL(_window->getSDLWindow(), glContext)) {
+#endif
 			ImGui::DestroyContext();
 			return;
 		}
@@ -613,7 +657,11 @@ void SdlGraphicsManager::initImGui(SDL_Renderer *renderer, void *glContext) {
 			glslVersion = "#version 110";
 		}
 		if (!ImGui_ImplOpenGL3_Init(glslVersion)) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			ImGui_ImplSDL3_Shutdown();
+#else
 			ImGui_ImplSDL2_Shutdown();
+#endif
 			ImGui::DestroyContext();
 			return;
 		}
@@ -621,7 +669,23 @@ void SdlGraphicsManager::initImGui(SDL_Renderer *renderer, void *glContext) {
 		_imGuiReady = true;
 	}
 #endif
-#ifdef USE_IMGUI_SDLRENDERER2
+#ifdef USE_IMGUI_SDLRENDERER3
+	if (!_imGuiReady && renderer) {
+		if (!ImGui_ImplSDL3_InitForSDLRenderer(_window->getSDLWindow(), renderer)) {
+			ImGui::DestroyContext();
+			return;
+		}
+
+		if (!ImGui_ImplSDLRenderer3_Init(renderer)) {
+			ImGui_ImplSDL3_Shutdown();
+			ImGui::DestroyContext();
+			return;
+		}
+
+		_imGuiReady = true;
+		_imGuiSDLRenderer = renderer;
+	}
+#elif defined(USE_IMGUI_SDLRENDERER2)
 	if (!_imGuiReady && renderer) {
 		if (!ImGui_ImplSDL2_InitForSDLRenderer(_window->getSDLWindow(), renderer)) {
 			ImGui::DestroyContext();
@@ -662,7 +726,11 @@ void SdlGraphicsManager::renderImGui() {
 		_imGuiInited = true;
 	}
 
-#ifdef USE_IMGUI_SDLRENDERER2
+#ifdef USE_IMGUI_SDLRENDERER3
+	if (_imGuiSDLRenderer) {
+		ImGui_ImplSDLRenderer3_NewFrame();
+	} else {
+#elif defined(USE_IMGUI_SDLRENDERER2)
 	if (_imGuiSDLRenderer) {
 		ImGui_ImplSDLRenderer2_NewFrame();
 	} else {
@@ -670,15 +738,23 @@ void SdlGraphicsManager::renderImGui() {
 #ifdef USE_OPENGL
 		ImGui_ImplOpenGL3_NewFrame();
 #endif
-#ifdef USE_IMGUI_SDLRENDERER2
+#if defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3)
 	}
 #endif
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	ImGui_ImplSDL3_NewFrame();
+#else
 	ImGui_ImplSDL2_NewFrame();
+#endif
 
 	ImGui::NewFrame();
 	_imGuiCallbacks.render();
 	ImGui::Render();
-#ifdef USE_IMGUI_SDLRENDERER2
+#ifdef USE_IMGUI_SDLRENDERER3
+	if (_imGuiSDLRenderer) {
+		ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), _imGuiSDLRenderer);
+	} else {
+#elif defined(USE_IMGUI_SDLRENDERER2)
 	if (_imGuiSDLRenderer) {
 		ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), _imGuiSDLRenderer);
 	} else {
@@ -692,7 +768,7 @@ void SdlGraphicsManager::renderImGui() {
 		ImGui::RenderPlatformWindowsDefault();
 		SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
 #endif
-#ifdef USE_IMGUI_SDLRENDERER2
+#if defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3)
 	}
 #endif
 }
@@ -709,7 +785,11 @@ void SdlGraphicsManager::destroyImGui() {
 	_imGuiInited = false;
 	_imGuiReady = false;
 
-#ifdef USE_IMGUI_SDLRENDERER2
+#ifdef USE_IMGUI_SDLRENDERER3
+	if (_imGuiSDLRenderer) {
+		ImGui_ImplSDLRenderer3_Shutdown();
+	} else {
+#elif defined(USE_IMGUI_SDLRENDERER2)
 	if (_imGuiSDLRenderer) {
 		ImGui_ImplSDLRenderer2_Shutdown();
 	} else {
@@ -717,10 +797,14 @@ void SdlGraphicsManager::destroyImGui() {
 #ifdef USE_OPENGL
 		ImGui_ImplOpenGL3_Shutdown();
 #endif
-#ifdef USE_IMGUI_SDLRENDERER2
+#if defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3)
 	}
 #endif
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	ImGui_ImplSDL3_Shutdown();
+#else
 	ImGui_ImplSDL2_Shutdown();
+#endif
 	ImGui::DestroyContext();
 }
 #endif
diff --git a/backends/graphics/sdl/sdl-graphics.h b/backends/graphics/sdl/sdl-graphics.h
index af5d76514f6..8225b2d4046 100644
--- a/backends/graphics/sdl/sdl-graphics.h
+++ b/backends/graphics/sdl/sdl-graphics.h
@@ -161,7 +161,10 @@ protected:
 	 * values stored by the graphics manager.
 	 */
 	void getWindowSizeFromSdl(int *width, int *height) const {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		assert(_window);
+		SDL_GetWindowSizeInPixels(_window->getSDLWindow(), width, height);
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 		assert(_window);
 		SDL_GL_GetDrawableSize(_window->getSDLWindow(), width, height);
 #else
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
index 9a63088c700..7158d90c1a7 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
@@ -62,6 +62,61 @@
 #define SDL_FULLSCREEN  0x40000000
 #endif
 
+static void destroySurface(SDL_Surface *surface) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_DestroySurface(surface);
+#else
+	SDL_FreeSurface(surface);
+#endif
+}
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static bool blitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect) {
+	return SDL_BlitSurface(src, srcrect, dst, dstrect);
+}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
+static bool blitSurface(SDL_Surface * src, const SDL_Rect * srcrect, SDL_Surface * dst, SDL_Rect * dstrect) {
+	return SDL_BlitSurface(src, srcrect, dst, dstrect) != -1;
+}
+#else
+static bool blitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) {
+	return SDL_BlitSurface(src, srcrect, dst, dstrect) != -1;
+}
+#endif
+
+
+static bool lockSurface(SDL_Surface *surface) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_LockSurface(surface);
+#else
+	return SDL_LockSurface(surface) != -1;
+#endif
+}
+
+static SDL_Surface *createSurface(int width, int height, SDL_Surface *surface) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(surface->format);
+	if (pixelFormatDetails == nullptr)
+		error("getting pixel format details failed");
+	return SDL_CreateSurface(width, height,
+					SDL_GetPixelFormatForMasks(
+						pixelFormatDetails->bits_per_pixel,
+						pixelFormatDetails->Rmask,
+						pixelFormatDetails->Gmask,
+						pixelFormatDetails->Bmask,
+						pixelFormatDetails->Amask));
+#else
+	return SDL_CreateRGBSurface(SDL_SWSURFACE,
+					width,
+					height,
+					surface->format->BitsPerPixel,
+					surface->format->Rmask,
+					surface->format->Gmask,
+					surface->format->Bmask,
+					surface->format->Amask);
+#endif
+}
+
 static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
 	{"surfacesdl", _s("SDL Surface"), GFX_SURFACESDL},
 	{nullptr, nullptr, 0}
@@ -189,13 +244,13 @@ SurfaceSdlGraphicsManager::~SurfaceSdlGraphicsManager() {
 	delete _scaler;
 	delete _mouseScaler;
 	if (_mouseOrigSurface) {
-		SDL_FreeSurface(_mouseOrigSurface);
+		destroySurface(_mouseOrigSurface);
 		if (_mouseOrigSurface == _mouseSurface) {
 			_mouseSurface = nullptr;
 		}
 	}
 	if (_mouseSurface) {
-		SDL_FreeSurface(_mouseSurface);
+		destroySurface(_mouseOrigSurface);
 	}
 	free(_currentPalette);
 	free(_overlayPalette);
@@ -484,6 +539,28 @@ OSystem::TransactionError SurfaceSdlGraphicsManager::endGFXTransaction() {
 	return (OSystem::TransactionError)errors;
 }
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+Graphics::PixelFormat SurfaceSdlGraphicsManager::convertSDLPixelFormat(SDL_PixelFormat format) const {
+	const SDL_PixelFormatDetails *in = SDL_GetPixelFormatDetails(format);
+	assert(in);
+	if (in->bytes_per_pixel == 1 && (
+		    (in->Rmask == 0xff && in->Gmask == 0xff && in->Bmask == 0xff) ||
+		    (in->Rmask == 0 && in->Gmask == 0 && in->Bmask == 0)
+		    ))
+		return Graphics::PixelFormat::createFormatCLUT8();
+	Graphics::PixelFormat out(in->bytes_per_pixel,
+		in->Rbits, in->Gbits,
+		in->Bbits, in->Abits,
+		in->Rshift, in->Gshift,
+		in->Bshift, in->Ashift);
+
+	// Workaround to SDL not providing an accurate Aloss value on some platforms.
+	if (in->Amask == 0)
+		out.aLoss = 8;
+
+	return out;
+}
+#else
 Graphics::PixelFormat SurfaceSdlGraphicsManager::convertSDLPixelFormat(SDL_PixelFormat *in) const {
 	if (in->BytesPerPixel == 1 && (
 		    (in->Rmask == 0xff && in->Gmask == 0xff && in->Bmask == 0xff) ||
@@ -502,6 +579,7 @@ Graphics::PixelFormat SurfaceSdlGraphicsManager::convertSDLPixelFormat(SDL_Pixel
 
 	return out;
 }
+#endif
 
 #ifdef USE_RGB_COLOR
 Common::List<Graphics::PixelFormat> SurfaceSdlGraphicsManager::getSupportedFormats() const {
@@ -533,6 +611,20 @@ void SurfaceSdlGraphicsManager::detectSupportedFormats() {
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		const SDL_DisplayMode* pDefaultMode = SDL_GetDesktopDisplayMode(_window->getDisplayIndex());
+		if (!pDefaultMode) {
+			error("Could not get default system display mode");
+		}
+
+		int bpp;
+		Uint32 rMask, gMask, bMask, aMask;
+		if (!SDL_GetMasksForPixelFormat(pDefaultMode->format, &bpp, &rMask, &gMask, &bMask, &aMask)) {
+			error("Could not convert system pixel format %s to masks", SDL_GetPixelFormatName(pDefaultMode->format));
+		}
+
+		const uint8 bytesPerPixel = SDL_BYTESPERPIXEL(pDefaultMode->format);
+#else
 		SDL_DisplayMode defaultMode;
 		if (SDL_GetDesktopDisplayMode(_window->getDisplayIndex(), &defaultMode) != 0) {
 			error("Could not get default system display mode");
@@ -545,6 +637,8 @@ void SurfaceSdlGraphicsManager::detectSupportedFormats() {
 		}
 
 		const uint8 bytesPerPixel = SDL_BYTESPERPIXEL(defaultMode.format);
+#endif
+
 		uint8 rBits, rShift, gBits, gShift, bBits, bShift, aBits, aShift;
 		maskToBitCount(rMask, rBits, rShift);
 		maskToBitCount(gMask, gBits, gShift);
@@ -853,7 +947,13 @@ void SurfaceSdlGraphicsManager::fixupResolutionForAspectRatio(AspectRatio desire
 	int bestW = 0, bestH = 0;
 	uint bestMetric = (uint)-1; // Metric is wasted space
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int numModes;
+	const int display = _window->getDisplayIndex();
+	SDL_DisplayMode** modes = SDL_GetFullscreenDisplayModes(display, &numModes);
+	for (int i = 0; i < numModes; ++i) {
+		SDL_DisplayMode* mode = modes[i];
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	const int display = _window->getDisplayIndex();
 	const int numModes = SDL_GetNumDisplayModes(display);
 	SDL_DisplayMode modeData, *mode = &modeData;
@@ -884,7 +984,10 @@ void SurfaceSdlGraphicsManager::fixupResolutionForAspectRatio(AspectRatio desire
 
 	// Make editors a bit more happy by having the same amount of closing as
 	// opening curley braces.
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	}
+	SDL_free(modes);
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	}
 #else
 	}
@@ -915,7 +1018,11 @@ void SurfaceSdlGraphicsManager::setupHardwareSize() {
 }
 
 void SurfaceSdlGraphicsManager::initGraphicsSurface() {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	Uint32 flags = 0;
+#else
 	Uint32 flags = SDL_SWSURFACE;
+#endif
 	if (_videoMode.fullscreen)
 		flags |= SDL_FULLSCREEN;
 
@@ -943,8 +1050,13 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
 	const Uint32 gMask = ((0xFF >> format.gLoss) << format.gShift);
 	const Uint32 bMask = ((0xFF >> format.bLoss) << format.bShift);
 	const Uint32 aMask = ((0xFF >> format.aLoss) << format.aShift);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	_screen = SDL_CreateSurface(_videoMode.screenWidth, _videoMode.screenHeight,
+						SDL_GetPixelFormatForMasks(_screenFormat.bytesPerPixel * 8, rMask, gMask, bMask, aMask));
+#else
 	_screen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.screenWidth, _videoMode.screenHeight,
 						_screenFormat.bytesPerPixel * 8, rMask, gMask, bMask, aMask);
+#endif
 	if (_screen == nullptr)
 		error("allocating _screen failed");
 
@@ -1013,14 +1125,7 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
 	//
 
 	// Need some extra bytes around when using 2xSaI
-	_tmpscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.screenWidth + _maxExtraPixels * 2,
-						_videoMode.screenHeight + _maxExtraPixels * 2,
-						_hwScreen->format->BitsPerPixel,
-						_hwScreen->format->Rmask,
-						_hwScreen->format->Gmask,
-						_hwScreen->format->Bmask,
-						_hwScreen->format->Amask);
-
+	_tmpscreen = createSurface(_videoMode.screenWidth + _maxExtraPixels * 2, _videoMode.screenHeight + _maxExtraPixels * 2, _hwScreen);
 	if (_tmpscreen == nullptr)
 		error("allocating _tmpscreen failed");
 
@@ -1030,13 +1135,7 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
 									_videoMode.screenWidth, _videoMode.screenHeight, _maxExtraPixels);
 	}
 
-	_overlayscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth, _videoMode.overlayHeight,
-						_hwScreen->format->BitsPerPixel,
-						_hwScreen->format->Rmask,
-						_hwScreen->format->Gmask,
-						_hwScreen->format->Bmask,
-						_hwScreen->format->Amask);
-
+	_overlayscreen = createSurface(_videoMode.overlayWidth, _videoMode.overlayHeight, _hwScreen);
 	if (_overlayscreen == nullptr)
 		error("allocating _overlayscreen failed");
 
@@ -1045,14 +1144,7 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
 	if (_overlayFormat.bytesPerPixel == 1 && _overlayFormat.rBits() == 0)
 		_overlayFormat = Graphics::PixelFormat(1, 3, 3, 2, 0, 5, 2, 0, 0);
 
-	_tmpscreen2 = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth + _maxExtraPixels * 2,
-						_videoMode.overlayHeight + _maxExtraPixels * 2,
-						_hwScreen->format->BitsPerPixel,
-						_hwScreen->format->Rmask,
-						_hwScreen->format->Gmask,
-						_hwScreen->format->Bmask,
-						_hwScreen->format->Amask);
-
+	_tmpscreen2 = createSurface(_videoMode.overlayWidth + _maxExtraPixels * 2, _videoMode.overlayHeight + _maxExtraPixels * 2, _hwScreen);
 	if (_tmpscreen2 == nullptr)
 		error("allocating _tmpscreen2 failed");
 
@@ -1066,7 +1158,7 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
 
 void SurfaceSdlGraphicsManager::unloadGFXMode() {
 	if (_screen) {
-		SDL_FreeSurface(_screen);
+		destroySurface(_screen);
 		_screen = nullptr;
 	}
 
@@ -1075,33 +1167,33 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() {
 #endif
 
 	if (_hwScreen) {
-		SDL_FreeSurface(_hwScreen);
+		destroySurface(_hwScreen);
 		_hwScreen = nullptr;
 	}
 
 	if (_tmpscreen) {
-		SDL_FreeSurface(_tmpscreen);
+		destroySurface(_tmpscreen);
 		_tmpscreen = nullptr;
 	}
 
 	if (_tmpscreen2) {
-		SDL_FreeSurface(_tmpscreen2);
+		destroySurface(_tmpscreen2);
 		_tmpscreen2 = nullptr;
 	}
 
 	if (_overlayscreen) {
-		SDL_FreeSurface(_overlayscreen);
+		destroySurface(_overlayscreen);
 		_overlayscreen = nullptr;
 	}
 
 #ifdef USE_OSD
 	if (_osdMessageSurface) {
-		SDL_FreeSurface(_osdMessageSurface);
+		destroySurface(_osdMessageSurface);
 		_osdMessageSurface = nullptr;
 	}
 
 	if (_osdIconSurface) {
-		SDL_FreeSurface(_osdIconSurface);
+		destroySurface(_osdIconSurface);
 		_osdIconSurface = nullptr;
 	}
 #endif
@@ -1128,15 +1220,15 @@ bool SurfaceSdlGraphicsManager::hotswapGFXMode() {
 
 	// Release the HW screen surface
 	if (_hwScreen) {
-		SDL_FreeSurface(_hwScreen);
+		destroySurface(_osdIconSurface);
 		_hwScreen = nullptr;
 	}
 	if (_tmpscreen) {
-		SDL_FreeSurface(_tmpscreen);
+		destroySurface(_tmpscreen);
 		_tmpscreen = nullptr;
 	}
 	if (_tmpscreen2) {
-		SDL_FreeSurface(_tmpscreen2);
+		destroySurface(_tmpscreen2);
 		_tmpscreen2 = nullptr;
 	}
 
@@ -1158,8 +1250,8 @@ bool SurfaceSdlGraphicsManager::hotswapGFXMode() {
 	SDL_BlitSurface(old_overlayscreen, nullptr, _overlayscreen, nullptr);
 
 	// Free the old surfaces
-	SDL_FreeSurface(old_screen);
-	SDL_FreeSurface(old_overlayscreen);
+	destroySurface(old_screen);
+	destroySurface(old_overlayscreen);
 
 	// Update cursor to new scale
 	blitCursor();
@@ -1318,7 +1410,7 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
 	if (_isDoubleBuf && _numDirtyRects)
 		_forceRedraw = true;
 
-#if defined(USE_IMGUI) && defined(USE_IMGUI_SDLRENDERER2)
+#if defined(USE_IMGUI) && (defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3))
 	if (_imGuiCallbacks.render) {
 		_forceRedraw = true;
 	}
@@ -1360,14 +1452,21 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
 			dst.x += _maxExtraPixels;	// Shift rect since some scalers need to access the data around
 			dst.y += _maxExtraPixels;	// any pixel to scale it, and we want to avoid mem access crashes.
 
-			if (SDL_BlitSurface(origSurf, r, srcSurf, &dst) != 0)
+			if (!blitSurface(origSurf, r, srcSurf, &dst))
 				error("SDL_BlitSurface failed: %s", SDL_GetError());
 		}
 
 		SDL_LockSurface(srcSurf);
 		SDL_LockSurface(_hwScreen);
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(_hwScreen->format);
+		if (!pixelFormatDetails)
+			error("SDL_GetPixelFormatDetails failed: %s", SDL_GetError());
+		bpp = pixelFormatDetails->bytes_per_pixel;
+#else
 		bpp = _hwScreen->format->BytesPerPixel;
+#endif
 		srcPitch = srcSurf->pitch;
 		dstPitch = _hwScreen->pitch;
 
@@ -1476,11 +1575,19 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
 					SDL_LockSurface(_hwScreen);
 
 					// Use white as color for now.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+					Uint32 rectColor = SDL_MapSurfaceRGB(_hwScreen, 0xFF, 0xFF, 0xFF);
+#else
 					Uint32 rectColor = SDL_MapRGB(_hwScreen->format, 0xFF, 0xFF, 0xFF);
+#endif
 
 					// First draw the top and bottom lines
 					// then draw the left and right lines
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+					if (pixelFormatDetails->bytes_per_pixel == 2) {
+#else
 					if (_hwScreen->format->BytesPerPixel == 2) {
+#endif
 						uint16 *top = (uint16 *)((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 2);
 						uint16 *bottom = (uint16 *)((byte *)_hwScreen->pixels + (y + h) * _hwScreen->pitch + x * 2);
 						byte *left = ((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 2);
@@ -1498,7 +1605,11 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
 							left += _hwScreen->pitch;
 							right += _hwScreen->pitch;
 						}
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+					} else if (pixelFormatDetails->bytes_per_pixel == 4) {
+#else
 					} else if (_hwScreen->format->BytesPerPixel == 4) {
+#endif
 						uint32 *top = (uint32 *)((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 4);
 						uint32 *bottom = (uint32 *)((byte *)_hwScreen->pixels + (y + h) * _hwScreen->pitch + x * 4);
 						byte *left = ((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 4);
@@ -1541,7 +1652,7 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 
-#if defined(USE_IMGUI) && defined(USE_IMGUI_SDLRENDERER2)
+#if defined(USE_IMGUI) && (defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3))
 	renderImGui();
 #endif
 
@@ -1564,8 +1675,7 @@ bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::Path &filename) con
 		return false;
 	}
 
-	int result = SDL_LockSurface(_hwScreen);
-	if (result < 0) {
+	if (!lockSurface(_hwScreen)) {
 		warning("Could not lock RGB surface");
 		return false;
 	}
@@ -1576,7 +1686,11 @@ bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::Path &filename) con
 
 	bool success;
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Palette *sdlPalette = SDL_CreateSurfacePalette(_hwScreen);
+#else
 	SDL_Palette *sdlPalette = _hwScreen->format->palette;
+#endif
 	if (sdlPalette) {
 		byte palette[256 * 3];
 		for (int i = 0; i < sdlPalette->ncolors; i++) {
@@ -1678,7 +1792,7 @@ void SurfaceSdlGraphicsManager::copyRectToScreen(const void *buf, int pitch, int
 	addDirtyRect(x, y, w, h, false);
 
 	// Try to lock the screen surface
-	if (SDL_LockSurface(_screen) == -1)
+	if (!lockSurface(_screen))
 		error("SDL_LockSurface failed: %s", SDL_GetError());
 
 	byte *dst = (byte *)_screen->pixels + y * _screen->pitch + x * _screenFormat.bytesPerPixel;
@@ -1708,7 +1822,7 @@ Graphics::Surface *SurfaceSdlGraphicsManager::lockScreen() {
 	_screenIsLocked = true;
 
 	// Try to lock the screen surface
-	if (SDL_LockSurface(_screen) == -1)
+	if (!lockSurface(_screen))
 		error("SDL_LockSurface failed: %s", SDL_GetError());
 
 	_framebuffer.init(_screen->w, _screen->h, _screen->pitch, _screen->pixels, _screenFormat);
@@ -1941,15 +2055,25 @@ void SurfaceSdlGraphicsManager::clearOverlay() {
 	dst.x = dst.y = _maxExtraPixels;
 	src.w = dst.w = _videoMode.screenWidth;
 	src.h = dst.h = _videoMode.screenHeight;
-	if (SDL_BlitSurface(_screen, &src, _tmpscreen, &dst) != 0)
+	if (!blitSurface(_screen, &src, _tmpscreen, &dst))
 		error("SDL_BlitSurface failed: %s", SDL_GetError());
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(_tmpscreen->format);
+	if (!pixelFormatDetails)
+		error("SDL_GetPixelFormatDetails failed: %s", SDL_GetError());
+#endif
+
 	SDL_LockSurface(_tmpscreen);
 	SDL_LockSurface(_overlayscreen);
 
 	// Transpose from game palette to RGB332 (overlay palette)
 	if (_isHwPalette) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		byte *p = (byte *)(_tmpscreen->pixels) + _maxExtraPixels * _tmpscreen->pitch + _maxExtraPixels * pixelFormatDetails->bytes_per_pixel;
+#else
 		byte *p = (byte *)(_tmpscreen->pixels) + _maxExtraPixels * _tmpscreen->pitch + _maxExtraPixels * _tmpscreen->format->BytesPerPixel;
+#endif
 		int pitchSkip = _tmpscreen->pitch - _videoMode.screenWidth;
 		for (int y = 0; y < _videoMode.screenHeight; y++) {
 			for (int x = 0; x < _videoMode.screenWidth; x++, p++) {
@@ -1960,7 +2084,11 @@ void SurfaceSdlGraphicsManager::clearOverlay() {
 		}
 	}
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	_scaler->scale((byte *)(_tmpscreen->pixels) + _maxExtraPixels * _tmpscreen->pitch + _maxExtraPixels * pixelFormatDetails->bytes_per_pixel, _tmpscreen->pitch,
+#else
 	_scaler->scale((byte *)(_tmpscreen->pixels) + _maxExtraPixels * _tmpscreen->pitch + _maxExtraPixels * _tmpscreen->format->BytesPerPixel, _tmpscreen->pitch,
+#endif
 	(byte *)_overlayscreen->pixels, _overlayscreen->pitch, _videoMode.screenWidth, _videoMode.screenHeight, 0, 0);
 
 #ifdef USE_ASPECT
@@ -1981,7 +2109,7 @@ void SurfaceSdlGraphicsManager::grabOverlay(Graphics::Surface &surface) const {
 	if (_overlayscreen == nullptr)
 		return;
 
-	if (SDL_LockSurface(_overlayscreen) == -1)
+	if (!lockSurface(_overlayscreen))
 		error("SDL_LockSurface failed: %s", SDL_GetError());
 
 	assert(surface.w >= _videoMode.overlayWidth);
@@ -2003,7 +2131,14 @@ void SurfaceSdlGraphicsManager::copyRectToOverlay(const void *buf, int pitch, in
 		return;
 
 	const byte *src = (const byte *)buf;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(_overlayscreen->format);
+	if (!pixelFormatDetails)
+		error("SDL_GetPixelFormatDetails failed: %s", SDL_GetError());
+	uint bpp = pixelFormatDetails->bytes_per_pixel;
+#else
 	uint bpp = _overlayscreen->format->BytesPerPixel;
+#endif
 
 	// Clip the coordinates
 	if (x < 0) {
@@ -2032,7 +2167,7 @@ void SurfaceSdlGraphicsManager::copyRectToOverlay(const void *buf, int pitch, in
 	// Mark the modified region as dirty
 	addDirtyRect(x, y, w, h, true);
 
-	if (SDL_LockSurface(_overlayscreen) == -1)
+	if (!lockSurface(_overlayscreen))
 		error("SDL_LockSurface failed: %s", SDL_GetError());
 
 	byte *dst = (byte *)_overlayscreen->pixels + y * _overlayscreen->pitch + x * bpp;
@@ -2185,7 +2320,7 @@ void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h,
 		_mouseCurState.h = h;
 
 		if (_mouseOrigSurface) {
-			SDL_FreeSurface(_mouseOrigSurface);
+			destroySurface(_mouseOrigSurface);
 
 			if (_mouseSurface == _mouseOrigSurface) {
 				_mouseSurface = nullptr;
@@ -2195,7 +2330,7 @@ void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h,
 		}
 
 		if (formatChanged && _mouseSurface) {
-			SDL_FreeSurface(_mouseSurface);
+			destroySurface(_mouseSurface);
 			_mouseSurface = nullptr;
 		}
 
@@ -2206,6 +2341,17 @@ void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h,
 		assert(!_mouseOrigSurface);
 
 		// Allocate bigger surface because scalers will read past the boudaries.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		_mouseOrigSurface = SDL_CreateSurface(
+						_mouseCurState.w + _maxExtraPixels * 2,
+						_mouseCurState.h + _maxExtraPixels * 2,
+						SDL_GetPixelFormatForMasks(
+							_cursorFormat.bytesPerPixel * 8,
+							((0xFF >> _cursorFormat.rLoss) << _cursorFormat.rShift),
+							((0xFF >> _cursorFormat.gLoss) << _cursorFormat.gShift),
+							((0xFF >> _cursorFormat.bLoss) << _cursorFormat.bShift),
+							((0xFF >> _cursorFormat.aLoss) << _cursorFormat.aShift)));
+#else
 		_mouseOrigSurface = SDL_CreateRGBSurface(SDL_SWSURFACE,
 						_mouseCurState.w + _maxExtraPixels * 2,
 						_mouseCurState.h + _maxExtraPixels * 2,
@@ -2214,6 +2360,7 @@ void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h,
 						((0xFF >> _cursorFormat.gLoss) << _cursorFormat.gShift),
 						((0xFF >> _cursorFormat.bLoss) << _cursorFormat.bShift),
 						((0xFF >> _cursorFormat.aLoss) << _cursorFormat.aShift));
+#endif
 
 		if (_mouseOrigSurface == nullptr) {
 			error("Allocating _mouseOrigSurface failed");
@@ -2234,8 +2381,14 @@ void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h,
 	}
 
 	if (keycolorChanged) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		uint32 flags = _disableMouseKeyColor ? 0 : SDL_SRCCOLORKEY | SDL_SRCALPHA;
+		SDL_SetSurfaceColorKey(_mouseOrigSurface, flags, _mouseKeyColor);
+		SDL_SetSurfaceRLE(_mouseOrigSurface, !_disableMouseKeyColor);
+#else
 		uint32 flags = _disableMouseKeyColor ? 0 : SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA;
 		SDL_SetColorKey(_mouseOrigSurface, flags, _mouseKeyColor);
+#endif
 	}
 
 	SDL_LockSurface(_mouseOrigSurface);
@@ -2253,9 +2406,13 @@ void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h,
 	}
 
 	// Draw from [_maxExtraPixels,_maxExtraPixels] since scalers will read past boudaries
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	Graphics::copyBlit((byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * _cursorFormat.bytesPerPixel,
+	                   (const byte *)buf, _mouseOrigSurface->pitch, w * _cursorFormat.bytesPerPixel, w, h, _cursorFormat.bytesPerPixel);
+#else
 	Graphics::copyBlit((byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * _mouseOrigSurface->format->BytesPerPixel,
 	                   (const byte *)buf, _mouseOrigSurface->pitch, w * _cursorFormat.bytesPerPixel, w, h, _cursorFormat.bytesPerPixel);
-
+#endif
 	SDL_UnlockSurface(_mouseOrigSurface);
 
 	blitCursor();
@@ -2316,8 +2473,26 @@ void SurfaceSdlGraphicsManager::blitCursor() {
 
 	if (sizeChanged || !_mouseSurface) {
 		if (_mouseSurface)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			SDL_DestroySurface(_mouseSurface);
+#else
 			SDL_FreeSurface(_mouseSurface);
+#endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(_mouseOrigSurface->format);
+		if (pixelFormatDetails == nullptr)
+			error("getting pixel format details failed");
+		_mouseSurface = SDL_CreateSurface(
+			_mouseCurState.rW,
+			_mouseCurState.rH,
+			SDL_GetPixelFormatForMasks(
+				pixelFormatDetails->bits_per_pixel,
+				pixelFormatDetails->Rmask,
+				pixelFormatDetails->Gmask,
+				pixelFormatDetails->Bmask,
+				pixelFormatDetails->Amask));
+#else
 		_mouseSurface = SDL_CreateRGBSurface(SDL_SWSURFACE,
 						_mouseCurState.rW,
 						_mouseCurState.rH,
@@ -2326,14 +2501,21 @@ void SurfaceSdlGraphicsManager::blitCursor() {
 						_mouseOrigSurface->format->Gmask,
 						_mouseOrigSurface->format->Bmask,
 						_mouseOrigSurface->format->Amask);
+#endif
 
 		if (_mouseSurface == nullptr)
 			error("Allocating _mouseSurface failed");
 	}
 
 	SDL_SetColors(_mouseSurface, _cursorPaletteDisabled ? _currentPalette : _cursorPalette, 0, 256);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	uint32 flags = _disableMouseKeyColor ? 0 : SDL_SRCCOLORKEY | SDL_SRCALPHA;
+	SDL_SetSurfaceColorKey(_mouseSurface, flags, _mouseKeyColor);
+	SDL_SetSurfaceRLE(_mouseSurface, !_disableMouseKeyColor);
+#else
 	uint32 flags = _disableMouseKeyColor ? 0 : SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA;
 	SDL_SetColorKey(_mouseSurface, flags, _mouseKeyColor);
+#endif
 
 	SDL_LockSurface(_mouseOrigSurface);
 	SDL_LockSurface(_mouseSurface);
@@ -2347,23 +2529,55 @@ void SurfaceSdlGraphicsManager::blitCursor() {
 		// fall back on the Normal scaler when a smaller cursor is supplied.
 		if (_mouseScaler && _scalerPlugin->canDrawCursor() && (uint)_mouseCurState.h >= _extraPixels) {
 			_mouseScaler->setFactor(_videoMode.scaleFactor);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(_mouseOrigSurface->format);
+			if (pixelFormatDetails == nullptr)
+				error("getting pixel format details failed");
+			_mouseScaler->scale(
+					(byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * pixelFormatDetails->bytes_per_pixel,
+					_mouseOrigSurface->pitch, (byte *)_mouseSurface->pixels, _mouseSurface->pitch,
+					_mouseCurState.w, _mouseCurState.h, 0, 0);
+#else
 			_mouseScaler->scale(
 					(byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * _mouseOrigSurface->format->BytesPerPixel,
 					_mouseOrigSurface->pitch, (byte *)_mouseSurface->pixels, _mouseSurface->pitch,
 					_mouseCurState.w, _mouseCurState.h, 0, 0);
+#endif
 		} else
 #endif
 		{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(_mouseOrigSurface->format);
+			if (pixelFormatDetails == nullptr)
+				error("getting pixel format details failed");
+			Graphics::scaleBlit((byte *)_mouseSurface->pixels, (const byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * pixelFormatDetails->bytes_per_pixel,
+			                    _mouseSurface->pitch, _mouseOrigSurface->pitch,
+				                _mouseCurState.w * _videoMode.scaleFactor, _mouseCurState.h * _videoMode.scaleFactor,
+			                    _mouseCurState.w, _mouseCurState.h, convertSDLPixelFormat(_mouseSurface->format));
+#else
 			Graphics::scaleBlit((byte *)_mouseSurface->pixels, (const byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * _mouseOrigSurface->format->BytesPerPixel,
 			                    _mouseSurface->pitch, _mouseOrigSurface->pitch,
 				                _mouseCurState.w * _videoMode.scaleFactor, _mouseCurState.h * _videoMode.scaleFactor,
 			                    _mouseCurState.w, _mouseCurState.h, convertSDLPixelFormat(_mouseSurface->format));
+#endif
 
 		}
 	} else {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		const SDL_PixelFormatDetails *srcPixelFormatDetails = SDL_GetPixelFormatDetails(_mouseOrigSurface->format);
+		if (srcPixelFormatDetails == nullptr)
+			error("getting pixel format details failed");
+		const SDL_PixelFormatDetails *dstPixelFormatDetails = SDL_GetPixelFormatDetails(_mouseSurface->format);
+		if (dstPixelFormatDetails == nullptr)
+			error("getting pixel format details failed");
+		Graphics::copyBlit((byte *)_mouseSurface->pixels, (const byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * srcPixelFormatDetails->bytes_per_pixel,
+		                   _mouseSurface->pitch, _mouseOrigSurface->pitch,
+		                   _mouseCurState.w, _mouseCurState.h, dstPixelFormatDetails->bytes_per_pixel);
+#else
 		Graphics::copyBlit((byte *)_mouseSurface->pixels, (const byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * _maxExtraPixels + _maxExtraPixels * _mouseOrigSurface->format->BytesPerPixel,
 		                   _mouseSurface->pitch, _mouseOrigSurface->pitch,
 		                   _mouseCurState.w, _mouseCurState.h, _mouseSurface->format->BytesPerPixel);
+#endif
 	}
 
 #ifdef USE_ASPECT
@@ -2445,7 +2659,7 @@ void SurfaceSdlGraphicsManager::drawMouse() {
 	// Note that SDL_BlitSurface() and addDirtyRect() will both perform any
 	// clipping necessary
 
-	if (SDL_BlitSurface(_mouseSurface, nullptr, _hwScreen, &dst) != 0)
+	if (!blitSurface(_mouseSurface, nullptr, _hwScreen, &dst))
 		error("SDL_BlitSurface failed: %s", SDL_GetError());
 }
 
@@ -2497,18 +2711,31 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const Common::U32String &msg
 	if (height > _hwScreen->h)
 		height = _hwScreen->h;
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(_hwScreen->format);
+	if (pixelFormatDetails == nullptr)
+		error("getting pixel format details failed");
+	_osdMessageSurface = SDL_CreateSurface(
+		width, height,
+		SDL_GetPixelFormatForMasks(pixelFormatDetails->bits_per_pixel, pixelFormatDetails->Rmask, pixelFormatDetails->Gmask, pixelFormatDetails->Bmask, pixelFormatDetails->Amask));
+#else
 	_osdMessageSurface = SDL_CreateRGBSurface(
 		SDL_SWSURFACE,
 		width, height, _hwScreen->format->BitsPerPixel, _hwScreen->format->Rmask, _hwScreen->format->Gmask, _hwScreen->format->Bmask, _hwScreen->format->Amask
 	);
+#endif
 
 	// Lock the surface
-	if (SDL_LockSurface(_osdMessageSurface))
+	if (!lockSurface(_osdMessageSurface))
 		error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
 
 	// Draw a dark gray rect
 	// TODO: Rounded corners ? Border?
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_FillSurfaceRect(_osdMessageSurface, nullptr, SDL_MapSurfaceRGB(_osdMessageSurface, 64, 64, 64));
+#else
 	SDL_FillRect(_osdMessageSurface, nullptr, SDL_MapRGB(_osdMessageSurface->format, 64, 64, 64));
+#endif
 
 	Graphics::Surface dst;
 	dst.init(_osdMessageSurface->w, _osdMessageSurface->h, _osdMessageSurface->pitch, _osdMessageSurface->pixels,
@@ -2518,7 +2745,11 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const Common::U32String &msg
 	for (i = 0; i < lines.size(); i++) {
 		font->drawString(&dst, lines[i],
 			0, 0 + i * lineHeight + vOffset + lineSpacing, width,
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			SDL_MapSurfaceRGB(_osdMessageSurface, 255, 255, 255),
+#else
 			SDL_MapRGB(_osdMessageSurface->format, 255, 255, 255),
+#endif
 			Graphics::kTextAlignCenter, 0, true);
 	}
 
@@ -2529,7 +2760,12 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const Common::U32String &msg
 	_osdMessageAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
 	_osdMessageFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay;
 	// Enable alpha blending
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_SetAlpha(_osdMessageSurface, SDL_SRCALPHA, _osdMessageAlpha);
+	SDL_SetSurfaceRLE(_osdMessageSurface, true);
+#else
 	SDL_SetAlpha(_osdMessageSurface, SDL_RLEACCEL | SDL_SRCALPHA, _osdMessageAlpha);
+#endif
 
 #if defined(MACOSX)
 	macOSTouchbarUpdate(msg.encode().c_str());
@@ -2564,13 +2800,28 @@ void SurfaceSdlGraphicsManager::displayActivityIconOnOSD(const Graphics::Surface
 	}
 
 	if (_osdIconSurface) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_DestroySurface(_osdIconSurface);
+#else
 		SDL_FreeSurface(_osdIconSurface);
+#endif
 		_osdIconSurface = nullptr;
 	}
 
 	if (icon) {
 		const Graphics::PixelFormat &iconFormat = icon->format;
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		_osdIconSurface = SDL_CreateSurface(
+				icon->w, icon->h,
+				SDL_GetPixelFormatForMasks(
+					iconFormat.bytesPerPixel * 8,
+					((0xFF >> iconFormat.rLoss) << iconFormat.rShift),
+					((0xFF >> iconFormat.gLoss) << iconFormat.gShift),
+					((0xFF >> iconFormat.bLoss) << iconFormat.bShift),
+					((0xFF >> iconFormat.aLoss) << iconFormat.aShift))
+		);
+#else
 		_osdIconSurface = SDL_CreateRGBSurface(
 				SDL_SWSURFACE,
 				icon->w, icon->h, iconFormat.bytesPerPixel * 8,
@@ -2579,9 +2830,10 @@ void SurfaceSdlGraphicsManager::displayActivityIconOnOSD(const Graphics::Surface
 				((0xFF >> iconFormat.bLoss) << iconFormat.bShift),
 				((0xFF >> iconFormat.aLoss) << iconFormat.aShift)
 		);
+#endif
 
 		// Lock the surface
-		if (SDL_LockSurface(_osdIconSurface))
+		if (!lockSurface(_osdIconSurface))
 			error("displayActivityIconOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
 
 		byte *dst = (byte *) _osdIconSurface->pixels;
@@ -2609,7 +2861,11 @@ SDL_Rect SurfaceSdlGraphicsManager::getOSDIconRect() const {
 void SurfaceSdlGraphicsManager::removeOSDMessage() {
 	// Remove the previous message
 	if (_osdMessageSurface) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_DestroySurface(_osdMessageSurface);
+#else
 		SDL_FreeSurface(_osdMessageSurface);
+#endif
 		_forceRedraw = true;
 	}
 
@@ -2635,7 +2891,12 @@ void SurfaceSdlGraphicsManager::updateOSD() {
 				const int startAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
 				_osdMessageAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration;
 			}
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			SDL_SetAlpha(_osdMessageSurface, SDL_SRCALPHA, _osdMessageAlpha);
+			SDL_SetSurfaceRLE(_osdMessageSurface, true);
+#else
 			SDL_SetAlpha(_osdMessageSurface, SDL_RLEACCEL | SDL_SRCALPHA, _osdMessageAlpha);
+#endif
 		}
 
 		if (_osdMessageAlpha == SDL_ALPHA_TRANSPARENT) {
@@ -2823,7 +3084,7 @@ void SurfaceSdlGraphicsManager::notifyResize(const int width, const int height)
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 void SurfaceSdlGraphicsManager::deinitializeRenderer() {
-#if defined(USE_IMGUI) && defined(USE_IMGUI_SDLRENDERER2)
+#if defined(USE_IMGUI) && (defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3))
 	destroyImGui();
 #endif
 
@@ -2840,12 +3101,18 @@ void SurfaceSdlGraphicsManager::recreateScreenTexture() {
 	if (!_renderer)
 		return;
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, _videoMode.filtering ? "linear" : "nearest");
+#endif
 
 	SDL_Texture *oldTexture = _screenTexture;
 	_screenTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, _videoMode.hardwareWidth, _videoMode.hardwareHeight);
-	if (_screenTexture)
+	if (_screenTexture) {
 		SDL_DestroyTexture(oldTexture);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_SetTextureScaleMode(_screenTexture, _videoMode.filtering ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST);
+#endif
+	}
 	else
 		_screenTexture = oldTexture;
 }
@@ -2854,15 +3121,27 @@ SDL_Surface *SurfaceSdlGraphicsManager::SDL_SetVideoMode(int width, int height,
 	deinitializeRenderer();
 
 	uint32 createWindowFlags = SDL_WINDOW_RESIZABLE;
-	uint32 rendererFlags = 0;
 	if ((flags & SDL_FULLSCREEN) != 0) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		createWindowFlags |= SDL_WINDOW_FULLSCREEN;
+#else
 		createWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+#endif
 	}
 
 	if (!createOrUpdateWindow(width, height, createWindowFlags)) {
 		return nullptr;
 	}
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if ((flags & SDL_FULLSCREEN) != 0) {
+		if (!SDL_SetWindowFullscreenMode(_window->getSDLWindow(), NULL))
+			warning("SDL_SetWindowFullscreenMode failed (%s)", SDL_GetError());
+		if (!SDL_SyncWindow(_window->getSDLWindow()))
+			warning("SDL_SyncWindow failed (%s)", SDL_GetError());
+	}
+#endif
+
 #if defined(MACOSX) && SDL_VERSION_ATLEAST(2, 0, 10)
 	// WORKAROUND: Bug #11430: "macOS: blurry content on Retina displays"
 	// Since SDL 2.0.10, Metal takes priority over OpenGL rendering on macOS,
@@ -2871,18 +3150,34 @@ SDL_Surface *SurfaceSdlGraphicsManager::SDL_SetVideoMode(int width, int height,
 	SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
 #endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_PropertiesID props = SDL_CreateProperties();
+	SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, _window->getSDLWindow());
+	SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, _videoMode.vsync ? 1 : 0);
+	_renderer = SDL_CreateRendererWithProperties(props);
+	SDL_DestroyProperties(props);
+#else
+	uint32 rendererFlags = 0;
 	if (_videoMode.vsync) {
 		rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
 	}
-
 	_renderer = SDL_CreateRenderer(_window->getSDLWindow(), -1, rendererFlags);
+#endif
 	if (!_renderer) {
 		if (_videoMode.vsync) {
 			// VSYNC might not be available, so retry without VSYNC
 			warning("SDL_SetVideoMode: SDL_CreateRenderer() failed with VSYNC option, retrying without it...");
 			_videoMode.vsync = false;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			props = SDL_CreateProperties();
+			SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, _window->getSDLWindow());
+			SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
+			_renderer = SDL_CreateRendererWithProperties(props);
+			SDL_DestroyProperties(props);
+#else
 			rendererFlags &= ~SDL_RENDERER_PRESENTVSYNC;
 			_renderer = SDL_CreateRenderer(_window->getSDLWindow(), -1, rendererFlags);
+#endif
 		}
 		if (!_renderer) {
 			deinitializeRenderer();
@@ -2893,23 +3188,36 @@ SDL_Surface *SurfaceSdlGraphicsManager::SDL_SetVideoMode(int width, int height,
 	getWindowSizeFromSdl(&_windowWidth, &_windowHeight);
 	handleResize(_windowWidth, _windowHeight);
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, _videoMode.filtering ? "linear" : "nearest");
+#endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_PixelFormat format = SDL_PIXELFORMAT_RGB565;
+#else
 	Uint32 format = SDL_PIXELFORMAT_RGB565;
+#endif
 
 	_screenTexture = SDL_CreateTexture(_renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height);
 	if (!_screenTexture) {
 		deinitializeRenderer();
 		return nullptr;
 	}
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_SetTextureScaleMode(_screenTexture, _videoMode.filtering ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST);
+#endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Surface *screen = SDL_CreateSurface(width, height, format);
+#else
 	SDL_Surface *screen = SDL_CreateRGBSurfaceWithFormat(0, width, height, SDL_BITSPERPIXEL(format), format);
+#endif
 	if (!screen) {
 		deinitializeRenderer();
 		return nullptr;
 	}
 
-#if defined(USE_IMGUI) && defined(USE_IMGUI_SDLRENDERER2)
+#if defined(USE_IMGUI) && (defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3))
 	// Setup Dear ImGui
 	initImGui(_renderer, nullptr);
 #endif
@@ -2940,21 +3248,55 @@ void SurfaceSdlGraphicsManager::SDL_UpdateRects(SDL_Surface *screen, int numrect
 
 	SDL_RenderClear(_renderer);
 
-	if (rotangle != 0)
+	if (rotangle != 0) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_FRect fViewport;
+		SDL_RectToFRect(&viewport, &fViewport);
+		SDL_RenderTextureRotated(_renderer, _screenTexture, nullptr, &fViewport, rotangle, nullptr, SDL_FLIP_NONE);
+#else
 		SDL_RenderCopyEx(_renderer, _screenTexture, nullptr, &viewport, rotangle, nullptr, SDL_FLIP_NONE);
-	else
+#endif
+	} else {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_FRect fViewport;
+		SDL_RectToFRect(&viewport, &fViewport);
+		SDL_RenderTexture(_renderer, _screenTexture, nullptr, &fViewport);
+#else
 		SDL_RenderCopy(_renderer, _screenTexture, nullptr, &viewport);
+#endif
+	}
 }
 
 int SurfaceSdlGraphicsManager::SDL_SetColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
+	if (palette) {
+		return !SDL_SetPaletteColors(palette, colors, firstcolor, ncolors) ? 1 : 0;
+	}
+#else
 	if (surface->format->palette) {
 		return !SDL_SetPaletteColors(surface->format->palette, colors, firstcolor, ncolors) ? 1 : 0;
-	} else {
-		return 0;
 	}
+#endif
+	return 0;
 }
 
 int SurfaceSdlGraphicsManager::SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (!SDL_SetSurfaceAlphaMod(surface, alpha)) {
+		return -1;
+	}
+
+	if (alpha == 255 || !flag) {
+		if (!SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE)) {
+			return -1;
+		}
+	} else {
+		if (!SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND)) {
+			return -1;
+		}
+	}
+#else
 	if (SDL_SetSurfaceAlphaMod(surface, alpha)) {
 		return -1;
 	}
@@ -2968,15 +3310,21 @@ int SurfaceSdlGraphicsManager::SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, U
 			return -1;
 		}
 	}
+#endif
+
 
 	return 0;
 }
 
 int SurfaceSdlGraphicsManager::SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_SetSurfaceColorKey(surface, flag, key) ? -1 : 0;
+#else
 	return ::SDL_SetColorKey(surface, flag ? SDL_TRUE : SDL_FALSE, key) ? -1 : 0;
+#endif
 }
 
-#if defined(USE_IMGUI) && defined(USE_IMGUI_SDLRENDERER2)
+#if defined(USE_IMGUI) && (defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3))
 void *SurfaceSdlGraphicsManager::getImGuiTexture(const Graphics::Surface &image, const byte *palette, int palCount) {
 
 	// Upload pixels into texture
@@ -2989,7 +3337,11 @@ void *SurfaceSdlGraphicsManager::getImGuiTexture(const Graphics::Surface &image,
 	Graphics::Surface *s = image.convertTo(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24), palette, palCount);
 	SDL_UpdateTexture(texture, nullptr, s->getPixels(), s->pitch);
 	SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
+#ifdef USE_IMGUI_SDLRENDERER3
+	SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_LINEAR);
+#elif defined(USE_IMGUI_SDLRENDERER2)
 	SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);
+#endif
 
 	s->free();
 	delete s;
@@ -3000,7 +3352,7 @@ void *SurfaceSdlGraphicsManager::getImGuiTexture(const Graphics::Surface &image,
 void SurfaceSdlGraphicsManager::freeImGuiTexture(void *texture) {
 	SDL_DestroyTexture((SDL_Texture *) texture);
 }
-#endif // defined(USE_IMGUI) && defined(USE_IMGUI_SDLRENDERER2)
+#endif // defined(USE_IMGUI) && (defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3))
 
 #endif // SDL_VERSION_ATLEAST(2, 0, 0)
 
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h
index 87abc4f7603..ce716bd5e19 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.h
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h
@@ -95,7 +95,11 @@ protected:
 	 * @param in    The SDL pixel format to convert
 	 * @param out   A pixel format to be written to
 	 */
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	Graphics::PixelFormat convertSDLPixelFormat(SDL_PixelFormat in) const;
+#else
 	Graphics::PixelFormat convertSDLPixelFormat(SDL_PixelFormat *in) const;
+#endif
 public:
 	void copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) override;
 	Graphics::Surface *lockScreen() override;
@@ -129,7 +133,7 @@ public:
 	void notifyVideoExpose() override;
 	void notifyResize(const int width, const int height) override;
 
-#if defined(USE_IMGUI) && defined(USE_IMGUI_SDLRENDERER2)
+#if defined(USE_IMGUI) && (defined(USE_IMGUI_SDLRENDERER2) || defined(USE_IMGUI_SDLRENDERER3))
 	void *getImGuiTexture(const Graphics::Surface &image, const byte *palette, int palCount) override;
 	void freeImGuiTexture(void *texture) override;
 #endif
diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
index f5983ac2668..e7aa3b81ad7 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
@@ -46,6 +46,36 @@
 #include "image/bmp.h"
 #endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static bool sdlGetAttribute(SDL_GLAttr attr, int *value) {
+	return SDL_GL_GetAttribute(attr, value);
+}
+#else
+static bool sdlGetAttribute(SDL_GLattr attr, int *value) {
+	return SDL_GL_GetAttribute(attr, value) != 0;
+}
+#endif
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static void sdlGLDestroyContext(SDL_GLContext context) {
+	SDL_GL_DestroyContext(context);
+}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
+static void sdlGLDestroyContext(SDL_GLContext context) {
+	SDL_GL_DeleteContext(context);
+}
+#endif
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static bool sdlSetSwapInterval(int interval) {
+	return SDL_GL_SetSwapInterval(interval);
+}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
+static bool sdlSetSwapInterval(int interval) {
+	return SDL_GL_SetSwapInterval(interval) == 0;
+}
+#endif
+
 OpenGLSdlGraphics3dManager::OpenGLSdlGraphics3dManager(SdlEventSource *eventSource, SdlWindow *window, bool supportsFrameBuffer)
 	: SdlGraphicsManager(eventSource, window),
 #if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -98,16 +128,16 @@ OpenGLSdlGraphics3dManager::OpenGLSdlGraphics3dManager(SdlEventSource *eventSour
 	// because then we already set up what we want to use.
 	//
 	// In case no defaults are given we prefer OpenGL over OpenGL ES.
-	if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &_glContextProfileMask) != 0) {
+	if (!sdlGetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &_glContextProfileMask)) {
 		_glContextProfileMask = 0;
 		noDefaults = true;
 	}
 
-	if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &_glContextMajor) != 0) {
+	if (!sdlGetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &_glContextMajor)) {
 		noDefaults = true;
 	}
 
-	if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &_glContextMinor) != 0) {
+	if (!sdlGetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &_glContextMinor)) {
 		noDefaults = true;
 	}
 
@@ -385,7 +415,10 @@ void OpenGLSdlGraphics3dManager::createOrUpdateScreen() {
 		g_system->quit();
 	}
 
-#if SDL_VERSION_ATLEAST(2, 0, 1)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int obtainedWidth = 0, obtainedHeight = 0;
+	SDL_GetWindowSizeInPixels(_window->getSDLWindow(), &obtainedWidth, &obtainedHeight);
+#elif SDL_VERSION_ATLEAST(2, 0, 1)
 	int obtainedWidth = 0, obtainedHeight = 0;
 	SDL_GL_GetDrawableSize(_window->getSDLWindow(), &obtainedWidth, &obtainedHeight);
 #else
@@ -410,8 +443,13 @@ void OpenGLSdlGraphics3dManager::notifyResize(const int width, const int height)
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 	// Get the updated size directly from SDL, in case there are multiple
 	// resize events in the message queue.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int newWidth = 0, newHeight = 0;
+	SDL_GetWindowSizeInPixels(_window->getSDLWindow(), &newWidth, &newHeight);
+#else
 	int newWidth = 0, newHeight = 0;
 	SDL_GL_GetDrawableSize(_window->getSDLWindow(), &newWidth, &newHeight);
+#endif
 
 	if (newWidth == _overlayScreen->getWidth() && newHeight == _overlayScreen->getHeight()) {
 		return; // nothing to do
@@ -457,7 +495,7 @@ void OpenGLSdlGraphics3dManager::initializeOpenGLContext() const {
 	OpenGLContext.initialize(_glContextType);
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
-	if (SDL_GL_SetSwapInterval(_vsync ? 1 : 0)) {
+	if (!sdlSetSwapInterval(_vsync ? 1 : 0)) {
 		warning("Unable to %s VSync: %s", _vsync ? "enable" : "disable", SDL_GetError());
 	}
 #endif
@@ -787,7 +825,15 @@ int16 OpenGLSdlGraphics3dManager::getOverlayWidth() const {
 }
 
 bool OpenGLSdlGraphics3dManager::showMouse(bool visible) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (visible) {
+		SDL_ShowCursor();
+	} else {
+		SDL_HideCursor();
+	}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
+#endif
 	return true;
 }
 
@@ -803,7 +849,7 @@ void OpenGLSdlGraphics3dManager::deinitializeRenderer() {
 	destroyImGui();
 #endif
 
-	SDL_GL_DeleteContext(_glContext);
+	sdlGLDestroyContext(_glContext);
 	_glContext = nullptr;
 }
 #endif // SDL_VERSION_ATLEAST(2, 0, 0)
diff --git a/backends/imgui/backends/imgui_impl_sdl3.cpp b/backends/imgui/backends/imgui_impl_sdl3.cpp
new file mode 100644
index 00000000000..51576346409
--- /dev/null
+++ b/backends/imgui/backends/imgui_impl_sdl3.cpp
@@ -0,0 +1,1156 @@
+// dear imgui: Platform Backend for SDL3 (*EXPERIMENTAL*)
+// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
+// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
+
+// (**IMPORTANT: SDL 3.0.0 is NOT YET RELEASED AND CURRENTLY HAS A FAST CHANGING API. THIS CODE BREAKS OFTEN AS SDL3 CHANGES.**)
+
+// Implemented features:
+//  [X] Platform: Clipboard support.
+//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+//  [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue.
+// Issues:
+//  [ ] Platform: Multi-viewport: Minimized windows seems to break mouse wheel events (at least under Windows).
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
+//  2024-09-11: (Docking) Added support for viewport->ParentViewportId field to support parenting at OS level. (#7973)
+//  2024-09-03: Update for SDL3 api changes: SDL_GetGamepads() memory ownership revert. (#7918, #7898, #7807)
+//  2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO:
+//               - io.GetClipboardTextFn    -> platform_io.Platform_GetClipboardTextFn
+//               - io.SetClipboardTextFn    -> platform_io.Platform_SetClipboardTextFn
+//               - io.PlatformSetImeDataFn  -> platform_io.Platform_SetImeDataFn
+//  2024-08-19: Storing SDL_WindowID inside ImGuiViewport::PlatformHandle instead of SDL_Window*.
+//  2024-08-19: ImGui_ImplSDL3_ProcessEvent() now ignores events intended for other SDL windows. (#7853)
+//  2024-07-22: Update for SDL3 api changes: SDL_GetGamepads() memory ownership change. (#7807)
+//  2024-07-18: Update for SDL3 api changes: SDL_GetClipboardText() memory ownership change. (#7801)
+//  2024-07-15: Update for SDL3 api changes: SDL_GetProperty() change to SDL_GetPointerProperty(). (#7794)
+//  2024-07-02: Update for SDL3 api changes: SDLK_x renames and SDLK_KP_x removals (#7761, #7762).
+//  2024-07-01: Update for SDL3 api changes: SDL_SetTextInputRect() changed to SDL_SetTextInputArea().
+//  2024-06-26: Update for SDL3 api changes: SDL_StartTextInput()/SDL_StopTextInput()/SDL_SetTextInputRect() functions signatures.
+//  2024-06-24: Update for SDL3 api changes: SDL_EVENT_KEY_DOWN/SDL_EVENT_KEY_UP contents.
+//  2024-06-03; Update for SDL3 api changes: SDL_SYSTEM_CURSOR_ renames.
+//  2024-05-15: Update for SDL3 api changes: SDLK_ renames.
+//  2024-04-15: Inputs: Re-enable calling SDL_StartTextInput()/SDL_StopTextInput() as SDL3 no longer enables it by default and should play nicer with IME.
+//  2024-02-13: Inputs: Fixed gamepad support. Handle gamepad disconnection. Added ImGui_ImplSDL3_SetGamepadMode().
+//  2023-11-13: Updated for recent SDL3 API changes.
+//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
+//  2023-05-04: Fixed build on Emscripten/iOS/Android. (#6391)
+//  2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
+//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
+//  2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
+//  2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.
+
+#include "backends/imgui/imgui.h"
+#ifndef IMGUI_DISABLE
+#include "backends/platform/sdl/sdl.h"
+#include "imgui_impl_sdl3.h"
+
+// Clang warnings with -Weverything
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
+#endif
+
+// SDL
+#include <SDL3/SDL.h>
+#if defined(__APPLE__)
+#include <TargetConditionals.h>
+#endif
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#endif
+
+#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__)
+#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    1
+#else
+#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    0
+#endif
+
+// FIXME-LEGACY: remove when SDL 3.1.3 preview is released.
+#ifndef SDLK_APOSTROPHE
+#define SDLK_APOSTROPHE SDLK_QUOTE
+#endif
+#ifndef SDLK_GRAVE
+#define SDLK_GRAVE SDLK_BACKQUOTE
+#endif
+
+// SDL Data
+struct ImGui_ImplSDL3_Data
+{
+    SDL_Window*             Window;
+    SDL_WindowID            WindowID;
+    SDL_Renderer*           Renderer;
+    Uint64                  Time;
+    char*                   ClipboardTextData;
+    bool                    UseVulkan;
+    bool                    WantUpdateMonitors;
+
+    // IME handling
+    SDL_Window*             ImeWindow;
+
+    // Mouse handling
+    Uint32                  MouseWindowID;
+    int                     MouseButtonsDown;
+    SDL_Cursor*             MouseCursors[ImGuiMouseCursor_COUNT];
+    SDL_Cursor*             MouseLastCursor;
+    int                     MousePendingLeaveFrame;
+    bool                    MouseCanUseGlobalState;
+    bool                    MouseCanReportHoveredViewport;  // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state.
+
+    // Gamepad handling
+    ImVector<SDL_Gamepad*>  Gamepads;
+    ImGui_ImplSDL3_GamepadMode  GamepadMode;
+    bool                    WantUpdateGamepadsList;
+
+    ImGui_ImplSDL3_Data()   { memset((void*)this, 0, sizeof(*this)); }
+};
+
+// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
+// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
+static ImGui_ImplSDL3_Data* ImGui_ImplSDL3_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplSDL3_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
+}
+
+// Forward Declarations
+static void ImGui_ImplSDL3_UpdateMonitors();
+static void ImGui_ImplSDL3_InitPlatformInterface(SDL_Window* window, void* sdl_gl_context);
+static void ImGui_ImplSDL3_ShutdownPlatformInterface();
+
+// Functions
+static const char* ImGui_ImplSDL3_GetClipboardText(ImGuiContext*)
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    if (bd->ClipboardTextData)
+        SDL_free(bd->ClipboardTextData);
+    const char* sdl_clipboard_text = SDL_GetClipboardText();
+    bd->ClipboardTextData = sdl_clipboard_text ? SDL_strdup(sdl_clipboard_text) : NULL;
+    return bd->ClipboardTextData;
+}
+
+static void ImGui_ImplSDL3_SetClipboardText(ImGuiContext*, const char* text)
+{
+    SDL_SetClipboardText(text);
+}
+
+static void ImGui_ImplSDL3_PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data)
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    SDL_WindowID window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle;
+    SDL_Window* window = SDL_GetWindowFromID(window_id);
+    if ((data->WantVisible == false || bd->ImeWindow != window) && bd->ImeWindow != NULL)
+    {
+        SDL_StopTextInput(bd->ImeWindow);
+        bd->ImeWindow = nullptr;
+    }
+    if (data->WantVisible)
+    {
+        SDL_Rect r;
+        r.x = (int)(data->InputPos.x - viewport->Pos.x);
+        r.y = (int)(data->InputPos.y - viewport->Pos.y + data->InputLineHeight);
+        r.w = 1;
+        r.h = (int)data->InputLineHeight;
+        SDL_SetTextInputArea(window, &r, 0);
+        SDL_StartTextInput(window);
+        bd->ImeWindow = window;
+    }
+}
+
+// Not static to allow third-party code to use that if they want to (but undocumented)
+ImGuiKey ImGui_ImplSDL3_KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode)
+{
+    // Keypad doesn't have individual key values in SDL3
+    switch (scancode)
+    {
+        case SDL_SCANCODE_KP_0: return ImGuiKey_Keypad0;
+        case SDL_SCANCODE_KP_1: return ImGuiKey_Keypad1;
+        case SDL_SCANCODE_KP_2: return ImGuiKey_Keypad2;
+        case SDL_SCANCODE_KP_3: return ImGuiKey_Keypad3;
+        case SDL_SCANCODE_KP_4: return ImGuiKey_Keypad4;
+        case SDL_SCANCODE_KP_5: return ImGuiKey_Keypad5;
+        case SDL_SCANCODE_KP_6: return ImGuiKey_Keypad6;
+        case SDL_SCANCODE_KP_7: return ImGuiKey_Keypad7;
+        case SDL_SCANCODE_KP_8: return ImGuiKey_Keypad8;
+        case SDL_SCANCODE_KP_9: return ImGuiKey_Keypad9;
+        case SDL_SCANCODE_KP_PERIOD: return ImGuiKey_KeypadDecimal;
+        case SDL_SCANCODE_KP_DIVIDE: return ImGuiKey_KeypadDivide;
+        case SDL_SCANCODE_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
+        case SDL_SCANCODE_KP_MINUS: return ImGuiKey_KeypadSubtract;
+        case SDL_SCANCODE_KP_PLUS: return ImGuiKey_KeypadAdd;
+        case SDL_SCANCODE_KP_ENTER: return ImGuiKey_KeypadEnter;
+        case SDL_SCANCODE_KP_EQUALS: return ImGuiKey_KeypadEqual;
+        default: break;
+    }
+    switch (keycode)
+    {
+        case SDLK_TAB: return ImGuiKey_Tab;
+        case SDLK_LEFT: return ImGuiKey_LeftArrow;
+        case SDLK_RIGHT: return ImGuiKey_RightArrow;
+        case SDLK_UP: return ImGuiKey_UpArrow;
+        case SDLK_DOWN: return ImGuiKey_DownArrow;
+        case SDLK_PAGEUP: return ImGuiKey_PageUp;
+        case SDLK_PAGEDOWN: return ImGuiKey_PageDown;
+        case SDLK_HOME: return ImGuiKey_Home;
+        case SDLK_END: return ImGuiKey_End;
+        case SDLK_INSERT: return ImGuiKey_Insert;
+        case SDLK_DELETE: return ImGuiKey_Delete;
+        case SDLK_BACKSPACE: return ImGuiKey_Backspace;
+        case SDLK_SPACE: return ImGuiKey_Space;
+        case SDLK_RETURN: return ImGuiKey_Enter;
+        case SDLK_ESCAPE: return ImGuiKey_Escape;
+        case SDLK_APOSTROPHE: return ImGuiKey_Apostrophe;
+        case SDLK_COMMA: return ImGuiKey_Comma;
+        case SDLK_MINUS: return ImGuiKey_Minus;
+        case SDLK_PERIOD: return ImGuiKey_Period;
+        case SDLK_SLASH: return ImGuiKey_Slash;
+        case SDLK_SEMICOLON: return ImGuiKey_Semicolon;
+        case SDLK_EQUALS: return ImGuiKey_Equal;
+        case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket;
+        case SDLK_BACKSLASH: return ImGuiKey_Backslash;
+        case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket;
+        case SDLK_GRAVE: return ImGuiKey_GraveAccent;
+        case SDLK_CAPSLOCK: return ImGuiKey_CapsLock;
+        case SDLK_SCROLLLOCK: return ImGuiKey_ScrollLock;
+        case SDLK_NUMLOCKCLEAR: return ImGuiKey_NumLock;
+        case SDLK_PRINTSCREEN: return ImGuiKey_PrintScreen;
+        case SDLK_PAUSE: return ImGuiKey_Pause;
+        case SDLK_LCTRL: return ImGuiKey_LeftCtrl;
+        case SDLK_LSHIFT: return ImGuiKey_LeftShift;
+        case SDLK_LALT: return ImGuiKey_LeftAlt;
+        case SDLK_LGUI: return ImGuiKey_LeftSuper;
+        case SDLK_RCTRL: return ImGuiKey_RightCtrl;
+        case SDLK_RSHIFT: return ImGuiKey_RightShift;
+        case SDLK_RALT: return ImGuiKey_RightAlt;
+        case SDLK_RGUI: return ImGuiKey_RightSuper;
+        case SDLK_APPLICATION: return ImGuiKey_Menu;
+        case SDLK_0: return ImGuiKey_0;
+        case SDLK_1: return ImGuiKey_1;
+        case SDLK_2: return ImGuiKey_2;
+        case SDLK_3: return ImGuiKey_3;
+        case SDLK_4: return ImGuiKey_4;
+        case SDLK_5: return ImGuiKey_5;
+        case SDLK_6: return ImGuiKey_6;
+        case SDLK_7: return ImGuiKey_7;
+        case SDLK_8: return ImGuiKey_8;
+        case SDLK_9: return ImGuiKey_9;
+        case SDLK_A: return ImGuiKey_A;
+        case SDLK_B: return ImGuiKey_B;
+        case SDLK_C: return ImGuiKey_C;
+        case SDLK_D: return ImGuiKey_D;
+        case SDLK_E: return ImGuiKey_E;
+        case SDLK_F: return ImGuiKey_F;
+        case SDLK_G: return ImGuiKey_G;
+        case SDLK_H: return ImGuiKey_H;
+        case SDLK_I: return ImGuiKey_I;
+        case SDLK_J: return ImGuiKey_J;
+        case SDLK_K: return ImGuiKey_K;
+        case SDLK_L: return ImGuiKey_L;
+        case SDLK_M: return ImGuiKey_M;
+        case SDLK_N: return ImGuiKey_N;
+        case SDLK_O: return ImGuiKey_O;
+        case SDLK_P: return ImGuiKey_P;
+        case SDLK_Q: return ImGuiKey_Q;
+        case SDLK_R: return ImGuiKey_R;
+        case SDLK_S: return ImGuiKey_S;
+        case SDLK_T: return ImGuiKey_T;
+        case SDLK_U: return ImGuiKey_U;
+        case SDLK_V: return ImGuiKey_V;
+        case SDLK_W: return ImGuiKey_W;
+        case SDLK_X: return ImGuiKey_X;
+        case SDLK_Y: return ImGuiKey_Y;
+        case SDLK_Z: return ImGuiKey_Z;
+        case SDLK_F1: return ImGuiKey_F1;
+        case SDLK_F2: return ImGuiKey_F2;
+        case SDLK_F3: return ImGuiKey_F3;
+        case SDLK_F4: return ImGuiKey_F4;
+        case SDLK_F5: return ImGuiKey_F5;
+        case SDLK_F6: return ImGuiKey_F6;
+        case SDLK_F7: return ImGuiKey_F7;
+        case SDLK_F8: return ImGuiKey_F8;
+        case SDLK_F9: return ImGuiKey_F9;
+        case SDLK_F10: return ImGuiKey_F10;
+        case SDLK_F11: return ImGuiKey_F11;
+        case SDLK_F12: return ImGuiKey_F12;
+        case SDLK_F13: return ImGuiKey_F13;
+        case SDLK_F14: return ImGuiKey_F14;
+        case SDLK_F15: return ImGuiKey_F15;
+        case SDLK_F16: return ImGuiKey_F16;
+        case SDLK_F17: return ImGuiKey_F17;
+        case SDLK_F18: return ImGuiKey_F18;
+        case SDLK_F19: return ImGuiKey_F19;
+        case SDLK_F20: return ImGuiKey_F20;
+        case SDLK_F21: return ImGuiKey_F21;
+        case SDLK_F22: return ImGuiKey_F22;
+        case SDLK_F23: return ImGuiKey_F23;
+        case SDLK_F24: return ImGuiKey_F24;
+        case SDLK_AC_BACK: return ImGuiKey_AppBack;
+        case SDLK_AC_FORWARD: return ImGuiKey_AppForward;
+        default: break;
+    }
+    return ImGuiKey_None;
+}
+
+static void ImGui_ImplSDL3_UpdateKeyModifiers(SDL_Keymod sdl_key_mods)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0);
+    io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0);
+    io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0);
+    io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0);
+}
+
+static ImGuiViewport* ImGui_ImplSDL3_GetViewportForWindowID(SDL_WindowID window_id)
+{
+    return ImGui::FindViewportByPlatformHandle((void*)(intptr_t)window_id);
+}
+
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
+// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    switch (event->type)
+    {
+        case SDL_EVENT_MOUSE_MOTION:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->motion.windowID) == NULL)
+                return false;
+            ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
+            if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
+            {
+                int window_x, window_y;
+                SDL_GetWindowPosition(SDL_GetWindowFromID(event->motion.windowID), &window_x, &window_y);
+                mouse_pos.x += window_x;
+                mouse_pos.y += window_y;
+            }
+            io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
+            io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
+            return true;
+        }
+        case SDL_EVENT_MOUSE_WHEEL:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->wheel.windowID) == NULL)
+                return false;
+            //IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY);
+            float wheel_x = -event->wheel.x;
+            float wheel_y = event->wheel.y;
+    #ifdef __EMSCRIPTEN__
+            wheel_x /= 100.0f;
+    #endif
+            io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
+            io.AddMouseWheelEvent(wheel_x, wheel_y);
+            return true;
+        }
+        case SDL_EVENT_MOUSE_BUTTON_DOWN:
+        case SDL_EVENT_MOUSE_BUTTON_UP:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->button.windowID) == NULL)
+                return false;
+            int mouse_button = -1;
+            if (event->button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
+            if (event->button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
+            if (event->button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
+            if (event->button.button == SDL_BUTTON_X1) { mouse_button = 3; }
+            if (event->button.button == SDL_BUTTON_X2) { mouse_button = 4; }
+            if (mouse_button == -1)
+                break;
+            io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
+            io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN));
+            bd->MouseButtonsDown = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
+            return true;
+        }
+        case SDL_EVENT_TEXT_INPUT:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->text.windowID) == NULL)
+                return false;
+            io.AddInputCharactersUTF8(event->text.text);
+            return true;
+        }
+        case SDL_EVENT_KEY_DOWN:
+        case SDL_EVENT_KEY_UP:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->key.windowID) == NULL)
+                return false;
+            //IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%d: key=%d, scancode=%d, mod=%X\n", (event->type == SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP", event->key.key, event->key.scancode, event->key.mod);
+            ImGui_ImplSDL3_UpdateKeyModifiers((SDL_Keymod)event->key.mod);
+            ImGuiKey key = ImGui_ImplSDL3_KeyEventToImGuiKey(event->key.key, event->key.scancode);
+            io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN));
+            io.SetKeyEventNativeData(key, event->key.key, event->key.scancode, event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
+            return true;
+        }
+        case SDL_EVENT_DISPLAY_ORIENTATION:
+        case SDL_EVENT_DISPLAY_ADDED:
+        case SDL_EVENT_DISPLAY_REMOVED:
+        case SDL_EVENT_DISPLAY_MOVED:
+        case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
+        {
+            bd->WantUpdateMonitors = true;
+            return true;
+        }
+        case SDL_EVENT_WINDOW_MOUSE_ENTER:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == NULL)
+                return false;
+            bd->MouseWindowID = event->window.windowID;
+            bd->MousePendingLeaveFrame = 0;
+            return true;
+        }
+        // - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late,
+        //   causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why
+        //   we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
+        // FIXME: Unconfirmed whether this is still needed with SDL3.
+        case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == NULL)
+                return false;
+            bd->MousePendingLeaveFrame = ImGui::GetFrameCount() + 1;
+            return true;
+        }
+        case SDL_EVENT_WINDOW_FOCUS_GAINED:
+        case SDL_EVENT_WINDOW_FOCUS_LOST:
+        {
+            if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == NULL)
+                return false;
+            io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED);
+            return true;
+        }
+        case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+        case SDL_EVENT_WINDOW_MOVED:
+        case SDL_EVENT_WINDOW_RESIZED:
+        {
+            ImGuiViewport* viewport = ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID);
+            if (viewport == NULL)
+                return false;
+            if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
+                viewport->PlatformRequestClose = true;
+            if (event->type == SDL_EVENT_WINDOW_MOVED)
+                viewport->PlatformRequestMove = true;
+            if (event->type == SDL_EVENT_WINDOW_RESIZED)
+                viewport->PlatformRequestResize = true;
+            return true;
+        }
+        case SDL_EVENT_GAMEPAD_ADDED:
+        case SDL_EVENT_GAMEPAD_REMOVED:
+        {
+            bd->WantUpdateGamepadsList = true;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void ImGui_ImplSDL3_SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window)
+{
+    viewport->PlatformHandle = (void*)(intptr_t)SDL_GetWindowID(window);
+    viewport->PlatformHandleRaw = nullptr;
+#if defined(_WIN32) && !defined(__WINRT__)
+    viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
+#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
+    viewport->PlatformHandleRaw = SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
+#endif
+}
+
+static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void* sdl_gl_context)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IMGUI_CHECKVERSION();
+    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
+    IM_UNUSED(sdl_gl_context); // Unused in this branch
+
+    // Check and store if we are on a SDL backend that supports global mouse position
+    // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
+    bool mouse_can_use_global_state = false;
+#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
+    const char* sdl_backend = SDL_GetCurrentVideoDriver();
+    const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
+    for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
+        if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
+            mouse_can_use_global_state = true;
+#endif
+
+    // Setup backend capabilities flags
+    ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)();
+    io.BackendPlatformUserData = (void*)bd;
+    io.BackendPlatformName = "imgui_impl_sdl3";
+    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;           // We can honor GetMouseCursor() values (optional)
+    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;            // We can honor io.WantSetMousePos requests (optional, rarely used)
+    if (mouse_can_use_global_state)
+        io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports;  // We can create multi-viewports on the Platform side (optional)
+
+    bd->Window = window;
+    bd->WindowID = SDL_GetWindowID(window);
+    bd->Renderer = renderer;
+
+    // SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960)
+    // We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame.
+    bd->MouseCanUseGlobalState = mouse_can_use_global_state;
+#ifndef __APPLE__
+    bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState;
+#else
+    bd->MouseCanReportHoveredViewport = false;
+#endif
+
+    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+    platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
+    platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
+    platform_io.Platform_SetImeDataFn = ImGui_ImplSDL3_PlatformSetImeData;
+
+    // Update monitor a first time during init
+    ImGui_ImplSDL3_UpdateMonitors();
+
+    // Gamepad handling
+    bd->GamepadMode = ImGui_ImplSDL3_GamepadMode_AutoFirst;
+    bd->WantUpdateGamepadsList = true;
+
+    // Load mouse cursors
+    bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
+    bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NS_RESIZE);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_EW_RESIZE);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NESW_RESIZE);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE);
+    bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER);
+    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED);
+
+    // Set platform dependent data in viewport
+    // Our mouse update function expect PlatformHandle to be filled for the main viewport
+    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+    ImGui_ImplSDL3_SetupPlatformHandles(main_viewport, window);
+
+    // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
+    // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
+    // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
+    // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
+    // you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
+    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+
+    // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
+    SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
+
+    // SDL 3.x : see https://github.com/libsdl-org/SDL/issues/6659
+    SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "0");
+
+    // We need SDL_CaptureMouse(), SDL_GetGlobalMouseState() from SDL 2.0.4+ to support multiple viewports.
+    // We left the call to ImGui_ImplSDL3_InitPlatformInterface() outside of #ifdef to avoid unused-function warnings.
+    if ((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (io.BackendFlags & ImGuiBackendFlags_PlatformHasViewports))
+        ImGui_ImplSDL3_InitPlatformInterface(window, sdl_gl_context);
+
+    return true;
+}
+
+// Should technically be a SDL_GLContext but due to typedef it is sane to keep it void* in public interface.
+bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
+{
+    return ImGui_ImplSDL3_Init(window, nullptr, sdl_gl_context);
+}
+
+bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window)
+{
+    if (!ImGui_ImplSDL3_Init(window, nullptr, nullptr))
+        return false;
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    bd->UseVulkan = true;
+    return true;
+}
+
+bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window)
+{
+#if !defined(_WIN32)
+    IM_ASSERT(0 && "Unsupported");
+#endif
+    return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
+}
+
+bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window)
+{
+    return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
+}
+
+bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
+{
+    return ImGui_ImplSDL3_Init(window, renderer, nullptr);
+}
+
+bool ImGui_ImplSDL3_InitForOther(SDL_Window* window)
+{
+    return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
+}
+
+static void ImGui_ImplSDL3_CloseGamepads();
+
+void ImGui_ImplSDL3_Shutdown()
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplSDL3_ShutdownPlatformInterface();
+
+    if (bd->ClipboardTextData)
+        SDL_free(bd->ClipboardTextData);
+    for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
+        SDL_DestroyCursor(bd->MouseCursors[cursor_n]);
+    ImGui_ImplSDL3_CloseGamepads();
+
+    io.BackendPlatformName = nullptr;
+    io.BackendPlatformUserData = nullptr;
+    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_PlatformHasViewports | ImGuiBackendFlags_HasMouseHoveredViewport);
+    IM_DELETE(bd);
+}
+
+// This code is incredibly messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4.
+static void ImGui_ImplSDL3_UpdateMouseData()
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+
+    // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below)
+#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
+    // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside
+    SDL_CaptureMouse(bd->MouseButtonsDown != 0);
+    SDL_Window* focused_window = SDL_GetKeyboardFocus();
+    const bool is_app_focused = (focused_window && (bd->Window == focused_window || ImGui_ImplSDL3_GetViewportForWindowID(SDL_GetWindowID(focused_window)) != NULL));
+#else
+    SDL_Window* focused_window = bd->Window;
+    const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
+#endif
+    if (is_app_focused)
+    {
+        // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
+        if (io.WantSetMousePos)
+        {
+#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
+            if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
+                SDL_WarpMouseGlobal(io.MousePos.x, io.MousePos.y);
+            else
+#endif
+                SDL_WarpMouseInWindow(bd->Window, io.MousePos.x, io.MousePos.y);
+        }
+
+        // (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION already provides this when hovered or captured)
+        if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0)
+        {
+            // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
+            // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor)
+            float mouse_x, mouse_y;
+            int window_x, window_y;
+            SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
+            if (!(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable))
+            {
+                SDL_GetWindowPosition(focused_window, &window_x, &window_y);
+                mouse_x -= window_x;
+                mouse_y -= window_y;
+            }
+            io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
+        }
+    }
+
+    // (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering.
+    // If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, Dear imGui will ignore this field and infer the information using its flawed heuristic.
+    // - [!] SDL backend does NOT correctly ignore viewports with the _NoInputs flag.
+    //       Some backend are not able to handle that correctly. If a backend report an hovered viewport that has the _NoInputs flag (e.g. when dragging a window
+    //       for docking, the viewport has the _NoInputs flag in order to allow us to find the viewport under), then Dear ImGui is forced to ignore the value reported
+    //       by the backend, and use its flawed heuristic to guess the viewport behind.
+    // - [X] SDL backend correctly reports this regardless of another viewport behind focused and dragged from (we need this to find a useful drag and drop target).
+    if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
+    {
+        ImGuiID mouse_viewport_id = 0;
+        if (ImGuiViewport* mouse_viewport = ImGui_ImplSDL3_GetViewportForWindowID(bd->MouseWindowID))
+            mouse_viewport_id = mouse_viewport->ID;
+        io.AddMouseViewportEvent(mouse_viewport_id);
+    }
+}
+
+static void ImGui_ImplSDL3_UpdateMouseCursor()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
+        return;
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+
+    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
+    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
+    {
+        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
+        SDL_HideCursor();
+    }
+    else
+    {
+        // Show OS mouse cursor
+        SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow];
+        if (bd->MouseLastCursor != expected_cursor)
+        {
+            SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
+            bd->MouseLastCursor = expected_cursor;
+        }
+        SDL_ShowCursor();
+    }
+}
+
+static void ImGui_ImplSDL3_CloseGamepads()
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    if (bd->GamepadMode != ImGui_ImplSDL3_GamepadMode_Manual)
+        for (SDL_Gamepad* gamepad : bd->Gamepads)
+            SDL_CloseGamepad(gamepad);
+    bd->Gamepads.resize(0);
+}
+
+void ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode mode, SDL_Gamepad** manual_gamepads_array, int manual_gamepads_count)
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    ImGui_ImplSDL3_CloseGamepads();
+    if (mode == ImGui_ImplSDL3_GamepadMode_Manual)
+    {
+        IM_ASSERT(manual_gamepads_array != nullptr && manual_gamepads_count > 0);
+        for (int n = 0; n < manual_gamepads_count; n++)
+            bd->Gamepads.push_back(manual_gamepads_array[n]);
+    }
+    else
+    {
+        IM_ASSERT(manual_gamepads_array == nullptr && manual_gamepads_count <= 0);
+        bd->WantUpdateGamepadsList = true;
+    }
+    bd->GamepadMode = mode;
+}
+
+static void ImGui_ImplSDL3_UpdateGamepadButton(ImGui_ImplSDL3_Data* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadButton button_no)
+{
+    bool merged_value = false;
+    for (SDL_Gamepad* gamepad : bd->Gamepads)
+        merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0;
+    io.AddKeyEvent(key, merged_value);
+}
+
+static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v  > 1.0f ? 1.0f : v; }
+static void ImGui_ImplSDL3_UpdateGamepadAnalog(ImGui_ImplSDL3_Data* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadAxis axis_no, float v0, float v1)
+{
+    float merged_value = 0.0f;
+    for (SDL_Gamepad* gamepad : bd->Gamepads)
+    {
+        float vn = Saturate((float)(SDL_GetGamepadAxis(gamepad, axis_no) - v0) / (float)(v1 - v0));
+        if (merged_value < vn)
+            merged_value = vn;
+    }
+    io.AddKeyAnalogEvent(key, merged_value > 0.1f, merged_value);
+}
+
+static void ImGui_ImplSDL3_UpdateGamepads()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+
+    // Update list of gamepads to use
+    if (bd->WantUpdateGamepadsList && bd->GamepadMode != ImGui_ImplSDL3_GamepadMode_Manual)
+    {
+        ImGui_ImplSDL3_CloseGamepads();
+        int sdl_gamepads_count = 0;
+        SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count);
+        for (int n = 0; n < sdl_gamepads_count; n++)
+            if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n]))
+            {
+                bd->Gamepads.push_back(gamepad);
+                if (bd->GamepadMode == ImGui_ImplSDL3_GamepadMode_AutoFirst)
+                    break;
+            }
+        bd->WantUpdateGamepadsList = false;
+        SDL_free(sdl_gamepads);
+    }
+
+    // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
+    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
+        return;
+    io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
+    if (bd->Gamepads.Size == 0)
+        return;
+    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
+
+    // Update gamepad inputs
+    const int thumb_dead_zone = 8000;           // SDL_gamepad.h suggests using this value.
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart,       SDL_GAMEPAD_BUTTON_START);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack,        SDL_GAMEPAD_BUTTON_BACK);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft,    SDL_GAMEPAD_BUTTON_WEST);           // Xbox X, PS Square
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight,   SDL_GAMEPAD_BUTTON_EAST);           // Xbox B, PS Circle
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp,      SDL_GAMEPAD_BUTTON_NORTH);          // Xbox Y, PS Triangle
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceDown,    SDL_GAMEPAD_BUTTON_SOUTH);          // Xbox A, PS Cross
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadLeft,    SDL_GAMEPAD_BUTTON_DPAD_LEFT);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadRight,   SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadUp,      SDL_GAMEPAD_BUTTON_DPAD_UP);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadDown,    SDL_GAMEPAD_BUTTON_DPAD_DOWN);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadL1,          SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadR1,          SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadL2,          SDL_GAMEPAD_AXIS_LEFT_TRIGGER,  0.0f, 32767);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadR2,          SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadL3,          SDL_GAMEPAD_BUTTON_LEFT_STICK);
+    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadR3,          SDL_GAMEPAD_BUTTON_RIGHT_STICK);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickLeft,  SDL_GAMEPAD_AXIS_LEFTX,  -thumb_dead_zone, -32768);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX,  +thumb_dead_zone, +32767);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickUp,    SDL_GAMEPAD_AXIS_LEFTY,  -thumb_dead_zone, -32768);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickDown,  SDL_GAMEPAD_AXIS_LEFTY,  +thumb_dead_zone, +32767);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickLeft,  SDL_GAMEPAD_AXIS_RIGHTX, -thumb_dead_zone, -32768);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, +thumb_dead_zone, +32767);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickUp,    SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone, -32768);
+    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown,  SDL_GAMEPAD_AXIS_RIGHTY, +thumb_dead_zone, +32767);
+}
+
+static void ImGui_ImplSDL3_UpdateMonitors()
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+    platform_io.Monitors.resize(0);
+    bd->WantUpdateMonitors = false;
+
+    int display_count;
+    SDL_DisplayID* displays = SDL_GetDisplays(&display_count);
+    for (int n = 0; n < display_count; n++)
+    {
+        // Warning: the validity of monitor DPI information on Windows depends on the application DPI awareness settings, which generally needs to be set in the manifest or at runtime.
+        SDL_DisplayID display_id = displays[n];
+        ImGuiPlatformMonitor monitor;
+        SDL_Rect r;
+        SDL_GetDisplayBounds(display_id, &r);
+        monitor.MainPos = monitor.WorkPos = ImVec2((float)r.x, (float)r.y);
+        monitor.MainSize = monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
+        SDL_GetDisplayUsableBounds(display_id, &r);
+        monitor.WorkPos = ImVec2((float)r.x, (float)r.y);
+        monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
+        // FIXME-VIEWPORT: On MacOS SDL reports actual monitor DPI scale, ignoring OS configuration. We may want to set
+        //  DpiScale to cocoa_window.backingScaleFactor here.
+        monitor.DpiScale = SDL_GetDisplayContentScale(display_id);
+        monitor.PlatformHandle = (void*)(intptr_t)n;
+        if (monitor.DpiScale <= 0.0f)
+            continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0, see #7902.
+        platform_io.Monitors.push_back(monitor);
+    }
+    SDL_free(displays);
+}
+
+void ImGui_ImplSDL3_NewFrame()
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    // Setup display size (every frame to accommodate for window resizing)
+    int w, h;
+    int display_w, display_h;
+    SDL_GetWindowSize(bd->Window, &w, &h);
+    if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
+        w = h = 0;
+    SDL_GetWindowSizeInPixels(bd->Window, &display_w, &display_h);
+    io.DisplaySize = ImVec2((float)w, (float)h);
+    if (w > 0 && h > 0)
+        io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
+
+    // Update monitors
+    if (bd->WantUpdateMonitors)
+        ImGui_ImplSDL3_UpdateMonitors();
+
+    // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
+    // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
+    static Uint64 frequency = SDL_GetPerformanceFrequency();
+    Uint64 current_time = SDL_GetPerformanceCounter();
+    if (current_time <= bd->Time)
+        current_time = bd->Time + 1;
+    io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f);
+    bd->Time = current_time;
+
+    if (bd->MousePendingLeaveFrame && bd->MousePendingLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0)
+    {
+        bd->MouseWindowID = 0;
+        bd->MousePendingLeaveFrame = 0;
+        io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
+    }
+
+    // Our io.AddMouseViewportEvent() calls will only be valid when not capturing.
+    // Technically speaking testing for 'bd->MouseButtonsDown == 0' would be more rigorous, but testing for payload reduces noise and potential side-effects.
+    if (bd->MouseCanReportHoveredViewport && ImGui::GetDragDropPayload() == nullptr)
+        io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport;
+    else
+        io.BackendFlags &= ~ImGuiBackendFlags_HasMouseHoveredViewport;
+
+    ImGui_ImplSDL3_UpdateMouseData();
+    ImGui_ImplSDL3_UpdateMouseCursor();
+
+    // Update game controllers (if enabled and available)
+    ImGui_ImplSDL3_UpdateGamepads();
+}
+
+//--------------------------------------------------------------------------------------------------------
+// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
+// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
+// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
+//--------------------------------------------------------------------------------------------------------
+
+// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
+struct ImGui_ImplSDL3_ViewportData
+{
+    SDL_Window*     Window;
+    SDL_Window*     ParentWindow;
+    Uint32          WindowID;
+    bool            WindowOwned;
+    SDL_GLContext   GLContext;
+
+    ImGui_ImplSDL3_ViewportData() { Window = ParentWindow = nullptr; WindowID = 0; WindowOwned = false; GLContext = nullptr; }
+    ~ImGui_ImplSDL3_ViewportData() { IM_ASSERT(Window == nullptr && GLContext == nullptr); }
+};
+
+static SDL_Window* ImGui_ImplSDL3_GetSDLWindowFromViewportID(ImGuiID viewport_id)
+{
+    if (viewport_id != 0)
+        if (ImGuiViewport* viewport = ImGui::FindViewportByID(viewport_id))
+        {
+            SDL_WindowID window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle;
+            return SDL_GetWindowFromID(window_id);
+        }
+    return nullptr;
+}
+
+static void ImGui_ImplSDL3_CreateWindow(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
+    ImGui_ImplSDL3_ViewportData* vd = IM_NEW(ImGui_ImplSDL3_ViewportData)();
+    viewport->PlatformUserData = vd;
+
+    vd->ParentWindow = ImGui_ImplSDL3_GetSDLWindowFromViewportID(viewport->ParentViewportId);
+
+    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+    ImGui_ImplSDL3_ViewportData* main_viewport_data = (ImGui_ImplSDL3_ViewportData*)main_viewport->PlatformUserData;
+
+    // Share GL resources with main context
+    bool use_opengl = (main_viewport_data->GLContext != nullptr);
+    SDL_GLContext backup_context = nullptr;
+    if (use_opengl)
+    {
+        backup_context = SDL_GL_GetCurrentContext();
+        SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
+        SDL_GL_MakeCurrent(main_viewport_data->Window, main_viewport_data->GLContext);
+    }
+
+    Uint32 sdl_flags = 0;
+    sdl_flags |= use_opengl ? SDL_WINDOW_OPENGL : (bd->UseVulkan ? SDL_WINDOW_VULKAN : 0);
+    sdl_flags |= SDL_GetWindowFlags(bd->Window);
+    sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? SDL_WINDOW_BORDERLESS : 0;
+    sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? 0 : SDL_WINDOW_RESIZABLE;
+    sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon) ? SDL_WINDOW_UTILITY : 0;
+    sdl_flags |= (viewport->Flags & ImGuiViewportFlags_TopMost) ? SDL_WINDOW_ALWAYS_ON_TOP : 0;
+    vd->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags);
+    SDL_SetWindowParent(vd->Window, vd->ParentWindow);
+    SDL_SetWindowPosition(vd->Window, (int)viewport->Pos.x, (int)viewport->Pos.y);
+    vd->WindowOwned = true;
+    if (use_opengl)
+    {
+        vd->GLContext = SDL_GL_CreateContext(vd->Window);
+        SDL_GL_SetSwapInterval(0);
+    }
+    if (use_opengl && backup_context)
+        SDL_GL_MakeCurrent(vd->Window, backup_context);
+
+    ImGui_ImplSDL3_SetupPlatformHandles(viewport, vd->Window);
+}
+
+static void ImGui_ImplSDL3_DestroyWindow(ImGuiViewport* viewport)
+{
+    if (ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData)
+    {
+        if (vd->GLContext && vd->WindowOwned)
+            SDL_GL_DestroyContext(vd->GLContext);
+        if (vd->Window && vd->WindowOwned)
+            SDL_DestroyWindow(vd->Window);
+        vd->GLContext = nullptr;
+        vd->Window = nullptr;
+        IM_DELETE(vd);
+    }
+    viewport->PlatformUserData = viewport->PlatformHandle = nullptr;
+}
+
+static void ImGui_ImplSDL3_ShowWindow(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+#if defined(_WIN32) && !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_GAMES))
+    HWND hwnd = (HWND)viewport->PlatformHandleRaw;
+
+    // SDL hack: Show icon in task bar (#7989)
+    // Note: SDL_WINDOW_UTILITY can be used to control task bar visibility, but on Windows, it does not affect child windows.
+    if (!(viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon))
+    {
+        LONG ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);
+        ex_style |= WS_EX_APPWINDOW;
+        ex_style &= ~WS_EX_TOOLWINDOW;
+        ::ShowWindow(hwnd, SW_HIDE);
+        ::SetWindowLong(hwnd, GWL_EXSTYLE, ex_style);
+    }
+#endif
+
+    SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) ? "0" : "1");
+    SDL_ShowWindow(vd->Window);
+}
+
+static void ImGui_ImplSDL3_UpdateWindow(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+
+    // Update SDL3 parent if it changed _after_ creation.
+    // This is for advanced apps that are manipulating ParentViewportID manually.
+    SDL_Window* new_parent = ImGui_ImplSDL3_GetSDLWindowFromViewportID(viewport->ParentViewportId);
+    if (new_parent != vd->ParentWindow)
+    {
+        vd->ParentWindow = new_parent;
+        SDL_SetWindowParent(vd->Window, vd->ParentWindow);
+    }
+}
+
+static ImVec2 ImGui_ImplSDL3_GetWindowPos(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    int x = 0, y = 0;
+    SDL_GetWindowPosition(vd->Window, &x, &y);
+    return ImVec2((float)x, (float)y);
+}
+
+static void ImGui_ImplSDL3_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    SDL_SetWindowPosition(vd->Window, (int)pos.x, (int)pos.y);
+}
+
+static ImVec2 ImGui_ImplSDL3_GetWindowSize(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    int w = 0, h = 0;
+    SDL_GetWindowSize(vd->Window, &w, &h);
+    return ImVec2((float)w, (float)h);
+}
+
+static void ImGui_ImplSDL3_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y);
+}
+
+static void ImGui_ImplSDL3_SetWindowTitle(ImGuiViewport* viewport, const char* title)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    SDL_SetWindowTitle(vd->Window, title);
+}
+
+static void ImGui_ImplSDL3_SetWindowAlpha(ImGuiViewport* viewport, float alpha)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    SDL_SetWindowOpacity(vd->Window, alpha);
+}
+
+static void ImGui_ImplSDL3_SetWindowFocus(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    SDL_RaiseWindow(vd->Window);
+}
+
+static bool ImGui_ImplSDL3_GetWindowFocus(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0;
+}
+
+static bool ImGui_ImplSDL3_GetWindowMinimized(ImGuiViewport* viewport)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_MINIMIZED) != 0;
+}
+
+static void ImGui_ImplSDL3_RenderWindow(ImGuiViewport* viewport, void*)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    if (vd->GLContext)
+        SDL_GL_MakeCurrent(vd->Window, vd->GLContext);
+}
+
+static void ImGui_ImplSDL3_SwapBuffers(ImGuiViewport* viewport, void*)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    if (vd->GLContext)
+    {
+        SDL_GL_MakeCurrent(vd->Window, vd->GLContext);
+        SDL_GL_SwapWindow(vd->Window);
+    }
+}
+
+// Vulkan support (the Vulkan renderer needs to call a platform-side support function to create the surface)
+// SDL is graceful enough to _not_ need <vulkan/vulkan.h> so we can safely include this.
+#include <SDL3/SDL_vulkan.h>
+static int ImGui_ImplSDL3_CreateVkSurface(ImGuiViewport* viewport, ImU64 vk_instance, const void* vk_allocator, ImU64* out_vk_surface)
+{
+    ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
+    (void)vk_allocator;
+    bool ret = SDL_Vulkan_CreateSurface(vd->Window, (VkInstance)vk_instance, (VkAllocationCallbacks*)vk_allocator, (VkSurfaceKHR*)out_vk_surface);
+    return ret ? 0 : 1; // ret ? VK_SUCCESS : VK_NOT_READY
+}
+
+static void ImGui_ImplSDL3_InitPlatformInterface(SDL_Window* window, void* sdl_gl_context)
+{
+    // Register platform interface (will be coupled with a renderer interface)
+    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+    platform_io.Platform_CreateWindow = ImGui_ImplSDL3_CreateWindow;
+    platform_io.Platform_DestroyWindow = ImGui_ImplSDL3_DestroyWindow;
+    platform_io.Platform_ShowWindow = ImGui_ImplSDL3_ShowWindow;
+    platform_io.Platform_UpdateWindow = ImGui_ImplSDL3_UpdateWindow;
+    platform_io.Platform_SetWindowPos = ImGui_ImplSDL3_SetWindowPos;
+    platform_io.Platform_GetWindowPos = ImGui_ImplSDL3_GetWindowPos;
+    platform_io.Platform_SetWindowSize = ImGui_ImplSDL3_SetWindowSize;
+    platform_io.Platform_GetWindowSize = ImGui_ImplSDL3_GetWindowSize;
+    platform_io.Platform_SetWindowFocus = ImGui_ImplSDL3_SetWindowFocus;
+    platform_io.Platform_GetWindowFocus = ImGui_ImplSDL3_GetWindowFocus;
+    platform_io.Platform_GetWindowMinimized = ImGui_ImplSDL3_GetWindowMinimized;
+    platform_io.Platform_SetWindowTitle = ImGui_ImplSDL3_SetWindowTitle;
+    platform_io.Platform_RenderWindow = ImGui_ImplSDL3_RenderWindow;
+    platform_io.Platform_SwapBuffers = ImGui_ImplSDL3_SwapBuffers;
+    platform_io.Platform_SetWindowAlpha = ImGui_ImplSDL3_SetWindowAlpha;
+    platform_io.Platform_CreateVkSurface = ImGui_ImplSDL3_CreateVkSurface;
+
+    // Register main window handle (which is owned by the main application, not by us)
+    // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports.
+    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+    ImGui_ImplSDL3_ViewportData* vd = IM_NEW(ImGui_ImplSDL3_ViewportData)();
+    vd->Window = window;
+    vd->WindowID = SDL_GetWindowID(window);
+    vd->WindowOwned = false;
+    vd->GLContext = (SDL_GLContext)sdl_gl_context;
+    main_viewport->PlatformUserData = vd;
+    main_viewport->PlatformHandle = (void*)(intptr_t)vd->WindowID;
+}
+
+static void ImGui_ImplSDL3_ShutdownPlatformInterface()
+{
+    ImGui::DestroyPlatformWindows();
+}
+
+//-----------------------------------------------------------------------------
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/backends/imgui/backends/imgui_impl_sdl3.h b/backends/imgui/backends/imgui_impl_sdl3.h
new file mode 100644
index 00000000000..b9b89e671da
--- /dev/null
+++ b/backends/imgui/backends/imgui_impl_sdl3.h
@@ -0,0 +1,51 @@
+// dear imgui: Platform Backend for SDL3 (*EXPERIMENTAL*)
+// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
+// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
+
+// (**IMPORTANT: SDL 3.0.0 is NOT YET RELEASED AND CURRENTLY HAS A FAST CHANGING API. THIS CODE BREAKS OFTEN AS SDL3 CHANGES.**)
+
+// Implemented features:
+//  [X] Platform: Clipboard support.
+//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+//  [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue.
+// Issues:
+//  [ ] Platform: Multi-viewport: Minimized windows seems to break mouse wheel events (at least under Windows).
+//  [x] Platform: Basic IME support. Position somehow broken in SDL3 + app needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "backends/imgui/imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct SDL_Window;
+struct SDL_Renderer;
+struct SDL_Gamepad;
+typedef union SDL_Event SDL_Event;
+
+// Follow "Getting Started" link and check examples/ folder to learn about using backends!
+IMGUI_IMPL_API bool     ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);
+IMGUI_IMPL_API bool     ImGui_ImplSDL3_InitForVulkan(SDL_Window* window);
+IMGUI_IMPL_API bool     ImGui_ImplSDL3_InitForD3D(SDL_Window* window);
+IMGUI_IMPL_API bool     ImGui_ImplSDL3_InitForMetal(SDL_Window* window);
+IMGUI_IMPL_API bool     ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer);
+IMGUI_IMPL_API bool     ImGui_ImplSDL3_InitForOther(SDL_Window* window);
+IMGUI_IMPL_API void     ImGui_ImplSDL3_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplSDL3_NewFrame();
+IMGUI_IMPL_API bool     ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event);
+
+// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. You may override this.
+// When using manual mode, caller is responsible for opening/closing gamepad.
+enum ImGui_ImplSDL3_GamepadMode { ImGui_ImplSDL3_GamepadMode_AutoFirst, ImGui_ImplSDL3_GamepadMode_AutoAll, ImGui_ImplSDL3_GamepadMode_Manual };
+IMGUI_IMPL_API void     ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode mode, SDL_Gamepad** manual_gamepads_array = NULL, int manual_gamepads_count = -1);
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/backends/imgui/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui/backends/imgui_impl_sdlrenderer3.cpp
new file mode 100644
index 00000000000..153d4b767c5
--- /dev/null
+++ b/backends/imgui/backends/imgui_impl_sdlrenderer3.cpp
@@ -0,0 +1,283 @@
+// dear imgui: Renderer Backend for SDL_Renderer for SDL3
+// (Requires: SDL 3.0.0+)
+
+// (**IMPORTANT: SDL 3.0.0 is NOT YET RELEASED AND CURRENTLY HAS A FAST CHANGING API. THIS CODE BREAKS OFTEN AS SDL3 CHANGES.**)
+
+// Note how SDL_Renderer is an _optional_ component of SDL3.
+// For a multi-platform app consider using e.g. SDL+DirectX on Windows and SDL+OpenGL on Linux/OSX.
+// If your application will want to render any non trivial amount of graphics other than UI,
+// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user and
+// it might be difficult to step out of those boundaries.
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+//  2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009).
+//  2024-05-14: *BREAKING CHANGE* ImGui_ImplSDLRenderer3_RenderDrawData() requires SDL_Renderer* passed as parameter.
+//  2024-02-12: Amend to query SDL_RenderViewportSet() and restore viewport accordingly.
+//  2023-05-30: Initial version.
+
+#include "backends/imgui/imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_sdlrenderer3.h"
+#include <stdint.h>     // intptr_t
+
+// Clang warnings with -Weverything
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wsign-conversion"    // warning: implicit conversion changes signedness
+#endif
+
+// SDL
+#include <SDL3/SDL.h>
+#if !SDL_VERSION_ATLEAST(3,0,0)
+#error This backend requires SDL 3.0.0+
+#endif
+
+// SDL_Renderer data
+struct ImGui_ImplSDLRenderer3_Data
+{
+    SDL_Renderer*           Renderer;       // Main viewport's renderer
+    SDL_Texture*            FontTexture;
+    ImVector<SDL_FColor>    ColorBuffer;
+
+    ImGui_ImplSDLRenderer3_Data()   { memset((void*)this, 0, sizeof(*this)); }
+};
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+// Functions
+bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IMGUI_CHECKVERSION();
+    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+    IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!");
+
+    // Setup backend capabilities flags
+    ImGui_ImplSDLRenderer3_Data* bd = IM_NEW(ImGui_ImplSDLRenderer3_Data)();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_sdlrenderer3";
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+
+    bd->Renderer = renderer;
+
+    return true;
+}
+
+void ImGui_ImplSDLRenderer3_Shutdown()
+{
+    ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
+
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+    IM_DELETE(bd);
+}
+
+static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer)
+{
+	// Clear out any viewports and cliprect set by the user
+    // FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process.
+	SDL_SetRenderViewport(renderer, nullptr);
+	SDL_SetRenderClipRect(renderer, nullptr);
+}
+
+void ImGui_ImplSDLRenderer3_NewFrame()
+{
+    ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()?");
+
+    if (!bd->FontTexture)
+        ImGui_ImplSDLRenderer3_CreateDeviceObjects();
+}
+
+// https://github.com/libsdl-org/SDL/issues/9009
+static int SDL_RenderGeometryRaw8BitColor(SDL_Renderer* renderer, ImVector<SDL_FColor>& colors_out, SDL_Texture* texture, const float* xy, int xy_stride, const SDL_Color* color, int color_stride, const float* uv, int uv_stride, int num_vertices, const void* indices, int num_indices, int size_indices)
+{
+    const Uint8* color2 = (const Uint8*)color;
+    colors_out.resize(num_vertices);
+    SDL_FColor* color3 = colors_out.Data;
+    for (int i = 0; i < num_vertices; i++)
+    {
+        color3[i].r = color->r / 255.0f;
+        color3[i].g = color->g / 255.0f;
+        color3[i].b = color->b / 255.0f;
+        color3[i].a = color->a / 255.0f;
+        color2 += color_stride;
+        color = (const SDL_Color*)color2;
+    }
+    return SDL_RenderGeometryRaw(renderer, texture, xy, xy_stride, color3, sizeof(*color3), uv, uv_stride, num_vertices, indices, num_indices, size_indices);
+}
+
+void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer)
+{
+    ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
+
+	// If there's a scale factor set by the user, use that instead
+    // If the user has specified a scale factor to SDL_Renderer already via SDL_RenderSetScale(), SDL will scale whatever we pass
+    // to SDL_RenderGeometryRaw() by that scale factor. In that case we don't want to be also scaling it ourselves here.
+    float rsx = 1.0f;
+	float rsy = 1.0f;
+	SDL_GetRenderScale(renderer, &rsx, &rsy);
+    ImVec2 render_scale;
+	render_scale.x = (rsx == 1.0f) ? draw_data->FramebufferScale.x : 1.0f;
+	render_scale.y = (rsy == 1.0f) ? draw_data->FramebufferScale.y : 1.0f;
+
+	// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
+	int fb_width = (int)(draw_data->DisplaySize.x * render_scale.x);
+	int fb_height = (int)(draw_data->DisplaySize.y * render_scale.y);
+	if (fb_width == 0 || fb_height == 0)
+		return;
+
+    // Backup SDL_Renderer state that will be modified to restore it afterwards
+    struct BackupSDLRendererState
+    {
+        SDL_Rect    Viewport;
+        bool        ViewportEnabled;
+        bool        ClipEnabled;
+        SDL_Rect    ClipRect;
+    };
+    BackupSDLRendererState old = {};
+    old.ViewportEnabled = SDL_RenderViewportSet(renderer);
+    old.ClipEnabled = SDL_RenderClipEnabled(renderer);
+    SDL_GetRenderViewport(renderer, &old.Viewport);
+    SDL_GetRenderClipRect(renderer, &old.ClipRect);
+
+	// Will project scissor/clipping rectangles into framebuffer space
+	ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
+	ImVec2 clip_scale = render_scale;
+
+    // Render command lists
+    ImGui_ImplSDLRenderer3_SetupRenderState(renderer);
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data;
+        const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data;
+
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplSDLRenderer3_SetupRenderState(renderer);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
+                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
+                if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
+                if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
+                if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; }
+                if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; }
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                SDL_Rect r = { (int)(clip_min.x), (int)(clip_min.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y) };
+                SDL_SetRenderClipRect(renderer, &r);
+
+                const float* xy = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, pos));
+                const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv));
+                const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+
+
+                // Bind texture, Draw
+				SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID();
+                SDL_RenderGeometryRaw8BitColor(renderer, bd->ColorBuffer, tex,
+                    xy, (int)sizeof(ImDrawVert),
+                    color, (int)sizeof(ImDrawVert),
+                    uv, (int)sizeof(ImDrawVert),
+                    cmd_list->VtxBuffer.Size - pcmd->VtxOffset,
+                    idx_buffer + pcmd->IdxOffset, pcmd->ElemCount, sizeof(ImDrawIdx));
+            }
+        }
+    }
+
+    // Restore modified SDL_Renderer state
+    SDL_SetRenderViewport(renderer, old.ViewportEnabled ? &old.Viewport : nullptr);
+    SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr);
+}
+
+// Called by Init/NewFrame/Shutdown
+bool ImGui_ImplSDLRenderer3_CreateFontsTexture()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
+
+    // Build texture atlas
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
+
+    // Upload texture to graphics system
+    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
+    bd->FontTexture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC, width, height);
+    if (bd->FontTexture == nullptr)
+    {
+        SDL_Log("error creating texture");
+        return false;
+    }
+    SDL_UpdateTexture(bd->FontTexture, nullptr, pixels, 4 * width);
+    SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND);
+    SDL_SetTextureScaleMode(bd->FontTexture, SDL_SCALEMODE_LINEAR);
+
+    // Store our identifier
+    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
+
+    return true;
+}
+
+void ImGui_ImplSDLRenderer3_DestroyFontsTexture()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
+    if (bd->FontTexture)
+    {
+        io.Fonts->SetTexID(0);
+        SDL_DestroyTexture(bd->FontTexture);
+        bd->FontTexture = nullptr;
+    }
+}
+
+bool ImGui_ImplSDLRenderer3_CreateDeviceObjects()
+{
+    return ImGui_ImplSDLRenderer3_CreateFontsTexture();
+}
+
+void ImGui_ImplSDLRenderer3_DestroyDeviceObjects()
+{
+    ImGui_ImplSDLRenderer3_DestroyFontsTexture();
+}
+
+//-----------------------------------------------------------------------------
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/backends/imgui/backends/imgui_impl_sdlrenderer3.h b/backends/imgui/backends/imgui_impl_sdlrenderer3.h
new file mode 100644
index 00000000000..6529469c025
--- /dev/null
+++ b/backends/imgui/backends/imgui_impl_sdlrenderer3.h
@@ -0,0 +1,42 @@
+// dear imgui: Renderer Backend for SDL_Renderer for SDL3
+// (Requires: SDL 3.0.0+)
+
+// (**IMPORTANT: SDL 3.0.0 is NOT YET RELEASED AND CURRENTLY HAS A FAST CHANGING API. THIS CODE BREAKS OFTEN AS SDL3 CHANGES.**)
+
+// Note how SDL_Renderer is an _optional_ component of SDL3.
+// For a multi-platform app consider using e.g. SDL+DirectX on Windows and SDL+OpenGL on Linux/OSX.
+// If your application will want to render any non trivial amount of graphics other than UI,
+// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user and
+// it might be difficult to step out of those boundaries.
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "backends/imgui/imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct SDL_Renderer;
+
+// Follow "Getting Started" link and check examples/ folder to learn about using backends!
+IMGUI_IMPL_API bool     ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer);
+IMGUI_IMPL_API void     ImGui_ImplSDLRenderer3_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplSDLRenderer3_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer);
+
+// Called by Init/NewFrame/Shutdown
+IMGUI_IMPL_API bool     ImGui_ImplSDLRenderer3_CreateFontsTexture();
+IMGUI_IMPL_API void     ImGui_ImplSDLRenderer3_DestroyFontsTexture();
+IMGUI_IMPL_API bool     ImGui_ImplSDLRenderer3_CreateDeviceObjects();
+IMGUI_IMPL_API void     ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/backends/imgui/scummvm.patch b/backends/imgui/scummvm.patch
index 937ff9e9553..e6488e8f41b 100644
--- a/backends/imgui/scummvm.patch
+++ b/backends/imgui/scummvm.patch
@@ -1,6 +1,6 @@
 diff --color -rbu ./backends/imgui_impl_opengl3.cpp ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3.cpp
---- ./backends/imgui_impl_opengl3.cpp	2024-10-07 18:21:01
-+++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3.cpp	2024-10-07 18:30:56
+--- ./backends/imgui_impl_opengl3.cpp	2025-02-18 20:51:22
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3.cpp	2025-01-25 16:39:54
 @@ -114,7 +114,7 @@
  #define _CRT_SECURE_NO_WARNINGS
  #endif
@@ -19,8 +19,8 @@ diff --color -rbu ./backends/imgui_impl_opengl3.cpp ../scummvm/scummvm/backends/
  #include "imgui_impl_opengl3_loader.h"
  #endif
 diff --color -rbu ./backends/imgui_impl_opengl3.h ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3.h
---- ./backends/imgui_impl_opengl3.h	2024-10-07 18:21:01
-+++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3.h	2024-10-07 18:30:56
+--- ./backends/imgui_impl_opengl3.h	2025-02-18 20:51:22
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3.h	2025-01-25 16:39:54
 @@ -27,7 +27,7 @@
  //  Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
  
@@ -40,8 +40,8 @@ diff --color -rbu ./backends/imgui_impl_opengl3.h ../scummvm/scummvm/backends/im
  #else
  // Otherwise imgui_impl_opengl3_loader.h will be used.
 diff --color -rbu ./backends/imgui_impl_opengl3_loader.h ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3_loader.h
---- ./backends/imgui_impl_opengl3_loader.h	2024-05-17 22:26:35
-+++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3_loader.h	2024-10-07 18:30:56
+--- ./backends/imgui_impl_opengl3_loader.h	2025-02-18 20:51:22
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_opengl3_loader.h	2025-01-25 16:39:54
 @@ -56,6 +56,11 @@
  #ifndef __gl3w_h_
  #define __gl3w_h_
@@ -77,8 +77,8 @@ diff --color -rbu ./backends/imgui_impl_opengl3_loader.h ../scummvm/scummvm/back
  #define WIN32_LEAN_AND_MEAN 1
  #endif
 diff --color -rbu ./backends/imgui_impl_sdl2.cpp ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl2.cpp
---- ./backends/imgui_impl_sdl2.cpp	2024-10-07 18:21:01
-+++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl2.cpp	2024-10-07 18:30:56
+--- ./backends/imgui_impl_sdl2.cpp	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl2.cpp	2025-01-25 16:39:54
 @@ -92,9 +92,11 @@
  //  2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
  //  2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
@@ -122,8 +122,8 @@ diff --color -rbu ./backends/imgui_impl_sdl2.cpp ../scummvm/scummvm/backends/img
 +#endif
  #endif // #ifndef IMGUI_DISABLE
 diff --color -rbu ./backends/imgui_impl_sdl2.h ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl2.h
---- ./backends/imgui_impl_sdl2.h	2024-10-07 18:21:01
-+++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl2.h	2024-10-07 18:30:56
+--- ./backends/imgui_impl_sdl2.h	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl2.h	2025-01-25 16:39:54
 @@ -23,7 +23,7 @@
  // - Introduction, links and more at the top of imgui.cpp
  
@@ -141,9 +141,35 @@ diff --color -rbu ./backends/imgui_impl_sdl2.h ../scummvm/scummvm/backends/imgui
  
  // Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. You may override this.
  // When using manual mode, caller is responsible for opening/closing gamepad.
+diff --color -rbu ./backends/imgui_impl_sdl3.cpp ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl3.cpp
+--- ./backends/imgui_impl_sdl3.cpp	2025-01-31 20:18:22
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl3.cpp	2025-01-31 20:20:20
+@@ -52,8 +52,9 @@
+ //  2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
+ //  2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.
+ 
+-#include "imgui.h"
++#include "backends/imgui/imgui.h"
+ #ifndef IMGUI_DISABLE
++#include "backends/platform/sdl/sdl.h"
+ #include "imgui_impl_sdl3.h"
+ 
+ // Clang warnings with -Weverything
+diff --color -rbu ./backends/imgui_impl_sdl3.h ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl3.h
+--- ./backends/imgui_impl_sdl3.h	2025-01-31 20:18:22
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdl3.h	2025-01-31 20:20:04
+@@ -24,7 +24,7 @@
+ // - Introduction, links and more at the top of imgui.cpp
+ 
+ #pragma once
+-#include "imgui.h"      // IMGUI_IMPL_API
++#include "backends/imgui/imgui.h"      // IMGUI_IMPL_API
+ #ifndef IMGUI_DISABLE
+ 
+ struct SDL_Window;
 diff --color -rbu ./backends/imgui_impl_sdlrenderer2.cpp ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer2.cpp
---- ./backends/imgui_impl_sdlrenderer2.cpp	2024-05-22 17:43:07
-+++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer2.cpp	2024-10-07 18:30:56
+--- ./backends/imgui_impl_sdlrenderer2.cpp	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer2.cpp	2025-01-25 16:39:54
 @@ -30,7 +30,7 @@
  //  2021-10-06: Backup and restore modified ClipRect/Viewport.
  //  2021-09-21: Initial version.
@@ -154,8 +180,8 @@ diff --color -rbu ./backends/imgui_impl_sdlrenderer2.cpp ../scummvm/scummvm/back
  #include "imgui_impl_sdlrenderer2.h"
  #include <stdint.h>     // intptr_t
 diff --color -rbu ./backends/imgui_impl_sdlrenderer2.h ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer2.h
---- ./backends/imgui_impl_sdlrenderer2.h	2024-10-07 18:21:01
-+++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer2.h	2024-10-07 18:30:56
+--- ./backends/imgui_impl_sdlrenderer2.h	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer2.h	2025-01-25 16:39:54
 @@ -22,8 +22,8 @@
  // - Introduction, links and more at the top of imgui.cpp
  
@@ -166,9 +192,95 @@ diff --color -rbu ./backends/imgui_impl_sdlrenderer2.h ../scummvm/scummvm/backen
  
  struct SDL_Renderer;
  
+diff --color -rbu ./backends/imgui_impl_sdlrenderer3.cpp ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer3.cpp
+--- ./backends/imgui_impl_sdlrenderer3.cpp	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer3.cpp	2025-01-25 16:39:54
+@@ -12,8 +12,6 @@
+ // Implemented features:
+ //  [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID!
+ //  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+-// Missing features:
+-//  [ ] Renderer: Multi-viewport support (multiple windows).
+ 
+ // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+ // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+@@ -29,7 +27,7 @@
+ //  2024-02-12: Amend to query SDL_RenderViewportSet() and restore viewport accordingly.
+ //  2023-05-30: Initial version.
+ 
+-#include "imgui.h"
++#include "backends/imgui/imgui.h"
+ #ifndef IMGUI_DISABLE
+ #include "imgui_impl_sdlrenderer3.h"
+ #include <stdint.h>     // intptr_t
+diff --color -rbu ./backends/imgui_impl_sdlrenderer3.h ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer3.h
+--- ./backends/imgui_impl_sdlrenderer3.h	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/backends/imgui_impl_sdlrenderer3.h	2025-01-25 16:39:54
+@@ -12,8 +12,6 @@
+ // Implemented features:
+ //  [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID!
+ //  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+-// Missing features:
+-//  [ ] Renderer: Multi-viewport support (multiple windows).
+ 
+ // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+ // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+@@ -24,7 +22,7 @@
+ // - Introduction, links and more at the top of imgui.cpp
+ 
+ #pragma once
+-#include "imgui.h"      // IMGUI_IMPL_API
++#include "backends/imgui/imgui.h"      // IMGUI_IMPL_API
+ #ifndef IMGUI_DISABLE
+ 
+ struct SDL_Renderer;
+diff --color -rbu ./imconfig.h ../scummvm/scummvm/backends/imgui/imconfig.h
+--- ./imconfig.h	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/imconfig.h	2025-01-25 16:39:54
+@@ -28,8 +28,8 @@
+ //#define IMGUI_API __attribute__((visibility("default")))  // GCC/Clang: override visibility when set is hidden
+ 
+ //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
+-//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+-//#define IMGUI_DISABLE_OBSOLETE_KEYIO                      // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
++#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
++#define IMGUI_DISABLE_OBSOLETE_KEYIO                      // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
+ 
+ //---- Disable all of Dear ImGui or don't implement standard windows/tools.
+ // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
+@@ -43,7 +43,7 @@
+ //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS         // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
+ //#define IMGUI_DISABLE_WIN32_FUNCTIONS                     // [Win32] Won't use and link with any Win32 function (clipboard, IME).
+ //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS      // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
+-//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS             // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
++#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS             // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
+ //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS            // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
+ //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS              // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
+ //#define IMGUI_DISABLE_FILE_FUNCTIONS                      // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
+diff --color -rbu ./imgui.cpp ../scummvm/scummvm/backends/imgui/imgui.cpp
+--- ./imgui.cpp	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/imgui.cpp	2025-01-25 16:39:54
+@@ -15234,7 +15234,7 @@
+             line_end[-1] = 0;
+             const char* name_end = line_end - 1;
+             const char* type_start = line + 1;
+-            char* type_end = (char*)(void*)ImStrchrRange(type_start, name_end, ']');
++            char* type_end = const_cast<char *>(ImStrchrRange(type_start, name_end, ']'));
+             const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
+             if (!type_end || !name_start)
+                 continue;
+@@ -21345,7 +21345,7 @@
+ 
+         if (TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size()))
+         {
+-            InputTextMultiline("##Ini", (char*)(void*)g.SettingsIniData.c_str(), g.SettingsIniData.Buf.Size, ImVec2(-FLT_MIN, GetTextLineHeight() * 20), ImGuiInputTextFlags_ReadOnly);
++            InputTextMultiline("##Ini", const_cast<char *>(g.SettingsIniData.c_str()), g.SettingsIniData.Buf.Size, ImVec2(-FLT_MIN, GetTextLineHeight() * 20), ImGuiInputTextFlags_ReadOnly);
+             TreePop();
+         }
+         TreePop();
 diff --color -rbu ./imgui.h ../scummvm/scummvm/backends/imgui/imgui.h
---- ./imgui.h	2024-10-07 18:21:01
-+++ ../scummvm/scummvm/backends/imgui/imgui.h	2024-10-07 18:30:56
+--- ./imgui.h	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/imgui.h	2025-01-25 16:39:54
 @@ -105,8 +105,10 @@
  #define IM_FMTARGS(FMT)             __attribute__((format(gnu_printf, FMT, FMT+1)))
  #define IM_FMTLIST(FMT)             __attribute__((format(gnu_printf, FMT, 0)))
@@ -182,9 +294,33 @@ diff --color -rbu ./imgui.h ../scummvm/scummvm/backends/imgui/imgui.h
  #else
  #define IM_FMTARGS(FMT)
  #define IM_FMTLIST(FMT)
+diff --color -rbu ./imgui_demo.cpp ../scummvm/scummvm/backends/imgui/imgui_demo.cpp
+--- ./imgui_demo.cpp	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/imgui_demo.cpp	2025-01-25 16:39:54
+@@ -3926,7 +3926,7 @@
+                             ImGui::TableNextColumn();
+                             ImGui::SetNextItemWidth(-FLT_MIN);
+                             ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
+-                            ImGui::InputText("###NoLabel", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly);
++                            ImGui::InputText("###NoLabel", const_cast<char *>(item_category), strlen(item_category), ImGuiInputTextFlags_ReadOnly);
+                             ImGui::PopStyleVar();
+                         }
+ 
+diff --color -rbu ./imgui_draw.cpp ../scummvm/scummvm/backends/imgui/imgui_draw.cpp
+--- ./imgui_draw.cpp	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/imgui_draw.cpp	2025-01-25 16:39:54
+@@ -3727,7 +3727,7 @@
+ 
+ void ImFont::SetGlyphVisible(ImWchar c, bool visible)
+ {
+-    if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)c))
++    if (ImFontGlyph* glyph = const_cast<ImFontGlyph *>(FindGlyph((ImWchar)c)))
+         glyph->Visible = visible ? 1 : 0;
+ }
+ 
 diff --color -rbu ./misc/freetype/imgui_freetype.cpp ../scummvm/scummvm/backends/imgui/misc/freetype/imgui_freetype.cpp
---- ./misc/freetype/imgui_freetype.cpp	2024-10-07 18:21:01
-+++ ../scummvm/scummvm/backends/imgui/misc/freetype/imgui_freetype.cpp	2024-10-07 18:32:37
+--- ./misc/freetype/imgui_freetype.cpp	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/misc/freetype/imgui_freetype.cpp	2025-01-25 16:39:54
 @@ -34,10 +34,10 @@
  
  // FIXME: cfg.OversampleH, OversampleV are not supported (but perhaps not so necessary with this rasterizer).
@@ -208,8 +344,8 @@ diff --color -rbu ./misc/freetype/imgui_freetype.cpp ../scummvm/scummvm/backends
  #endif
  
 diff --color -rbu ./misc/freetype/imgui_freetype.h ../scummvm/scummvm/backends/imgui/misc/freetype/imgui_freetype.h
---- ./misc/freetype/imgui_freetype.h	2024-05-13 22:08:39
-+++ ../scummvm/scummvm/backends/imgui/misc/freetype/imgui_freetype.h	2024-10-07 18:32:44
+--- ./misc/freetype/imgui_freetype.h	2025-02-18 13:09:05
++++ ../scummvm/scummvm/backends/imgui/misc/freetype/imgui_freetype.h	2025-01-25 16:39:54
 @@ -2,7 +2,7 @@
  // (headers)
  
diff --git a/backends/mixer/sdl/sdl-mixer.cpp b/backends/mixer/sdl/sdl-mixer.cpp
index 022d50e0598..7505ea74c1b 100644
--- a/backends/mixer/sdl/sdl-mixer.cpp
+++ b/backends/mixer/sdl/sdl-mixer.cpp
@@ -44,8 +44,13 @@ SdlMixerManager::~SdlMixerManager() {
 	if (_mixer)
 		_mixer->setReady(false);
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_CloseAudioDevice(SDL_GetAudioStreamDevice(_stream));
+	SDL_DestroyAudioStream(_stream);
+#else
 	if (_isAudioOpen)
 		SDL_CloseAudio();
+#endif
 
 	if (_isSubsystemInitialized)
 		SDL_QuitSubSystem(SDL_INIT_AUDIO);
@@ -63,7 +68,11 @@ void SdlMixerManager::init() {
 	SDL_AudioSpec desired = getAudioSpec(SAMPLES_PER_SEC);
 
 	// Start SDL Audio subsystem
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+#else
 	if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
+#endif
 		warning("Could not initialize SDL audio subsystem: %s", SDL_GetError());
 		return;
 	}
@@ -80,6 +89,10 @@ void SdlMixerManager::init() {
 #endif
 	debug(1, "Using SDL Audio Driver \"%s\"", sdlDriverName);
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	_isAudioOpen = true;
+	_obtained = desired;
+#else
 	// Needed as SDL_OpenAudio as of SDL-1.2.14 mutates fields in
 	// "desired" if used directly.
 	SDL_AudioSpec fmt = desired;
@@ -106,11 +119,13 @@ void SdlMixerManager::init() {
 
 		_obtained = desired;
 	}
+#endif
 
 	debug(1, "Output sample rate: %d Hz", _obtained.freq);
 	if (_obtained.freq != desired.freq)
 		warning("SDL mixer output sample rate: %d differs from desired: %d", _obtained.freq, desired.freq);
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	debug(1, "Output buffer size: %d samples", _obtained.samples);
 	if (_obtained.samples != desired.samples)
 		warning("SDL mixer output buffer size: %d differs from desired: %d", _obtained.samples, desired.samples);
@@ -118,14 +133,23 @@ void SdlMixerManager::init() {
 	debug(1, "Output channels: %d", _obtained.channels);
 	if (_obtained.channels != 1 && _obtained.channels != 2)
 		error("SDL mixer output requires mono or stereo output device");
+#endif
 
-	_mixer = new Audio::MixerImpl(_obtained.freq, _obtained.channels >= 2, desired.samples);
+	int desiredSamples = 0;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &_obtained, &desiredSamples);
+#else
+	desiredSamples = desired.samples;
+#endif
+
+	_mixer = new Audio::MixerImpl(_obtained.freq, _obtained.channels >= 2, desiredSamples);
 	assert(_mixer);
 	_mixer->setReady(true);
 
 	startAudio();
 }
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 static uint32 roundDownPowerOfTwo(uint32 samples) {
 	// Public domain code from Sean Eron Anderson
 	// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
@@ -143,6 +167,7 @@ static uint32 roundDownPowerOfTwo(uint32 samples) {
 
 	return rounded;
 }
+#endif
 
 SDL_AudioSpec SdlMixerManager::getAudioSpec(uint32 outputRate) {
 	SDL_AudioSpec desired;
@@ -184,18 +209,28 @@ SDL_AudioSpec SdlMixerManager::getAudioSpec(uint32 outputRate) {
 
 	memset(&desired, 0, sizeof(desired));
 	desired.freq = freq;
-	desired.format = AUDIO_S16SYS;
 	desired.channels = channels;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	desired.format = SDL_AUDIO_S16;
+#else
+	desired.format = AUDIO_S16SYS;
 	desired.samples = roundDownPowerOfTwo(samples);
 	desired.callback = sdlCallback;
 	desired.userdata = this;
+#endif
 
 	return desired;
 }
 
 void SdlMixerManager::startAudio() {
 	// Start the sound system
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &_obtained, sdl3Callback, this);
+	if (_stream)
+		SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(_stream));
+#else
 	SDL_PauseAudio(0);
+#endif
 }
 
 void SdlMixerManager::callbackHandler(byte *samples, int len) {
@@ -210,18 +245,45 @@ void SdlMixerManager::sdlCallback(void *this_, byte *samples, int len) {
 	manager->callbackHandler(samples, len);
 }
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+void SdlMixerManager::sdl3Callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount) {
+	if (additional_amount > 0) {
+		Uint8 *data = SDL_stack_alloc(Uint8, additional_amount);
+		if (data) {
+			SdlMixerManager *manager = (SdlMixerManager *)userdata;
+			manager->sdlCallback(userdata, data, additional_amount);
+			SDL_PutAudioStreamData(stream, data, additional_amount);
+			SDL_stack_free(data);
+		}
+	}
+}
+#endif
+
 void SdlMixerManager::suspendAudio() {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_CloseAudioDevice(SDL_GetAudioStreamDevice(_stream));
+	SDL_DestroyAudioStream(_stream);
+#else
 	SDL_CloseAudio();
+#endif
 	_audioSuspended = true;
 }
 
 int SdlMixerManager::resumeAudio() {
 	if (!_audioSuspended)
 		return -2;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &_obtained, sdl3Callback, this);
+	if(!_stream)
+		return -1;
+	if (SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(_stream)))
+		return -1;
+#else
 	if (SDL_OpenAudio(&_obtained, nullptr) < 0) {
 		return -1;
 	}
 	SDL_PauseAudio(0);
+#endif
 	_audioSuspended = false;
 	return 0;
 }
diff --git a/backends/mixer/sdl/sdl-mixer.h b/backends/mixer/sdl/sdl-mixer.h
index 2c8ea2232b8..3dd0670396f 100644
--- a/backends/mixer/sdl/sdl-mixer.h
+++ b/backends/mixer/sdl/sdl-mixer.h
@@ -80,9 +80,16 @@ protected:
 	 * by subclasses, so it invokes the non-static function callbackHandler()
 	 */
 	static void sdlCallback(void *this_, byte *samples, int len);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	static void sdl3Callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount);
+#endif
 
 	bool _isSubsystemInitialized;
 	bool _isAudioOpen;
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_AudioStream *_stream = nullptr;
+#endif
 };
 
 #endif
diff --git a/backends/module.mk b/backends/module.mk
index d1acc3d8d54..7d3c71cc11b 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -185,7 +185,7 @@ endif
 # derive from the SDL backend, and they all need the following files.
 ifdef SDL_BACKEND
 MODULE_OBJS += \
-	events/sdl/sdl-events.o \
+	events/sdl/sdl-common-events.o \
 	graphics/sdl/sdl-graphics.o \
 	graphics/surfacesdl/surfacesdl-graphics.o \
 	mixer/sdl/sdl-mixer.o \
@@ -193,6 +193,23 @@ MODULE_OBJS += \
 	mutex/sdl/sdl-mutex.o \
 	timer/sdl/sdl-timer.o
 
+ifndef USE_SDL3
+ifndef USE_SDL2
+MODULE_OBJS += \
+	events/sdl/sdl1-events.o
+endif
+endif
+
+ifdef USE_SDL2
+MODULE_OBJS += \
+	events/sdl/sdl2-events.o
+endif
+
+ifdef USE_SDL3
+MODULE_OBJS += \
+	events/sdl/sdl3-events.o
+endif
+
 ifndef RISCOS
 ifndef KOLIBRIOS
 MODULE_OBJS += plugins/sdl/sdl-provider.o
@@ -510,5 +527,20 @@ MODULE_OBJS += \
 endif
 endif
 
+ifdef USE_SDL3
+ifdef USE_IMGUI
+ifdef USE_OPENGL
+MODULE_OBJS += \
+	imgui/backends/imgui_impl_opengl3.o
+endif
+ifdef USE_IMGUI_SDLRENDERER3
+MODULE_OBJS += \
+	imgui/backends/imgui_impl_sdlrenderer3.o
+endif
+MODULE_OBJS += \
+	imgui/backends/imgui_impl_sdl3.o
+endif
+endif
+
 # Include common rules
 include $(srcdir)/rules.mk
diff --git a/backends/mutex/sdl/sdl-mutex.cpp b/backends/mutex/sdl/sdl-mutex.cpp
index 3ada8e5ecd0..11e951b4845 100644
--- a/backends/mutex/sdl/sdl-mutex.cpp
+++ b/backends/mutex/sdl/sdl-mutex.cpp
@@ -34,11 +34,29 @@ public:
 	SdlMutexInternal() { _mutex = SDL_CreateMutex(); }
 	~SdlMutexInternal() override { SDL_DestroyMutex(_mutex); }
 
-	bool lock() override { return (SDL_mutexP(_mutex) == 0); }
-	bool unlock() override { return (SDL_mutexV(_mutex) == 0); }
+	bool lock() override {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_LockMutex(_mutex);
+		return true;
+#else
+		return (SDL_mutexP(_mutex) == 0);
+#endif
+	}
+	bool unlock() override {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_UnlockMutex(_mutex);
+		return true;
+#else
+		return (SDL_mutexV(_mutex) == 0);
+#endif
+	}
 
 private:
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Mutex *_mutex;
+#else
 	SDL_mutex *_mutex;
+#endif
 };
 
 Common::MutexInternal *createSdlMutexInternal() {
diff --git a/backends/platform/sdl/amigaos/amigaos.cpp b/backends/platform/sdl/amigaos/amigaos.cpp
index 7d07d627137..db01f3cb2a1 100644
--- a/backends/platform/sdl/amigaos/amigaos.cpp
+++ b/backends/platform/sdl/amigaos/amigaos.cpp
@@ -29,6 +29,16 @@
 
 static bool cleanupDone = false;
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static bool sdlGLLoadLibrary(const char *path) {
+	return SDL_GL_LoadLibrary(path);
+}
+#else
+static bool sdlGLLoadLibrary(const char *path) {
+	return SDL_GL_LoadLibrary(path) != 0;
+}
+#endif
+
 static void cleanup() {
 	if (!cleanupDone)
 		g_system->destroy();
@@ -83,7 +93,7 @@ void OSystem_AmigaOS::initBackend() {
 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-		if (SDL_GL_LoadLibrary(NULL) < 0) {
+		if (!sdlGLLoadLibrary(NULL)) {
 			if (force) {
 				warning("OpenGL implementation chosen is unsupported, falling back");
 				force = 0;
@@ -101,7 +111,7 @@ void OSystem_AmigaOS::initBackend() {
 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
-		if (SDL_GL_LoadLibrary(NULL) < 0) {
+		if (!sdlGLLoadLibrary(NULL)) {
 			if (force) {
 				warning("OpenGL implementation chosen is unsupported, falling back");
 				force = 0;
diff --git a/backends/platform/sdl/macosx/macosx-window.mm b/backends/platform/sdl/macosx/macosx-window.mm
index d8481eaee46..59e194d529f 100644
--- a/backends/platform/sdl/macosx/macosx-window.mm
+++ b/backends/platform/sdl/macosx/macosx-window.mm
@@ -27,7 +27,7 @@
 #include <AppKit/NSWindow.h>
 
 float SdlWindow_MacOSX::getDpiScalingFactor() const {
-#if SDL_VERSION_ATLEAST(2, 0, 0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+#if !SDL_VERSION_ATLEAST(3, 0, 0) && SDL_VERSION_ATLEAST(2, 0, 0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
 	SDL_SysWMinfo wmInfo;
 	if (getSDLWMInformation(&wmInfo)) {
 		NSWindow *nswindow = wmInfo.info.cocoa.window;
diff --git a/backends/platform/sdl/sdl-sys.h b/backends/platform/sdl/sdl-sys.h
index 87bd6cabfab..1eb5129887f 100644
--- a/backends/platform/sdl/sdl-sys.h
+++ b/backends/platform/sdl/sdl-sys.h
@@ -187,12 +187,18 @@
 
 #endif
 
+#ifdef USE_SDL3
+#include <SDL3/SDL.h>
+#else
 #include <SDL.h>
+#endif
 
 // Ignore warnings from system headers pulled by SDL
 #pragma warning(push)
 #pragma warning(disable:4121) // alignment of a member was sensitive to packing
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 #include <SDL_syswm.h>
+#endif
 #pragma warning(pop)
 
 // Restore the forbidden exceptions from the hack above
diff --git a/backends/platform/sdl/sdl-window.cpp b/backends/platform/sdl/sdl-window.cpp
index 508ffe8a6c8..16efad4d9a8 100644
--- a/backends/platform/sdl/sdl-window.cpp
+++ b/backends/platform/sdl/sdl-window.cpp
@@ -29,7 +29,9 @@
 
 #include "icons/scummvm.xpm"
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static const uint32 fullscreenMask = SDL_WINDOW_FULLSCREEN;
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 static const uint32 fullscreenMask = SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN;
 #endif
 
@@ -122,7 +124,11 @@ void SdlWindow::setupIcon() {
 		}
 	}
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Surface *sdl_surf = SDL_CreateSurfaceFrom(w, h, SDL_GetPixelFormatForMasks(32, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000), icon, w * 4);
+#else
 	SDL_Surface *sdl_surf = SDL_CreateRGBSurfaceFrom(icon, w, h, 32, w * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000);
+#endif
 	if (!sdl_surf) {
 		warning("SDL_CreateRGBSurfaceFrom(icon) failed");
 	}
@@ -135,7 +141,11 @@ void SdlWindow::setupIcon() {
 	SDL_WM_SetIcon(sdl_surf, NULL);
 #endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_DestroySurface(sdl_surf);
+#else
 	SDL_FreeSurface(sdl_surf);
+#endif
 	free(icon);
 #endif
 }
@@ -154,7 +164,11 @@ void SdlWindow::setWindowCaption(const Common::String &caption) {
 void SdlWindow::grabMouse(bool grab) {
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 	if (_window) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_SetWindowMouseGrab(_window, grab);
+#else
 		SDL_SetWindowGrab(_window, grab ? SDL_TRUE : SDL_FALSE);
+#endif
 #if SDL_VERSION_ATLEAST(2, 0, 18)
 		SDL_SetWindowMouseRect(_window, grab ? &grabRect : NULL);
 #endif
@@ -187,7 +201,10 @@ void SdlWindow::setMouseRect(const Common::Rect &rect) {
 }
 
 bool SdlWindow::lockMouse(bool lock) {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_SetWindowRelativeMouseMode(_window, lock);
+	_inputLockState = lock;
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE);
 	_inputLockState = lock;
 #else
@@ -244,6 +261,7 @@ void SdlWindow::iconifyWindow() {
 #endif
 }
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 bool SdlWindow::getSDLWMInformation(SDL_SysWMinfo *info) const {
 	SDL_VERSION(&info->version);
 #if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -254,9 +272,17 @@ bool SdlWindow::getSDLWMInformation(SDL_SysWMinfo *info) const {
 	return false;
 #endif
 }
+#endif
 
 Common::Rect SdlWindow::getDesktopResolution() {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const SDL_DisplayMode* pDisplayMode = SDL_GetDesktopDisplayMode(getDisplayIndex());
+	if (pDisplayMode) {
+		_desktopRes = Common::Rect(pDisplayMode->w, pDisplayMode->h);
+	} else {
+		warning("SDL_GetDesktopDisplayMode failed: %s", SDL_GetError());
+	}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	SDL_DisplayMode displayMode;
 	if (!SDL_GetDesktopDisplayMode(getDisplayIndex(), &displayMode)) {
 		_desktopRes = Common::Rect(displayMode.w, displayMode.h);
@@ -279,7 +305,9 @@ void SdlWindow::getDisplayDpi(float *dpi, float *defaultDpi) const {
 		*defaultDpi = systemDpi;
 
 	if (dpi) {
-#if SDL_VERSION_ATLEAST(2, 0, 4)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		*dpi = SDL_GetWindowDisplayScale(_window) * systemDpi;
+#elif SDL_VERSION_ATLEAST(2, 0, 4)
 		if (SDL_GetDisplayDPI(getDisplayIndex(), nullptr, dpi, nullptr) != 0) {
 			*dpi = systemDpi;
 		}
@@ -303,7 +331,9 @@ float SdlWindow::getDpiScalingFactor() const {
 }
 
 float SdlWindow::getSdlDpiScalingFactor() const {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_GetWindowDisplayScale(getSDLWindow());
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	int windowWidth, windowHeight;
 	SDL_GetWindowSize(getSDLWindow(), &windowWidth, &windowHeight);
 	int realWidth, realHeight;
@@ -316,6 +346,15 @@ float SdlWindow::getSdlDpiScalingFactor() const {
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 SDL_Surface *copySDLSurface(SDL_Surface *src) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const bool locked = SDL_MUSTLOCK(src);
+
+	if (locked) {
+		if (!SDL_LockSurface(src)) {
+			return nullptr;
+		}
+	}
+#else
 	const bool locked = SDL_MUSTLOCK(src) == SDL_TRUE;
 
 	if (locked) {
@@ -323,11 +362,17 @@ SDL_Surface *copySDLSurface(SDL_Surface *src) {
 			return nullptr;
 		}
 	}
+#endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Surface *res = SDL_CreateSurfaceFrom(src->w, src->h, src->format,
+						   src->pixels, src->pitch);
+#else
 	SDL_Surface *res = SDL_CreateRGBSurfaceFrom(src->pixels,
 	                       src->w, src->h, src->format->BitsPerPixel,
 	                       src->pitch, src->format->Rmask, src->format->Gmask,
 	                       src->format->Bmask, src->format->Amask);
+#endif
 
 	if (locked) {
 		SDL_UnlockSurface(src);
@@ -337,6 +382,16 @@ SDL_Surface *copySDLSurface(SDL_Surface *src) {
 }
 
 int SdlWindow::getDisplayIndex() const {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int display = 0;
+	int num_displays;
+	SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
+	if (num_displays > 0) {
+		display = static_cast<int>(displays[0]);
+	}
+	SDL_free(displays);
+	return display;
+#else
 	if (_window) {
 		int displayIndex = SDL_GetWindowDisplayIndex(_window);
 		if (displayIndex >= 0)
@@ -344,12 +399,19 @@ int SdlWindow::getDisplayIndex() const {
 	}
 	// Default to primary display
 	return 0;
+#endif
 }
 
 bool SdlWindow::createOrUpdateWindow(int width, int height, uint32 flags) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (_inputGrabState) {
+		flags |= SDL_WINDOW_MOUSE_GRABBED;
+	}
+#else
 	if (_inputGrabState) {
 		flags |= SDL_WINDOW_INPUT_GRABBED;
 	}
+#endif
 
 	// SDL_WINDOW_RESIZABLE can also be updated without recreating the window
 	// starting with SDL 2.0.5, but it is not treated as updateable here
@@ -360,7 +422,11 @@ bool SdlWindow::createOrUpdateWindow(int width, int height, uint32 flags) {
 	// 2. Users (particularly on Windows) will sometimes swap older SDL DLLs
 	//    to avoid bugs, which would be impossible if the feature was enabled
 	//    at compile time using SDL_VERSION_ATLEAST.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const uint32 updateableFlagsMask = fullscreenMask | SDL_WINDOW_MOUSE_GRABBED;
+#else
 	const uint32 updateableFlagsMask = fullscreenMask | SDL_WINDOW_INPUT_GRABBED;
+#endif
 
 	const uint32 oldNonUpdateableFlags = _lastFlags & ~updateableFlagsMask;
 	const uint32 newNonUpdateableFlags = flags & ~updateableFlagsMask;
@@ -387,7 +453,9 @@ bool SdlWindow::createOrUpdateWindow(int width, int height, uint32 flags) {
 	) {
 		int top, left, bottom, right;
 
-#if SDL_VERSION_ATLEAST(2, 0, 5)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (!_window || !SDL_GetWindowBordersSize(_window, &top, &left, &bottom, &right))
+#elif SDL_VERSION_ATLEAST(2, 0, 5)
 		if (!_window || SDL_GetWindowBordersSize(_window, &top, &left, &bottom, &right) < 0)
 #endif
 		{
@@ -408,13 +476,31 @@ bool SdlWindow::createOrUpdateWindow(int width, int height, uint32 flags) {
 
 	if (!_window || oldNonUpdateableFlags != newNonUpdateableFlags) {
 		destroyWindow();
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_PropertiesID props = SDL_CreateProperties();
+		SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, _windowCaption.c_str());
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, _lastX);
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, _lastY);
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags);
+		_window = SDL_CreateWindowWithProperties(props);
+		SDL_DestroyProperties(props);
+#else
 		_window = SDL_CreateWindow(_windowCaption.c_str(), _lastX,
 								   _lastY, width, height, flags);
+#endif
 		if (_window) {
 			setupIcon();
 		}
 	} else {
 		if (fullscreenFlags) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			if (!SDL_SetWindowFullscreenMode(_window, NULL))
+				warning("SDL_SetWindowFullscreenMode failed: %s", SDL_GetError());
+			if (!SDL_SyncWindow(_window))
+				warning("SDL_SyncWindow failed: %s", SDL_GetError());
+#else
 			SDL_DisplayMode fullscreenMode;
 			fullscreenMode.w = width;
 			fullscreenMode.h = height;
@@ -422,6 +508,7 @@ bool SdlWindow::createOrUpdateWindow(int width, int height, uint32 flags) {
 			fullscreenMode.format = 0;
 			fullscreenMode.refresh_rate = 0;
 			SDL_SetWindowDisplayMode(_window, &fullscreenMode);
+#endif
 		} else {
 			SDL_SetWindowSize(_window, width, height);
 		}
@@ -429,8 +516,13 @@ bool SdlWindow::createOrUpdateWindow(int width, int height, uint32 flags) {
 		SDL_SetWindowFullscreen(_window, fullscreenFlags);
 	}
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const bool shouldGrab = (flags & SDL_WINDOW_MOUSE_GRABBED) || fullscreenFlags;
+	SDL_SetWindowMouseGrab(_window, shouldGrab);
+#else
 	const bool shouldGrab = (flags & SDL_WINDOW_INPUT_GRABBED) || fullscreenFlags;
 	SDL_SetWindowGrab(_window, shouldGrab ? SDL_TRUE : SDL_FALSE);
+#endif
 #if SDL_VERSION_ATLEAST(2, 0, 18)
 	SDL_SetWindowMouseRect(_window, shouldGrab ? &grabRect : NULL);
 #endif
diff --git a/backends/platform/sdl/sdl-window.h b/backends/platform/sdl/sdl-window.h
index b3e4933874c..1aabe99df90 100644
--- a/backends/platform/sdl/sdl-window.h
+++ b/backends/platform/sdl/sdl-window.h
@@ -78,6 +78,7 @@ public:
 	 */
 	void iconifyWindow();
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	/**
 	 * Query platform specific SDL window manager information.
 	 *
@@ -85,6 +86,7 @@ public:
 	 * for accessing it in a version safe manner.
 	 */
 	bool getSDLWMInformation(SDL_SysWMinfo *info) const;
+#endif
 
 	/*
 	 * Retrieve the current desktop resolution.
@@ -109,7 +111,11 @@ public:
 	virtual float getDpiScalingFactor() const;
 
 	bool mouseIsGrabbed() const {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (_window) {
+			return SDL_GetWindowMouseGrab(_window);
+		}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 		if (_window) {
 			return SDL_GetWindowGrab(_window) == SDL_TRUE;
 		}
@@ -118,7 +124,9 @@ public:
 	}
 
 	bool mouseIsLocked() const {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		return SDL_GetWindowRelativeMouseMode(_window);
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 		return SDL_GetRelativeMouseMode() == SDL_TRUE;
 #else
 		return _inputLockState;
diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp
index 3fc1f20b305..36e04dd0d10 100644
--- a/backends/platform/sdl/sdl.cpp
+++ b/backends/platform/sdl/sdl.cpp
@@ -20,6 +20,7 @@
  */
 
 #define FORBIDDEN_SYMBOL_ALLOW_ALL
+#define SDL_FUNCTION_POINTER_IS_VOID_POINTER
 
 #include "backends/platform/sdl/sdl.h"
 #include "common/config-manager.h"
@@ -72,10 +73,22 @@
 #include <SDL_net.h>
 #endif
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+#include <SDL3/SDL_clipboard.h>
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 #include <SDL_clipboard.h>
 #endif
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static bool sdlGetAttribute(SDL_GLAttr attr, int *value) {
+	return SDL_GL_GetAttribute(attr, value);
+}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
+static bool sdlGetAttribute(SDL_GLattr attr, int *value) {
+	return SDL_GL_GetAttribute(attr, value) != 0;
+}
+#endif
+
 OSystem_SDL::OSystem_SDL()
 	:
 #ifdef USE_MULTIPLE_RENDERERS
@@ -97,7 +110,11 @@ OSystem_SDL::OSystem_SDL()
 }
 
 OSystem_SDL::~OSystem_SDL() {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_ShowCursor();
+#else
 	SDL_ShowCursor(SDL_ENABLE);
+#endif
 
 #ifdef USE_MULTIPLE_RENDERERS
 	clearGraphicsModes();
@@ -162,8 +179,12 @@ void OSystem_SDL::init() {
 #endif
 
 #if !defined(OPENPANDORA)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_HideCursor();
+#else
 	// Disable OS cursor
 	SDL_ShowCursor(SDL_DISABLE);
+#endif
 #endif
 
 	if (_window == nullptr)
@@ -206,7 +227,13 @@ bool OSystem_SDL::hasFeature(Feature f) {
 #if defined(USE_SCUMMVMDLC) && defined(USE_LIBCURL)
 	if (f == kFeatureDLC) return true;
 #endif
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (f == kFeatureTouchpadMode) {
+		int count = 0;
+		SDL_free(SDL_GetTouchDevices(&count));
+		return count > 0;
+	}
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
 	if (f == kFeatureTouchpadMode) {
 		return SDL_GetNumTouchDevices() > 0;
 	}
@@ -383,18 +410,28 @@ void OSystem_SDL::detectOpenGLFeaturesSupport() {
 #else
 	// Spawn a 32x32 window off-screen with a GL context to test if framebuffers are supported
 #if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_PropertiesID props = SDL_CreateProperties();
+	SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "");
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 32);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 32);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
+	SDL_Window *window = SDL_CreateWindowWithProperties(props);
+	SDL_DestroyProperties(props);
+#else
 	SDL_Window *window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 32, 32, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
+#endif
 	if (!window) {
 		return;
 	}
 
 	int glContextProfileMask, glContextMajor;
-	if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &glContextProfileMask) != 0) {
+	if (!sdlGetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &glContextProfileMask)) {
 		SDL_DestroyWindow(window);
 		return;
 	}
 	if (glContextProfileMask == SDL_GL_CONTEXT_PROFILE_ES) {
-		if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &glContextMajor) != 0) {
+		if (!sdlGetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &glContextMajor)) {
 			SDL_DestroyWindow(window);
 			return;
 		}
@@ -417,7 +454,11 @@ void OSystem_SDL::detectOpenGLFeaturesSupport() {
 	_supportsFrameBuffer = OpenGLContext.framebufferObjectSupported;
 	_supportsShaders = OpenGLContext.enginesShadersSupported;
 	OpenGLContext.reset();
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_GL_DestroyContext(glContext);
+#else
 	SDL_GL_DeleteContext(glContext);
+#endif
 	SDL_DestroyWindow(window);
 #else
 	SDL_putenv(const_cast<char *>("SDL_VIDEO_WINDOW_POS=9000,9000"));
@@ -443,7 +484,17 @@ void OSystem_SDL::detectAntiAliasingSupport() {
 		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, requestedSamples);
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_PropertiesID props = SDL_CreateProperties();
+		SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "");
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 32);
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 32);
+		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
+		SDL_Window *window = SDL_CreateWindowWithProperties(props);
+		SDL_DestroyProperties(props);
+#else
 		SDL_Window *window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 32, 32, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
+#endif
 		if (window) {
 			SDL_GLContext glContext = SDL_GL_CreateContext(window);
 			if (glContext) {
@@ -454,7 +505,11 @@ void OSystem_SDL::detectAntiAliasingSupport() {
 					_antiAliasLevels.push_back(requestedSamples);
 				}
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+				SDL_GL_DestroyContext(glContext);
+#else
 				SDL_GL_DeleteContext(glContext);
+#endif
 			}
 
 			SDL_DestroyWindow(window);
@@ -533,11 +588,17 @@ void OSystem_SDL::initSDL() {
 		// or otherwise the application won't start.
 		uint32 sdlFlags = SDL_INIT_VIDEO;
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 		if (ConfMan.hasKey("disable_sdl_parachute"))
 			sdlFlags |= SDL_INIT_NOPARACHUTE;
+#endif
 
 		// Initialize SDL (SDL Subsystems are initialized in the corresponding sdl managers)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (!SDL_Init(sdlFlags))
+#else
 		if (SDL_Init(sdlFlags) == -1)
+#endif
 			error("Could not initialize SDL: %s", SDL_GetError());
 
 		_initedSDL = true;
@@ -657,7 +718,19 @@ Common::WriteStream *OSystem_SDL::createLogFile() {
 
 Common::String OSystem_SDL::getSystemLanguage() const {
 
-#if SDL_VERSION_ATLEAST(2, 0, 14)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int count = 0;
+	SDL_Locale **pLocales = SDL_GetPreferredLocales(&count);
+	if (pLocales) {
+		SDL_Locale *locales = *pLocales;
+		if (locales[0].language != NULL) {
+			Common::String str = Common::String::format("%s_%s", locales[0].country, locales[0].language);
+			SDL_free(locales);
+			return str;
+		}
+		SDL_free(pLocales);
+	}
+#elif SDL_VERSION_ATLEAST(2, 0, 14)
 	SDL_Locale *locales = SDL_GetPreferredLocales();
 	if (locales) {
 		if (locales[0].language != NULL) {
@@ -667,7 +740,7 @@ Common::String OSystem_SDL::getSystemLanguage() const {
 		}
 		SDL_free(locales);
 	}
-#endif // SDL_VERSION_ATLEAST(2, 0, 14)
+#endif
 #if defined(USE_DETECTLANG) && !defined(WIN32)
 	// Activating current locale settings
 	const Common::String locale = setlocale(LC_ALL, "");
@@ -703,8 +776,13 @@ Common::String OSystem_SDL::getSystemLanguage() const {
 }
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
+
 bool OSystem_SDL::hasTextInClipboard() {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_HasClipboardText();
+#else
 	return SDL_HasClipboardText() == SDL_TRUE;
+#endif
 }
 
 Common::U32String OSystem_SDL::getTextFromClipboard() {
@@ -748,7 +826,11 @@ void OSystem_SDL::messageBox(LogMessageType::Type type, const char *message) {
 
 #if SDL_VERSION_ATLEAST(2, 0, 14)
 bool OSystem_SDL::openUrl(const Common::String &url) {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (!SDL_OpenURL(url.c_str())) {
+#else
 	if (SDL_OpenURL(url.c_str()) != 0) {
+#endif
 		warning("Failed to open URL: %s", SDL_GetError());
 		return false;
 	}
diff --git a/backends/timer/sdl/sdl-timer.cpp b/backends/timer/sdl/sdl-timer.cpp
index 81812888f8c..cda5923c3a7 100644
--- a/backends/timer/sdl/sdl-timer.cpp
+++ b/backends/timer/sdl/sdl-timer.cpp
@@ -28,16 +28,25 @@
 
 #include "common/textconsole.h"
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+static Uint32 timer_handler(void *userdata, SDL_TimerID timerID, Uint32 interval) {
+	((DefaultTimerManager *)userdata)->handler();
+	return interval;
+}
+#else
 static Uint32 timer_handler(Uint32 interval, void *param) {
 	((DefaultTimerManager *)param)->handler();
 	return interval;
 }
+#endif
 
 SdlTimerManager::SdlTimerManager() {
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	// Initializes the SDL timer subsystem
 	if (SDL_InitSubSystem(SDL_INIT_TIMER) == -1) {
 		error("Could not initialize SDL: %s", SDL_GetError());
 	}
+#endif
 
 	// Creates the timer callback
 	_timerID = SDL_AddTimer(10, &timer_handler, this);
@@ -47,7 +56,9 @@ SdlTimerManager::~SdlTimerManager() {
 	// Removes the timer callback
 	SDL_RemoveTimer(_timerID);
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	SDL_QuitSubSystem(SDL_INIT_TIMER);
+#endif
 }
 
 #endif
diff --git a/base/commandLine.cpp b/base/commandLine.cpp
index 44507806144..3dcdd2b3e72 100644
--- a/base/commandLine.cpp
+++ b/base/commandLine.cpp
@@ -1964,7 +1964,10 @@ bool processSettings(Common::String &command, Common::StringMap &settings, Commo
 	} else if (command == "version") {
 		printf("%s\n", gScummVMFullVersion);
 #ifdef SDL_BACKEND
-#ifdef USE_SDL2
+#ifdef USE_SDL3
+		int sdlLinkedVersion = SDL_GetVersion();
+		printf("Using SDL backend with SDL %d.%d.%d\n", SDL_VERSIONNUM_MAJOR(sdlLinkedVersion), SDL_VERSIONNUM_MINOR(sdlLinkedVersion), SDL_VERSIONNUM_MICRO(sdlLinkedVersion));
+#elif defined(USE_SDL2)
 		SDL_version sdlLinkedVersion;
 		SDL_GetVersion(&sdlLinkedVersion);
 		printf("Using SDL backend with SDL %d.%d.%d\n", sdlLinkedVersion.major, sdlLinkedVersion.minor, sdlLinkedVersion.patch);
diff --git a/configure b/configure
index 95f073d598d..3c8cded3c87 100755
--- a/configure
+++ b/configure
@@ -536,8 +536,8 @@ find_sdlconfig() {
 	IFS="$ac_save_ifs"
 
 	if test -z "$_sdlconfig"; then
-		echo "none found!"
-		exit 1
+		_sdlconfig="pkg-config sdl3"
+		echo "no sdl1, no sdl2 -> try sdl3"
 	fi
 }
 
@@ -4257,21 +4257,31 @@ fi
 # Setup SDL specifics for SDL based backends
 #
 if test "$_sdl" = auto ; then
-	find_sdlconfig
+	if test "$_pkg_config" = "yes" && ($_pkgconfig --exists sdl || $_pkgconfig --exists sdl2); then
+		find_sdlconfig
+		_sdlversion=`$_sdlconfig --version`
+		cat > $TMPC << EOF
+#include "SDL.h"
+int main(int argc, char *argv[]) { SDL_Init(0); return 0; }
+EOF
+	elif test "$_pkg_config" = "yes" && $_pkgconfig --exists sdl3; then
+		_sdlconfig="pkg-config sdl3"
+		_sdlversion=`$_sdlconfig --modversion`
+		cat > $TMPC << EOF
+#include "SDL3/SDL.h"
+int main(int argc, char *argv[]) { SDL_Init(0); return 0; }
+EOF
+	fi
+
 	append_var SDL_CFLAGS "`$_sdlconfig --cflags | sed 's/[[:space:]]*-Dmain=SDL_main//g'`"
 	if test "$_static_build" = yes ; then
 		append_var SDL_LIBS "`$_sdlconfig --static-libs`"
 	else
 		append_var SDL_LIBS "`$_sdlconfig --libs`"
 	fi
-	_sdlversion=`$_sdlconfig --version`
 
 	echocheck "SDL"
 	_sdl=no
-	cat > $TMPC << EOF
-#include "SDL.h"
-int main(int argc, char *argv[]) { SDL_Init(0); return 0; }
-EOF
 	cc_check $LIBS $SDL_LIBS $INCLUDES $SDL_CFLAGS && _sdl=yes
 	echo "$_sdl"
 	if test "$_sdl" = no ; then
@@ -4286,6 +4296,11 @@ if test "$_sdl" = yes ; then
 	append_var INCLUDES "$SDL_CFLAGS"
 	append_var LIBS "$SDL_LIBS"
 	case $_sdlversion in
+		3.*.*)
+			append_var DEFINES "-DUSE_SDL3"
+			add_line_to_config_mk "USE_SDL3 = 1"
+			_sdlMajorVersionNumber=3
+			;;
 		2.*.*)
 			append_var DEFINES "-DUSE_SDL2"
 			add_line_to_config_mk "USE_SDL2 = 1"
@@ -4306,14 +4321,26 @@ fi
 # of SDL-net or SDL2-net that does not require SDL or SDL2 respectively
 #
 if test "$_sdlnet" = auto ; then
+	# If SDL3 was detected, then test for SDL3_net exclusively
 	# If SDL2 was detected, then test for SDL2_net exclusively
 	# If SDL was detected, then test for SDL_net exclusively
-	# If neither SDL nor SDL2 detected, then test for both (SDL2_net success takes priority)
+	# If neither SDL nor SDL2 detected nor SDL3 detected, then test for both (SDL3_net success takes priority)
+	set_var SDL3_NET_LIBS   "$SDL_NET_LIBS"
+	set_var SDL3_NET_CFLAGS "$SDL_NET_CFLAGS"
 	set_var SDL2_NET_LIBS   "$SDL_NET_LIBS"
 	set_var SDL2_NET_CFLAGS "$SDL_NET_CFLAGS"
 	set_var SDL1_NET_LIBS   "$SDL_NET_LIBS"
 	set_var SDL1_NET_CFLAGS "$SDL_NET_CFLAGS"
 
+	if test "$_sdl" = no || test "$_sdlMajorVersionNumber" = 3; then
+		if test "$_pkg_config" = "yes" && $_pkgconfig --exists SDL3_net; then
+			append_var SDL3_NET_LIBS "`$_pkgconfig --libs SDL3_net`"
+			append_var SDL3_NET_CFLAGS "`$_pkgconfig --cflags SDL3_net | sed 's/[[:space:]]*-Dmain=SDL_main//g'`"
+		else
+			append_var SDL3_NET_LIBS "-lSDL3_net"
+		fi
+	fi
+
 	if test "$_sdl" = no || test "$_sdlMajorVersionNumber" = 2; then
 		if test "$_pkg_config" = "yes" && $_pkgconfig --exists SDL2_net; then
 			append_var SDL2_NET_LIBS "`$_pkgconfig --libs SDL2_net`"
@@ -4339,7 +4366,6 @@ if test "$_sdlnet" = auto ; then
 #include "SDL_net.h"
 int main(int argc, char *argv[]) { SDLNet_Init(); return 0; }
 EOF
-
 	cc_check $SDL2_NET_LIBS $LIBS $INCLUDES $SDL2_NET_CFLAGS && _sdlnet=yes
 	if test "$_sdlnet" = yes ; then
 		set_var SDL_NET_LIBS   "$SDL2_NET_LIBS"
@@ -4355,6 +4381,17 @@ EOF
 			set_var SDL_NET_LIBS   "$SDL1_NET_LIBS"
 			set_var SDL_NET_CFLAGS "$SDL1_NET_CFLAGS"
 			add_line_to_config_mk "SDL_NET_MAJOR = 1"
+		else
+			cat > $TMPC << EOF
+#include "SDL3/SDL_net.h"
+int main(int argc, char *argv[]) { SDLNet_Init(); return 0; }
+EOF
+			cc_check $SDL3_NET_LIBS $LIBS $INCLUDES $SDL3_NET_CFLAGS && _sdlnet=yes
+			if test "$_sdlnet" = yes ; then
+				set_var SDL_NET_LIBS   "$SDL2_NET_LIBS"
+				set_var SDL_NET_CFLAGS "$SDL2_NET_CFLAGS"
+				add_line_to_config_mk "SDL_NET_MAJOR = 2"
+			fi
 		fi
 	fi
 
@@ -6849,7 +6886,26 @@ if test "$_imgui" != no ; then
 	if test "$_freetype2" = yes ; then
 		case $_backend in
 			sdl | sailfish)
-				if test "$_sdlMajorVersionNumber" -ge 2 ; then
+				if test "$_sdlMajorVersionNumber" -ge 3 ; then
+					cat > $TMPC << EOF
+#include <SDL3/SDL.h>
+#if !SDL_VERSION_ATLEAST(3,0,0)
+#error Missing SDL_RenderGeometryRaw() function
+#endif
+int main(int argc, char *argv[]) { return 0; }
+EOF
+					if cc_check $LIBS $SDL_LIBS $INCLUDES $SDL_CFLAGS; then
+						define_in_config_if_yes yes 'USE_IMGUI_SDLRENDERER3'
+						_imgui=yes
+						echo "yes"
+					elif test "$_opengl" = yes; then
+						_imgui=yes
+						echo "yes"
+					else
+						_imgui=no
+						echo "no (requires OpenGL or recent SDL)"
+					fi
+				elif test "$_sdlMajorVersionNumber" -ge 2 ; then
 					cat > $TMPC << EOF
 #include "SDL.h"
 #if !SDL_VERSION_ATLEAST(2,0,18)




More information about the Scummvm-git-logs mailing list