diff --git a/Makefile.common b/Makefile.common
index 73152664bbc8..1cffd12c6561 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1664,7 +1664,14 @@ endif
endif
-ifeq ($(HAVE_SDL2), 1)
+ifeq ($(HAVE_SDL3), 1)
+ SDL3_CFLAGS = $(shell pkg-config sdl3 --cflags)
+ SDL3_LIBS = $(shell pkg-config sdl3 --libs)
+ DEF_FLAGS += $(SDL3_CFLAGS)
+ LIBS += $(SDL3_LIBS)
+ DEFINES += -DHAVE_SDL3
+ OBJ += input/drivers_joypad/sdl3_joypad.o
+else ifeq ($(HAVE_SDL2), 1)
HAVE_SDL_COMMON = 1
OBJ += gfx/drivers/sdl2_gfx.o \
gfx/common/sdl2_common.o
diff --git a/Makefile.win b/Makefile.win
index ec1b9dfb692d..cd33af41feaa 100644
--- a/Makefile.win
+++ b/Makefile.win
@@ -34,6 +34,7 @@ HAVE_WINMM = 1
HAVE_SDL := 0
HAVE_SDL2 := 0
+HAVE_SDL3 := 0
HAVE_RSOUND := 0
HAVE_STB_FONT := 1
@@ -64,6 +65,11 @@ SDL2_LIBS := -lSDL2
SDL2_CFLAGS := -ISDL2 -DHAVE_SDL2
endif
+ifeq ($(HAVE_SDL3), 1)
+SDL3_LIBS := -lSDL3
+SDL3_CFLAGS := -ISDL3 -DHAVE_SDL3
+endif
+
ifeq ($(HAVE_D3D8), 1)
D3D8_LIBS := -ld3d8
ifeq ($(HAVE_D3DX), 1)
diff --git a/config.features.h b/config.features.h
index 8e022251f3cd..21be83a98335 100644
--- a/config.features.h
+++ b/config.features.h
@@ -62,6 +62,12 @@
#define SUPPORTS_SDL2 false
#endif
+#ifdef HAVE_SDL3
+#define SUPPORTS_SDL3 true
+#else
+#define SUPPORTS_SDL3 false
+#endif
+
#ifdef HAVE_THREADS
#define SUPPORTS_THREAD true
#else
diff --git a/configuration.c b/configuration.c
index 58c0437c16c7..220f81a9a3ee 100644
--- a/configuration.c
+++ b/configuration.c
@@ -1290,7 +1290,9 @@ const char *config_get_default_joypad(void)
case JOYPAD_ANDROID:
return "android";
case JOYPAD_SDL:
-#ifdef HAVE_SDL2
+#ifdef HAVE_SDL3
+ return "sdl3";
+#elif defined(HAVE_SDL2)
return "sdl2";
#else
return "sdl";
diff --git a/griffin/griffin.c b/griffin/griffin.c
index 8b84bc0ad3d6..b82ff47bb3b7 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -894,7 +894,9 @@ AUDIO
#include "../audio/drivers/xaudio.c"
#endif
-#if defined(HAVE_SDL2)
+#if defined(HAVE_SDL3)
+#include "../input/drivers_joypad/sdl3_joypad.c"
+#elif defined(HAVE_SDL2)
#include "../audio/drivers/sdl_audio.c"
#include "../input/drivers/sdl_input.c"
#include "../input/drivers_joypad/sdl_joypad.c"
diff --git a/input/drivers_joypad/sdl3_joypad.c b/input/drivers_joypad/sdl3_joypad.c
new file mode 100644
index 000000000000..2dfe44db5791
--- /dev/null
+++ b/input/drivers_joypad/sdl3_joypad.c
@@ -0,0 +1,543 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2017 - Daniel De Matteis
+ * Copyright (C) 2014-2017 - Higor Euripedes
+ * Copyright (C) 2023 - Carlo Refice
+ *
+ * RetroArch 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 Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch 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 RetroArch.
+ * If not, see .
+ */
+
+#include
+#include
+
+#include
+
+#include "../input_driver.h"
+
+#include "../../tasks/tasks_internal.h"
+#include "../../verbosity.h"
+
+typedef struct _sdl3_joypad
+{
+ SDL_Joystick *joypad;
+ SDL_Gamepad *gamepad;
+ SDL_JoystickID jid; /* 0 = Invalid ID */
+ unsigned num_axes;
+ unsigned num_buttons;
+ unsigned num_hats;
+} sdl3_joypad_t;
+
+/* TODO/FIXME - static globals */
+static sdl3_joypad_t sdl3_joypads[MAX_USERS];
+
+static const char *sdl3_joypad_name(unsigned pad)
+{
+ if (pad >= MAX_USERS || !sdl3_joypads[pad].jid)
+ return NULL;
+
+ if (sdl3_joypads[pad].gamepad)
+ return SDL_GetGamepadName(sdl3_joypads[pad].gamepad);
+ else if (sdl3_joypads[pad].joypad)
+ return SDL_GetJoystickName(sdl3_joypads[pad].joypad);
+ return NULL;
+}
+
+static uint8_t sdl3_joypad_get_button(sdl3_joypad_t *pad, unsigned button)
+{
+ if (pad->gamepad)
+ return (uint8_t)SDL_GetGamepadButton(pad->gamepad, (SDL_GamepadButton)button);
+ else if (pad->joypad)
+ return (uint8_t)SDL_GetJoystickButton(pad->joypad, button);
+ return 0;
+}
+
+static uint8_t sdl3_joypad_get_hat(sdl3_joypad_t *pad, unsigned hat)
+{
+ /* Gamepads always have num_hats=0; this is only reached for raw joysticks. */
+ return SDL_GetJoystickHat(pad->joypad, hat);
+}
+
+static int16_t sdl3_joypad_get_axis(sdl3_joypad_t *pad, unsigned axis)
+{
+ if (pad->gamepad)
+ return SDL_GetGamepadAxis(pad->gamepad, (SDL_GamepadAxis)axis);
+ else if (pad->joypad)
+ return SDL_GetJoystickAxis(pad->joypad, (int)axis);
+ return 0;
+}
+
+static void sdl3_joypad_connect(SDL_JoystickID jid)
+{
+ int i;
+ int slot = -1;
+ int32_t vendor = 0;
+ int32_t product = 0;
+ sdl3_joypad_t *pad = NULL;
+ SDL_Gamepad *gamepad = NULL;
+ SDL_Joystick *joypad = NULL;
+
+ /* Connect to the device. */
+ if (SDL_IsGamepad(jid))
+ {
+ gamepad = SDL_OpenGamepad(jid);
+ if (gamepad)
+ joypad = SDL_GetGamepadJoystick(gamepad);
+
+ if (!gamepad || !joypad)
+ {
+ RARCH_ERR("[SDL3] Couldn't open gamepad %" SDL_PRIu32 ": %s.\n", jid, SDL_GetError());
+ if (gamepad)
+ SDL_CloseGamepad(gamepad);
+ return;
+ }
+ }
+ else
+ {
+ joypad = SDL_OpenJoystick(jid);
+ if (!joypad)
+ {
+ RARCH_ERR("[SDL3] Couldn't open joystick %" SDL_PRIu32 ": %s.\n", jid, SDL_GetError());
+ return;
+ }
+ }
+
+ /* Gamepads allow restoring the player index, so re-use that for the slot if possible. */
+ if (gamepad)
+ {
+ int player = SDL_GetGamepadPlayerIndex(gamepad);
+ if (player >= 0 && player < MAX_USERS && !sdl3_joypads[player].jid)
+ slot = player;
+ }
+
+ /* Fallback to the first free slot. */
+ if (slot < 0)
+ {
+ for (i = 0; i < MAX_USERS; i++)
+ {
+ if (!sdl3_joypads[i].jid)
+ {
+ slot = i;
+ break;
+ }
+ }
+ }
+
+ /* Fail if a slot is still not found. */
+ if (slot < 0)
+ {
+ RARCH_WARN("[SDL3] No free joypad slots for joystick %" SDL_PRIu32 ".\n", jid);
+ if (gamepad)
+ SDL_CloseGamepad(gamepad);
+ else
+ SDL_CloseJoystick(joypad);
+ return;
+ }
+
+ pad = &sdl3_joypads[slot];
+ pad->jid = jid;
+ pad->gamepad = gamepad;
+ pad->joypad = joypad;
+
+ if (gamepad)
+ {
+ vendor = SDL_GetGamepadVendor(gamepad);
+ product = SDL_GetGamepadProduct(gamepad);
+
+ /* Ensure the player index matches the slot. */
+ if (SDL_GetGamepadPlayerIndex(gamepad) != slot) {
+ SDL_SetGamepadPlayerIndex(gamepad, (int)slot);
+ }
+ }
+ else
+ {
+ vendor = SDL_GetJoystickVendor(joypad);
+ product = SDL_GetJoystickProduct(joypad);
+ }
+
+ input_autoconfigure_connect(
+ sdl3_joypad_name(slot),
+ NULL, NULL,
+ sdl_joypad.ident,
+ slot,
+ vendor,
+ product);
+
+ if (gamepad)
+ {
+ /* SDL_Gamepad internally supports all axis/button IDs, even if
+ * the controller's mapping does not have a binding for it.
+ *
+ * So, we can claim to support all axes/buttons, and when we try to poll
+ * an unbound ID, SDL simply returns the correct unpressed value.
+ *
+ * Note that, in addition to 0 trackballs, we also have 0 hats. This is
+ * because the d-pad is in the button list, as the last 4 enum entries.
+ *
+ * -flibit
+ */
+ pad->num_axes = SDL_GAMEPAD_AXIS_COUNT;
+ pad->num_buttons = SDL_GAMEPAD_BUTTON_COUNT;
+ pad->num_hats = 0;
+ }
+ else
+ {
+ pad->num_axes = SDL_GetNumJoystickAxes(joypad);
+ pad->num_buttons = SDL_GetNumJoystickButtons(joypad);
+ pad->num_hats = SDL_GetNumJoystickHats(joypad);
+ }
+}
+
+static void sdl3_joypad_disconnect(SDL_JoystickID jid)
+{
+ for (int i = 0; i < MAX_USERS; i++)
+ {
+ if (sdl3_joypads[i].jid != jid)
+ continue;
+
+ if (sdl3_joypads[i].gamepad)
+ SDL_CloseGamepad(sdl3_joypads[i].gamepad);
+ else if (sdl3_joypads[i].joypad)
+ SDL_CloseJoystick(sdl3_joypads[i].joypad);
+
+ input_autoconfigure_disconnect(i, sdl_joypad.ident);
+
+ memset(&sdl3_joypads[i], 0, sizeof(sdl3_joypads[i]));
+ return;
+ }
+}
+
+static void sdl3_joypad_destroy(void)
+{
+ for (int i = 0; i < MAX_USERS; i++)
+ {
+ if (sdl3_joypads[i].gamepad)
+ SDL_CloseGamepad(sdl3_joypads[i].gamepad);
+ else if (sdl3_joypads[i].joypad)
+ SDL_CloseJoystick(sdl3_joypads[i].joypad);
+ }
+
+ memset(sdl3_joypads, 0, sizeof(sdl3_joypads));
+ SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
+}
+
+static void *sdl3_joypad_init(void *data)
+{
+ int i, count = 0;
+ SDL_InitFlags sdl_subsystem_flags = SDL_WasInit(0);
+
+ if (sdl_subsystem_flags == 0)
+ {
+ if (!SDL_Init(SDL_INIT_GAMEPAD))
+ return NULL;
+ }
+ else if ((sdl_subsystem_flags & SDL_INIT_GAMEPAD) == 0)
+ {
+ if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD))
+ return NULL;
+ }
+
+ memset(sdl3_joypads, 0, sizeof(sdl3_joypads));
+
+ /* Joystick connected events are triggered after load, so no need to initialize them here. */
+
+ return (void*)-1;
+}
+
+static int32_t sdl3_joypad_button_state(sdl3_joypad_t *pad, uint16_t joykey)
+{
+ unsigned hat_dir = GET_HAT_DIR(joykey);
+
+ if (hat_dir)
+ {
+ uint8_t dir;
+ uint16_t hat = GET_HAT(joykey);
+
+ if (hat >= pad->num_hats)
+ return 0;
+
+ dir = sdl3_joypad_get_hat(pad, hat);
+
+ switch (hat_dir)
+ {
+ case HAT_UP_MASK:
+ return (dir & SDL_HAT_UP);
+ case HAT_DOWN_MASK:
+ return (dir & SDL_HAT_DOWN);
+ case HAT_LEFT_MASK:
+ return (dir & SDL_HAT_LEFT);
+ case HAT_RIGHT_MASK:
+ return (dir & SDL_HAT_RIGHT);
+ default:
+ break;
+ }
+ /* hat requested and no hat button down */
+ }
+ else if (joykey < pad->num_buttons)
+ return sdl3_joypad_get_button(pad, joykey);
+
+ return 0;
+}
+
+static int32_t sdl3_joypad_button(unsigned port, uint16_t joykey)
+{
+ if (port >= MAX_USERS)
+ return 0;
+
+ if (!sdl3_joypads[port].joypad)
+ return 0;
+
+ return sdl3_joypad_button_state(&sdl3_joypads[port], joykey);
+}
+
+static int16_t sdl3_joypad_axis_state(sdl3_joypad_t *pad, uint32_t joyaxis)
+{
+ if (AXIS_NEG_GET(joyaxis) < pad->num_axes)
+ {
+ int16_t val = sdl3_joypad_get_axis(pad, AXIS_NEG_GET(joyaxis));
+ if (val < 0)
+ {
+ /* Clamp - -0x8000 can cause trouble if we later abs() it. */
+ if (val < -0x7fff)
+ return -0x7fff;
+ return val;
+ }
+ }
+ else if (AXIS_POS_GET(joyaxis) < pad->num_axes)
+ {
+ int16_t val = sdl3_joypad_get_axis(pad, AXIS_POS_GET(joyaxis));
+ if (val > 0)
+ return val;
+ }
+
+ return 0;
+}
+
+static int16_t sdl3_joypad_axis(unsigned port, uint32_t joyaxis)
+{
+ if (port >= MAX_USERS)
+ return 0;
+
+ if (!sdl3_joypads[port].joypad)
+ return 0;
+
+ return sdl3_joypad_axis_state(&sdl3_joypads[port], joyaxis);
+}
+
+static int16_t sdl3_joypad_state(
+ rarch_joypad_info_t *joypad_info,
+ const struct retro_keybind *binds,
+ unsigned port)
+{
+ int i;
+ int16_t ret = 0;
+ uint16_t port_idx = joypad_info->joy_idx;
+ sdl3_joypad_t *pad;
+
+ if (port_idx >= MAX_USERS)
+ return 0;
+
+ pad = &sdl3_joypads[port_idx];
+ if (!pad->joypad)
+ return 0;
+
+ for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
+ {
+ /* Auto-binds are per joypad, not per user. */
+ const uint64_t joykey = (binds[i].joykey != NO_BTN)
+ ? binds[i].joykey : joypad_info->auto_binds[i].joykey;
+ const uint32_t joyaxis = (binds[i].joyaxis != AXIS_NONE)
+ ? binds[i].joyaxis : joypad_info->auto_binds[i].joyaxis;
+
+ if (
+ (uint16_t)joykey != NO_BTN
+ && sdl3_joypad_button_state(pad, (uint16_t)joykey)
+ )
+ ret |= (1 << i);
+ else if (joyaxis != AXIS_NONE &&
+ ((float)abs(sdl3_joypad_axis_state(pad, joyaxis))
+ / 0x8000) > joypad_info->axis_threshold)
+ ret |= (1 << i);
+ }
+
+ return ret;
+}
+
+static void sdl3_joypad_poll(void)
+{
+ SDL_Event event;
+
+ SDL_PumpEvents();
+
+ while (SDL_PeepEvents(&event, 1, SDL_GETEVENT,
+ SDL_EVENT_JOYSTICK_ADDED, SDL_EVENT_JOYSTICK_REMOVED) > 0)
+ {
+ switch (event.type)
+ {
+ case SDL_EVENT_JOYSTICK_ADDED:
+ sdl3_joypad_connect(event.jdevice.which);
+ break;
+ case SDL_EVENT_JOYSTICK_REMOVED:
+ sdl3_joypad_disconnect(event.jdevice.which);
+ break;
+ }
+ }
+
+ SDL_UpdateGamepads();
+ /* Discard all remaining joystick/gamepad input events (axis, button, hat,
+ * etc.) - we sample state directly via SDL_GetGamepadAxis / SDL_GetJoystickButton
+ * and don't need the event copies piling up in the queue. */
+ SDL_FlushEvents(SDL_EVENT_JOYSTICK_AXIS_MOTION, SDL_EVENT_GAMEPAD_REMAPPED);
+}
+
+static bool sdl3_joypad_set_rumble(unsigned pad,
+ enum retro_rumble_effect effect, uint16_t strength)
+{
+ uint16_t low = 0;
+ uint16_t high = 0;
+
+ if (pad >= MAX_USERS)
+ return false;
+
+ switch (effect)
+ {
+ case RETRO_RUMBLE_STRONG:
+ low = strength;
+ break;
+ case RETRO_RUMBLE_WEAK:
+ high = strength;
+ break;
+ default:
+ return false;
+ }
+
+ if (sdl3_joypads[pad].gamepad)
+ return SDL_RumbleGamepad(sdl3_joypads[pad].gamepad, low, high, 5000);
+ else if (sdl3_joypads[pad].joypad)
+ return SDL_RumbleJoystick(sdl3_joypads[pad].joypad, low, high, 5000);
+
+ return false;
+}
+
+/**
+ * Enables or disables a sensor on the specified gamepad.
+ *
+ * @param pad Index of the gamepad.
+ * @param action Sensor action to perform (enable/disable gyroscope or accelerometer).
+ * @param rate Requested sensor update rate (unused).
+ * @return true if the sensor state was set successfully, false otherwise.
+ */
+static bool sdl3_joypad_set_sensor_state(unsigned pad,
+ enum retro_sensor_action action, unsigned rate)
+{
+ if (pad >= MAX_USERS)
+ return false;
+
+ if (!sdl3_joypads[pad].gamepad)
+ return false;
+
+ switch (action)
+ {
+ case RETRO_SENSOR_GYROSCOPE_ENABLE:
+ case RETRO_SENSOR_GYROSCOPE_DISABLE:
+ if (SDL_GamepadHasSensor(sdl3_joypads[pad].gamepad, SDL_SENSOR_GYRO))
+ return SDL_SetGamepadSensorEnabled(sdl3_joypads[pad].gamepad, SDL_SENSOR_GYRO,
+ action == RETRO_SENSOR_GYROSCOPE_ENABLE);
+ return false;
+
+ case RETRO_SENSOR_ACCELEROMETER_ENABLE:
+ case RETRO_SENSOR_ACCELEROMETER_DISABLE:
+ if (SDL_GamepadHasSensor(sdl3_joypads[pad].gamepad, SDL_SENSOR_ACCEL))
+ return SDL_SetGamepadSensorEnabled(sdl3_joypads[pad].gamepad, SDL_SENSOR_ACCEL,
+ action == RETRO_SENSOR_ACCELEROMETER_ENABLE);
+ return false;
+
+ default:
+ return false;
+ }
+}
+
+/**
+ * Retrieves input data from a connected sensor device, such as a gyroscope or accelerometer.
+ *
+ * @return True if the sensor input was successfully handled by this function, false otherwise.
+ */
+static bool sdl3_joypad_get_sensor_input(unsigned pad, unsigned id, float *value)
+{
+ SDL_SensorType sensor_type;
+ float sensor_data[3];
+
+ if (pad >= MAX_USERS)
+ return false;
+
+ if (!sdl3_joypads[pad].gamepad)
+ return false;
+
+ if ((id >= RETRO_SENSOR_ACCELEROMETER_X) && (id <= RETRO_SENSOR_ACCELEROMETER_Z))
+ sensor_type = SDL_SENSOR_ACCEL;
+ else if ((id >= RETRO_SENSOR_GYROSCOPE_X) && (id <= RETRO_SENSOR_GYROSCOPE_Z))
+ sensor_type = SDL_SENSOR_GYRO;
+ else
+ return false;
+
+ if (!SDL_GetGamepadSensorData(sdl3_joypads[pad].gamepad, sensor_type, sensor_data, 3))
+ return false;
+
+ switch (id)
+ {
+ case RETRO_SENSOR_ACCELEROMETER_X:
+ *value = sensor_data[0] / SDL_STANDARD_GRAVITY;
+ break;
+ case RETRO_SENSOR_ACCELEROMETER_Y:
+ *value = sensor_data[2] / SDL_STANDARD_GRAVITY;
+ break;
+ case RETRO_SENSOR_ACCELEROMETER_Z:
+ *value = sensor_data[1] / SDL_STANDARD_GRAVITY;
+ break;
+ case RETRO_SENSOR_GYROSCOPE_X:
+ *value = sensor_data[0];
+ break;
+ case RETRO_SENSOR_GYROSCOPE_Y:
+ *value = -sensor_data[2];
+ break;
+ case RETRO_SENSOR_GYROSCOPE_Z:
+ *value = sensor_data[1];
+ break;
+ }
+
+ return true;
+}
+
+static bool sdl3_joypad_query_pad(unsigned pad)
+{
+ if (pad >= MAX_USERS || !sdl3_joypads[pad].joypad)
+ return false;
+ if (sdl3_joypads[pad].gamepad)
+ return SDL_GamepadConnected(sdl3_joypads[pad].gamepad);
+ return SDL_JoystickConnected(sdl3_joypads[pad].joypad);
+}
+
+input_device_driver_t sdl_joypad = {
+ sdl3_joypad_init,
+ sdl3_joypad_query_pad,
+ sdl3_joypad_destroy,
+ sdl3_joypad_button,
+ sdl3_joypad_state,
+ NULL, /* get_buttons */
+ sdl3_joypad_axis,
+ sdl3_joypad_poll,
+ sdl3_joypad_set_rumble,
+ NULL, /* set_rumble_gain */
+ sdl3_joypad_set_sensor_state,
+ sdl3_joypad_get_sensor_input,
+ sdl3_joypad_name,
+ "sdl3", /* TODO: Rename all the SDL Controller Drivers to "sdl"? You can't use them at the same time anyway, and it will fix autoconfigs. */
+};
diff --git a/input/input_autodetect_builtin.c b/input/input_autodetect_builtin.c
index dc5ca1dea273..4d732d9fbb2b 100644
--- a/input/input_autodetect_builtin.c
+++ b/input/input_autodetect_builtin.c
@@ -32,11 +32,16 @@
#include
#endif
+#ifdef HAVE_SDL3
+#include
+#endif
+
#define DECL_BTN(btn, bind) "input_" #btn "_btn = " #bind "\n"
#define DECL_BTN_EX(btn, bind, name) "input_" #btn "_btn = " #bind "\ninput_" #btn "_btn_label = \"" name "\"\n"
#define DECL_AXIS(axis, bind) "input_" #axis "_axis = " #bind "\n"
#define DECL_AXIS_EX(axis, bind, name) "input_" #axis "_axis = " #bind "\ninput_" #axis "_axis_label = \"" name "\"\n"
#define DECL_MENU(btn) "input_menu_toggle_btn = " #btn "\n"
+#define DECL_MENU_EX(btn, name) "input_menu_toggle_btn = " #btn "\ninput_menu_toggle_btn_label = \"" name "\"\n"
#define DECL_AUTOCONF_DEVICE(device, driver, binds) "input_device = \"" device "\"\ninput_driver = \"" driver "\"\n" binds
#define DECL_AUTOCONF_PID(pid, vid, driver, binds) "input_product_id = " #pid "\ninput_vendor_id = " #vid "\ninput_driver = \"" driver "\"\n" binds
@@ -66,6 +71,33 @@ DECL_AXIS(r_x_minus, -2) \
DECL_AXIS(r_y_plus, -3) \
DECL_AXIS(r_y_minus, +3)
+#define SDL3_DEFAULT_BINDS \
+DECL_BTN_EX(a, SDL_GAMEPAD_BUTTON_EAST, "Right Face Button") \
+DECL_BTN_EX(b, SDL_GAMEPAD_BUTTON_SOUTH, "Bottom Face Button") \
+DECL_BTN_EX(x, SDL_GAMEPAD_BUTTON_NORTH, "Top Face Button") \
+DECL_BTN_EX(y, SDL_GAMEPAD_BUTTON_WEST, "Left Face Button") \
+DECL_BTN_EX(select, SDL_GAMEPAD_BUTTON_BACK, "Back") \
+DECL_BTN_EX(start, SDL_GAMEPAD_BUTTON_START, "Start") \
+DECL_BTN_EX(up, SDL_GAMEPAD_BUTTON_DPAD_UP, "D-Pad Up") \
+DECL_BTN_EX(down, SDL_GAMEPAD_BUTTON_DPAD_DOWN, "D-Pad Down") \
+DECL_BTN_EX(left, SDL_GAMEPAD_BUTTON_DPAD_LEFT, "D-Pad Left") \
+DECL_BTN_EX(right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "D-Pad Right") \
+DECL_BTN_EX(l, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, "Left Shoulder") \
+DECL_BTN_EX(r, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, "Right Shoulder") \
+DECL_AXIS_EX(l2, +SDL_GAMEPAD_AXIS_LEFT_TRIGGER, "Left Trigger") \
+DECL_AXIS_EX(r2, +SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, "Right Trigger") \
+DECL_BTN_EX(l3, SDL_GAMEPAD_BUTTON_LEFT_STICK, "Left Stick") \
+DECL_BTN_EX(r3, SDL_GAMEPAD_BUTTON_RIGHT_STICK, "Right Stick") \
+DECL_AXIS_EX(l_x_plus, +SDL_GAMEPAD_AXIS_LEFTX, "Left Thumbstick X+") \
+DECL_AXIS_EX(l_x_minus, -SDL_GAMEPAD_AXIS_LEFTX, "Left Thumbstick X-") \
+DECL_AXIS_EX(l_y_plus, +SDL_GAMEPAD_AXIS_LEFTY, "Left Thumbstick Y+") \
+DECL_AXIS_EX(l_y_minus, -SDL_GAMEPAD_AXIS_LEFTY, "Left Thumbstick Y-") \
+DECL_AXIS_EX(r_x_plus, +SDL_GAMEPAD_AXIS_RIGHTX, "Right Thumbstick X+") \
+DECL_AXIS_EX(r_x_minus, -SDL_GAMEPAD_AXIS_RIGHTX, "Right Thumbstick X-") \
+DECL_AXIS_EX(r_y_plus, -SDL_GAMEPAD_AXIS_RIGHTY, "Right Thumbstick Y+") \
+DECL_AXIS_EX(r_y_minus, +SDL_GAMEPAD_AXIS_RIGHTY, "Right Thumbstick Y-") \
+DECL_MENU_EX(SDL_GAMEPAD_BUTTON_GUIDE, "Guide")
+
#if defined(DINGUX) && defined(HAVE_SDL_DINGUX)
#define DINGUX_SDL_DEFAULT_BINDS \
DECL_BTN_EX(a, 8, "A") \
@@ -720,6 +752,9 @@ const char* const input_builtin_autoconfs[] =
#ifdef HAVE_SDL2
DECL_AUTOCONF_DEVICE("Standard Gamepad", "sdl2", SDL2_DEFAULT_BINDS),
#endif
+#ifdef HAVE_SDL3
+ DECL_AUTOCONF_DEVICE("Gamepad", "sdl3", SDL3_DEFAULT_BINDS),
+#endif
#if defined(DINGUX) && defined(HAVE_SDL_DINGUX)
DECL_AUTOCONF_DEVICE("Dingux Gamepad", "sdl_dingux", DINGUX_SDL_DEFAULT_BINDS),
#endif
diff --git a/input/input_driver.c b/input/input_driver.c
index 365e3746723b..9afef6956529 100644
--- a/input/input_driver.c
+++ b/input/input_driver.c
@@ -272,7 +272,7 @@ input_device_driver_t *joypad_drivers[] = {
#ifdef ANDROID
&android_joypad,
#endif
-#if defined(HAVE_SDL) || defined(HAVE_SDL2)
+#if defined(HAVE_SDL3) || defined(HAVE_SDL) || defined(HAVE_SDL2)
&sdl_joypad,
#endif
#if defined(DINGUX) && defined(HAVE_SDL_DINGUX)
@@ -591,7 +591,7 @@ const input_device_driver_t *input_joypad_init_driver(
}
}
/* Fall back to first available driver */
- return input_joypad_init_first(data);
+ return input_joypad_init_first(data);
}
static bool input_driver_button_combo_hold(
@@ -3006,7 +3006,7 @@ void input_overlay_load_active(
/**
* input_overlay_next_move_touch_masks
* @ol : Overlay handle.
- *
+ *
* Finds similar descs in the next overlay (i.e. same location and type)
* and moves touch masks from active overlay to next.
*/
@@ -6167,14 +6167,14 @@ static void input_keys_pressed(
else
input_st->flags |= INP_FLAG_BLOCK_HOTKEY;
}
-
+
#ifdef HAVE_MENU
/* Prevent triggering menu actions after binding */
if ( !(input_st->flags & INP_FLAG_MENU_PRESS_PENDING)
&& menu_state_get_ptr()->input_driver_flushing_input)
input_st->flags |= INP_FLAG_WAIT_INPUT_RELEASE;
#endif
-
+
/* Check libretro input if emulated device type is active,
* except device type must be always active in menu. */
if ( !(input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT)
diff --git a/input/input_driver.h b/input/input_driver.h
index 2cd9a23e0a85..c766e659b84c 100644
--- a/input/input_driver.h
+++ b/input/input_driver.h
@@ -1253,7 +1253,7 @@ extern input_device_driver_t linuxraw_joypad;
extern input_device_driver_t parport_joypad;
extern input_device_driver_t udev_joypad;
extern input_device_driver_t xinput_joypad;
-extern input_device_driver_t sdl_joypad;
+extern input_device_driver_t sdl_joypad; /* SDL2 or SDL3. @see sdl_joypad.c, sdl3_joypad.c. */
extern input_device_driver_t sdl_dingux_joypad;
extern input_device_driver_t ps4_joypad;
extern input_device_driver_t ps3_joypad;
diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c
index a872c3e78966..6e803d0e17d6 100644
--- a/menu/menu_displaylist.c
+++ b/menu/menu_displaylist.c
@@ -408,7 +408,7 @@ static int filebrowser_parse(
? FILE_TYPE_VIDEO_FONT
: (enum msg_file_type)type_default;
- if ( type == DISPLAYLIST_CORES_DETECTED
+ if ( type == DISPLAYLIST_CORES_DETECTED
&& path_is_compressed_file(file_path))
file_type = FILE_TYPE_CARCHIVE;
break;
@@ -631,7 +631,7 @@ static int menu_displaylist_parse_core_info(
core_info = core_info_menu;
}
- if ( !core_info
+ if ( !core_info
|| !(core_info->flags & CORE_INFO_FLAG_HAS_INFO))
{
if (menu_entries_append(list,
@@ -2390,6 +2390,9 @@ static unsigned menu_displaylist_parse_system_info(file_list_t *list)
#ifdef HAVE_SDL2
{SUPPORTS_SDL2, "SDL 2"},
#endif
+#ifdef HAVE_SDL3
+ {SUPPORTS_SDL3, "SDL 3"},
+#endif
#ifdef HAVE_X11
{SUPPORTS_X11, "X11"},
#endif
@@ -5059,7 +5062,7 @@ static unsigned menu_displaylist_parse_content_information(
if (core_info_find(core_path, &core_info))
{
- core_supports_no_game = (core_info->flags
+ core_supports_no_game = (core_info->flags
& CORE_INFO_FLAG_SUPPORTS_NO_GAME);
if (!string_is_empty(core_info->display_name))
strlcpy(core_name, core_info->display_name, sizeof(core_name));
@@ -10084,7 +10087,7 @@ unsigned menu_displaylist_build_list(
MENU_ENUM_LABEL_VIDEO_HDR_SCANLINES,
PARSE_ONLY_BOOL, false) == 0)
count++;
-
+
if(settings->bools.video_hdr_scanlines)
{
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
diff --git a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj
index 09ca583db37f..267474b68367 100644
--- a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj
+++ b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj
@@ -444,6 +444,7 @@
05C5D58220E3DD0900654EE4 /* input_autodetect_builtin.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = input_autodetect_builtin.c; sourceTree = ""; };
05C5D58320E3DD0900654EE4 /* input_keymaps.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = input_keymaps.c; sourceTree = ""; };
05C5D58720E3DD0900654EE4 /* sdl_joypad.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sdl_joypad.c; sourceTree = ""; };
+ 05C5D58720E3DD0900654EE4 /* sdl3_joypad.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sdl3_joypad.c; sourceTree = ""; };
05C5D58D20E3DD0900654EE4 /* mfi_joypad.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = mfi_joypad.m; sourceTree = ""; };
05C5D59820E3DD0A00654EE4 /* hid_joypad.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hid_joypad.c; sourceTree = ""; };
05C5D5A320E3DD0A00654EE4 /* input_driver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = input_driver.c; sourceTree = ""; };
diff --git a/qb/config.libs.sh b/qb/config.libs.sh
index 5c78baa8914f..294dce6f6436 100644
--- a/qb/config.libs.sh
+++ b/qb/config.libs.sh
@@ -273,7 +273,13 @@ check_val '' PIPEWIRE -lpipewire-0.3 '' libpipewire-0.3 '' '' false
check_val '' PIPEWIRE_STABLE -lpipewire-0.3 '' libpipewire-0.3 1.0.0 '' false
check_val '' SDL -lSDL SDL sdl 1.2.10 '' true
check_val '' SDL2 -lSDL2 SDL2 sdl2 2.0.0 '' true
+check_val '' SDL3 -lSDL3 SDL3 sdl3 3.2.20 '' true
+if [ "$HAVE_SDL3" = 'yes' ] && { [ "$HAVE_SDL2" = 'yes' ] || [ "$HAVE_SDL" = 'yes' ]; }; then
+ die : 'Notice: SDL drivers will be replaced by SDL3 ones.'
+ HAVE_SDL=no
+ HAVE_SDL2=no
+fi
if [ "$HAVE_SDL2" = 'yes' ] && [ "$HAVE_SDL" = 'yes' ]; then
die : 'Notice: SDL drivers will be replaced by SDL2 ones.'
HAVE_SDL=no
diff --git a/qb/config.params.sh b/qb/config.params.sh
index e58f0460a5c2..5893e2bd3b3d 100644
--- a/qb/config.params.sh
+++ b/qb/config.params.sh
@@ -48,6 +48,7 @@ HAVE_DYNAMIC=yes # Dynamic loading of libretro library
HAVE_SDL=auto # SDL support
C89_SDL=no
HAVE_SDL2=auto # SDL2 support (disables SDL 1.x)
+HAVE_SDL3=auto # SDL3 support (disables SDL 1.x and SDL 2.x)
C89_SDL2=no
HAVE_LIBUSB=auto # Libusb HID support
C89_LIBUSB=no
diff --git a/retroarch.c b/retroarch.c
index 3ee2db28af83..a59d50fd8573 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -220,7 +220,9 @@
#include "ai/game_ai.h"
#endif
-#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
+#if defined(HAVE_SDL3)
+#include
+#elif defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
#include "SDL.h"
#endif
@@ -4284,7 +4286,7 @@ bool command_event(enum event_command cmd, void *data)
video_driver_state_t
*video_st = video_state_get_ptr();
rarch_system_info_t *sys_info = &runloop_st->system;
-
+
/* Restore unpaused state */
runloop_st->paused_hotkey = false;
command_event(CMD_EVENT_UNPAUSE, NULL);
@@ -5900,7 +5902,7 @@ static void global_free(struct rarch_state *p_rarch)
retroarch_override_setting_free_state();
}
-#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
+#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) || defined(HAVE_SDL3)
static void sdl_exit(void)
{
/* Quit any SDL subsystems, then quit
@@ -6002,7 +6004,7 @@ void main_exit(void *args)
CoUninitialize();
#endif
-#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
+#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) || defined(HAVE_SDL3)
sdl_exit();
#endif
}
@@ -6466,6 +6468,9 @@ static void retroarch_print_features(void)
#ifdef HAVE_SDL2
_len += _PSUPP_BUF(buf, _len, SUPPORTS_SDL2, "SDL2", "SDL2 input/audio/video drivers");
#endif
+#ifdef HAVE_SDL3
+ _len += _PSUPP_BUF(buf, _len, SUPPORTS_SDL3, "SDL3", "SDL3 joypad driver");
+#endif
#ifdef HAVE_X11
_len += _PSUPP_BUF(buf, _len, SUPPORTS_X11, "X11", "X11 input/video drivers");
#endif